8.8 KiB
emoji-picker — 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-24 · Alcance: 100% — 22 de 22 knobs por token público
- Knobs de apariencia: 26 — público 22 · privado 0 · global 0 · literal 0 · sistema 4 · excepción 3 (los dos últimos, fuera del ratio)
- Contrato hoy (
lib/recipes/base.ts): 21 pública(s) —trigger-size,open-trigger-fg,panel-width,panel-gap,header-gap,grid-gap,grid-max-block-size,grid-padding-inline,grid-padding-block-end,option-radius,option-font-size,tone-marker-size,tone-marker-radius,tone-marker-bg,footer-gap,footer-border-fg,preview-gap,preview-glyph-size,preview-name-font-size,preview-name-fg,preview-min-block-size - Eje
size: no · ficheros:emoji-picker.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 (3) — 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 | emoji-picker.css:64 |
[data-emoji-picker-content] |
inline-size |
100% |
| 2 | emoji-picker.css:111 |
[data-emoji-picker-content] [data-emoji-picker-option] |
line-height |
1 |
| 3 | emoji-picker.css:155 |
[data-emoji-picker-preview] > :first-child |
line-height |
1 |
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 | emoji-picker.css:38 |
[data-emoji-picker-trigger]:hover:not([data-disabled]) |
background-image |
linear-gradient(var(--state-hover), var(--state-hover)) |
| 2 | emoji-picker.css:42 |
[data-emoji-picker-trigger]:focus-visible |
outline |
var(--focus-ring-width) solid var(--focus-ring-color) |
| 3 | emoji-picker.css:48 |
[data-emoji-picker-trigger][data-state='open'] |
background-image |
linear-gradient(var(--state-hover), var(--state-hover)) |
| 4 | emoji-picker.css:53 |
[data-emoji-picker-trigger][data-disabled] |
opacity |
var(--opacity-disabled) |
3. Privados de la receta — ¿de dónde sale su valor?
La receta no declara privados propios en su CSS.
4. Propuesta de corrección
4.1 Tokens a declarar en lib/recipes/base.ts (0)
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 (--emoji-picker-…) |
scope TSC | valor propuesto | usos |
|---|
4.2 Sin nombre mecánico (3)
- ⚠ 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 — 1:inline-size. - ⚠ 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 — 2:line-height.
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 — 61 % → 100 %. Siete claves nuevas, tres retiradas por muertas, tres literales firmados y cinco adjudicaciones medidas. Contrato 17 → 21. Diff de computed 0 sobre 6.080 valores en 8 estados (176 nodos con el panel abierto), capturas 2× antes/después idénticas.
Las siete, con el nombre que dio el catálogo: open-trigger-fg
(accordion lo nombra igual), grid-padding-inline + grid-padding-block-end
(el eje lógico completo, como en card-group / combobox / command) y las
tres del marcador de tono, la familia timeline.marker-{size,radius,bg}. Se
llama tone-marker-* y no marker-* porque el pie lleva además un selector de
TONO: marker a secas sería ambiguo dentro del propio componente.
TRES CLAVES RETIRADAS — el cromo de reposo del trigger no es suyo. El
trigger lleva también data-popover-trigger, y
[data-popover-trigger]:not([data-archetype='field-trigger']) es (0,2,0)
contra el (0,1,0) de esta receta: el radio, la tinta y el tamaño de letra los
pinta popover.css. Medido: mover --emoji-picker-trigger-{fg,radius,font-size}
no cambiaba NADA. Retiradas con sus declaraciones, 0 diffs, que es la prueba
de que estaban muertas. Es el defecto que gradient-picker documentó (36 de sus
45 tokens) y la misma respuesta. Sobreviven dos, y por razones distintas:
trigger-size porque popover dimensiona con height y no con el par lógico, y
open-trigger-fg porque su selector añade [data-state='open'] y gana por
orden. trigger-fg no llegó a existir: se acuñó y se midió muerta en el
mismo pase.
NOTA 2026-08-25 — la PREMISA de ese párrafo caducó; la retirada sigue siendo correcta. El sobre de trigger ya no es (0,2,0): la firma §55 «el sobre de trigger de popover es el SUELO» lo emite desde
:where(...), (0,0,0), así que hoy PIERDE contra el (0,1,0) de esta receta. Las tres claves siguen retiradas y no hay defecto visual —la receta calla y el suelo pinta, que es su trabajo—, pero si volvieran ganarían: la razón por la que estaban muertas ya no es la que dice arriba. Se anota en vez de reescribirse porque la medida de entonces fue correcta con la cascada de entonces.
Cinco adjudicaciones, todas medidas forzando sobre nodos reales: las tres
del marcador de tono (la demo monta cero celdas data-tone-capable; forzado
el atributo sobre una celda real, el ::after alcanza — 4px → 37px, 9999px →
37px, y el fondo), preview-name-fg (los hijos de la vista previa son <span>
desnudos, sin atributo del componente, así que quedan fuera del conjunto
medido) y open-trigger-fg, que es el caso interesante.
⚠ open-trigger-fg OSCILA entre corridas: 17/21, 16/21, 16/21 con el mismo
código. Medido vivo cuatro veces —la última replicando paso a paso el
staticPass del propio guard, que movió a la primera talla—, así que el token
no miente: lo que falla es el instrumento. La causa: el panel se cierra en algún
punto de la pasada y, durante la animación de SALIDA, el content sigue en el DOM
mientras el trigger ya perdió data-state='open' — que es lo que la regla
selecciona; reopen() mira el CONTENT, cree que está abierto y no reabre. Probé
un openMarker sobre el trigger y no basta (el clic de reapertura cae en esa
misma ventana), así que lo retiré en vez de dejar un override que no arregla. El
arreglo real es que reopen() espere a la CONDICIÓN en vez de dormir un número
fijo — §13. Su entrada de ledger saldrá STALE en las corridas donde alcanza,
y eso está escrito en la propia razón para que nadie la borre por ello.
Y el instrumento de captura estaba roto desde siempre: __shot.ts tenía el
puerto 5173 cableado, así que cualquier sesión en otro puerto moría con
ERR_EMPTY_RESPONSE. Es probablemente por lo que los pasos 6 y 13 del protocolo
se saltaron en 33 componentes. Ahora toma la URL como argumento y un --open
opcional para los componentes cuya superficie sólo existe abierta.
Verificación: sonda antes/después 0 diffs (6.080 valores · 8 estados) ·
R-5.4 17/21 con las cinco adjudicadas, exit 0 · component:audit PASS · censo
100 % · eidos-lint 17 morfo-backed / 0 invalid, 0 class-hooks ·
rtl:check 0 · docs:check 0 · capturas 2× antes y después, miradas.