9.9 KiB
menubar — 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: 88% — 23 de 26 knobs por token público
- Knobs de apariencia: 27 — público 23 · privado 0 · global 3 · literal 0 · sistema 1 · excepción 0 (los dos últimos, fuera del ratio)
- Contrato hoy (
lib/recipes/base.ts): 35 pública(s) —bg,border-fg,border-width,radius,gap-sm,gap-md,gap-lg,padding-sm,padding-md,padding-lg,height-sm,height-md,height-lg,trigger-fg,trigger-font-family,trigger-font-weight,trigger-line-height,trigger-font-size-sm,trigger-font-size-md,trigger-font-size-lg,trigger-padding-inline-sm,trigger-padding-inline-md,trigger-padding-inline-lg,open-trigger-bg,open-trigger-fg,disabled-trigger-fg,panel-z,panel-min-width,panel-max-height,panel-padding,panel-radius,panel-fg,panel-font-size-sm,panel-font-size-md,panel-font-size-lg - Eje
size: sí · ficheros:menubar.css
1. Knobs fuera de alcance
1.1 Directo a primitivo global (3)
| # | fichero:línea | selector | propiedad | valor |
|---|---|---|---|---|
| 1 | menubar.css:91 |
[data-menubar-trigger]:hover:not([data-disabled]), [data-menubar-trigger][data-highlighted]:not([data-disabled]) |
background |
var(--color-surface-overlay) |
| 2 | menubar.css:188 |
[data-menubar-panel] |
font-family |
var(--style-label-font-family) |
| 3 | menubar.css:190 |
[data-menubar-panel] |
line-height |
var(--leading-ui) |
1.2 A través de un privado (0)
Ninguno.
1.3 Literales (0)
Ninguno.
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 (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 | menubar.css:102 |
[data-menubar-trigger]: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? |
|---|---|---|---|---|
--_menubar-height |
3 | var(--menubar-height-md), var(--menubar-height-sm), var(--menubar-height-lg) |
public | sí |
--_menubar-padding |
3 | var(--menubar-padding-md), var(--menubar-padding-sm), var(--menubar-padding-lg) |
public | sí |
--_menubar-gap |
3 | var(--menubar-gap-md), var(--menubar-gap-sm), var(--menubar-gap-lg) |
public | sí |
--_menubar-radius |
1 | var(--menubar-radius) |
public | sí |
--_menubar-trigger-padding-inline |
3 | var(--menubar-trigger-padding-inline-md), var(--menubar-trigger-padding-inline-sm), var(--menubar-trigger-padding-inline-lg) |
public | sí |
--_menubar-font-size |
3 | var(--menubar-trigger-font-size-md), var(--menubar-trigger-font-size-sm), var(--menubar-trigger-font-size-lg) |
public | sí |
4. Propuesta de corrección
- Consume la capa compartida
list-surface. Un eje que la capa posee se consume comovar(--_x, var(--x)); el consumidor no acuña--menubar-{eje}para él — sería un vocabulario paralelo (README deeidos/components, «Capas compartidas» regla 2). - Consume la capa compartida
menu-indicator. Un eje que la capa posee se consume comovar(--_x, var(--x)); el consumidor no acuña--menubar-{eje}para él — sería un vocabulario paralelo (README deeidos/components, «Capas compartidas» regla 2). - 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 (3)
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 (--menubar-…) |
scope TSC | valor propuesto | usos |
|---|---|---|---|
hover-trigger-bg |
root |
var(--color-surface-overlay) |
1 |
panel-font-family |
root |
var(--style-label-font-family) |
1 |
panel-line-height |
root |
var(--leading-ui) |
1 |
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 — 15 % → 88 %, 30 claves nuevas (5 → 35). El 12 % restante son TRES filas que NO se acuñan, y ninguna es deuda de este eje.
Lo acuñado, por parte:
- La barra: superficie (
bg), marco (border-fg+border-width) yradius—uno solo, porque la receta lo desacopla de la talla a propósito (modelo Radix) y el motor de forma deriva el del disparador de él—, más las tres escalas por talla (gap-*,padding-*,height-*). - El disparador: tinta, tipografía (familia, peso, interlínea), la escala
de
font-sizey la depadding-inline, el par de la entrada ABIERTA (open-trigger-bg/-fg, que hoy comparten valor con el reposo pero son otro estado) y la tinta deshabilitada. - El Panel (la superficie
role=dialog, la alternativa al Content): sus cinco claves de siempre más la tinta y la escala defont-size.
Los pasos de talla leen el BUNDLE, no el primitivo: height-{k} →
--size-{k}-control-height y font-size-{k} → --size-{k}-font-size (regla
de recipe-css-contract; el CSS venía leyendo --control-height-md y
--font-size-md crudos). El calc(var(--space-1) * 0.5) del paso sm es
var(--space-0-5) exacto: mismo computado, un literal menos.
El hueco del panel deja de repetir el valor del padding: las tres reglas de
--floating-gap compensaban el sangrado del disparador dentro de la barra con
una COPIA del padding (--space-1, * 0.5, --space-2). Ahora leen
--menubar-padding-{k}, que es su fuente — si un tema ensancha el padding, el
panel deja de quedar a ras. No pueden leer el privado --_menubar-padding
porque esas superficies viajan POR PORTAL, y un privado declarado en la raíz no
cruza esa frontera (la misma ley que mató el eje size del color-picker).
Las dos que NO se acuñan, medidas: el plano overlay GANA a la receta.
Acuñé panel-font-family y panel-line-height, y el centinela las dio
muertas. La causa, medida: [data-depth='overlay'] declara las DOS con la
misma especificidad (0,1,0) y más tarde en la cascada, así que gana — mientras
panel-fg y panel-font-size-*, del MISMO bloque, sí alcanzan (el plano no
las declara). Retiradas del contrato y devueltas a su fuente literal con la
razón escrita en el CSS: es la cuarta prueba de §12.9, y la primera que da el
MECANISMO — no es que el plano imponga tipografía donde el componente calla,
es que gana donde el componente habla. Mientras §12.9 no se firme, un token de
tipografía en cualquier superficie overlay MIENTE.
La tercera es el hover, y es del OTRO eje. background: var(--color-surface-overlay)
sobre el disparador es una invención por componente Y mata la capa del sistema:
medido en hover, background-image computa none con --state-hover vivo.
Dos razones, las dos documentadas ya en navigation-menu.css, que retiró esta
misma regla: el shorthand resetea background-image, y la regla del arquetipo
va en :where() (especificidad CERO), así que cualquier regla del componente
la tapa. El eje navigation-menu tiene a menubar en su cola por esto mismo;
acuñar aquí un hover-trigger-bg habría fosilizado la invención.
Instrumento — dos cegueras arregladas antes de medir (la barra suelta DOS
superficies y sólo una lleva sus knobs): la sonda y el centinela abrían el
PRIMER disparador, que suelta un Content role=menu cuyo cromo es de
dropdown-menu; el Panel cuelga de la entrada «Format». Con OPEN_TRIGGER en la
sonda y openWith en el guard, el panel entra en la medida. Y el guard
necesitó openMarker: su comprobación de «ya está abierto» busca
[data-{c}-content], que aquí no existe, así que volvía a pulsar el
disparador antes de cada token y CERRABA el panel — media corrida contra una
superficie cerrada.
Verificación: diff de computed = 0 sobre 1.470 valores en 8 estados ·
centinela 34/35 (la deshabilitada, adjudicada por escrito) · censo 88 % ·
--names sin desviadas · component:audit PASS · rtl:check 0 ·
docs:check 0 · suite eidos con el rojo conocido (skin-media-player, ajeno) ·
check sin errores propios · captura de barra, tallas sm/lg, menú abierto y
panel.