You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/audit/theming/progress.md

182 lines
11 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# progress — alcance de tema: análisis y propuesta
> Generado por `node --import tsx/esm scripts/theming-census.ts --report`.
> Lo **medido** y la **propuesta** se regeneran; el **Veredicto** (§5) se conserva.
> Vista de conjunto: [README](./README.md) · método y protocolo:
> [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7.
- **Medido**: 2026-08-25 · **Alcance**: **93%** — 26 de 28 knobs por token público
- **Knobs de apariencia**: 30 — público 26 · privado 0 · global 2 · literal 0 · sistema 0 · excepción 6 · estructural 0 · puente 0 · canal 2 _(los cinco últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 43 pública(s) — `height-xs`, `height-sm`, `height-md`, `height-lg`, `height-xl`, `vertical-height`, `row-gap`, `column-gap`, `radius-xs`, `radius-sm`, `radius-md`, `radius-lg`, `radius-xl`, `ring-size-xs`, `ring-size-sm`, `ring-size-md`, `ring-size-lg`, `ring-size-xl`, `ring-thickness-xs`, `ring-thickness-sm`, `ring-thickness-md`, `ring-thickness-lg`, `ring-thickness-xl`, `label-font-family`, `label-font-size`, `label-font-weight`, `label-line-height`, `label-fg`, `value-text-font-family`, `value-text-font-size`, `value-text-font-weight`, `value-text-line-height`, `value-text-fg`, `track-bg`, `ring-track-bg`, `ring-center-bg`, `indicator-bg`, `indicator-bg-loaded`, `transition-duration`, `transition-ease`, `indeterminate-width`, `indeterminate-duration`, `indeterminate-ease`
- **Eje `size`**: sí · **ficheros**: `progress.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (2)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `progress.css:127` | `[data-progress][data-shape='circular'] [data-progress-indicator]` | `border-radius` | `var(--radius-full)` |
| 2 | `progress.css:139` | `[data-progress][data-shape='circular'] [data-progress-indicator]::before` | `border-radius` | `var(--radius-full)` |
### 1.2 A través de un privado (0)
Sólo el **residuo**: el puente de paleta (§2-ter) y el canal de valor
(§2-quater) salen aparte, porque no son deuda ni tienen nombre que acuñar.
_Ninguno._
### 1.3 Literales (0)
_Ninguno._
### 1.4 Excepciones firmadas (6) — fuera del ratio
Literales que llevan su anotación `/* literal: <razón> */` en la propia
declaración: la válvula de recipe-contract §3, la misma que honra
`component-audit`. **Una desviación firmada no es deuda** — se listan para que la
razón se lea, no para acuñarlas.
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `progress.css:13` | `[data-progress]` | `inline-size` | `100%` |
| 2 | `progress.css:45` | `[data-progress][data-orientation='vertical']` | `inline-size` | `max-content` |
| 3 | `progress.css:55` | `[data-progress-indicator]` | `inline-size` | `100%` |
| 4 | `progress.css:82` | `[data-progress][data-orientation='vertical'] [data-progress-indicator]::before` | `inline-size` | `100%` |
| 5 | `progress.css:103` | `[data-progress][data-orientation='vertical'][data-state='indeterminate'] [data-progress-indicator]::before` | `inline-size` | `100%` |
| 6 | `progress.css:108` | `[data-progress][data-shape='circular']` | `inline-size` | `max-content` |
## 2. Sistema transversal (0) — informativo, fuera del ratio
Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
_Ninguno._
## 2-quater. Canal de valor (2) — fuera del ratio
La receta lee un privado que **nadie declara** — ni su CSS ni el generador: lo
escribe soma o el envoltorio **por instancia** (un %, un rect medido, la talla
que pide una prop). Un tema no debe alcanzarlo: fijarlo rompe el
comportamiento, y por eso `tabs` rechazó por escrito esa misma propuesta.
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `progress.css:67` | `[data-progress-indicator]::before` | `inline-size` | `calc(var(--_progress-value-pct, 0) * 1%)` |
| 2 | `progress.css:83` | `[data-progress][data-orientation='vertical'] [data-progress-indicator]::before` | `block-size` | `calc(var(--_progress-value-pct, 0) * 1%)` |
## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_progress-height` | 5 | `var(--progress-height-md)`, `var(--progress-height-xs)`, `var(--progress-height-sm)`, `var(--progress-height-lg)`, `var(--progress-height-xl)` | public | **sí** |
| `--_progress-radius` | 5 | `var(--progress-radius-md)`, `var(--progress-radius-xs)`, `var(--progress-radius-sm)`, `var(--progress-radius-lg)`, `var(--progress-radius-xl)` | public | **sí** |
| `--_progress-ring-size` | 5 | `var(--progress-ring-size-md)`, `var(--progress-ring-size-xs)`, `var(--progress-ring-size-sm)`, `var(--progress-ring-size-lg)`, `var(--progress-ring-size-xl)` | public | **sí** |
| `--_progress-ring-thickness` | 5 | `var(--progress-ring-thickness-md)`, `var(--progress-ring-thickness-xs)`, `var(--progress-ring-thickness-sm)`, `var(--progress-ring-thickness-lg)`, `var(--progress-ring-thickness-xl)` | public | **sí** |
| `--_progress-indicator-bg` | 2 | `var(--progress-indicator-bg)`, `var(--progress-indicator-bg-loaded)` | public | **sí** |
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_progress-value-pct`.
## 4. Propuesta de corrección
- **Tiene eje `size`**: los tokens dimensionales van por talla (`{part}-{eje}-{k}`) apuntando al bundle `--size-{k}-*`, nunca al primitivo crudo (theming §5; el guard `recipe-css-contract` prohíbe el primitivo).
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (2)
Valor **verbatim** del CSS de hoy: el default no se mueve, sólo cambia quién
puede moverlo. Nombres derivados de recipe-contract §1 (ejes lógicos, talla
al final) y theming §6.7 (slots de color, modificador delante). Un token con
DOS valores distintos es una colisión de nombre: son dos knobs, o el nombre
no distingue lo que debería — se marca `⚠`.
| token (`--progress-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `width` | `root` | `max-content` | 2 |
| `indicator-radius` | `root` | `var(--radius-full)` | 2 |
### 4.2 Sin nombre mecánico (4)
- **⚠ decisión: `100%` es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar** — 4: `inline-size`.
### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)
- [ ] **Privado que no deriva de un público** — §3 lo marca; el privado debe leer el público o desaparecer.
- [ ] **Velo o acento en el nodo equivocado** (`archetype: 'item'` en un envoltorio, un `background` en shorthand que mata la capa de estado) — se mide desde el píxel hacia arriba.
- [ ] **Doble animación** al mover un sello a una superficie con animación propia — registro de `animationstart`/`animationend`.
- [ ] **Diff de computed = 0** en reposo · hover · abierto · disabled · foco, por talla, antes y después.
- [ ] **Centinela por token nuevo**: valor imposible en el root → el nodo lo sigue. Si no, el token miente.
## 5. Veredicto
<!-- veredicto:start -->
**Ejecutado 2026-08-23 — 69 % → 87 %.** Dos claves acuñadas, seis literales
firmados, cuatro knobs adjudicados. Diff de computed **0** sobre 60
configuraciones (2 formas × 2 orientaciones × 3 estados × 5 tallas) y capturas
2× **idénticas byte a byte**. Guard R-5.4: **40 de 43** tokens mueven un
computed; los tres callados, medidos y escritos en el ledger.
**Lo que entra (2 claves, el ÚNICO knob que iba a un primitivo a pelo):**
- `row-gap` (`--space-2`) y `column-gap` (`--space-3`). Eran un `gap` en
atajo de DOS valores distintos, y por eso se parte: el hueco de fila separa
la línea de texto de la barra, el de columna separa el rótulo del valor. Un
`gap` único habría nombrado un knob para dos. El catálogo tiene las dos
palabras (`row-gap` ×10, `column-gap` ×4) y el molde exacto es
`chart.legend-row-gap` / `-column-gap`.
**Lo que se FIRMA en vez de acuñarse (6 literales, fuera del ratio):** los
cuatro `100%` y los dos `max-content` son la geometría de la barra, no una
decisión de tema — llena la fila que le dan (`100%`) o se encoge a su propio
grosor / diámetro (`max-content`). Llevan su `/* literal: <razón> */` en la
declaración; válvula de recipe-contract §3.
**Lo que se RECHAZA de la propuesta §4 (dos de las tres filas):**
1. `indicator-radius` — el `--radius-full` del anillo. Su gemelo `meter` ya
dejó el veredicto escrito EN EL CSS: es lo que hace circular a
`shape='circular'`, y un token dejaría que un tema des-redondease una forma
que el consumidor pidió POR SU NOMBRE. **Identidad, no knob.** Se copia el
comentario del gemelo. Son los dos únicos `global` que quedan, y el censo no
puede sacarlos del ratio: la válvula `/* literal: */` sólo reclasifica la
clase `literal`, nunca un `global` (mismo techo que `meter`).
2. `width: max-content` — la §4 fundía en UN nombre dos nodos distintos (la
raíz vertical y la raíz circular) y además lo llamaba por el primer selector,
no por lo que pinta. Es el literal de identidad de arriba.
**Los dos knobs `private` que quedan son el CANAL DE VALOR, no deuda.**
`--_progress-value-pct` lo escribe **soma** en el estilo inline del provider en
cada render (`progress-provider.svelte.ts`, 0–100 saturado); la receta lo lee
para el ancho del relleno lineal y el alto del vertical. Un público encima
mentiría: un tema no puede fijar el progreso de una tarea. Es la misma clase que
el `z-index` inline de `drag-drop` y el gap de `carousel`. Con ellos y con los
dos `--radius-full` de identidad, **el techo honesto de este componente es
87 %**, no el 100 %.
**Lo que el guard da por callado (3 de 43, todos medidos sobre el nodo real):**
- `indicator-bg-loaded` — sólo pinta bajo `[data-state='loaded']`, y la corrida
mantiene `indeterminate` encendido para alcanzar los tokens del barrido:
forzado → `oklch(0.6406 0.1329 157.68)` → `rgb(1,2,3)`.
- `transition-duration` / `transition-ease` — los congela el propio guard por
diseño; pasada SIN congelar → `0.18s` → `11.5s` y
`cubic-bezier(0.4, 0, 0.2, 1)` → `steps(7)`. Mismo par que en `meter`.
**Lo que enseñó el instrumento.** La sonda estándar mide 4 nodos — que son
TODAS las partes del morfo — pero una sola configuración: lineal, horizontal,
`loading`, `md`. Medio contrato (el anillo, el eje vertical, el barrido
indeterminado, la tinta de completado) no entra en ese diff, y el gate habría
pasado en verde sin haberlo mirado. El guard lo dijo antes: **22 de 41** en la
página por defecto. Con `sweepAttr: data-shape` (el interruptor que su gemelo
`meter` ya tenía) sube a 34; encendiendo el interruptor `indeterminate` de la
demo, a 37; y el chip `vertical`, a 38 — **cada control comprobado por separado,
antes y después, porque montar más puede medir menos**. Aquí no tapó ninguno.
**Y una trampa nueva del instrumento, medida aquí**: una sonda que CACHEA los
nodos y luego mide en bucle da 8.352 falsos diffs si el HMR reemplaza el
subárbol a mitad de corrida — `getComputedStyle` sobre un nodo DESACOPLADO
devuelve la cadena VACÍA en todas las propiedades, y el diff lee cada vacío como
un cambio. Se arregla re-consultando los nodos en cada instantánea, y se detecta
haciendo fallar la sonda ante un computed vacío. Es pariente de
`hmr-stale-tab-phantom-findings`, con otra cara.
<!-- veredicto:end -->

Powered by TurnKey Linux.