8.3 KiB
search-field — 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-25 · Alcance: 100% — 10 de 10 knobs por token público
- Knobs de apariencia: 11 — público 10 · privado 0 · global 0 · literal 0 · sistema 1 · excepción 4 · estructural 0 · puente 0 · canal 0 (los cinco últimos, fuera del ratio)
- Contrato hoy (
lib/recipes/base.ts): 17 pública(s) —clear-hidden-opacity,icon-size-xs,icon-size-sm,icon-size-md,icon-size-lg,icon-size-xl,icon-fg,icon-margin-inline-end,loading-indicator-size-xs,loading-indicator-size-sm,loading-indicator-size-md,loading-indicator-size-lg,loading-indicator-size-xl,loading-indicator-fg,loading-indicator-track,loading-indicator-thickness,loading-indicator-duration - Eje
size: sí · ficheros:search-field.css
1. Knobs fuera de alcance
1.1 Directo a primitivo global (0)
Ninguno.
1.2 A través de un privado (0)
Ninguno.
1.3 Literales (0)
Ninguno.
1.4 Excepciones firmadas (4) — 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 | search-field.css:68 |
[data-search-field-icon] |
line-height |
1 |
| 2 | search-field.css:86 |
[data-search-field-loading-indicator]::before |
inline-size |
100% |
| 3 | search-field.css:87 |
[data-search-field-loading-indicator]::before |
block-size |
100% |
| 4 | search-field.css:88 |
[data-search-field-loading-indicator]::before |
border-radius |
50% |
2. Sistema transversal (1) — 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 | search-field.css:105 |
[data-search-field-loading-indicator]::before |
opacity |
var(--opacity-muted) |
3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
|---|---|---|---|---|
--_search-field-icon-size |
5 | var(--search-field-icon-size-md), var(--search-field-icon-size-xs), var(--search-field-icon-size-sm), var(--search-field-icon-size-lg), var(--search-field-icon-size-xl) |
public | sí |
--_search-field-loading-size |
5 | var(--search-field-loading-indicator-size-md), var(--search-field-loading-indicator-size-xs), var(--search-field-loading-indicator-size-sm), var(--search-field-loading-indicator-size-lg), var(--search-field-loading-indicator-size-xl) |
public | sí |
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 guardrecipe-css-contractprohíbe el primitivo).
4.1 Tokens a declarar en lib/recipes/base.ts (1)
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 (--search-field-…) |
scope TSC | valor propuesto | usos |
|---|---|---|---|
loading-indicator-radius |
root |
50% |
1 |
4.2 Sin nombre mecánico (3)
- ⚠ decisión:
1es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar — 1:line-height. - ⚠ 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,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, 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
Ejecutado 2026-08-23 — 71 % → 100 %, y CERO claves nuevas. Diff de computed 0
sobre 1.216 valores en 8 estados (5 nodos); R-5.4 de 7/17 a 17/17 sin una sola
adjudicación, dos corridas del mismo código de acuerdo; capturas 2×
antes/después idénticas byte a byte (a7a83949…).
Su deuda eran CUATRO literales que son identidad, y ahora van firmados con
/* literal: … */ (válvula de recipe-contract §3): el line-height: 1 del icono
—un glifo solo no tiene interlineado, la misma anotación que su hermana
field.css—, el 100% doble del ::before —el anillo ES la caja del indicador,
que ya mide por --_search-field-loading-size— y el border-radius: 50%. Este
último la §4 pedía acuñarlo como loading-indicator-radius: sería un knob que
sólo puede romper el componente. El arco es un BORDE que gira; con cualquier
otro valor la ruleta deja de leerse como una. Es la clase 4 de F2-B, «el 50 % de
un círculo», al pie de la letra.
Diez de sus diecisiete tokens no se habían medido nunca, y ninguno miente.
El centinela leía 7/17 porque la demo no monta dos estados: el indicador de carga
sólo existe con loading (ocho tokens) y la X sólo se desvanece con el campo
VACÍO, y el escenario arranca con valor (clear-hidden-opacity). Los dos
interruptores viven ya en el guard (prepareWith), y el orden importa: el clic
en la X tiene que ir PRIMERO, porque la propia regla que enciende le quita
después el pointer-events al botón que la disparó. La sonda necesita el
interruptor de loading por lo mismo: 4 → 5 nodos, y sin perder ninguno.
Los dos privados NO son deuda: --_search-field-icon-size y
--_search-field-loading-size son el conmutador por talla y sus cinco fuentes
son, cada una, una clave pública. Se dejan como están: 58 de las 162 recetas
resuelven así su eje size —incluidas las cerradas al 100 % este mismo mes
(empty-state, banner)—, y convertirlas al ámbito size: del TSC cambiaría la
especificidad de la emisión sin ganar un punto de alcance.
Casi nada de su superficie es suyo, y está bien: el cromo de la fila (borde,
radio, fondo, altura, padding, hueco, tipografía del valor, foco, inválido,
deshabilitado) es de field.css por [data-field-control], y el de la X es de
button.css. La receta lo dice en su cabecera y no realiasa NADA. Fuera del
ratio queda además --opacity-muted del indicador bajo
prefers-reduced-motion, que es capa de sistema.
⚠ Un default medido, ANOTADO, no cambiado: las dos escalas del mismo renglón
divergen en los dos últimos peldaños. Medido en Chrome con transiciones y
animación congeladas, forzando data-size sobre el nodo real —icono →
indicador—: xs 14→14, sm 16→16, md 18→18, lg 20→18, xl 32→20. El contrato
lo declara así de frente: loading-indicator-size-lg apunta a
--size-md-icon-size y -xl a --size-lg-icon-size, mientras la escala del
icono sube peldaño a peldaño. En xl la ruleta es DOCE píxeles menor que la lupa
que tiene al lado. No es deuda de alcance —las dos escalas son públicas y las
diez claves alcanzan—, es una decisión de diseño que huele a copia que se quedó a
medias. Cambiarla mueve píxel: D-TH.5 la saca de este eje.