13 KiB
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 · método y protocolo: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 · 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: <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.
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 comovar(--_x, var(--x)); el consumidor no acuña--gradient-builder-{eje}para él — sería un vocabulario paralelo (README deeidos/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, unbackgrounden 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:
- Forma por talla: como en todos — coordenadas
{part}-{eje}-{k}enroot(la tabla ya las trae con valores verbatim CORRECTOS, incluidos loscalc(Npx * var(--scaling,1))que conservan la participación en el zoom) + nombre resuelto condeclarationsdefaulthost(md) ysize:sm/lg— la receta consume sólo el resuelto (patrón Sidebar).gap-sm/gap-lgson coordenadas de ese mismo eje (gap), no tokens sueltos. stop-shadow⚠: los dos valores son reposo vs SELECCIONADA — la segunda ya existe comoselected-stop-shadow; resolver por selector y dejarstop-shadow(reposo) +selected-stop-shadow, sin colisión. Ojo: ambos valores llevancolor-mix(… var(--color-neutral-contrast) …)— es sombra, no hover, así que no toca R-4.3; se acuñan tal cual.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:
- Bloque
'gradient-builder'enrecipes/base.tscon 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). - 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 porchecker-fg+checker-cell(nombre post-codemod D-TH.6); el patrón de cuatro gradientes queda privado. paddingshorthand partido enpadding-block/padding-inline(ejes lógicos, recipe-contract §1).- Los 4
*-font-sizeconsumen la coordenada del bundle (--size-sm-font-size), no el primitivo — lo exigió el guardrecipe-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
Buttoncompuesto 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-bordercon 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/-cellno mueven píxel hoy: el wrapper pinta el degradado con el shorthandbackgroundinline, que reseteabackground-imagey tapa el damero. El token es real; el damero no se ve. Deuda anotada (corregirlo mueve píxel ⇒ decisión).- Guards:
component:auditPASS ·eidos-lint0 invalid · suite eidos sin rojos nuevos (queda el conocidoskin-media-player) ·rtl:check0 ·docs:check0 ·checksin 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.