11 KiB
navigation-menu — 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% — 33 de 33 knobs por token público
- Knobs de apariencia: 37 — público 33 · privado 0 · global 0 · literal 0 · sistema 4 · excepción 1 (los dos últimos, fuera del ratio)
- Contrato hoy (
lib/recipes/base.ts): 44 pública(s) —content-z,indicator-w,indicator-thickness,indicator-bg,indicator-radius,trigger-height-sm,trigger-height-md,trigger-height-lg,trigger-font-size-sm,trigger-font-size-md,trigger-font-size-lg,trigger-line-height-sm,trigger-line-height-md,trigger-line-height-lg,trigger-padding-inline-sm,trigger-padding-inline-md,trigger-padding-inline-lg,trigger-radius-sm,trigger-radius-md,trigger-radius-lg,trigger-height,trigger-font-size,trigger-line-height,trigger-padding-inline,trigger-radius,trigger-fg,active-trigger-fg,active-trigger-bg,disabled-trigger-fg,trigger-font-family,trigger-gap,chevron-size,chevron-stroke,list-gap-sm,list-gap-md,list-gap-lg,list-gap,content-min-width,content-padding,content-radius,content-fg,content-link-padding-block,content-link-padding-inline,content-link-radius - Eje
size: no · ficheros:navigation-menu.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 (1) — 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 | navigation-menu.css:360 |
[data-navigation-menu-indicator][data-state='open'] |
opacity |
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 | navigation-menu.css:130 |
[data-navigation-menu-trigger]:focus-visible, [data-navigation-menu-link]:focus-visible |
outline |
var(--focus-ring-width) solid var(--focus-ring-color) |
| 2 | navigation-menu.css:151 |
[data-navigation-menu-trigger]::after |
opacity |
var(--opacity-muted) |
| 3 | navigation-menu.css:232 |
[data-navigation-menu-content] :is(a, [data-navigation-menu-link]):hover |
background-image |
linear-gradient(var(--state-hover), var(--state-hover)) |
| 4 | navigation-menu.css:236 |
[data-navigation-menu-content] :is(a, [data-navigation-menu-link]):focus-visible |
outline |
var(--focus-ring-width) solid var(--focus-ring-color) |
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
- Consume la capa compartida
menu-indicator. Un eje que la capa posee se consume comovar(--_x, var(--x)); el consumidor no acuña--navigation-menu-{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 (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 (--navigation-menu-…) |
scope TSC | valor propuesto | usos |
|---|
4.2 Sin nombre mecánico (1)
- ⚠ 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:opacity.
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 — 44 % → 100 %. Contrato 26 → 44 claves (19 nuevas, una retirada). Diff de computed 0 sobre 2.752 valores en 8 estados; guard R-5.4 43/44 con una adjudicación escrita. Bloque B1 de D-TH.4.
Los 19 knobs, y de dónde salió cada nombre. Ninguno se inventó: el catálogo
los tenía. dropdown-menu / context-menu (los gemelos cerrados el mismo día)
dan content-{min-width,padding,radius,fg} y chevron-{size,stroke};
accordion da trigger-font-family y disabled-trigger-fg; nav-tree y
card-group confirman que el tamaño de un chevron es un literal en las
cuatro recetas que lo declaran (0.42em, 0.5em, 0.625rem) — un glifo no es
un paso de la escala de espacio. Al revés con el subrayado: 2px ES
--border-width-medium, y el precedente dropdown-menu.separator-thickness
dice que un grosor decorativo se nombra por esa familia.
Lo que la §4 generada proponía, y por qué NO se siguió — cinco fallos, uno
nuevo. Llamaba trigger-width / trigger-height al CHEVRON (nombrando por el
primer selector de la regla, […-trigger]::after, y colisionando con
trigger-height, que ya existe y es la altura del control); fundía en
content-radius dos nodos con dos valores (panel --radius-lg y fila
--radius-md); cruzaba los nombres (content-padding-* para lo que es de la
FILA, y el padding real del panel a §4.2 «sin nombre mecánico»); llamaba
content-width a un min-inline-size. Y el fallo NUEVO: abría pidiendo
«consume la capa compartida menu-indicator» — homonimia: esa capa es el
CHECK de los CheckboxItem de dropdown/context-menu, no tiene nada que ver con
el subrayado de esta barra. LAYER_VOCABULARY sólo registra calendar-surface,
y nav-menu no consume ninguna capa.
Las filas del panel no son item- ni link-. En los gemelos, item ES la
fila; aquí item es el <li> de la barra (parte del morfo) y el Link de barra
viste el cromo del trigger. Un --navigation-menu-item-radius o un
--navigation-menu-link-radius prometerían a un tema un nodo que no alcanzan.
Se acuñan como content-link-* — el nodo, nombrado por donde vive. Su tinta
sigue leyendo trigger-fg, como venía (D-TH.5: el default no se mueve).
El privado era una ESCALA, no un conmutador. --_navigation-menu-list-gap
tenía tres fuentes (0 / --space-1 / --space-2) por data-size: eso sube
al TSC como list-gap-{sm,md,lg} + el resuelto, el mismo molde que el bundle
del trigger ya tenía al lado. Los dos bloques [data-size] del CSS se retiran
(los emite el TSC; dejarlos vivos da un diff de 0 por la ruta vieja).
indicator-x era declaración MUERTA y se retiró. Soma escribe las cuatro
coordenadas del rect INLINE sobre el indicador (-x, -y, -w, -h); la
receta lee sólo -w y -h, y su propio comentario registra que sumar
translateX(var(--…-x)) DUPLICABA el desplazamiento. Nadie la leía. Retirada,
0 diffs — la prueba de que estaba muerta (precedente: el re-point de
--command-radius, el trío item-gap* de carousel). -w se queda: vive, y es
canal de valor, no superficie de tema.
El panel NO acuña tipografía, y es a propósito. Lleva
[data-depth='overlay'], y ese plano declara font-family / line-height con
la misma especificidad (0,1,0) y más tarde en la cascada: un token de la receta
ahí leería muerto. Es la firma §12.9 pendiente, la misma que hizo retirar claves
en menubar, dropdown-menu, context-menu y tooltip. Lo que sí se acuña es la
familia de la BARRA (trigger-font-family), que no es overlay y alcanza.
Lo que el instrumento aprendió aquí (tres cosas, todas medidas):
- La sonda y el centinela eran ciegos a la mitad del componente. Las filas
del mega-menú son los
<a>DESNUDOS del consumidor, sin atributo propio: el filtrodata-{c}-*veía 10 nodos y ninguno era el panel ni una fila — 9 de los 19 knobs quedaban fuera del diff. ConEXTRA_NODES/extraNodesla sonda pasa a 16 nodos abiertos. Y el pasoopende la sonda no honrabaEXTRA_NODES: ahora sí. - El guard cerraba el panel él mismo. Este mega-menú abre por
pointerenterypointerleaveprograma el cierre; el guard abría con clic y después aparcaba el puntero en (0,0) — que es exactamente lo que lo cierra. Dos corridas del mismo código discrepaban encontent-link-padding-inline. ConopenBy: 'hover'el cursor se queda en el trigger: dos corridas idénticas, 43/44. El síntoma tenía además una cara de falso POSITIVO: ese mismo token leía «vivo» antes de ver las filas, porque su padding ensancha el panel que sí se medía. colorestá en transición en esta parte, así que forzardata-disabledy leer en el acto devuelve el valor VIEJO: tres diagnósticos falsos («ni el inline lo mueve») hasta congelartransition. Es la trampa del cuentagotas del color-picker, en otro componente. Congelado: reposooklch(0.2435 0 0)→ disabledoklch(0.61 0 0), y el centinela lo mueve argb(1,2,3)en el trigger y en el Link.
La adjudicación (1): disabled-trigger-fg — la demo no monta ningún trigger
ni Link deshabilitado; forzado alcanza en las dos partes. En el ledger.
Una nota de contabilidad, no del componente: esta ficha dice ahora «eje
size: no». El censo detecta el eje grepeando data-size en el CSS de la
receta, y el protocolo §7.4 ORDENA retirar esos bloques cuando la cascada la
emite el TSC — así que todo componente que aplique bien el protocolo aparecerá
sin eje. Ya le pasa a sidebar y nav-tree. Anotado en next-features §13; el
eje existe y se emite en generated/base.css.
Verificación: sonda antes/después 0 diffs (2.752 valores · 8 estados ·
16 nodos con el panel abierto) · R-5.4 43/44, dos corridas idénticas ·
component:audit PASS · censo 100 % · eidos-lint 30 morfo-backed / 3
eidos-only / 0 invalid, 0 class-hooks · rtl:check 0 · docs:check 0 ·
suite eidos 439/440 (el único rojo es skin-media-player, el conocido) ·
check sin error nuevo en los ficheros tocados (el de sideOffset en
navigation-menu-content.svelte es preexistente, commit 3097cfcb6) ·
verificado en Chrome con el panel abierto (panel 8/10px/224px · fila 8/12/6px ·
trigger 36px/6px/gap 4px · indicador 2px). Tras el cambio del guard,
command, table y tooltip re-verificados sin regresión ni STALE.