uix(sidebar): la fila que navega decía «te sentí» a nadie, y el libro dice cómo se dice
«Navegar una fila es nativo y no suena» era una decisión de la fase 1 tomada en
silencio (el plan la dejó como «decidir en fase 1») sobre una premisa falsa —
«item = Link puro»—: la fila es `<button>` cuando no tiene `href`, la acción es
un botón pelado y el `MenuItem` posee un flyout. Medido en navegador: pulsar
fila, sub-fila o acción estampaba CERO; el Trigger sí acusaba recibo, porque
compone `Button`.
El libro no deja margen: cap. 22 §9 compone el enlace como «contact.press
seguido de shift.navigate: la presión no es la navegación», cap. 27 §8 lo
repite y cap. 22 §12 llama antipatrón al contacto mudo. Así que la fila declara
las dos ocurrencias, y la aparición del flyout —cap. 26 §5-§6, un menú anclado—
deja de ser un cambio de estado sin evento.
Dónde se estampa cada una lo decidió la doctrina, no la comodidad: el gesto en
la mano (la fila, anclado por instancia: es parte REPETIDA) y el cruce en la
superficie que cruza como unidad (el provider). Eso evita la quinta pareja
`queue` que yo iba a declarar: calendar ya movió su `shift-navigate` fuera del
botón porque las dos ocurrencias se comían la única ranura y el press-squeeze
no llegaba a pintarse.
Voz: contact y shift toman la de su familia (`touch`, `slide`) sin escribir
nada; los dos `emerge-*-sub` van SILENT como el tooltip — el flyout se abre al
pasar el puntero, y un barrido por el raíl serían ocho apariciones sonando
(cap. 26 §7, Criterio 12).
El `trigger` NO declara contacto: lo trae por composición de `Button`. El
`rail` sí, porque es una franja de cromo y nadie más lo estampa.
Medido en el navegador tras el cambio: fila con href → `contact-activate`
(press-squeeze corriendo) en ESA fila + `shift-navigate` en el shell; fila
deshabilitada → nada; sub-fila → el mismo par; rail → contacto propio +
`emerge-collapse` del panel; en modo icono, hover → `emerge-open-sub`
(present-rise), hover repetido → nada, salir → `emerge-close-sub`
(dismiss-fade). Suite nueva de 8 contratos, `check` con delta CERO contra la
base medida con stash (72/72), morfo:check PASS, contracts sin fallos nuevos.
Plan y decisiones firmadas: docs/process/PLAN-sidebar.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
# PLAN — `Sidebar`: la fila habla lo que es (sema por el libro) y viste la talla del sistema (eje `size`)
> **Estado: PLAN ENTREGADO 2026-08-19 — NADA CONSTRUIDO, NADA FIRMADO.** Falta
> que el autor firme las decisiones D-SB de §4 (una por mensaje, con sus cinco
> preguntas) y ordene el arranque de F1.
>
> **Origen (conversación 2026-08-19)**: el autor pidió analizar por qué
> `Sidebar.MenuButton` / `Sidebar.MenuItem` no emiten semántica y contrastarlo
> con el libro, y añadió: «el componente sidebar está mal construido, la
> tipografía y los tamaños no responden a los del sistema». Su veredicto,
> textual, manda sobre este documento: **«emitir o no emitir no es una
> decisión, ya que el libro es el canon, y la naturaleza de los eventos es la
> que es; por lo tanto tienen que estar acorde al tipo de eventos que emite. Hay
> que corregirlo.»**
>
> **Kickoff para sesión nueva**: _«Lee `docs/process/PLAN-sidebar.md`; si las
> D-SB de §4 están firmadas, ejecuta la fase que toque de §5 con sus gates; si
> no, PARA y preséntalas una a una.»_ Cada fase lista qué leer antes, qué
> producir y con qué guard se verifica.
>
docs(sidebar): el handoff del eje, y las seis deudas que abrió en otros
En este eje no queda nada: nueve decisiones firmadas, cinco fases ejecutadas y
verificadas, y la revisión adversarial pasada con sus cuatro hallazgos
arreglados. Lo que queda es de OTROS ejes, y por eso el handoff los lista con
nombre y sitio en vez de dejarlos en la cabeza de quien estuvo aquí: T-1
(`navigation-menu` emite `commit-select` al navegar — el antipatrón «success de
navegación»), T-2 (nav-tree · anchor-nav · breadcrumb siguen mudos por el
contrato que aquí se retiró), T-3 y T-4 (dos fichas del ledger de blocks que
suponen lo contrario de lo que ahora hay), T-5 (el guard del bundle sólo ve
tokens, no reglas) y T-6 (la densidad y el zoom no bajan a un subárbol, y eso
no está escrito en ninguna doc).
El handoff también deja por escrito las dos veces que hoy mintió el
INSTRUMENTO y no el código: con la pantalla del navegador oculta rAF se congela
y `getBoundingClientRect` daba 256px para un panel de 52; y leer `animationName`
dentro del callback de un MutationObserver mide el instante anterior al flush,
que es como di por viva una animación que moría 3,5 ms después.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
> **⚠️ AL DÍA (2026-08-19): las 9 decisiones están firmadas y las 5 fases
> EJECUTADAS** — tres commits (`f6d5fa159` · `63b2de03c` · `bae03e55c`). El
> handoff con el resumen de una pantalla y lo que queda (nada en este eje; seis
> deudas en otros) es [`CONTINUE-sidebar.md`](./CONTINUE-sidebar.md).
>
uix(sidebar): la fila que navega decía «te sentí» a nadie, y el libro dice cómo se dice
«Navegar una fila es nativo y no suena» era una decisión de la fase 1 tomada en
silencio (el plan la dejó como «decidir en fase 1») sobre una premisa falsa —
«item = Link puro»—: la fila es `<button>` cuando no tiene `href`, la acción es
un botón pelado y el `MenuItem` posee un flyout. Medido en navegador: pulsar
fila, sub-fila o acción estampaba CERO; el Trigger sí acusaba recibo, porque
compone `Button`.
El libro no deja margen: cap. 22 §9 compone el enlace como «contact.press
seguido de shift.navigate: la presión no es la navegación», cap. 27 §8 lo
repite y cap. 22 §12 llama antipatrón al contacto mudo. Así que la fila declara
las dos ocurrencias, y la aparición del flyout —cap. 26 §5-§6, un menú anclado—
deja de ser un cambio de estado sin evento.
Dónde se estampa cada una lo decidió la doctrina, no la comodidad: el gesto en
la mano (la fila, anclado por instancia: es parte REPETIDA) y el cruce en la
superficie que cruza como unidad (el provider). Eso evita la quinta pareja
`queue` que yo iba a declarar: calendar ya movió su `shift-navigate` fuera del
botón porque las dos ocurrencias se comían la única ranura y el press-squeeze
no llegaba a pintarse.
Voz: contact y shift toman la de su familia (`touch`, `slide`) sin escribir
nada; los dos `emerge-*-sub` van SILENT como el tooltip — el flyout se abre al
pasar el puntero, y un barrido por el raíl serían ocho apariciones sonando
(cap. 26 §7, Criterio 12).
El `trigger` NO declara contacto: lo trae por composición de `Button`. El
`rail` sí, porque es una franja de cromo y nadie más lo estampa.
Medido en el navegador tras el cambio: fila con href → `contact-activate`
(press-squeeze corriendo) en ESA fila + `shift-navigate` en el shell; fila
deshabilitada → nada; sub-fila → el mismo par; rail → contacto propio +
`emerge-collapse` del panel; en modo icono, hover → `emerge-open-sub`
(present-rise), hover repetido → nada, salir → `emerge-close-sub`
(dismiss-fade). Suite nueva de 8 contratos, `check` con delta CERO contra la
base medida con stash (72/72), morfo:check PASS, contracts sin fallos nuevos.
Plan y decisiones firmadas: docs/process/PLAN-sidebar.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
> Fuentes de doctrina que este plan cita y NO copia: el libro
> (`docs/Disenando_lo_que_ocurre_FINAL.pdf`, anclas en `docs/book-map.md`),
> [`docs/CANON.md`](../CANON.md), [`docs/architecture/sema.md`](../architecture/sema.md),
> [`docs/theming/reference.md`](../theming/reference.md) §5,
> [`docs/guides/component-guide.md`](../guides/component-guide.md) §Build contract.
---
## 0. Veredicto corto (la valoración que pidió el autor)
1. **La fila muda contradice el libro, no lo aplica.** Cap. 22 §9 compone el
enlace como «Link → contact.press seguido de shift.navigate: la presión no
es la navegación»; cap. 27 §8 repite «Navegación → contact.press ·
shift.navigate»; cap. 22 §12 nombra el antipatrón «Contacto mudo»; CANON §8
regla 1 hace obligatorio el contact «when there is direct action». La fila
del sidebar es un control de navegación POR DECLARACIÓN (`< a href > ` dentro
del landmark `<nav>` , `aria-current="page"` del mismo prop que
`data-active` ): sabe que navega, luego declara las dos ocurrencias. Lo que
NO sabe —la orientación del destino: foco, título, landmarks (cap. 27 §9)—
sigue siendo deber del shell/app y no excusa a la fila.
2. ** «Nativo y sin cue» era una decisión de fase 1 tomada en silencio**, no una
firma: el plan dejó el punto abierto («activación de item si el item es
más que un `Link` — decidir en fase 1», `PLAN-blocks.md:394-397` ), la fase 0
firmó sólo el `emerge` del panel (`PLAN-blocks.md:1659-1664`), y la premisa
«item = Link puro» no se cumple: `MenuButton` es `<button>` sin `href` ,
`MenuAction` es un `<button>` pelado, `MenuItem` POSEE un flyout.
3. **El framework ya se contradice a sí mismo** sobre el mismo gesto:
`navigation-menu` emite `commit-select + affirm` al pulsar su `link`
(«Navigating IS the evaluable act... it had no voice»,
`navigation-menu-provider.svelte.ts:715-728` ), mientras `link` /
`anchor-nav` / `breadcrumb` / `nav-tree` / `sidebar` callan «por contrato
escrito» (A-50 del ledger de blocks). Por el libro ninguna de las dos es
exacta: «shift no es commit» (cap. 27 §2). Este plan arregla el sidebar;
los demás son deuda transversal (§6) que se firma aparte, sin cascada.
4. **El silencio sólo es respuesta válida como CANAL, nunca como evento sin
declarar**: CANON §7 («where no base exists, silence — a valid answer») y el
pack `SILENT` del tooltip hablan de la VOZ; Apéndice A del libro exige el
evento declarado («el runtime no debe adivinar»). La corrección es declarar
los eventos que la naturaleza de cada parte dicta y dejar que el pack decida
cuánto se oye.
5. **Tallas** : el sidebar se construyó fuera del canon de tamaño (sin `size` ,
un token de ESPACIO como altura de control, `sm` clavado, fuente del UA en la
acción). La corrección es mecánica y tiene precedentes en el propio repo
(`breadcrumb` para el eje `data-size` , `dialog/context.ts` para la
derivación container→part).
---
## 1. Inventario de defectos — medidos, con evidencia y canon
Todo lo de abajo está **medido en navegador** (dev server `verify` , ruta
`/uix/components/sidebar` , 2026-08-19): MutationObserver sobre `data-event*`
en todo el documento; control positivo el `Trigger` (compone `Button` ), que
estampó `contact-activate` (sig-0) y `emerge-collapse` en el `panel` (sig-1).
### 1.1 Sema — el libro es el canon
| Id | Parte | Hoy (medido) | Canon que incumple | Evidencia en código |
| -------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **S-1** | `menu-button` (`< a href > ` — la fila que navega) | clic → **0 estampas** , 0 sonido | Cap. 22 §9 «Link → contact.press · shift.navigate»; cap. 27 §5/§8; cap. 22 §12 «Contacto mudo»; CANON §8 r.1; cap. 36 §8 («un botón puede participar en… shift.navigate») | morfo `sidebar.ts:48-52` («Navigating a menu item is native and unsonified»), `:319-342` (0 eventos, sin archetype); provider `sidebar-provider.svelte.ts:584-589` (props sin `onclick` ); pack `sema/components/sidebar.ts:16-18` |
| **S-1b** | `menu-button` (`< button > ` — sin `href` , «only opens a sub-menu») | clic → 0 estampas y **0 comportamiento** | Cap. 22 §9 «Botón → contact.press»; A-62 del ledger («el contacto inicia y nada resuelve» — aquí ni inicia) | `sidebar-menu-button.svelte:48-51` |
| **S-2** | `menu-sub-button` | clic → 0 estampas | = S-1 («Same active contract as MenuButton», `sidebar.ts:399` ) | `sidebar-provider.svelte.ts:732-735` |
| **S-3** | `menu-action` (`< button archetype = trigger > `) | clic → 0 estampas; **fuente 13,33px / `line-height: normal` del UA** | Cap. 22 §9 «Botón → contact.press»; Build contract «Composition» (componer `Button` , no reimplementar) | morfo `sidebar.ts:347-363` ; eidos `sidebar-menu-action.svelte:3-6` («Native button… not the `Button` component») |
| **S-4** | `rail` (`< button archetype = trigger > `) | clic → `emerge-*` en `panel` , **nada en el propio rail** (el `Trigger` sí acusa recibo porque compone `Button` ) | Cap. 22 §9; CANON §8 r.1 — el mismo toggle, dos voces distintas según el botón que se pulse | `sidebar-provider.svelte.ts:267-273` ; eidos `sidebar-rail.svelte:3-6` |
| **S-5** | `menu-item` — dueño del **flyout** de modo icono (`openSub`/`closeSub`) | hover/foco → el sub-menú flotante aparece y desaparece con **0 eventos** | Cap. 26 §5 (`emerge.open/close`: «algo se abre o se cierra con marco propio: menú, popover, panel»), §6 «aparición anclada — dropdown»; precedente `dropdown-menu` `emerge-open-sub` /`-close-sub` («Not a wiring bug: there was no event», `dropdown-menu.ts:62-96` ) | `sidebar-provider.svelte.ts:484-496` , `:470-475` ; morfo `menu-sub` `:381-397` ya declara `states: ['open','closed']` |
uix(sidebar): la fila que navega decía «te sentí» a nadie, y el libro dice cómo se dice
«Navegar una fila es nativo y no suena» era una decisión de la fase 1 tomada en
silencio (el plan la dejó como «decidir en fase 1») sobre una premisa falsa —
«item = Link puro»—: la fila es `<button>` cuando no tiene `href`, la acción es
un botón pelado y el `MenuItem` posee un flyout. Medido en navegador: pulsar
fila, sub-fila o acción estampaba CERO; el Trigger sí acusaba recibo, porque
compone `Button`.
El libro no deja margen: cap. 22 §9 compone el enlace como «contact.press
seguido de shift.navigate: la presión no es la navegación», cap. 27 §8 lo
repite y cap. 22 §12 llama antipatrón al contacto mudo. Así que la fila declara
las dos ocurrencias, y la aparición del flyout —cap. 26 §5-§6, un menú anclado—
deja de ser un cambio de estado sin evento.
Dónde se estampa cada una lo decidió la doctrina, no la comodidad: el gesto en
la mano (la fila, anclado por instancia: es parte REPETIDA) y el cruce en la
superficie que cruza como unidad (el provider). Eso evita la quinta pareja
`queue` que yo iba a declarar: calendar ya movió su `shift-navigate` fuera del
botón porque las dos ocurrencias se comían la única ranura y el press-squeeze
no llegaba a pintarse.
Voz: contact y shift toman la de su familia (`touch`, `slide`) sin escribir
nada; los dos `emerge-*-sub` van SILENT como el tooltip — el flyout se abre al
pasar el puntero, y un barrido por el raíl serían ocho apariciones sonando
(cap. 26 §7, Criterio 12).
El `trigger` NO declara contacto: lo trae por composición de `Button`. El
`rail` sí, porque es una franja de cromo y nadie más lo estampa.
Medido en el navegador tras el cambio: fila con href → `contact-activate`
(press-squeeze corriendo) en ESA fila + `shift-navigate` en el shell; fila
deshabilitada → nada; sub-fila → el mismo par; rail → contacto propio +
`emerge-collapse` del panel; en modo icono, hover → `emerge-open-sub`
(present-rise), hover repetido → nada, salir → `emerge-close-sub`
(dismiss-fade). Suite nueva de 8 contratos, `check` con delta CERO contra la
base medida con stash (72/72), morfo:check PASS, contracts sin fallos nuevos.
Plan y decisiones firmadas: docs/process/PLAN-sidebar.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
`menu-item` como `<li>` posicionador es marco estructural (CANON §5): 0
eventos es correcto **en esa faceta** . Su defecto es S-5: posee una aparición
y no la declara.
### 1.2 Eidos — el canon de tamaño y el Build contract
| Id | Qué | Hoy (medido / leído) | Canon que incumple | Evidencia |
| ------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| **E-1** | Eje `size` inexistente | filas `font-size: var(--font-size-sm)` (14px medido) + `min-block-size: var(--space-8)` (32px) — un token de ESPACIO (escala `density-space` ) como altura de CONTROL (escala `density-control` ); `data-size` ausente; `group-label` clava `xs` | theming/reference §5: bundle `--size-{k}-*` obligatorio; «Nav controls xs..lg»; size→font 1:1 y «compact md=14» REVOCADO 2026-06-17; Build contract «Size (controls)» («consuming none of the bundle» es la deriva nombrada) y «Density / spacing» | `sidebar.css:209-231` , `:173-181` ; tokens `recipes/base.ts:4725-4741` ; eidos `types.ts:35-42` (sin `size` ) |
| **E-2** | El guard no lo ve | `recipe-css-contract.test.ts:613-639` escanea `RECIPE_TOKENS` ; el `font-size` va escrito en el `.css` y `--space-8` no casa con `RAW_COORDINATE` | memoria `a-guard-that-inspects-nothing-passes` — verde y ciego | `sidebar.css:221` |
| **E-3** | `MenuAction` sin fuente | 13,33px `line-height: normal` (UA) — fuera de la escala | theming §5 1:1; Build contract «Composition» | `sidebar.css:260-273` |
| **E-4** | Hover a mano | `background: var(--color-surface-raised)` en `:hover` | Build contract «State (hover/active)»: capa `--state-{hover,press,selected}` , no `--x-hover-bg` por componente (`archetypes.css:59-83`) | `sidebar.css:233-237` , `:275-278` |
| **E-5** | Tallas clavadas en las composiciones | `Trigger` `size="sm"` ; `MenuBadge` `size="xs"` | theming §5 «Container→part derivation: capped at md» (precedente `dialog/context.ts:6-13` + `Dialog.Close` ) | `sidebar-trigger.svelte:15` ; `sidebar-menu-badge.svelte:16` |
| **E-6** | Ancho del raíl de iconos fijo | `--sidebar-width-icon: 3rem` = 32px de fila + 2× 8px; en cuanto la fila siga al bundle (md = 36px) el raíl **no cabe** | derivación, no constante (la propia justificación del token: «widths are TOKENS, never TS constants») | `recipes/base.ts:4727` ; `sidebar.css:102-104` |
| **E-7** | Iconos de la fila | la demo pasa `size="sm"` a cada `Icon` a mano; la fila no dimensiona su glifo | bundle `--size-{k}-icon-size` (como `Button` ) | `web/routes/uix/components/sidebar/+page.svelte:158-220` |
uix(sidebar): la fila que navega decía «te sentí» a nadie, y el libro dice cómo se dice
«Navegar una fila es nativo y no suena» era una decisión de la fase 1 tomada en
silencio (el plan la dejó como «decidir en fase 1») sobre una premisa falsa —
«item = Link puro»—: la fila es `<button>` cuando no tiene `href`, la acción es
un botón pelado y el `MenuItem` posee un flyout. Medido en navegador: pulsar
fila, sub-fila o acción estampaba CERO; el Trigger sí acusaba recibo, porque
compone `Button`.
El libro no deja margen: cap. 22 §9 compone el enlace como «contact.press
seguido de shift.navigate: la presión no es la navegación», cap. 27 §8 lo
repite y cap. 22 §12 llama antipatrón al contacto mudo. Así que la fila declara
las dos ocurrencias, y la aparición del flyout —cap. 26 §5-§6, un menú anclado—
deja de ser un cambio de estado sin evento.
Dónde se estampa cada una lo decidió la doctrina, no la comodidad: el gesto en
la mano (la fila, anclado por instancia: es parte REPETIDA) y el cruce en la
superficie que cruza como unidad (el provider). Eso evita la quinta pareja
`queue` que yo iba a declarar: calendar ya movió su `shift-navigate` fuera del
botón porque las dos ocurrencias se comían la única ranura y el press-squeeze
no llegaba a pintarse.
Voz: contact y shift toman la de su familia (`touch`, `slide`) sin escribir
nada; los dos `emerge-*-sub` van SILENT como el tooltip — el flyout se abre al
pasar el puntero, y un barrido por el raíl serían ocho apariciones sonando
(cap. 26 §7, Criterio 12).
El `trigger` NO declara contacto: lo trae por composición de `Button`. El
`rail` sí, porque es una franja de cromo y nadie más lo estampa.
Medido en el navegador tras el cambio: fila con href → `contact-activate`
(press-squeeze corriendo) en ESA fila + `shift-navigate` en el shell; fila
deshabilitada → nada; sub-fila → el mismo par; rail → contacto propio +
`emerge-collapse` del panel; en modo icono, hover → `emerge-open-sub`
(present-rise), hover repetido → nada, salir → `emerge-close-sub`
(dismiss-fade). Suite nueva de 8 contratos, `check` con delta CERO contra la
base medida con stash (72/72), morfo:check PASS, contracts sin fallos nuevos.
Plan y decisiones firmadas: docs/process/PLAN-sidebar.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
**Corrección a mi propio análisis inicial**: dije que `--sidebar-width` /
`--sidebar-width-mobile` (16rem / 18rem) «ignoran `--scaling` ». Son anchos de
LAYOUT, y el sistema fija sus propios anchos de layout en rem
(`--content-width-*`, `base.css:146-152` ) — no siguen `--scaling` por
diseño. **No son defecto.** Sólo `width-icon` (E-6) lo es, porque es una
medida de CONTROL (fila + padding).
---
## 2. Contrato objetivo — sema
Los eventos que el morfo del sidebar declara tras la corrección. Nombres bajo
la gramática `{family}-{verb}[-{nuance}]` (CANON §6); anclaje por instancia
porque `menu-button` , `menu-sub-button` y `menu-sub` son partes REPETIDAS
(memoria `repeated-part-cannot-be-the-subject` ; `SomaRuntimePart.trigger` anclado,
`runtime.svelte.ts:243-258` ; `partInstance` `:299` ).
| Evento | Familia · verbo | `target` (+ `allowedTargets` ) | `sequence` | Cuándo lo emite el soma | Voz por defecto (pack) |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `emerge-expand` / `emerge-collapse` | (sin cambio) | `panel` | post | `toggle()` | `emerge.soft` / `emerge.exit.soft` (como hoy) |
| ** `contact-activate` ** | contact · activate — el verbo de `Button` (`button.ts:56-71`); cap. 22 §6: los verbos de contact «no son gestos distintos que exijan semánticas distintas» | `menu-button` + `allowedTargets: [menu-sub-button, rail]` | **pre** | `onclick` de la fila / sub-fila (`< a > ` y `<button>` , ratón y Enter/Espacio nativos) y del rail ANTES de `toggle()` ; guardado por `disabled` ; el `mergeProps` de la casa ya deja al consumidor vetar con `preventDefault` (`props.ts:20-27`) | familia: `touch` + háptico `tick` (`sema-map.ts:411-424`); firma visual `press-squeeze` GRATIS por regla de familia (`base.css:6636`) — sin CSS nuevo |
| ** `shift-navigate` ** | shift · navigate — cap. 27 §5 «Navegación: página A → página B» | ** `provider` ** (el shell que cruza como unidad — D-SB.1bis; antes decía `menu-button` + `queue` ) | **post** , sin `regime` | mismo `onclick` , emitido SIN anclar (`runtime.trigger`), sólo si hay `href` y no está `disabled` (la fila es un control de navegación; el `<button>` sin `href` NO lo emite — su consecuencia la pone lo que el consumidor compone). Sin `direction` (CANON §9: «an invented sense is worse than none»); sin intent | familia: `slide` (`sema-map.ts:472-482`); sin firma visual (sin `direction` no hay cruce que dibujar, `presets/css.ts:387-412` ) — el cruce lo pinta la página |
| ** `emerge-open-sub` ** / ** `emerge-close-sub` ** | emerge · open / close — cap. 26 §5-§6 (menú anclado) | `menu-sub` (repetida → anclada) | post / pre; `commits` `data-state` open/closed sobre `menu-sub` (patrón `dropdown-menu.ts:62-96` ) | `openSub()` / `closeSub()` — los ÚNICOS mutadores (pointerenter, focusin, focusout, pointerleave, Escape, y el `watch` de `floating` ); sólo en modo icono (`floating`) y sólo cuando el estado CAMBIA (nada de emisiones fantasma, lección de nav-menu) | **D-SB.3** (recomiendo `SILENT` como el tooltip: barrido del puntero por el raíl = una aparición por fila; cap. 26 §7 «el sonido, normalmente, sobra»; cap. 7 §13 Criterio 12) |
uix(sidebar): la fila que navega decía «te sentí» a nadie, y el libro dice cómo se dice
«Navegar una fila es nativo y no suena» era una decisión de la fase 1 tomada en
silencio (el plan la dejó como «decidir en fase 1») sobre una premisa falsa —
«item = Link puro»—: la fila es `<button>` cuando no tiene `href`, la acción es
un botón pelado y el `MenuItem` posee un flyout. Medido en navegador: pulsar
fila, sub-fila o acción estampaba CERO; el Trigger sí acusaba recibo, porque
compone `Button`.
El libro no deja margen: cap. 22 §9 compone el enlace como «contact.press
seguido de shift.navigate: la presión no es la navegación», cap. 27 §8 lo
repite y cap. 22 §12 llama antipatrón al contacto mudo. Así que la fila declara
las dos ocurrencias, y la aparición del flyout —cap. 26 §5-§6, un menú anclado—
deja de ser un cambio de estado sin evento.
Dónde se estampa cada una lo decidió la doctrina, no la comodidad: el gesto en
la mano (la fila, anclado por instancia: es parte REPETIDA) y el cruce en la
superficie que cruza como unidad (el provider). Eso evita la quinta pareja
`queue` que yo iba a declarar: calendar ya movió su `shift-navigate` fuera del
botón porque las dos ocurrencias se comían la única ranura y el press-squeeze
no llegaba a pintarse.
Voz: contact y shift toman la de su familia (`touch`, `slide`) sin escribir
nada; los dos `emerge-*-sub` van SILENT como el tooltip — el flyout se abre al
pasar el puntero, y un barrido por el raíl serían ocho apariciones sonando
(cap. 26 §7, Criterio 12).
El `trigger` NO declara contacto: lo trae por composición de `Button`. El
`rail` sí, porque es una franja de cromo y nadie más lo estampa.
Medido en el navegador tras el cambio: fila con href → `contact-activate`
(press-squeeze corriendo) en ESA fila + `shift-navigate` en el shell; fila
deshabilitada → nada; sub-fila → el mismo par; rail → contacto propio +
`emerge-collapse` del panel; en modo icono, hover → `emerge-open-sub`
(present-rise), hover repetido → nada, salir → `emerge-close-sub`
(dismiss-fade). Suite nueva de 8 contratos, `check` con delta CERO contra la
base medida con stash (72/72), morfo:check PASS, contracts sin fallos nuevos.
Plan y decisiones firmadas: docs/process/PLAN-sidebar.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
Dos hechos que fijan la forma:
- **Dos ocurrencias, dos nodos — sin `queue` .** La superficie `data-event-*` es
UNA RANURA por elemento (memoria `perceptual-surface-one-slot` ) y `sema.md`
manda «reach for `regime` last»: el gesto se estampa donde está la mano (la
fila) y el cruce en la superficie que cruza como unidad (el `provider` ) —
el precedente de `calendar` (el sello de shift se movió del botón porque la
compresión no se pintaba) y de `field-langs` (`provider`). ~~La fila que
navega es la quinta pareja `queue` ~~ — enmendado por D-SB.1bis.
- **`contact` de `Trigger` y `MenuAction` NO va en el morfo**: lo aporta
`Button` por composición (regla de nav-menu, `navigation-menu.ts:64-68` :
«re-declaring it here would put the same verb twice on the same node»). El
`rail` sí se declara porque NO compone `Button` (es una franja de cromo, no
un control de acción — razón válida del wrapper). La FILA tampoco compone
`Button` : su cromo es de fila (`data-active`, `aria-current` , glifo+badge) y
su segundo evento (`shift`) es del sidebar; declarar ambos en el mismo morfo
es lo que hace `queue` verificable (una sola runtime).
Precedentes que este contrato copia: `toggle` (contact + consecuencia en un
nodo con `queue` ), `field-langs` / `carousel` / `calendar` (shift emitido por
el componente que sabe que cruza), `dropdown-menu` (`emerge-*-sub` desde el
mutador único), `navigation-menu` (anclaje por instancia en una parte
repetida), «CTA que navega» del README de `Button` (un `<a>` que navega SÍ
lleva contact en esta casa).
## 3. Contrato objetivo — eidos (`size`)
- **Prop `size` ** en el wrapper eidos `<Sidebar>` : `xs | sm | md | lg`
(«Nav controls», theming §5), `ResponsiveProp` resuelto con
`eidos.resolve(size, 'md')` y estampado como ** `data-size` en el provider**
— atributo VISUAL del wrapper, **no del morfo** (`render-css.ts:2122-2127`;
memoria memoria `visual-wrapper-attrs-not-in-morfo` ); patrón exacto
`breadcrumb.svelte:11-17` . Default ** `md` ** (theming §5: «`md` is the
default»; 1:1 → fila 36px / 16px). Quien quiera la densidad de hoy pasa
`size="sm"` (30px / 14px).
- **Tokens por talla** en `recipes/base.ts` (patrón `breadcrumb`
`base.ts:519-529` ), todos referencias al bundle — así el guard del bundle
por fin los VE: `row-height-{k}: var(--size-{k}-control-height)` ·
`font-size-{k}: var(--size-{k}-font-size)` · `line-height-{k}` ·
`letter-spacing-{k}` · `icon-size-{k}` · `row-padding-inline-{k}` ·
`row-gap-{k}` · `row-radius-{k}` . Internos `--_sidebar-*` conmutados por
`[data-sidebar][data-size]` ; el `.css` sólo consume internos (E-2 deja de ser
ciego porque el valor vive en el token). Se retiran `row-height` ,
`row-radius` , `row-gap` (sustituidos por su forma por talla).
- **`group-label`**: **D-SB.4** — recomiendo un paso por debajo de la fila
(`--size-{k-1}-font-size`), porque ES una etiqueta (nombra al `menu` por
`aria-labelledby` ) y la doctrina de etiqueta (theming §5 «Fields: input 1:1
- the label one step below») se funda en la jerarquía etiqueta↔control, no
en que sea un `<label>` . A `sm` reproduce la proporción de shadcn (xs/sm).
uix(sidebar): la fila que navega decía «te sentí» a nadie, y el libro dice cómo se dice
«Navegar una fila es nativo y no suena» era una decisión de la fase 1 tomada en
silencio (el plan la dejó como «decidir en fase 1») sobre una premisa falsa —
«item = Link puro»—: la fila es `<button>` cuando no tiene `href`, la acción es
un botón pelado y el `MenuItem` posee un flyout. Medido en navegador: pulsar
fila, sub-fila o acción estampaba CERO; el Trigger sí acusaba recibo, porque
compone `Button`.
El libro no deja margen: cap. 22 §9 compone el enlace como «contact.press
seguido de shift.navigate: la presión no es la navegación», cap. 27 §8 lo
repite y cap. 22 §12 llama antipatrón al contacto mudo. Así que la fila declara
las dos ocurrencias, y la aparición del flyout —cap. 26 §5-§6, un menú anclado—
deja de ser un cambio de estado sin evento.
Dónde se estampa cada una lo decidió la doctrina, no la comodidad: el gesto en
la mano (la fila, anclado por instancia: es parte REPETIDA) y el cruce en la
superficie que cruza como unidad (el provider). Eso evita la quinta pareja
`queue` que yo iba a declarar: calendar ya movió su `shift-navigate` fuera del
botón porque las dos ocurrencias se comían la única ranura y el press-squeeze
no llegaba a pintarse.
Voz: contact y shift toman la de su familia (`touch`, `slide`) sin escribir
nada; los dos `emerge-*-sub` van SILENT como el tooltip — el flyout se abre al
pasar el puntero, y un barrido por el raíl serían ocho apariciones sonando
(cap. 26 §7, Criterio 12).
El `trigger` NO declara contacto: lo trae por composición de `Button`. El
`rail` sí, porque es una franja de cromo y nadie más lo estampa.
Medido en el navegador tras el cambio: fila con href → `contact-activate`
(press-squeeze corriendo) en ESA fila + `shift-navigate` en el shell; fila
deshabilitada → nada; sub-fila → el mismo par; rail → contacto propio +
`emerge-collapse` del panel; en modo icono, hover → `emerge-open-sub`
(present-rise), hover repetido → nada, salir → `emerge-close-sub`
(dismiss-fade). Suite nueva de 8 contratos, `check` con delta CERO contra la
base medida con stash (72/72), morfo:check PASS, contracts sin fallos nuevos.
Plan y decisiones firmadas: docs/process/PLAN-sidebar.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- **`width-icon` derivado**, con `scope: 'host'` (TSC: nada derivado en
`:root` ): `calc(var(--_sidebar-row-height) + 2 * var(--sidebar-padding))` .
- **Derivación container→part, capada en `md` ** (theming §5; contexto eidos
como `dialog/context.ts` ): `Trigger` (compone `Button` ), `MenuAction`
(pasa a componer ** `IconButton variant="ghost"` ** — contact + talla + capa
de estado gratis; el recipe de la acción desaparece), `MenuBadge`
(`Badge`, recortado a su subset `xs..lg` ). Un `size` explícito en la parte
gana. Recogido en **D-SB.5** .
- **Capa de estado**: hover de fila y sub-fila por
`background-image: linear-gradient(var(--state-hover), var(--state-hover))`
(las filas no llevan archetype a propósito — precedente `anchor-nav.ts:68-69`
— así que el recipe aplica la capa, como documenta `archetypes.css:59-83` );
el acento `[data-active]` (`--color-primary-track/-text`) se queda: «per-variant
accent stays in the recipe». La pulsación NO se escribe: la pone la firma de
familia `contact` .
- **Glifo de fila**: la fila dimensiona su primer hijo `svg` con
`--_sidebar-icon-size` (como `Button` ); la demo deja de pasar `size="sm"` .
- **Se conservan** `width` , `width-mobile` (layout), `padding` , `gap` ,
`sub-indent` , `sub-z` , `bg` , `border-color` , `rail-width` .
- **A verificar en F3, no detectado**: hit-area táctil de las filas bajo
`pointer: coarse` (Build contract §37) — sin archetype, la regla de
`archetypes.css` no las alcanza; si falta, `::before` slop.
---
## 4. Decisiones que sólo el autor firma (D-SB)
Una por mensaje, con las cinco preguntas. Todas traen mi recomendación; el
resto del plan asume la recomendada salvo firma en contra.
| Id | Decisión | Recomendación y por qué |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
uix(sidebar): la fila que navega decía «te sentí» a nadie, y el libro dice cómo se dice
«Navegar una fila es nativo y no suena» era una decisión de la fase 1 tomada en
silencio (el plan la dejó como «decidir en fase 1») sobre una premisa falsa —
«item = Link puro»—: la fila es `<button>` cuando no tiene `href`, la acción es
un botón pelado y el `MenuItem` posee un flyout. Medido en navegador: pulsar
fila, sub-fila o acción estampaba CERO; el Trigger sí acusaba recibo, porque
compone `Button`.
El libro no deja margen: cap. 22 §9 compone el enlace como «contact.press
seguido de shift.navigate: la presión no es la navegación», cap. 27 §8 lo
repite y cap. 22 §12 llama antipatrón al contacto mudo. Así que la fila declara
las dos ocurrencias, y la aparición del flyout —cap. 26 §5-§6, un menú anclado—
deja de ser un cambio de estado sin evento.
Dónde se estampa cada una lo decidió la doctrina, no la comodidad: el gesto en
la mano (la fila, anclado por instancia: es parte REPETIDA) y el cruce en la
superficie que cruza como unidad (el provider). Eso evita la quinta pareja
`queue` que yo iba a declarar: calendar ya movió su `shift-navigate` fuera del
botón porque las dos ocurrencias se comían la única ranura y el press-squeeze
no llegaba a pintarse.
Voz: contact y shift toman la de su familia (`touch`, `slide`) sin escribir
nada; los dos `emerge-*-sub` van SILENT como el tooltip — el flyout se abre al
pasar el puntero, y un barrido por el raíl serían ocho apariciones sonando
(cap. 26 §7, Criterio 12).
El `trigger` NO declara contacto: lo trae por composición de `Button`. El
`rail` sí, porque es una franja de cromo y nadie más lo estampa.
Medido en el navegador tras el cambio: fila con href → `contact-activate`
(press-squeeze corriendo) en ESA fila + `shift-navigate` en el shell; fila
deshabilitada → nada; sub-fila → el mismo par; rail → contacto propio +
`emerge-collapse` del panel; en modo icono, hover → `emerge-open-sub`
(present-rise), hover repetido → nada, salir → `emerge-close-sub`
(dismiss-fade). Suite nueva de 8 contratos, `check` con delta CERO contra la
base medida con stash (72/72), morfo:check PASS, contracts sin fallos nuevos.
Plan y decisiones firmadas: docs/process/PLAN-sidebar.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
| **D-SB.1** | ¿La fila declara ** `contact-activate` + `shift-navigate` ** (libro) — o sólo `contact` y el `shift` lo emite app-land en `afterNavigate` ? | **Las dos en la fila.** El libro compone el enlace así (cap. 22 §9, 27 §8); la fila es control de navegación por declaración; `field-langs` /`carousel` ya emiten `shift` desde el componente que sabe que cruza. App-land conserva `shift` para las consecuencias que ningún componente conoce (el `Select` de idioma) y la orientación del destino (cap. 27 §9). |
| **D-SB.2** | Verbo de contact: `activate` (Button) o `press` (toggle/switch) | ** `activate` ** — misma gramática que `Trigger` (Button) y que el rail; el sonido es idéntico (`touch`, verbo indiferente). |
| **D-SB.3** | Voz del flyout `emerge-open-sub` / `-close-sub` | ** `SILENT` ** en el pack (como tooltip): hover por el raíl = una aparición por fila; cap. 26 §7; Criterio 12. La firma visual (`present-rise`) se queda. |
| **D-SB.4** | Talla del `group-label` | **Un paso por debajo de la fila** (doctrina de etiqueta, §3). Alternativa: misma talla que la fila. |
| **D-SB.5** | Derivación de `Trigger` / `MenuAction` / `MenuBadge` | **La regla que ya existe** (container→part capada en `md` ), sin inventar un «un paso por debajo» para controles anidados. `MenuAction` = `IconButton ghost` . |
| **D-SB.6** | Default de `size` | ** `md` ** (canon). Cambia el aspecto de la demo y del `app-shell` (filas 32/14 → 36/16): se mide y se enseña. |
| **D-SB.7** | Rama de trabajo y ejecutor | Rama del eje a nombrar por el autor; construye un agente Opus con brief de §5, Fable supervisa (patrón `PLAN-background.md` §10-11). |
uix(sidebar): la fila que navega decía «te sentí» a nadie, y el libro dice cómo se dice
«Navegar una fila es nativo y no suena» era una decisión de la fase 1 tomada en
silencio (el plan la dejó como «decidir en fase 1») sobre una premisa falsa —
«item = Link puro»—: la fila es `<button>` cuando no tiene `href`, la acción es
un botón pelado y el `MenuItem` posee un flyout. Medido en navegador: pulsar
fila, sub-fila o acción estampaba CERO; el Trigger sí acusaba recibo, porque
compone `Button`.
El libro no deja margen: cap. 22 §9 compone el enlace como «contact.press
seguido de shift.navigate: la presión no es la navegación», cap. 27 §8 lo
repite y cap. 22 §12 llama antipatrón al contacto mudo. Así que la fila declara
las dos ocurrencias, y la aparición del flyout —cap. 26 §5-§6, un menú anclado—
deja de ser un cambio de estado sin evento.
Dónde se estampa cada una lo decidió la doctrina, no la comodidad: el gesto en
la mano (la fila, anclado por instancia: es parte REPETIDA) y el cruce en la
superficie que cruza como unidad (el provider). Eso evita la quinta pareja
`queue` que yo iba a declarar: calendar ya movió su `shift-navigate` fuera del
botón porque las dos ocurrencias se comían la única ranura y el press-squeeze
no llegaba a pintarse.
Voz: contact y shift toman la de su familia (`touch`, `slide`) sin escribir
nada; los dos `emerge-*-sub` van SILENT como el tooltip — el flyout se abre al
pasar el puntero, y un barrido por el raíl serían ocho apariciones sonando
(cap. 26 §7, Criterio 12).
El `trigger` NO declara contacto: lo trae por composición de `Button`. El
`rail` sí, porque es una franja de cromo y nadie más lo estampa.
Medido en el navegador tras el cambio: fila con href → `contact-activate`
(press-squeeze corriendo) en ESA fila + `shift-navigate` en el shell; fila
deshabilitada → nada; sub-fila → el mismo par; rail → contacto propio +
`emerge-collapse` del panel; en modo icono, hover → `emerge-open-sub`
(present-rise), hover repetido → nada, salir → `emerge-close-sub`
(dismiss-fade). Suite nueva de 8 contratos, `check` con delta CERO contra la
base medida con stash (72/72), morfo:check PASS, contracts sin fallos nuevos.
Plan y decisiones firmadas: docs/process/PLAN-sidebar.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
### 4.1 D-SB.1 — la contra-lectura `commit.select` (app-shell README, Gaps) y por qué el libro no la sostiene
La sesión que destapó el defecto lo dejó escrito en la fila de Gaps de
`src/uix/blocks/app-shell/README.md` («`Sidebar.MenuButton` es MUDO y
`NavigationMenu.Link` no») con un matiz: «en un shell la fila del raíl casi
nunca navega: SELECCIONA la sección (`aria-current="page"`), que es exactamente
el caso de `commit.select` . No se decide desde el tier». La pregunta es la
correcta —¿qué queda FIJADO o qué MARCO cambia?— y el libro la responde en
contra de `commit.select` para esta fila:
1. **El libro nombra el acto** : cap. 8 (TABLA de reubicación) «selection →
commit.select» y, aparte, «navigation → shift.navigate»; «context → shift si
cambia marco; emerge si sólo aparece superficie». Cap. 27 §1: «ir a otra
pantalla, avanzar en un flujo, **cambiar de vista** … todo eso es shift»; §5
«Navegación — shift.navigate: lista → detalle, **página A → página B** »; §3
«lista frente a detalle: cambia qué puedo hacer, qué significa cada acción,
qué es relevante y **cómo vuelvo** ». Inbox → Drafts → Settings cambia las
tres cosas y el botón «atrás» del navegador lo prueba.
2. ** `aria-current="page"` es HUELLA, no evento** — y es huella de NAVEGACIÓN.
Cap. 1 §7 (TABLA plano/pregunta): «seleccionado» es ESTADO, «seleccionar» es
EVENTO; cap. 12 §6 la huella. La fila no fija nada al pulsar: `active` es un
PROP que el app le pasa DESPUÉS de que el router cambió de ruta; en `Tabs`
el componente posee `value` y lo fija — ahí `commit.select` es honesto,
aquí sería atribuirle a la fila un estado que no es suyo. Y ARIA reserva la
selección a `aria-selected` (tabs, listbox, grid); `aria-current="page"`
significa «la página actual dentro de un conjunto de páginas» — el
vocabulario de la navegación, elegido a propósito por el dossier §P4
(APG Disclosure Navigation + enlaces + `<nav>` ). Un lector anuncia «enlace,
página actual»; sema no puede decir «elegido» sobre el mismo nodo.
3. **La lectura «selecciona la sección» ya tiene componente** : `Tabs`
(vertical) — que existe y habla `commit-select` . El morfo del sidebar sólo
conoce dos formas de fila: `<a>` «when it navigates», `<button>` «when it
only opens a sub-menu». No hay tercera («selecciona sin navegar»); si un
shell la necesita, compone `Tabs` , no re-semantiza el `Sidebar` .
4. ** «casi nunca navega» confunde persistencia del cromo con ausencia de
navegación**: en la demo (`href="/app/drafts"`) cambian URL, historial y
título; que el shell persista es la DEFINICIÓN de app shell, no la negación
de «página A → página B».
5. ** `commit-select + affirm` sobre un enlace es un antipatrón con nombre**:
cap. 10 §12 (TABLA de antipatrones) «Success de navegación — cambio de
contexto con logro → shift + commit.fulfill si procede». Es exactamente lo
que hace hoy `navigation-menu` (T-1): el precedente que hay que reabrir,
no el que hay que copiar.
Consecuencia: D-SB.1 se mantiene (contact + shift en la fila); la fila de Gaps
del `app-shell` se cierra desde este eje cuando F1 aterrice, y T-1 gana la cita
del antipatrón.
---
## 5. Fases y gates
Cada fase: **qué leer ANTES** (entero, no a medias — memoria `read-full-doctrine-before-auditing` ),
**qué producir**, **guard** . Ninguna fase arranca sin la anterior en verde.
### F0 — Lectura obligatoria (sin producir nada)
Libro cap. 22 (§6, §9, §11-12), cap. 26 (§5-§7, §9), cap. 27 (§2, §5, §8-§10),
cap. 30 §4 (regla 1), Apéndice A · `docs/CANON.md` entero ·
`docs/architecture/sema.md` (cascada, pack, `regime` ) ·
`docs/architecture/morfo.md` (partes repetidas, `allowedTargets` ) ·
`docs/theming/reference.md` §5 y §23 · `docs/guides/component-guide.md`
§Build contract + §2 (audit Morfo/Sema) · `src/uix/eidos/components/README.md`
· los tres precedentes: `toggle.ts` , `dropdown-menu.ts` , `breadcrumb.*` ·
`CONTINUE-perceptual-surface.md` §3.0 (una ranura, `queue` , anclaje) ·
memorias: memoria `perceptual-surface-one-slot` , memoria `repeated-part-cannot-be-the-subject` ,
memoria `hidden-pane-suspends-raf` , memoria `hmr-stale-tab-phantom-findings` ,
memoria `a-guard-that-inspects-nothing-passes` .
### F1 — Morfo + sema (el contrato)
- Producir: `sidebar.ts` — los 5 eventos nuevos de §2 con sus docblocks
citando el libro (borrar «native and unsonified»); `sema/components/sidebar.ts`
— docblock nuevo + reglas sólo donde se DIFIERE del default (D-SB.3 →
`sound: SILENT` en los dos `emerge-*-sub` ; nada para contact/shift si se
acepta la familia).
- Guard: `npx vitest run src/uix/morfo src/uix/sema src/uix/contracts.test.ts --project=server`
(validateMorfo: nombres/verbos/`intentRequirement`; pack-census:
selectores sobre `target` /`allowedTargets`) · `npm run morfo:check` ·
`npm run check` delta 0 contra base medida con **stash** (comparar por
MÉTODO, no por cifra).
### F2 — Soma (la emisión)
- Producir: `SidebarMenuButtonProvider.props.onclick` y
`SidebarMenuSubButtonProvider.props.onclick` → `runtimePart.trigger('contact-activate')`
y, si `href && !disabled` , `runtimePart.trigger('shift-navigate')` ;
`SidebarToggleProvider` (rail): contact anclado antes de `toggle()` — sólo
la instancia `rail` , el `trigger` sigue callado en soma porque el eidos le
compone `Button` ; `SidebarMenuItemProvider.openSub/closeSub` →
`runtime.partInstance('menu-sub', subRef)?.trigger('emerge-open-sub' | 'emerge-close-sub')`
sólo en `floating` y sólo si el estado cambia; `events` del runtime sin
handler para los nuevos (no mutan estado del sidebar).
- Guard: suite browser del componente (`*.svelte.test.ts` — crear si no
existe, con `createActiveUix` REAL, nunca fakes): clic en fila →
`contact-activate` y después `shift-navigate` con `data-event-id` distintos
y el segundo tras el hold; `<button>` sin href → sólo contact; disabled →
nada; rail → contact en rail + emerge en panel; icono+hover →
`emerge-open-sub` en ESA `menu-sub` , leave → `-close-sub` , expandido → nada.
### F3 — Eidos (`size` + capa de estado + composición)
- Producir: `types.ts` (`size`), `sidebar.svelte` (`resolvedSize` +
`data-size` + contexto eidos de talla), tokens por talla en
`recipes/base.ts` (+ regenerar `generated/base.css` por el script, NUNCA a
mano), `sidebar.css` (internos `--_sidebar-*` , capa `--state-hover` , glifo,
`width-icon` derivado), `sidebar-menu-action.svelte` → `IconButton` ,
`sidebar-trigger.svelte` / `sidebar-menu-badge.svelte` → talla derivada.
- Guard: `npx vitest run src/uix/eidos` (recipe-css-contract: bundle, sin
literales px/rem en `font-size-*` ; elevation; TSC) · `npx tsx scripts/eidos-lint.ts sidebar`
(0 invalid) · `npm run rtl:check` · `npm run component:audit -- --only sidebar`
PASS · **navegador** : computed `min-block-size` de la fila =
`--size-md-control-height` (36px) y `font-size` = `--size-md-font-size`
(16px) por defecto; con `size="sm"` 30/14; `MenuAction` = `IconButton`
con fuente de la escala; `--sidebar-width-icon` = fila + 2·padding en modo
icono; hover pinta `--state-hover` ; `data-size` responsive cambia al
redimensionar; densidad `compact` mueve fila y padding a la vez.
### F4 — Docs + demo
- Producir: README soma (§Sema events: tabla completa con `Cuándo` y
«anclado por instancia») · README eidos (eventos + `size` + tokens) ·
demo `+page.svelte` : control `size` (paridad demos↔subset,
`demo-authoring.md` §6), texto de la pestaña sema, tabla de tokens ·
`PLAN-blocks.md` §F1.7: una línea de registro que apunte aquí ·
memoria del proyecto actualizada.
- Guard: `npm run docs:check` 0 errores · `npm run lint` .
### F5 — Verificación final en navegador (Chrome REAL, panel visible)
- Con `sound: true` (BootUix): clic en fila → `touch` y luego `slide` (osc +2
y +2, separados por el hold); rail → `touch` + `emerge.exit.soft` ; hover en
raíl → 0 osciladores (SILENT) y `present-rise` visible; nada al hacer hover
en filas expandidas. `press-squeeze` corriendo en la fila durante la ventana
de contact. RTL (`side="right"` + `:dir(rtl)` ) sin regresión del
off-canvas. 375px: Drawer intacto. Reduced motion: sin transiciones.
- Trampas conocidas: pane oculto congela rAF y el sello nunca cae
(memoria `hidden-pane-suspends-raf` ); pestaña con HMR encima = hallazgos fantasma;
`performance` desde `javascript_tool` miente; el `once:true` ya enganchó.
- Cierre: commit(s) por fase con `-F` , sin `--no-verify` ; el `check` final
comparado con la base por stash.
---
## 6. Deuda transversal detectada — NO se ejecuta en este eje
Cada una necesita su propia firma y su propia sesión (memoria `no-cascade-changes` ).
Se listan para que no se pierdan, con la contradicción delante del autor.
| Id | Dónde | Qué | Por qué es del libro |
| ------------------------------------ | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ~~**T-1**~~ **EJECUTADA 2026-08-19** | `navigation-menu` `link` | emitía `commit-select + affirm` al navegar; ahora declara el mismo par que la fila del sidebar — `contact-activate` anclado en el enlace + `shift-navigate` en el `<nav>` . Enmienda D.6 (PROJECT_CANON). Medido en Chromium: contacto con `press-squeeze` EN el enlace + shift en el `<nav>` , 4 osciladores, cero familia `commit` . La cita correcta del antipatrón es **TABLA 9.3** , no cap. 10 §12 | cap. 27 §2 «shift no es commit»; cap. 22 §9; **TABLA 9.3** antipatrón «Success de navegación» (cambio de contexto con logro) — verificada en el PDF; el ancla «cap. 10 §12» que escribió este plan es errónea (ahí están «semáforo redivivo / celebración prematura / intent decorativo»), aunque la apertura de cap. 10 sí dice «una navegación no es exitosa por cambiar de pantalla». Mismo gesto que la fila del sidebar → misma familia. Reabierta, no pisada, la decisión de la auditoría sema del 2026-08-05 (ledger A-54, que movió el evento fuera del marco: mover estaba bien, la familia no). La fila de Gaps de `src/uix/blocks/app-shell/README.md` («`Sidebar.MenuButton` es MUDO y `NavigationMenu.Link` no») se cierra con este eje + T-1 (ver §4.1). |
| **T-2** | `nav-tree` link (F1.8) · `anchor-nav` link (F1.6) · `breadcrumb` link | «links nativos → 0 eventos» (`nav-tree.ts:27-39`, `anchor-nav.ts:25-30` , `breadcrumb.ts:8-11` ) | = S-1. Misma forma de arreglo (contact + shift en la parte `link` , anclado). Cada uno en su sesión; `nav-tree.css:17` arrastra además el pin `sm` (= E-1). |
| **T-3** | `Link` (primitivo) | eidos-only (`scope: ['eidos']`, `link.ts:21` ): sin runtime, no PUEDE emitir; la casa cubre el caso con «CTA que navega» (`Button` + `child` ) | Darle voz exige membresía soma (decisión de autor). Mientras, la ficha **A-62** del ledger de blocks se equivoca al suponer que un `Link` «emite `shift-navigate` »: hoy no emite nada — corregir la ficha en el eje de blocks. |
| **T-4** | Ledger blocks **A-50** (REFUTADO) | se sostuvo en «el silencio de `Link` es contrato escrito, no deriva» | Bajo el veredicto del autor, ese contrato escrito es el defecto. Reabrir en el eje de blocks. |
| **T-5** | Guard del bundle de talla | `recipe-css-contract` no lee `font-size:` escritos en `.css` (E-2) | Un guard que inspecciona el conjunto equivocado pasa en verde; endurecerlo es un eje de eidos (probar por mutación). **Nota** : al mover el `font-size` del `.css` al TOKEN, el guard SÍ vio el único primitivo crudo que escribí (`label-font-size-xs`) y lo rechazó — la deuda es que sólo ve tokens, no reglas. |
| **T-6** | `--control-height-*` / `--space-*` en `:root` | Un `data-density` o un `--scaling` puestos en un SUBÁRBOL no re-derivan las alturas de control: medido 2026-08-19, con `data-density='compact'` sobre la escena el hijo ve `--density-control-scale: 0.9` pero `--control-height-md` sigue valiendo `calc(36px * 1 * 1)` , porque un custom property hereda su valor YA SUSTITUIDO desde donde se declaró. Idéntico para la fila del sidebar, el `Trigger` y un `Button` canónico (36px los tres en todos los casos) | No es defecto del sidebar —es paridad exacta con el canon— pero sí una propiedad del eje de theming que ninguna doc enuncia: la densidad y el zoom sólo son per-documento, no per-subárbol. Eje de eidos/theming. |
uix(sidebar): la fila que navega decía «te sentí» a nadie, y el libro dice cómo se dice
«Navegar una fila es nativo y no suena» era una decisión de la fase 1 tomada en
silencio (el plan la dejó como «decidir en fase 1») sobre una premisa falsa —
«item = Link puro»—: la fila es `<button>` cuando no tiene `href`, la acción es
un botón pelado y el `MenuItem` posee un flyout. Medido en navegador: pulsar
fila, sub-fila o acción estampaba CERO; el Trigger sí acusaba recibo, porque
compone `Button`.
El libro no deja margen: cap. 22 §9 compone el enlace como «contact.press
seguido de shift.navigate: la presión no es la navegación», cap. 27 §8 lo
repite y cap. 22 §12 llama antipatrón al contacto mudo. Así que la fila declara
las dos ocurrencias, y la aparición del flyout —cap. 26 §5-§6, un menú anclado—
deja de ser un cambio de estado sin evento.
Dónde se estampa cada una lo decidió la doctrina, no la comodidad: el gesto en
la mano (la fila, anclado por instancia: es parte REPETIDA) y el cruce en la
superficie que cruza como unidad (el provider). Eso evita la quinta pareja
`queue` que yo iba a declarar: calendar ya movió su `shift-navigate` fuera del
botón porque las dos ocurrencias se comían la única ranura y el press-squeeze
no llegaba a pintarse.
Voz: contact y shift toman la de su familia (`touch`, `slide`) sin escribir
nada; los dos `emerge-*-sub` van SILENT como el tooltip — el flyout se abre al
pasar el puntero, y un barrido por el raíl serían ocho apariciones sonando
(cap. 26 §7, Criterio 12).
El `trigger` NO declara contacto: lo trae por composición de `Button`. El
`rail` sí, porque es una franja de cromo y nadie más lo estampa.
Medido en el navegador tras el cambio: fila con href → `contact-activate`
(press-squeeze corriendo) en ESA fila + `shift-navigate` en el shell; fila
deshabilitada → nada; sub-fila → el mismo par; rail → contacto propio +
`emerge-collapse` del panel; en modo icono, hover → `emerge-open-sub`
(present-rise), hover repetido → nada, salir → `emerge-close-sub`
(dismiss-fade). Suite nueva de 8 contratos, `check` con delta CERO contra la
base medida con stash (72/72), morfo:check PASS, contracts sin fallos nuevos.
Plan y decisiones firmadas: docs/process/PLAN-sidebar.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
---
## 7. Riesgos y trampas ya pagadas (memoria)
- **`queue` medido con timers falsos pasa y está mal** — sólo el navegador
real dice si el `shift` llega tras el hold y no 1,6 s tarde.
- **Partes repetidas**: registrar/anclar por instancia; nunca `runtime.trigger`
pelado desde una fila — caería en la última registrada.
- **Un hijo que se registra desde `$effect` mata el efecto raíz** (A30) — el
`hasSub` del sub ya lo hace desde `$effect` ; no añadir más registros ahí.
- **`display:none` corta la firma de cierre**: si `emerge-close-sub` (pre) no
se ve porque el flyout se oculta en el mismo tick, gatear la ocultación en el
`Presence` que el shell flotante ya crea (dropdown lo hace); se decide
midiendo en F5, no antes.
- **Instrumento antes que código**: dev server sirviendo pre-HEAD por caché de
Vite; contar nodos WebAudio no es medir la salida.
- **`mergeProps`**: el consumidor que hace `preventDefault` en su `onclick`
apaga la emisión de la fila (semántica de la casa, `props.ts:20-27` ) — es
la vía para «yo poseo este gesto», y se documenta en el README.
---
## 8. Registro
- 2026-08-19 — Análisis + medición en navegador (esta sesión). Plan
entregado. **Nada construido, nada firmado.**
- 2026-08-19 — **D-SB.1 FIRMADA** («ok, seguimos»): la fila declara
`contact-activate` + `shift-navigate` (queue); la contra-lectura
`commit.select` del app-shell README queda refutada en §4.1.
- 2026-08-19 — **D-SB.2 FIRMADA** : verbo `activate` (`contact-activate`)
para fila, sub-fila y rail.
- 2026-08-19 — **D-SB.3 FIRMADA** : `emerge-open-sub` / `emerge-close-sub`
declarados sobre `menu-sub` , `SILENT` en el pack (como tooltip).
- 2026-08-19 — **D-SB.4 FIRMADA** : `group-label` un paso por debajo de la
fila (`label-font-size-{k}` = `--size-{k-1}-font-size` ).
- 2026-08-19 — **D-SB.5 FIRMADA** : derivación container→part capada en `md`
vía contexto eidos; `Trigger` = `Button` , `MenuAction` = `IconButton ghost` ,
`MenuBadge` = `Badge` recortado a `xs..lg` ; `size` explícito gana.
- 2026-08-19 — **D-SB.6 FIRMADA** : default `md` (36px / 16px); `sm` para la
densidad actual; se mide antes/después en demo y `app-shell` .
- 2026-08-19 — **D-SB.7 FIRMADA** : en la rama actual (`alpha-0.1-background`),
ejecuta esta sesión, un commit por fase. **Las 7 firmadas → arranca F0/F1.**
uix(sidebar): cuatro cosas que sólo se ven cuando alguien intenta refutarte
Revisión adversarial del eje (seis lentes, un escéptico por hallazgo): 21
hallazgos, 17 refutados, 4 reales. Los cuatro, arreglados.
1. El `MenuBadge` no llegaba nunca a `lg`. Indexaba la tabla de su excepción
por `part`, que ya venía capado en `md`, así que la rama `lg→md` era código
muerto y un sidebar `lg` sacaba el chip un paso pequeño. Ahora indexa por la
talla del sidebar: 26 · 26 · 30 · 36.
2. `emerge-close-sub` se sellaba en un nodo que el mismo tick ocultaba. El
`display:none` de `[data-state='closed']` mataba `dismiss-fade` antes de
pintar un frame y, como el pack silencia ese evento a propósito, la retirada
del flyout se quedaba sin NINGÚN canal: una ocurrencia declarada que no
ocurre. La regla se guarda ahora con `:not([data-event^='emerge-close'])`,
que es justo la ventana que el canal visual mantiene abierta. Medido:
opacidad 0,85 → 0,35 → 0,03 con la animación viva, y el nodo oculto a los
~516 ms, cuando el sello se retira.
⚠️ Mi primera sonda dijo que `dismiss-fade` corría, y era mentira: leía
`animationName` DENTRO del callback del MutationObserver, o sea antes del
flush que ocultaba el elemento. El instrumento midió el instante equivocado.
3. La pestaña sema de la demo clavaba las SEIS previsualizaciones en el panel.
Con cuatro eventos apuntando a otras partes, el ▶ de `contact-activate`
estampaba la familia en el panel de 16rem y `press-squeeze` encogía el panel
ENTERO —lo contrario de lo que el propio párrafo de al lado explica—, y los
dos `emerge-*-sub` no podían encontrarse con la regla que los silencia,
atada a `[data-sidebar-menu-sub]`. `SemaPanel` recibe ahora la acción
(parámetro opcional: las otras 23 páginas siguen con su thunk sin
argumentos) y la página resuelve la parte desde el contrato compilado.
Medido: contacto → una fila, open-sub → el sub, shift → el provider.
4. La fila de Gaps del README de `app-shell` seguía afirmando que «sus morfos
sólo declaran emerge-expand/collapse», que es exactamente lo que este eje
dejó de ser cierto. Reescrita: queda viva sólo la mitad que lo sigue siendo
—`NavigationMenu` emitiendo `commit-select` al navegar, el antipatrón
«success de navegación» del cap. 10 §12—, y se registra por qué se refutó la
lectura «la fila SELECCIONA la sección».
Gates: eidos-lint 49/0 invalid · component:audit PASS · rtl:check 0 ·
docs:check 0/639 · vitest eidos 434/435 (el rojo es ajeno) · suite del sidebar
8/8 · `check` con delta CERO contra la base medida con stash (72/72).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- 2026-08-19 — **F1+F2 EJECUTADAS** (commit `f6d5fa159` ) · **F3+F4 EJECUTADAS**
(commit `63b2de03c` ) · **F5 MEDIDA** . Verificación perceptual con Playwright
headless (la pantalla del navegador estaba oculta, y ahí rAF se congela: las
medidas de layout salían falsas — trampa conocida, pagada otra vez):
· fila con `href` → `contact-activate` con `press-squeeze` corriendo EN la
fila + `shift-navigate` en el shell, **4 osciladores** (dos sonidos) ·
fila deshabilitada → 0 eventos, **0 osciladores** · raíl → contacto propio
(`press-squeeze`) + `emerge-collapse` del panel (`collapse-conceal`), 4 osc ·
flyout en modo icono: abrir → `emerge-open-sub` con `present-rise` y
**0 osciladores** , cerrar → `emerge-close-sub` con `dismiss-fade` y 0 osc —
el `SILENT` del pack demostrado por diferencial con la misma maquinaria ·
reduced-motion → transiciones a 0s · 375px → `data-mobile` , raíl oculto, sin
desbordamiento horizontal · 0 errores de consola.
Tallas medidas end-to-end desde el prop: 26/12 · 30/14 · 36/16 · 44/20, con
glifo 14/16/18/20, rótulo 10/12/14/16, raíl de iconos 42/46/52/60 y las
composiciones capadas en `md` (a `lg` el `Trigger` y la acción se quedan en
36px).
- 2026-08-19 — **REVISIÓN ADVERSARIAL** del eje entero (6 lentes × verificador
escéptico por hallazgo, 30 agentes): **21 hallazgos, 17 refutados, 4 reales** ,
los cuatro arreglados:
1. **El `MenuBadge` no llegaba a `lg`** — indexaba la tabla de la excepción
por `part` , que `clampToPartSize` ya había capado en `md` , así que la rama
`lg→md` era código muerto y `size="lg"` daba un chip un paso pequeño.
Ahora indexa por la talla del SIDEBAR. Medido: 26 · 26 · 30 · **36** .
2. **La fila de Gaps del README de `app-shell` afirmaba lo contrario de lo
que ahora hay** («sus morfos sólo declaran emerge-expand/collapse»): la
reescribí, dejando vivo sólo lo que sigue siéndolo (T-1).
3. **La pestaña sema de la demo clavaba las SEIS previsualizaciones en el
panel**: con cuatro eventos apuntando a otras partes, el ▶ de
`contact-activate` estampaba la familia en el panel de 16rem y
`press-squeeze` encogía el panel ENTERO, y los dos `emerge-*-sub` no
podían encontrarse con la regla que los silencia (está atada a
`[data-sidebar-menu-sub]` ). `SemaPanel` recibe ahora la acción y la página
resuelve la parte desde el contrato compilado. Medido: contacto → una
fila con `press-squeeze` , open-sub → el sub con `present-rise` , shift →
el provider.
4. ** `emerge-close-sub` se sellaba en un nodo que el mismo tick ocultaba**:
`display: none` mataba `dismiss-fade` antes de pintar un frame, y como el
pack lo silencia a propósito, la retirada se quedaba SIN NINGÚN canal. La
regla se guarda ahora con `:not([data-event^='emerge-close'])` . Medido en
Chromium: opacidad 0,85 → 0,35 → 0,03 con la animación `running` y el
nodo oculto sólo a los ~516 ms, cuando el canal visual des-sella.
⚠️ **Mi primera sonda dijo que `dismiss-fade` corría, y era mentira** :
leía `animationName` DENTRO del callback del MutationObserver, o sea antes
del flush que ocultaba el elemento. El instrumento midió el instante
equivocado, no el código.
uix(sidebar): un token de ESPACIO no es la altura de un control, y el raíl de iconos no es una constante
El componente vivía fuera del canon de talla: la fila clavaba
`min-block-size: var(--space-8)` —un token de la escala de ESPACIO haciendo de
altura de CONTROL, que es otra escala de densidad— y `font-size:
var(--font-size-sm)` escrito en el recipe, donde el guard del bundle no lo ve.
El rótulo clavaba `xs`, el trigger `sm`, el badge `xs`, y la acción no componía
`Button`: se quedaba con los 13,33px del user-agent y sin acuse perceptivo
propio.
Ahora el sidebar tiene eje `size` (xs..lg, default `md`, `data-size` en el
WRAPPER —nunca en el morfo, o la resolución de soma lo pisaría con undefined—)
y cada coordenada de la fila sale del bundle `--size-{k}-*`. Medido: 26/12 ·
30/14 · 36/16 · 44/20, con el glifo siguiendo (14/16/18/20) por `--icon-size`,
que es la costura que ya usa `Button` porque un `<Icon>` pinta su tamaño INLINE
y gana a cualquier regla de hoja.
El raíl de iconos deja de ser `3rem`: se DERIVA (fila + 2×padding), así que da
42 · 46 · 52 · 60 y a `md` ya no recorta la fila que tiene que contener.
Tres composiciones más, por la regla que ya existía (container→part capada en
`md`, precedente `Dialog.Close`): el `Trigger` deriva su talla, la `MenuAction`
pasa a ser `IconButton ghost` —y con ella llegan su cromo, su anillo, su capa
de estado y su `contact-activate`—, y el `MenuBadge` deriva con UNA excepción
firmada: baja un paso, porque con la regla al pie el chip sale a 36/16 junto a
una fila de 36/16 e iguala al control en vez de anotarlo.
El hover deja de ser un `--color-surface-raised` a mano y pasa a la capa
canónica `--state-hover`. Al medirlo salió un defecto que no buscaba: la fila
activa no daba NINGUNA respuesta al hover, porque su acento usaba el shorthand
`background`, que resetea el longhand `background-image` donde vive la capa.
Con `background-color` la capa compone encima, que es justo para lo que existe.
Verificado con Playwright headless (la pantalla del navegador estaba oculta y
ahí rAF se congela: las medidas de layout salían falsas). Gates: eidos-lint 49
morfo-backed / 0 invalid · component:audit PASS 0E/0W · vitest src/uix/eidos
434/435 (el rojo es `skin-media-player`, ajeno, idéntico en base) · rtl:check 0
· docs:check 0/639 · `check` con delta CERO contra la base medida con stash
(72/72).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- 2026-08-19 — **D-SB.5bis FIRMADA** (salió de F3, medida en navegador): el
`MenuBadge` es la ÚNICA excepción a la derivación de D-SB.5 — baja un paso
(`xs→xs · sm→xs · md→sm · lg→md`). Con la letra de D-SB.5 el chip mide
36px/16px con la fila en 36px/16px: iguala el control en vez de anotarlo, y
su cifra pesa lo mismo que la etiqueta. Misma razón que D-SB.4 (jerarquía
anotación↔control) y que el propio tipo de `Badge` («etiqueta hermana del
texto; una talla mayor competiría»). Queda escrita como excepción en el
README de eidos con su medida.
uix(navigation-menu): navegar no fija nada, y el megamenú que se abre solo no debería sonar
T-1 de PLAN-sidebar.md §6. El `Link` emitía `commit-select + affirm` al navegar.
El libro lo nombra: TABLA 9.3, «Success de navegación — cambio de contexto con
logro», corregido como «shift + commit.fulfill si procede»; cap. 27 §2 «shift no
es commit… ir a otra pantalla no implica que la operación haya terminado»; y la
apertura de cap. 10, «una navegación no es exitosa por cambiar de pantalla».
Nada queda fijado al pulsar un enlace del nav: `aria-current="page"` es la huella
que deja el router DESPUÉS, no un estado que la parte fije.
Ahora habla el par de cap. 22 §9 —«Link → contact.press seguido de shift.navigate:
la presión no es la navegación»—, el mismo que la fila del Sidebar declaró ayer:
- `contact-activate` ANCLADO en el enlace pulsado (parte repetida → `partInstance`).
- `shift-navigate` en el `provider`, sin anclar: `data-event-*` es UNA ranura por
elemento, y el sujeto del cruce es el `<nav>` que cruza como unidad, no el
control que lo inicia. Sólo si el `<a>` tiene `href` — aquí no es un opt, viaja
en restProps, así que se lee del elemento que la parte ya tenía atado.
Esto REABRE la decisión del 2026-08-05 (ledger A-54), no la pisa: mover el evento
fuera del marco estaba bien; la familia era el defecto.
El pack estuvo a punto de borrarse, y esa habría sido la equivocación de la
sesión. Al quitarle la regla del commit se quedaba con dos selectores vacíos y
propuse retirarlo como ruido. El autor paró y pidió releer: la pregunta no era
qué escribe el fichero, sino qué debe decir la aparición del megamenú. Se abre
por HOVER, y medido con el default de familia un barrido del puntero por dos
triggers y salir de la barra emitía `open` · `open` · `close` — tres sonidos, 6
osciladores, sin un solo clic. Cap. 17 §3 («emerge — normalmente no necesita
sonido»), cap. 26 §7, cap. 32 §10-§11, cap. 5 §11. Así que `emerge-open` y
`emerge-close` son SILENT, la postura que `tooltip` y el flyout del `sidebar`
(D-SB.3) ya tienen para el mismo gesto. El pack DIFIERE del default y vuelve a
ser un pack.
Enmienda D.6 de book-deviations.md (PROJECT_CANON): `navigation-menu` sale del
alcance del `commit.subtle`, y quedan corregidas tres cláusulas suyas caducadas
— el `emerge-open/close` declarado desde el 2026-08-05, el `commit.subtle` que
murió en 86fa9a1b7 sin sustituto, y el `emerge.soft` (0.08) que el catálogo
absorbió (la base `open` es 0.09). `menubar` tiene la misma clase de defecto en
su trigger y NO se toca: otro componente, otra sesión.
Medido en Chromium headless (la pantalla oculta congela rAF; sonda desde la raíz):
- clic en enlace → `contact-activate` (familia contact, sin intent) sobre el
`[data-navigation-menu-link]` con `press-squeeze` corriendo EN el enlace a +8 y
+30 ms y retirado a +120 · `shift-navigate` sobre `[data-navigation-menu]` ·
4 osciladores · cero estampados de familia `commit`.
- Enter sobre el enlace enfocado: idéntico (`handleListKeydown` no intercepta
Enter; el ancla nativa dispara el click). Sin `href`: sólo contacto, 2 osc.
- RTL: mismo par, sin `data-event-direction` — un `href` no tiene sentido propio.
- El SILENT demostrado por diferencial con la MISMA maquinaria: el barrido sigue
estampando `emerge-open`/`emerge-close` y baja de 6 osciladores a 0.
- Enlace pulsado con el panel abierto: cuatro ocurrencias en tres nodos distintos
(open@trigger · contact@link · shift@nav · close@trigger), 4 osciladores — las
dos silenciosas no compiten con el par del enlace.
La demo clavaba las cuatro previsualizaciones en el `<li>`: con eventos en tres
partes, el ▶ del contacto habría encogido el item entero. Ahora resuelve la parte
del contrato compilado (mismo arreglo que la revisión adversarial del sidebar).
Medido: contacto→link, shift→nav, emerge→trigger.
El README de soma describía el estado ANTERIOR al 2026-08-05 (target `item`,
emisión en `openNow`, cascada por `closest()`): reescrito entero.
Gates: check 72 errores con CERO en los ficheros tocados (el de nav-menu que
queda vive en eidos/navigation-menu-content.svelte, `sideOffset`, intacto) ·
suite nav 8/8, cuatro contratos nuevos · sema 327/327 · morfo:check exit 0 ·
morfo:vocabulary 0 (el WARN es de cropper) · component:audit --only
navigation-menu PASS · eidos-lint 32 morfo-backed / 0 invalid · docs:check 0/640
· rtl:check 0 · blocks:check 0/19.
Queda ABIERTO y no es de este eje: `emerge-open` estampa en el TRIGGER y le
corre `present-rise` encima —el botón «aparece» como si fuera él la superficie—,
y la receta pinta abierto/seleccionado con `background` plano sobre un radio
fijo. Los dos son anteriores a este commit y piden su propio eje (la doctrina ya
tiene la forma: `targetFallback`, resolución por estado de montaje).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-19 — **T-1 EJECUTADA** (deuda de OTRO eje, §6, firmada aparte por el
autor: «adóptalo con las cinco preguntas»). `navigation-menu` habla ahora el
mismo par que la fila del sidebar. Lo que las cinco preguntas destaparon y el
plan NO tenía: (a) la cita del antipatrón es **TABLA 9.3** , no cap. 10 §12;
(b) **D.6 de `book-deviations.md` es PROJECT_CANON** y prescribía
`commit.subtle` + `tap` para este componente por nombre — enmendada en el
mismo commit, con dos cláusulas suyas que ya estaban caducadas (el
`emerge-open/close` declarado desde el 2026-08-05, y un `commit.subtle` que
el tier de verbos había dejado sin implementar); (c) el README de soma
describía el estado ANTERIOR al 2026-08-05 (target `item` , emisión en
`openNow` ); (d) el pack se quedaba sin una sola firma y propuse BORRARLO — el
autor paró y pidió releer con el canon y el libro: la pregunta no era «qué
escribe el fichero» sino «qué debe decir la aparición del megamenú», y la
respuesta es **SILENT** en `emerge-open` / `emerge-close` (se abre por HOVER;
medido con el default de familia, un barrido por dos triggers y salir sonaba
`open` · `open` · `close` , 6 osciladores, sin un clic) — cap. 17 §3, cap. 26
§7, cap. 32 §10-§11, la postura de `tooltip` y de D-SB.3. **Firmado SILENT.**
Con eso el pack DIFIERE del default y vuelve a ser un pack; `expression:
'pack'` se queda.
uix(navigation-menu): navegar no fija nada, y el megamenú que se abre solo no debería sonar
T-1 de PLAN-sidebar.md §6. El `Link` emitía `commit-select + affirm` al navegar.
El libro lo nombra: TABLA 9.3, «Success de navegación — cambio de contexto con
logro», corregido como «shift + commit.fulfill si procede»; cap. 27 §2 «shift no
es commit… ir a otra pantalla no implica que la operación haya terminado»; y la
apertura de cap. 10, «una navegación no es exitosa por cambiar de pantalla».
Nada queda fijado al pulsar un enlace del nav: `aria-current="page"` es la huella
que deja el router DESPUÉS, no un estado que la parte fije.
Ahora habla el par de cap. 22 §9 —«Link → contact.press seguido de shift.navigate:
la presión no es la navegación»—, el mismo que la fila del Sidebar declaró ayer:
- `contact-activate` ANCLADO en el enlace pulsado (parte repetida → `partInstance`).
- `shift-navigate` en el `provider`, sin anclar: `data-event-*` es UNA ranura por
elemento, y el sujeto del cruce es el `<nav>` que cruza como unidad, no el
control que lo inicia. Sólo si el `<a>` tiene `href` — aquí no es un opt, viaja
en restProps, así que se lee del elemento que la parte ya tenía atado.
Esto REABRE la decisión del 2026-08-05 (ledger A-54), no la pisa: mover el evento
fuera del marco estaba bien; la familia era el defecto.
El pack estuvo a punto de borrarse, y esa habría sido la equivocación de la
sesión. Al quitarle la regla del commit se quedaba con dos selectores vacíos y
propuse retirarlo como ruido. El autor paró y pidió releer: la pregunta no era
qué escribe el fichero, sino qué debe decir la aparición del megamenú. Se abre
por HOVER, y medido con el default de familia un barrido del puntero por dos
triggers y salir de la barra emitía `open` · `open` · `close` — tres sonidos, 6
osciladores, sin un solo clic. Cap. 17 §3 («emerge — normalmente no necesita
sonido»), cap. 26 §7, cap. 32 §10-§11, cap. 5 §11. Así que `emerge-open` y
`emerge-close` son SILENT, la postura que `tooltip` y el flyout del `sidebar`
(D-SB.3) ya tienen para el mismo gesto. El pack DIFIERE del default y vuelve a
ser un pack.
Enmienda D.6 de book-deviations.md (PROJECT_CANON): `navigation-menu` sale del
alcance del `commit.subtle`, y quedan corregidas tres cláusulas suyas caducadas
— el `emerge-open/close` declarado desde el 2026-08-05, el `commit.subtle` que
murió en 86fa9a1b7 sin sustituto, y el `emerge.soft` (0.08) que el catálogo
absorbió (la base `open` es 0.09). `menubar` tiene la misma clase de defecto en
su trigger y NO se toca: otro componente, otra sesión.
Medido en Chromium headless (la pantalla oculta congela rAF; sonda desde la raíz):
- clic en enlace → `contact-activate` (familia contact, sin intent) sobre el
`[data-navigation-menu-link]` con `press-squeeze` corriendo EN el enlace a +8 y
+30 ms y retirado a +120 · `shift-navigate` sobre `[data-navigation-menu]` ·
4 osciladores · cero estampados de familia `commit`.
- Enter sobre el enlace enfocado: idéntico (`handleListKeydown` no intercepta
Enter; el ancla nativa dispara el click). Sin `href`: sólo contacto, 2 osc.
- RTL: mismo par, sin `data-event-direction` — un `href` no tiene sentido propio.
- El SILENT demostrado por diferencial con la MISMA maquinaria: el barrido sigue
estampando `emerge-open`/`emerge-close` y baja de 6 osciladores a 0.
- Enlace pulsado con el panel abierto: cuatro ocurrencias en tres nodos distintos
(open@trigger · contact@link · shift@nav · close@trigger), 4 osciladores — las
dos silenciosas no compiten con el par del enlace.
La demo clavaba las cuatro previsualizaciones en el `<li>`: con eventos en tres
partes, el ▶ del contacto habría encogido el item entero. Ahora resuelve la parte
del contrato compilado (mismo arreglo que la revisión adversarial del sidebar).
Medido: contacto→link, shift→nav, emerge→trigger.
El README de soma describía el estado ANTERIOR al 2026-08-05 (target `item`,
emisión en `openNow`, cascada por `closest()`): reescrito entero.
Gates: check 72 errores con CERO en los ficheros tocados (el de nav-menu que
queda vive en eidos/navigation-menu-content.svelte, `sideOffset`, intacto) ·
suite nav 8/8, cuatro contratos nuevos · sema 327/327 · morfo:check exit 0 ·
morfo:vocabulary 0 (el WARN es de cropper) · component:audit --only
navigation-menu PASS · eidos-lint 32 morfo-backed / 0 invalid · docs:check 0/640
· rtl:check 0 · blocks:check 0/19.
Queda ABIERTO y no es de este eje: `emerge-open` estampa en el TRIGGER y le
corre `present-rise` encima —el botón «aparece» como si fuera él la superficie—,
y la receta pinta abierto/seleccionado con `background` plano sobre un radio
fijo. Los dos son anteriores a este commit y piden su propio eje (la doctrina ya
tiene la forma: `targetFallback`, resolución por estado de montaje).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Medido en Chromium headless: `contact-activate` (familia `contact` , sin
intent) sobre `[data-navigation-menu-link]` con `press-squeeze` corriendo EN
el enlace a +8 y +30 ms y retirado a +120 ms · `shift-navigate` sobre
`[data-navigation-menu]` · **4 osciladores** (dos sonidos) · cero estampados
de familia `commit` · el par `emerge-open` /`emerge-close` intacto en el
trigger (2 osc cada uno) · 0 errores de consola.
uix(sidebar): la fila que navega decía «te sentí» a nadie, y el libro dice cómo se dice
«Navegar una fila es nativo y no suena» era una decisión de la fase 1 tomada en
silencio (el plan la dejó como «decidir en fase 1») sobre una premisa falsa —
«item = Link puro»—: la fila es `<button>` cuando no tiene `href`, la acción es
un botón pelado y el `MenuItem` posee un flyout. Medido en navegador: pulsar
fila, sub-fila o acción estampaba CERO; el Trigger sí acusaba recibo, porque
compone `Button`.
El libro no deja margen: cap. 22 §9 compone el enlace como «contact.press
seguido de shift.navigate: la presión no es la navegación», cap. 27 §8 lo
repite y cap. 22 §12 llama antipatrón al contacto mudo. Así que la fila declara
las dos ocurrencias, y la aparición del flyout —cap. 26 §5-§6, un menú anclado—
deja de ser un cambio de estado sin evento.
Dónde se estampa cada una lo decidió la doctrina, no la comodidad: el gesto en
la mano (la fila, anclado por instancia: es parte REPETIDA) y el cruce en la
superficie que cruza como unidad (el provider). Eso evita la quinta pareja
`queue` que yo iba a declarar: calendar ya movió su `shift-navigate` fuera del
botón porque las dos ocurrencias se comían la única ranura y el press-squeeze
no llegaba a pintarse.
Voz: contact y shift toman la de su familia (`touch`, `slide`) sin escribir
nada; los dos `emerge-*-sub` van SILENT como el tooltip — el flyout se abre al
pasar el puntero, y un barrido por el raíl serían ocho apariciones sonando
(cap. 26 §7, Criterio 12).
El `trigger` NO declara contacto: lo trae por composición de `Button`. El
`rail` sí, porque es una franja de cromo y nadie más lo estampa.
Medido en el navegador tras el cambio: fila con href → `contact-activate`
(press-squeeze corriendo) en ESA fila + `shift-navigate` en el shell; fila
deshabilitada → nada; sub-fila → el mismo par; rail → contacto propio +
`emerge-collapse` del panel; en modo icono, hover → `emerge-open-sub`
(present-rise), hover repetido → nada, salir → `emerge-close-sub`
(dismiss-fade). Suite nueva de 8 contratos, `check` con delta CERO contra la
base medida con stash (72/72), morfo:check PASS, contracts sin fallos nuevos.
Plan y decisiones firmadas: docs/process/PLAN-sidebar.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- 2026-08-19 — **D-SB.1bis FIRMADA** (salió de F0): `shift-navigate` se
estampa en el ** `provider` ** (el shell que cruza como unidad), post, **sin
`queue` **, emitido sin anclar desde el `onclick` de la fila; `contact-activate`
queda en la fila (la mano), anclado. Precedentes: `calendar` (el sello de
shift se movió del botón a la vista que cruza — la compresión no se pintaba)
y `field-langs` (`provider`). «Reach for `regime` last» (`sema.md`). §2 del
plan queda enmendado por esta línea.