|
|
# 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-22 · **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 _(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%` |
|
|
|
|
|
|
## 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
|
|
|
|
|
|
<!-- veredicto:start -->
|
|
|
|
|
|
**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.
|
|
|
|
|
|
<!-- veredicto:end -->
|