Primera tanda del último componente de F1, con el alcance firmado por el
usuario (17 partes · submenú flotante en modo icono · sema emerge · parada
tras morfo+soma). Suelo de paridad: dossier §P4, leído del `sidebar.tsx` real
de shadcn (23 partes); nadie headless lo tiene (Radix/Base/Ark/React-Aria).
Morfo (17 partes, 2 eventos)
- DOS ejes, no uno: `data-state` (expanded|collapsed) + `data-collapsible`
(offcanvas|icon|none), este último SIEMPRE estampado (shadcn solo lo pone
colapsado, lo que obliga a cada regla a guardar por estado). Más
`data-side` y `data-mobile`.
- `panel` (no `sidebar`, que daría `data-sidebar-sidebar`) = `<aside>`
NOMBRADO; `content` = `<nav>` NOMBRADO, con header y footer FUERA: así el
landmark nombra la navegación y no el cromo. Ninguna referencia trae
landmark.
- `rail` es una parte física y un `<button>` REAL en el orden de tabulación
(shadcn lo deja en tabIndex=-1, inalcanzable por teclado).
- `aria-current="page"` sale del MISMO prop que `data-active`, en
`menu-button` y en `menu-sub-button`.
- `menu-badge` va DENTRO del control de la fila (doctrina de nav-tree: su
texto entra en el nombre accesible). `separator` e `input` NO son partes:
se componen el `Separator` y el `Field` canónicos.
Soma
- Estado controlado/no controlado con las costuras exigidas por el dossier
desde v1: `open` bindable, `defaultOpen`, `onOpenChange` y `toggle()`
público — sin ellas, persistencia y atajo global en app-land serían
rediseño posterior.
- `mobile` sale del breakpoint del SISTEMA (`uix.dom.isAtLeast`), nunca de un
`matchMedia` propio; soma solo decide el hecho y estampa `data-mobile` — la
presentación como `Drawer` la compone el eidos (y con ella su foco atrapado,
bloqueo de scroll y descarte).
- Submenú que SOBREVIVE al modo icono: `menu-sub` se vuelve superficie
flotante anclada al botón de su fila (`createFloatingShellRoot` + anchor +
content), se abre por hover/foco y se cierra por salida, blur o Escape
devolviendo el foco al botón. shadcn los OCULTA y deja ramas enteras
inalcanzables.
- Dos `partRef` del morfo se resuelven last-write-wins entre instancias, así
que soma da el id per-instancia (precedente `navigation-menu`):
`aria-labelledby` del menú → la etiqueta de SU grupo; `aria-controls` del
trigger/rail → el id del panel.
Sema: pack propio con `emerge.soft` / `emerge.exit.soft` sobre `panel`,
espejando nav-tree y collapsible. Langs: 4 textos (label, nav, trigger, rail).
Verificado: `morfo:check` carga el contrato sin invariantes rotas ·
`component:audit --only sidebar` **PASS** (0E/0W) · `svelte-check` 0 errores
en estos archivos · `contracts.test` solo con los 2 fallos ajenos conocidos
(menubar DOM-write, radio-group data-ready).
Pendiente de la siguiente tanda: eidos (raíl, anchos tokenizados, modo icono,
Drawer móvil), demo y review adversarial.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>