# 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-23 · **Alcance**: **87%** — 26 de 30 knobs por token público - **Knobs de apariencia**: 30 — público 26 · privado 2 · global 2 · literal 0 · sistema 0 · excepción 6 _(los dos ú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 (2) | # | 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%)` | ### 1.3 Literales (0) _Ninguno._ ### 1.4 Excepciones firmadas (6) — fuera del ratio Literales que llevan su anotación `/* literal: */` 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._ ## 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 (6) - **⚠ 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`. - **⚠ decisión: el privado que alimenta este knob no se declara en el CSS (viene de `base.ts` o de un estilo inline) — hay que resolverlo antes de nombrarlo** — 2: `inline-size`, `block-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 **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: */` 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.