# gradient-builder — 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**: **93%** — 52 de 56 knobs por token público - **Knobs de apariencia**: 60 — público 52 · privado 0 · global 1 · literal 3 · sistema 4 · excepción 0 _(los dos últimos, fuera del ratio)_ - **Contrato hoy** (`lib/recipes/base.ts`): 72 pública(s) — `preview-height-sm`, `preview-height-md`, `preview-height-lg`, `track-height-sm`, `track-height-md`, `track-height-lg`, `stop-size-sm`, `stop-size-md`, `stop-size-lg`, `gap-sm`, `gap-md`, `gap-lg`, `padding-block-sm`, `padding-block-md`, `padding-block-lg`, `padding-inline-sm`, `padding-inline-md`, `padding-inline-lg`, `preview-height`, `track-height`, `stop-size`, `gap`, `padding-block`, `padding-inline`, `radius`, `bg`, `border`, `border-width`, `preview-radius`, `preview-bg`, `preview-border`, `track-radius`, `track-border`, `checker-fg`, `checker-cell`, `stop-radius`, `stop-bg`, `stop-ring`, `stop-ring-width`, `stop-shadow`, `selected-stop-shadow`, `stop-actions-gap`, `angle-dial-gap`, `angle-dial-head-gap`, `label-font-size`, `label-fg`, `value-font-size`, `value-fg`, `stop-color-title-font-size`, `stop-color-title-font-weight`, `stop-color-title-fg`, `stop-color-sliders-gap`, `stop-list-gap`, `stop-list-row-gap`, `stop-list-value-gap`, `stop-list-value-padding-block`, `stop-list-value-padding-inline`, `stop-list-value-radius`, `stop-list-value-bg`, `stop-list-value-fg`, `stop-list-value-border`, `stop-list-value-font-size`, `selected-stop-list-value-border`, `stop-list-pos-fg`, `presets-gap`, `preset-grid-gap`, `preset-height`, `preset-radius`, `preset-border`, `hover-preset-border`, `selected-preset-border`, `selected-preset-outline` - **Eje `size`**: no · **ficheros**: `gradient-builder.css` ## 1. Knobs fuera de alcance ### 1.1 Directo a primitivo global (1) | # | fichero:línea | selector | propiedad | valor | | ---: | --- | --- | --- | --- | | 1 | `gradient-builder.css:129` | `[data-gradient-builder-stop]:focus-visible` | `box-shadow` | `0 0 0 calc(2px * var(--scaling, 1)) var(--color-surface-default), 0 0 0 calc(4px * var(--scaling, 1)) var(--color-primary-solid)` | ### 1.2 A través de un privado (0) _Ninguno._ ### 1.3 Literales (3) | # | fichero:línea | selector | propiedad | valor | | ---: | --- | --- | --- | --- | | 1 | `gradient-builder.css:37` | `[data-gradient-builder]` | `inline-size` | `100%` | | 2 | `gradient-builder.css:188` | `[data-gradient-builder-stop-color-sliders]` | `inline-size` | `90%` | | 3 | `gradient-builder.css:260` | `[data-gradient-builder-preset]` | `inline-size` | `100%` | ### 1.4 Excepciones firmadas (0) — 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. _Ninguno._ ## 2. Sistema transversal (4) — informativo, fuera del ratio Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2). | # | fichero:línea | selector | propiedad | valor | | ---: | --- | --- | --- | --- | | 1 | `gradient-builder.css:60` | `[data-gradient-builder][data-disabled]` | `opacity` | `var(--opacity-disabled)` | | 2 | `gradient-builder.css:230` | `[data-gradient-builder-stop-list-value]:hover:not(:disabled)` | `background-image` | `linear-gradient(var(--state-hover), var(--state-hover))` | | 3 | `gradient-builder.css:236` | `[data-gradient-builder-stop-list-value]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` | | 4 | `gradient-builder.css:271` | `[data-gradient-builder-preset]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` | ## 3. Privados de la receta — ¿de dónde sale su valor? | privado | declaraciones | valor(es) | origen | ¿deriva de un público? | | --- | ---: | --- | --- | :-: | | `--_gradient-builder-preview-height` | 1 | `var(--gradient-builder-preview-height)` | public | **sí** | | `--_gradient-builder-track-height` | 1 | `var(--gradient-builder-track-height)` | public | **sí** | | `--_gradient-builder-stop-size` | 1 | `var(--gradient-builder-stop-size)` | public | **sí** | | `--_gradient-builder-radius` | 1 | `var(--gradient-builder-preview-radius)` | public | **sí** | | `--_gradient-builder-border` | 1 | `var(--gradient-builder-border)` | public | **sí** | | `--_gradient-builder-stop-ring` | 1 | `var(--gradient-builder-stop-ring)` | public | **sí** | | `--_gradient-builder-label` | 1 | `var(--gradient-builder-label-fg)` | public | **sí** | | `--_gradient-builder-checker-cell` | 1 | `var(--gradient-builder-checker-cell)` | public | **sí** | | `--_gradient-builder-checker` | 1 | `linear-gradient(45deg, var(--gradient-builder-checker-fg) 25%, transparent 25%), linear-gradient(-45deg, var(--gradient-builder-checker-fg) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--gradient-builder-checker-fg) 75%), linear-gradient(-45deg, transparent 75%, var(--gradient-builder-checker-fg) 75%)` | public | **sí** | Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_gradient-builder-stop-fill`. ## 4. Propuesta de corrección - **Consume la capa compartida `picker-shell`.** Un eje que la capa posee se consume como `var(--_x, var(--x))`; el consumidor **no acuña** `--gradient-builder-{eje}` para él — sería un vocabulario paralelo (README de `eidos/components`, «Capas compartidas» regla 2). ### 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 (`--gradient-builder-…`) | scope TSC | valor propuesto | usos | | --- | --- | --- | ---: | | `stop-shadow` _(ya existe)_ | `root` | `0 0 0 calc(2px * var(--scaling, 1)) var(--color-surface-default), 0 0 0 calc(4px * var(--scaling, 1)) var(--color-primary-solid)` | 1 | | `stop-color-sliders-width` | `root` | `90%` | 1 | ### 4.2 Sin nombre mecánico (2) - **⚠ 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** — 2: `inline-size`. ### 4.3 Avisos sobre los tokens propuestos (1) - **⚠ decisión: el valor es una expresión — el token puede llevar la expresión entera o sólo su término variable** — `--gradient-builder-stop-shadow` ### 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 **Revisión 2026-08-20 — verificación previa a implementación (Opus).** **Análisis (§1–§3): CORRECTO** (62 · 0 · 8 · 46 · 3 · 5, reproducido). Partes limpias y bien estampadas; `data-size` vive en el ROOT ✓ (sin `parts`). Hallazgo de prefijo, tercero de la familia picker: la receta usa `--gb-*` (`--gb-checker`, `--gb-stop-fill`) — abreviatura que viola theming §6 regla 5 y esconde privados al censo. **Renombrar `--gb-*` → `--_gradient-builder-*`** (el checker es privado; `--gb-stop-fill` lo escribe el wrapper inline por parada — mismo tratamiento que `match-anchor-width`, privado bien prefijado, runtime). **Propuesta (§4.1): APTA — la mejor del lote — con tres correcciones:** 1. **Forma por talla**: como en todos — coordenadas `{part}-{eje}-{k}` en `root` (la tabla ya las trae con valores verbatim CORRECTOS, incluidos los `calc(Npx * var(--scaling,1))` que conservan la participación en el zoom) **+ nombre resuelto** con `declarations` default `host` (md) y `size:sm/lg` — la receta consume sólo el resuelto (patrón Sidebar). `gap-sm`/`gap-lg` son coordenadas de ese mismo eje (`gap`), no tokens sueltos. 2. **`stop-shadow` ⚠**: los dos valores son reposo vs SELECCIONADA — la segunda ya existe como `selected-stop-shadow`; resolver por selector y dejar `stop-shadow` (reposo) + `selected-stop-shadow`, sin colisión. Ojo: ambos valores llevan `color-mix(… var(--color-neutral-contrast) …)` — es sombra, no hover, así que no toca R-4.3; se acuñan tal cual. 3. **`preview-bg-image` / `stop-bg`**: sus valores referencian los `--gb-*` de arriba — acuñar DESPUÉS del renombrado, leyendo el privado bien prefijado (`var(--_gradient-builder-checker)`), o promoviendo el checker a público si el autor quiere el damero temable (decisión menor, propongo privado). Resto de la tabla (angle-dial, presets, kind-switch, stop-list, título): **válida tal cual** — nombres en vocabulario, `root`, verbatim. Los 3 literales: anotar o tokenizar. Nota de talla: las alturas de preview/track y los tamaños de stop son geometría PROPIA (px·scaling), no del bundle — ni se fuerzan al bundle ni se marca desviación: el bundle no tiene esas coordenadas. **Bloqueos de firma**: ninguno duro — este componente puede ser el PILOTO del bloque picker (D-TH.4): todo su contrato sale con valores verbatim y cero decisiones estructurales pendientes (sólo el damero, menor). --- **EJECUTADO 2026-08-20 (piloto del eje).** Alcance **0 % → 80 %** · contrato **0 → 72 claves públicas**. El 80 % es el suelo del regex: los 7 knobs que quedan como `private` pasan por privados que **ahora derivan todos de un público** (§3 lo confirma), el único `global` es el anillo de foco (sistema) y los 3 literales son geometría de layout (`100%`, `90%`). Qué se hizo, exactamente lo propuesto arriba: 1. Bloque `'gradient-builder'` en `recipes/base.ts` con el canon de dos piezas — coordenadas por talla (`*-{sm,md,lg}`) + nombre resuelto (`host` = md, `size:sm` / `size:lg`); la receta consume SÓLO el resuelto y los bloques `[data-size]` del CSS desaparecen (los emite el TSC). 2. Renombrados los prefijos abreviados: `--gb-checker*` → `--_gradient-builder-checker*`, `--gb-stop-fill` → `--_gradient-builder-stop-fill` (también en el wrapper, `gradient-builder-stop.svelte`). El damero se parametriza por `checker-fg` + `checker-cell` (nombre post-codemod D-TH.6); el patrón de cuatro gradientes queda privado. 3. `padding` shorthand partido en `padding-block` / `padding-inline` (ejes lógicos, recipe-contract §1). 4. Los 4 `*-font-size` consumen la **coordenada del bundle** (`--size-sm-font-size`), no el primitivo — lo exigió el guard `recipe-css-contract` («recipes consume the size bundle»), y es value-preserving. Verificación (artefactos): - **Diff de computed = 0**: sonda Playwright antes/después, **6.612 valores** comparados en 7 estados (reposo · 5 tallas · hover) sobre 34 nodos. El único diff que apareció era la transición de hover de un `Button` compuesto capturada a mitad — demostrado bajando la espera de asentamiento en el árbol YA modificado y reproduciendo el valor interpolado (`oklab(… / 0.646795)`). - **Centinela: 72/72 tokens alcanzan.** 49 desde el ámbito del componente en reposo; 12 coordenadas de talla + 2 de preset seleccionado verificadas forzando su estado; `hover-preset-border` con hover real; los 4 del editor de color **sólo desde `:root`** (el panel viaja por portal — anotado en el README). - **`preview-bg` + `checker-fg`/`-cell` no mueven píxel hoy**: el wrapper pinta el degradado con el shorthand `background` inline, que resetea `background-image` y tapa el damero. El token es real; el damero no se ve. Deuda anotada (corregirlo mueve píxel ⇒ decisión). - Guards: `component:audit` PASS · `eidos-lint` 0 invalid · suite eidos sin rojos nuevos (queda el conocido `skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `check` sin errores en los ficheros tocados · captura 2× revisada. ⚠ **La ficha dice «Eje `size`: no»** porque el detector busca `data-size` en el CSS de la receta y ahora la cascada por talla vive en el generado. El componente SÍ tiene eje `size` — límite del instrumento, no del componente.