uix(toolbar): temable — 91 % → 94 % CERRADO, y ceder el cromo del botón SALE GRATIS

Cero claves acuñadas, y eso es el resultado: el censo daba `global 0` — la
receta ya lee su propio contrato en cada declaración, con el eje `size`
resuelto por el TSC a través de siete privados que sólo reapuntan claves
públicas por talla. Lo que quedaba fuera del alcance eran tres cosas
CLASIFICADAS, no tres deudas. La mitad del trabajo de este eje es adjudicar.

1. Los DOS privados son CONMUTADORES por variante, medidos variante a variante
   sobre el nodo real: `--_toolbar-bg` vale `var(--toolbar-bg)`
   (oklch(0.9821 0 0)) en `surface` y `transparent` en `outline` y `ghost`;
   `--_toolbar-shadow` vale `var(--toolbar-shadow)` y `none` en las mismas. Ese
   segundo valor es la IDENTIDAD de la variante, no un knob: una barra `ghost`
   ES la que no tiene relleno ni sombra, y un público encima dejaría que un tema
   la rellenara y matara la variante en silencio. Se quedan privados (clase
   «conmutador» de F2-B, sexto caso tras textarea, spinner, skeleton, code y
   label). Ni escala por talla ni puente THM-2: esta barra no tiene paleta.

2. El único literal es una IDENTIDAD y ahora está FIRMADO: el
   `inline-size: 100%` del separador horizontal abarca el eje transversal de la
   barra — el mismo trabajo que `align-self: stretch` hace en el gemelo vertical
   (medido: 112 px sobre un contenedor de 126 px). Su grosor y su suelo sí son
   tokens. Con su anotación `/* literal: */` sale del ratio.

3. LA CASCADA DEL COMPONENTE COMPUESTO NO LE CUESTA UNA SOLA CLAVE, y es la
   lección. `Toolbar.Button` compone el `<Button>` canónico, así que sus nodos
   llevan `data-button` Y `data-toolbar-button`, y `[data-button]` casa con la
   misma especificidad (0,1,0) pero se emite DESPUÉS. La diferencia con `form`
   —que conserva 24 declaraciones que Button anula— es que esta receta YA CEDIÓ
   el cromo del botón (A-112) y no escribe ni una regla `[data-toolbar-button]`.
   Medido ANTES de tocar nada: 51/54 vivos, y ninguna de las tres silenciosas
   era del botón. Las claves `control-*` visten el Link y el GroupItem, que sí
   son superficie de la barra.

Censo: 35 → 34 knobs · público 30 · privado 2 · literal 1 → 0 · excepción
0 → 1 · sistema 2 · 91 % → 94 %. Contrato 54 claves, intacto. Global del
catálogo 68 %, sin bajar.

Diff de computed: VACÍO — 2.464 valores en 7 estados sobre 11 nodos (la barra,
sus tres botones compuestos, dos separadores, el grupo, sus tres items y el
enlace). Capturas 2× antes/después: idénticas.

Centinela R-5.4: 52/54, dos adjudicadas, cero muertas sin razón.

- `transition-duration` / `transition-ease` SON la transición que el guard
  congela para poder leer todo lo demás. Medidas sin congelar sobre el Link
  real, que transiciona CUATRO propiedades: `0.12s ×4 → 4.321s ×4` y
  `cubic-bezier(0.4, 0, 0.2, 1) ×4 → steps(3) ×4`. Al ledger con esa razón.
- `disabled-opacity` NO se adjudica: se ARREGLA. El guard enciende ahora el
  interruptor «group disabled» de la demo, y comprobado que no tapa nada — la
  regla de hover es `[data-toolbar-link]:hover,
  [data-toolbar-group-item]:hover:not([data-disabled])` y el Link no pertenece
  al grupo, así que conserva las dos claves de hover. Corrido antes y después
  del control: 51/54 → 52/54, el conjunto muerto encoge en uno y no gana
  ninguno.

Guards: censo `--only` 94 % · `component-audit --only toolbar` PASS ·
`eidos-lint toolbar` 16 morfo-backed / 5 eidos-only / 0 invalid ·
`vitest run src/uix/eidos` 35/36 ficheros (el rojo es `skin-media-player`, el
conocido) · `rtl:check` 0 · `docs:check` 0 · `check` COMPLETED sin un solo
error atribuible a este componente · prettier: la anotación `/* literal: */`
sobrevive en la MISMA línea y mi bloque del README queda limpio; los 12 hunks
de deriva del README y los 12 de la demo son PREEXISTENTES en HEAD y no se
tocan.

Artefactos de documentación del protocolo: README con su `## Talla y tema`
derivada del contrato (los nueve ejes + la clasificación de los privados),
pestaña `Tokens` en la demo (54 claves resueltas en vivo, verificada en
navegador) y veredicto §5 de la ficha.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent e76d2db2be
commit 26713f2887

@ -99,6 +99,62 @@ no distingue lo que debería — se marca `⚠`.
<!-- veredicto:start -->
_(pendiente — lo escribe el autor; se conserva al regenerar)_
**Ejecutado 2026-08-24 · 91 % → 94 % — CERRADO en su número.** 34 knobs · 30
públicos · 2 privados · 1 excepción firmada · 2 sistema. Contrato **54** claves,
sin acuñar ninguna nueva. Diff de computed **VACÍO** (2.464 valores, 7 estados,
11 nodos). Centinela **52/54**, 2 adjudicadas.
**No había nada que acuñar, y eso es el hallazgo.** El censo daba `global 0`: la
receta ya lee su propio contrato en cada declaración, con el eje `size`
resuelto por el TSC a través de siete privados que sólo reapuntan claves
públicas por talla. Lo que quedaba fuera del alcance eran **tres cosas
clasificadas, no tres deudas**.
**1. Los dos privados son CONMUTADORES por variante** (medido variante a
variante en el nodo real): `--_toolbar-bg` vale `var(--toolbar-bg)`
(`oklch(0.9821 0 0)`) en `surface` y `transparent` en `outline` y `ghost`;
`--_toolbar-shadow` vale `var(--toolbar-shadow)` y `none` en las mismas. Ese
segundo valor es la **identidad de la variante**, no un knob: una barra `ghost`
ES la que no tiene relleno ni sombra, y un público encima dejaría que un tema la
rellenara y matara la variante en silencio. Aplanarlo obligaría además a
duplicar cada regla por variante — la clase «conmutador» de F2-B, quinto caso
tras `textarea`, `spinner`, `skeleton`, `code` y `label`. `--_toolbar-border` es
el mismo conmutador; el censo lo cuenta como público porque el atajo `border`
lleva también el ancho, que sí es token. **Ni escala por talla ni puente THM-2:
esta barra no tiene paleta.**
**2. El único literal es una IDENTIDAD y ahora está firmado.** El
`inline-size: 100%` del separador horizontal abarca el eje transversal de la
barra — el mismo trabajo que `align-self: stretch` hace en el gemelo vertical
(medido: 112 px sobre un contenedor de 126 px). Su grosor y su suelo (`min-inline-size`)
sí son tokens. Con su anotación `/* literal: */` sale del ratio: 91 % → 94 %.
**3. La cascada del componente compuesto NO le cuesta una sola clave.**
`Toolbar.Button` compone el `<Button>` canónico, así que sus nodos llevan
`data-button` Y `data-toolbar-button`, y `[data-button]` casa con la misma
especificidad (0,1,0) pero se emite DESPUÉS. La diferencia con `form` —que
conserva 24 declaraciones que Button anula— es que esta receta **ya cedió el
cromo del botón** (A-112) y no escribe ni una regla `[data-toolbar-button]`.
Medido antes de tocar nada: 51/54 vivos, y las tres silenciosas no eran del
botón. **Las claves `control-*` visten el Link y el GroupItem**, que sí son
superficie de la barra.
**Las dos silenciosas del guard, medidas.** `transition-duration` y
`transition-ease` **SON** la transición que el centinela congela para poder leer
todo lo demás. Medidas sin congelar sobre el Link real, que transiciona CUATRO
propiedades: `0.12s ×4 → 4.321s ×4` y `cubic-bezier(0.4, 0, 0.2, 1) ×4 →
steps(3) ×4`. Al ledger con esa razón.
**Y una que NO hizo falta adjudicar.** `disabled-opacity` leía muerta porque el
escenario no lleva el estado. El guard enciende ahora el interruptor «group
disabled» de la demo, y no tapa nada: la regla de hover es
`[data-toolbar-link]:hover, [data-toolbar-group-item]:hover:not([data-disabled])`
y el Link no pertenece al grupo, así que conserva las dos claves de hover.
Comprobado corriendo el guard antes y después del control: 51/54 → 52/54, el
conjunto muerto encoge en uno y no gana ninguno.
**Veredicto: CERRADO en 94 %.** Lo que queda fuera es un conmutador de dos
ramas cuya segunda rama es la identidad de la variante. Forzar la cifra sería
romper `variant`.
<!-- veredicto:end -->

@ -621,6 +621,16 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'pre-padding':
'the DEMO HARNESS outweighs the component: `[data-uix-docs] pre` (uix.css:801, 0-1-1) beats `[data-code-block-pre]` (0-1-0) and paints padding/border/radius/bg/font on the <pre>. Forced with !important → reaches (16px 20px → 12px). Seven properties measured, registered in next-features §13'
},
toolbar: {
// 2026-08-24. Its ONLY two silences, and both are the freeze the guard
// needs to read everything else. Measured unfrozen on the real Link, which
// transitions FOUR properties (background, border-color, color, box-shadow)
// — hence four entries in the computed list.
'transition-duration':
'the guard freezes transitions and this token IS the transition; measured unfrozen on [data-toolbar-link] -> reaches (0.12s x4 -> 4.321s x4)',
'transition-ease':
'same freeze, same node -> reaches (cubic-bezier(0.4, 0, 0.2, 1) x4 -> steps(3) x4)'
},
'tree-view': {
'disabled-row-fg':
'no disabled row in the demo (0 mounted, counted); forced data-disabled → reaches (oklch(0.61 0 0) → rgb(1,2,3))'

@ -312,6 +312,17 @@ const COMPONENT_OVERRIDES: Record<
'[data-uix-control]:has([data-uix-control-label]:text-is("disabled")) input[type=checkbox]'
]
},
// Its opacity floor is the only knob behind a state, and this demo has no URL
// switch for it. Turning the GROUP's toggle on costs nothing measurable: the
// hover rule of the bar is `[data-toolbar-link]:hover, [data-toolbar-group-item]:hover:not([data-disabled])`,
// and the Link is not part of the group — it keeps both hover keys alive
// (verified by running the guard before and after this control: the dead set
// shrank by one and gained nothing).
toolbar: {
prepareWith: [
'[data-uix-control]:has([data-uix-control-label]:text-is("group disabled")) input[type=checkbox]'
]
},
// Half its contract lives in parts the default stage does not mount: the
// AutoFields tree (array, item, widget) only exists in the demo's `auto`
// mode, and the panel chrome only under `variant='panel'`. 18 of 54 tokens

@ -120,6 +120,53 @@ References:
</Toolbar>
```
## Talla y tema
**54 claves públicas** en `lib/recipes/base.ts`, y **eje `size` xs·sm·md·lg**
resuelto por el TSC: la receta lee siempre el nombre resuelto a través de un
privado (`--_toolbar-{eje}`), y `[data-size]` sólo reapunta ese privado. La
pestaña **Tokens** de la demo las resuelve en vivo.
| eje | claves | apunta a |
| -------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| barra · ritmo | `gap-{k}`, `padding-inline-{k}`, `padding-block-{k}` | `--space-*` |
| barra · cromo | `radius`, `bg`, `border`, `border-width`, `shadow`, `fg` | superficie / borde / sombra del sistema |
| barra · texto | `font-family`, `font-size-{k}`, `line-height` | `--style-label-font-family`, el bundle `--size-{k}-font-size`, `--leading-ui` |
| control (Link · GroupItem) | `control-height-{k}`, `control-padding-inline-{k}`, `control-gap-{k}` | el bundle `--size-{k}-control-height` y `--space-*` |
| control · cromo | `control-radius`, `control-bg`, `control-fg`, `control-border`, `control-border-width` | — |
| control · estados | `hover-control-{fg,border}`, `active-control-{bg,fg,border}` | rol `primary` para el activo |
| grupo | `group-gap` | `--space-*` |
| separador | `separator-{bg,thickness,length,radius}` | `--color-neutral-separator`, `--border-width`, el bundle `--size-md-control-height` |
| transición | `transition-duration`, `transition-ease`, `disabled-opacity` | `--duration-fast`, `--ease-default`, `--opacity-disabled` |
**Lo que la barra NO pinta: su propio `Toolbar.Button`.** Compone el `<Button>`
canónico (A-112), y Button es dueño de su cromo — variante, hover, foco y
disabled. Por eso no existe una regla `[data-toolbar-button]` en la receta, y
por eso la cascada del componente compuesto (mismo peso, gana el que se emite
después) **no le cuesta a esta barra ni una clave muerta**: donde `form` conserva
24 declaraciones que Button anula, aquí no hay ninguna que anular. Las claves
`control-*` visten el Link y el GroupItem, que sí son superficie de la barra.
**Lo que se queda PRIVADO a propósito** (y por eso el techo no es el 100 %):
`--_toolbar-bg` y `--_toolbar-shadow` son **conmutadores por variante** — leen
`var(--toolbar-bg)` / `var(--toolbar-shadow)` en `surface` y valen `transparent`
/ `none` en `outline` y `ghost`. Ese segundo valor es la **identidad** de la
variante, no un knob: una barra `ghost` ES la que no tiene relleno ni sombra, y
un público encima dejaría que un tema la rellenara y matara la variante.
Aplanarlo obligaría además a duplicar cada regla por variante. `--_toolbar-border`
es el mismo conmutador (el censo lo cuenta como público porque el atajo `border`
lleva también el ancho, que sí es token).
**Y una identidad firmada**: el `inline-size: 100%` del separador horizontal
lleva su anotación `/* literal: */` — abarca el eje transversal de la barra,
igual que `align-self: stretch` en el gemelo vertical. Su grosor y su suelo sí
son tokens.
**Lo que el guard da por silencioso**: `transition-duration` y `transition-ease`,
porque el centinela CONGELA las transiciones para poder leer todo lo demás.
Medidos sin congelar sobre el Link real, que transiciona cuatro propiedades:
`0.12s ×4 → 4.321s ×4` y `cubic-bezier(0.4, 0, 0.2, 1) ×4 → steps(3) ×4`.
## Gaps
| Gap | Disposición | Detalle |

@ -155,7 +155,7 @@
[data-toolbar-separator][data-orientation='horizontal'] {
block-size: var(--toolbar-separator-thickness);
inline-size: 100%;
inline-size: 100%; /* literal: identity — spans the bar's cross axis, the same job `align-self: stretch` does on the vertical twin. Its thickness and its floor ARE tokens. */
min-inline-size: var(--toolbar-separator-length);
}

@ -8,8 +8,9 @@
import { compileMorfo } from '$uix/morfo';
import { toolbarMorfo } from '@/uix/morfo/components/toolbar';
import { getActiveUix } from '$active-uix';
import TokensPanel from '../../lib/TokensPanel.svelte';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens' | 'a11y';
type Orientation = 'horizontal' | 'vertical';
type Direction = 'ltr' | 'rtl';
type GroupType = 'single' | 'multiple';
@ -203,6 +204,9 @@
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}
>Recipe</button
>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>54</span>
</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div>
@ -587,6 +591,10 @@
</section>
{/if}
{#if tab === 'tokens'}
<TokensPanel component="toolbar" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'}
<section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2>

Loading…
Cancel
Save

Powered by TurnKey Linux.