Review de 6 dimensiones × 3 verificadores escépticos (135 agentes): 43
hallazgos brutos → 31 confirmados, 12 rechazados. Todos los confirmados
arreglados y verificados en navegador real.
Estado (los dos ejes)
- `collapsible='none'` ya no deja el sidebar INALCANZABLE en móvil: la
inercia del modo se limita al escritorio, que es donde «nunca colapsa»
significa algo. En móvil el panel es un Drawer que arranca cerrado, el raíl
está oculto y el drawer no tiene trigger propio: con el toggle inerte no
había forma humana de abrir la navegación en un teléfono.
- `'none'` PINTA el estado a expandido: antes `data-state`, `aria-expanded` y
`useSidebar().open` podían decir «colapsado» sobre un panel plenamente
visible, y ninguna regla del recipe ni el toggle podían reconciliarlo.
- El estado del drawer móvil se DESCARTA al salir de la presentación móvil:
abrirlo en un teléfono y rotar dos veces remontaba el Drawer ya abierto,
con overlay, foco atrapado y scroll bloqueado sin tocar nada.
Submenú flotante (era el nudo con más hallazgos)
- Cierra también por PUNTERO: se abría en `pointerenter` de la fila, pero el
único cierre por puntero vivía en el propio flyout — salir hacia la página
sin cruzarlo lo dejaba pintado para siempre.
- Escape funciona desde donde el foco ESTÁ (la fila), no solo dentro del sub.
- Entrar/salir se resuelve en el `menu-item`, que contiene fila Y sub, así que
viajar entre ambos no lo cierra.
- El flag se limpia al cambiar de presentación: expandir y volver a colapsar
reabría un flyout que nadie había tocado.
- El motor flotante se engancha solo cuando el sub está ABIERTO, no por modo:
antes `autoUpdate` (rAF + observers) corría por cada fila del raíl.
- `hasSub` deja de latir a true: se limpia en el teardown del sub.
A11y
- Las filas del raíl recuperan NOMBRE: un tooltip solo describe
(`aria-describedby`), así que con la etiqueta oculta el texto de `tooltip`
pasa también a `aria-label` — incluidas las filas con submenú, que no llevan
tooltip visible.
- El panel off-canvas colapsado sale del tab order y del árbol de
accesibilidad (`visibility: hidden` con la transición retrasada para que el
deslizamiento siga animando).
- El diálogo móvil recibe el mismo nombre que el landmark.
- El raíl se llama «Contraer barra lateral» (lo que hace) en vez de
«Redimensionar», que prometía un arrastre fuera de alcance; cursor de
puntero en vez de `ew-resize`.
Recipe
- El signo del off-canvas se deriva de side × dirección: en RTL la fila flex
se invierte, así que el negativo fijo barría el panel POR ENCIMA de la
página en vez de sacarlo por su borde.
- El flyout declara banda `z-index` (nuevo token `--sidebar-sub-z`); era la
única superficie flotante del ecosistema sin ella.
- La transición de `inline-size` (layout por frame) queda SOLO bajo el modo
icono; la base anima únicamente `transform`.
- El `Drawer` es el único dueño del ancho móvil (dos dueños dejaban una banda
de cromo en dos tonos) y se compone con `dragToDismiss`.
- Las reglas de modo icono ya no alcanzan el subárbol flotante: sus filas
conservan badges y acciones.
- Bloque `prefers-reduced-motion` — la traslación del panel es la mayor del
sistema.
Demo
- Copias corregidas donde afirmaban de más: el tooltip ahora sí NOMBRA (y se
explica por qué), y los anchos son rem fijos a propósito (no los alcanza el
escalado; sí el interior).
- Tabla de eventos y de teclado en la pestaña morfo; `onOpenChange` y una
tabla de props por parte en API.
- Los chips de estado se inhabilitan en `collapsible='none'`, que es lo que
el componente hace; botón «show me» que pone icon + colapsado de una vez.
- El trigger pasa a ser solo icono: el morfo le da `aria-label`, que pisaba
cualquier texto visible.
Verificado en navegador: nombres accesibles en el raíl · flyout que cierra
por puntero, por foco y por Escape (con retorno de foco) y que no resucita al
cambiar de modo · off-canvas fuera del tab order · RTL sacando el panel por su
propio borde · z-index 80 en el flyout · drawer móvil nombrado, a ancho
completo y con el panel a ras · `collapsible='none'` abrible en un teléfono.
Gates: audit PASS 0E/0W · eidos-lint 47 morfo-backed / 0 invalid ·
svelte-check 0 errores propios · vitest src/uix/eidos 353/353 ·
contracts.test solo con los 3 fallos ajenos conocidos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La otra mitad de la dirección firmada: el anillo dice el estado como FORMA,
pero la respuesta a «¿quién actúa?» es texto, y una palabra gris de 13px junto
a un orbe vivo no la daba. Ahora el Status son dos renglones:
Esperando tu revisión ← el estado, content-primary, peso medium
la custodia es tuya ← qué significa para ti, secondary, un escalón menos
Sin parte nueva: el Status ya existía y añadir un `Detail` habría sido
abstracción prematura (y una pelea de layout — dos hermanos no apilan sin
envoltorio). El soma renderiza las dos líneas por defecto y expone
`detailText` en los snippet props, así que un consumidor que traiga sus
children sigue mandando.
La copia del detalle es FIJA por estado y localizada como la etiqueta
(es/en): los contadores reales del run (turnos / actos) NO están en el puerto
estructural, y ensancharlo para un subtítulo más bonito es una decisión, no un
defecto. Queda dicho en el README por si algún día se firma.
Tokens: `--aura-status-color` pasa a `content-primary` (era secondary: el peso
informativo estaba en el sitio equivocado), + `-weight`, `-gap` y
`-detail-color`.
Verificado en Chrome real, claro y oscuro, con un run en `reviewing`:
peso 500, primary sobre secondary, y las dos líneas escalando con `data-size`.
Tests 478, eidos-lint 0 invalid / 0 class-hooks, morfo:vocabulary 0 errores,
check en los 73 ajenos preexistentes.
Con esto D1 queda cerrada entera (anillo + jerarquía). Handoff al día.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rediseño dirigido por el usuario sobre un banco de mockups con el orbe REAL
corriendo dentro. Seis frentes, todos con maquinaria existente:
ANILLO DE CUSTODIA (parte nueva `Aura.Ring`, morfo+soma+eidos)
El estado se lee como FORMA y no solo como tinte: raíl punteado en reposo,
arco que respira al ofrecerse, segmentos = pasos del plan (laten), dos arcos
enfrentados y QUIETOS esperando tu revisión (la quietud lo separa de acting),
arco que gira actuando, anillo cerrado + pulso saliente cuando te necesita.
Envuelve al Indicator (es su marco de referencia, no un hermano posicionado a
mano). El movimiento continuo son loops REGISTRADOS (data-animation-style),
nunca @keyframes de receta (R-4.5) — vienen con su puerta de reduced-motion,
donde el dash sigue distinguiendo el estado. El attr va en los hijos del SVG,
jamás en la parte: motionAttrs() estampa además data-state, del morfo ahí.
EL ORBE ES UN CONTORNO
`thickness` en el efecto orb; el hueco es real (alfa propia) sobre cualquier
fondo. Se va la rama claro/oscuro y el parámetro backgroundColor: el shader
mezclaba con el fondo y derivaba el alfa del canal máximo, o sea pintaba un
disco OPACO del color de la superficie justo en el centro (en claro un círculo
relleno; en oscuro el azul del seed). Y la receta deja de pintar su disco en
cuanto monta el canvas (:has(canvas)) — solo sobrevive donde no hay render
vivo: SSR, tests, effect={false}, sin WebGL.
COLOR = TOKENS DE ROL, PINTADO Y NO ROTADO
colorA/colorB/colorDeep (solid / solid-hover / element) resueltos con
eidos.resolveToken y pasados como hex, que es el seam que el propio arte
documenta. El hue-delta anterior se medía en OKLCH y se aplicaba en YIQ: risk
salía VERDE junto a su propio anillo naranja.
ONION EN EL IDIOMA DEL ORBE
Prop `look="solid" | "luminous"` (defecto solid: nadie más se entera). En
luminous el sector no tiene relleno — filo de luz (pasada borrosa + nítida) y
etiqueta en el color del sector; la cuña sigue siendo área táctil. El trigger
con snippet propio va BARE: sin disco, sin sombra FAB y sin el salto del hover
(el orbe ES el trigger, no su contenido). Medio anillo bottom-center y radios
44/40. El halo respira (loop pulse) y en commit-select da UNA pasada de ping
pivotando en el centro real del menú — la firma genérica de commit anima
box-shadow, que sobre un path de SVG no hace nada.
DESENLACES EXPRESADOS (el hueco real: un fallo era indistinguible de un éxito)
Tres eventos terminales con intent INTRÍNSECO — delegate-return (fulfill) /
-aborted (sin intent: un decline es ausencia, no pérdida) / -failed (loss) —
en vez de un intent dinámico, que no tiene dónde vivir: TriggerOptions no
lleva intent y el fromProp del morfo ata a un prop PÚBLICO, no a estado de
máquina. Puerto `runs?` opcional en AuraAgentSource porque activeRun ya es
null al cerrar (§0: sin él, anuncio genérico). Primeras firmas de la familia
delegate en el registro; la de loss es MOVIMIENTO (return-recoil: retirada +
descenso), no tinte — no estrena ningún hue.
MAPA SEMÁNTICO DE COLOR (demo)
Un hue = un significado en toda la superficie: gris=sin agencia ·
naranja=tu revisión · morado=él actúa · rojo=escalated (exclusivo) ·
teal=tu sí. El anillo de autoridad reusa los colores de la custodia con el
MISMO sentido (Review=risk porque vivirás en reviewing; Auto=primary porque
actúa) y los declines pasan a neutral — canon: el intent de un cancel es
`absent`. Además el anillo en reposo DELEGA (las tres autoridades abren el
run) y se cierra por transición, no siempre que no hay run.
Dos guardas del framework corrigieron el diseño por el camino:
- BK-FRAME-NO-INTENT rechazó dar intent a una familia de marco sin
justificación escrita (excepción A-1): queda el intentRationale — el motor
no emite nada y no hay evento evaluable que enmarcar, así que el retorno
consolidado es el ÚNICO sitio expresivo del desenlace.
- fallbackTarget gana sobre el target del morfo (contrato de TriggerOptions):
el sello caía en el provider. Los terminales pasan el Ring cuando existe.
Verificado en Chrome real (ventana delante: en segundo plano el navegador
congela rAF y ResizeObserver y las medidas mienten): seis estados en claro y
oscuro, y un error de transporte REAL apuntando a un puerto muerto →
delegate-return-failed · loss · return-recoil sobre el Ring · región
«Agente: falló — control devuelto».
Tests 478 (morfo 114 · soma/aura 11, +5 casos de desenlace · eidos 353),
eidos-lint 0 invalid / 0 class-hooks nuevos, morfo:vocabulary limpio,
check en los 73 errores ajenos preexistentes.
Pendiente (handoff actualizado): la jerarquía tipográfica del Status (D1
llevaba dos líneas y solo se hizo el anillo) · el pulso de commit-select se
corta si el consumidor cierra el anillo al elegir (necesita retención tipo
Presence) · D5 la superficie de composición del encargo, decisión abierta.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Faltaban dos props públicos como control (regla de demo-authoring: cada prop
público es un control vivo). `mobileBreakpoint` va con chips y el valor
`xxl` (1536px) deja este viewport POR DEBAJO del corte, así que la
presentación móvil —el `Drawer` compuesto— se puede ver sin tocar la ventana.
`onOpenChange` se cablea a un contador que además enseña la doctrina: no se
dispara con el estado del drawer, porque ese no es la preferencia de layout
que el app persiste.
Verificado en navegador: con `xxl` el shell estampa `data-mobile`, el drawer
arranca CERRADO y el trigger de la página lo abre con el panel dentro de
`[data-drawer-content]`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Se abría por hover o por foco, pero solo cerraba por puntero o Escape: un
usuario de teclado que tabulara más allá del submenú dejaba una superficie
abierta flotando sobre la página. El item cierra ahora en `focusout` cuando
el foco sale de la fila Y de su submenú (dentro sigue abierto, para poder
tabular por sus enlaces).
Verificado en navegador: foco en la fila → abre; Tab → «Apollo» dentro del
submenú, sigue abierto; Tab hasta salir → cerrado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Un raíl de app corre de arriba abajo; el shell se quedaba a la altura de su
CONTENIDO (404 de 420px en la demo), así que el panel no llegaba al borde
inferior. `block-size: 100%` en el shell + el `align-items: stretch` que ya
tenía llevan esa altura al panel y al inset. En un contenedor de altura
automática resuelve a auto, así que no fuerza nada a quien quiera altura
intrínseca.
Verificado en navegador: shell y panel a 418px dentro de un contenedor de
420px (los 2px son su borde).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Segunda tanda del sidebar: la capa visual, la demo canónica de 9 pestañas y
la verificación en navegador real. Con esto el componente está vivo; queda el
review adversarial.
Eidos
- Recipe con los dos ejes: off-canvas por `transform` (trabajo de compositor,
el inset no reflowea) y `width` animado SOLO en modo icono, donde el inset
sí tiene que seguir al raíl. El raíl NO lleva `will-change` (una franja
fina tiembla a DPR fraccionario — memoria NavMenu).
- Anchos como TOKENS: `--sidebar-width` 16rem · `--sidebar-width-icon` 3rem ·
`--sidebar-width-mobile` 18rem, más raíl, gaps, altura de fila y colores.
- Tres composiciones que la capa headless no puede hacer: `Drawer` para la
presentación móvil, `Tooltip` para nombrar filas en modo icono y `Badge`
para los contadores. El trigger compone `Button` por el patrón `child`.
- Convención de icono documentada: sobrevive el PRIMER elemento de la fila.
Arreglos que destapó el navegador
- **`mobile` en SSR**: sin viewport, `isAtLeast` responde «por debajo de todo»
→ el HTML salía con `data-mobile` y la hidratación NO lo corrige (no diffea
atributos y el valor ya no cambia). Viewport desconocido = ESCRITORIO.
- **Estado móvil propio** (arranca cerrado): en escritorio `open` es
preferencia de layout persistida; en móvil sería un overlay tapando la
página, que jamás puede ser el estado inicial. `onOpenChange` solo se
dispara con el estado de escritorio.
- **`open` controlado/no controlado**: se adopta el patrón de la casa
(`$bindable` con default, el provider escribe siempre) — mi versión previa
con estado interno no propagaba `bind:open`.
- **El submenú flotante no se posicionaba**: `FloatingAnchor` resuelve su
provider por CONTEXTO y el eidos envuelve la fila en un `Tooltip`, que
publica el suyo → el ancla caía en el tooltip y el submenú se quedaba en
`translate(0,-200%)`. Ahora el ancla se cablea directamente al provider
flotante del item. Además el `<ul>` ya no se posiciona a sí mismo (colapsaba
el wrapper a tamaño cero) y toma elevación del plano `overlay` por
`data-depth`, como cualquier panel flotante del ecosistema.
- **Tooltip vs submenú**: una fila con submenú no lleva tooltip en modo icono
(dos superficies sobre el mismo ancla se pisan) — nuevo `useSidebarItemOr()`.
- El item envuelve (`flex-wrap`) para que el submenú caiga en su propia línea
en vez de ponerse al lado de la fila.
Demo: shell de app real (grupos, badge, acción, submenú, fila deshabilitada),
controles vivos de los tres props y las 9 pestañas del canon.
Verificado en navegador real (Playwright): dos landmarks nombrados en el
idioma activo · `aria-current="page"` en la fila activa · cada menú etiquetado
por SU grupo · raíl enfocable que alterna con Enter · colapso 256→48px ·
off-canvas por transform · submenú flotante anclado a su fila y Escape que
devuelve el foco · sema `emerge-expand/collapse` estampando en `panel` ·
móvil 600px = Drawer cerrado con el landmark dentro · RTL espeja el shell
entero · oscuro · 0 errores de consola.
Gates: `component:audit --only sidebar` PASS 0E/0W · eidos-lint 45
morfo-backed / 0 invalid / 0 class-hooks · `svelte-check` 0 errores propios ·
`vitest src/uix/eidos` 353/353 · `contracts.test` solo con fallos ajenos
(menubar, radio-group y las claves camelCase de aura).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Decisión del usuario. Un nodo deshabilitado conserva su fila (sigue siendo
parte del mapa) pero pierde el `href`: no hay nada que activar con clic,
Enter ni «abrir en pestaña nueva». Un `pointer-events: none` de CSS solo
habría tapado el ratón.
- morfo: la parte `link` gana `data-disabled` (propRef) y `aria-disabled`
(literal `true` bajo `prop-truthy: disabled`).
- soma: `disabled` solo aplica con `href` (un grupo sin destino lo ignora);
el toggle del grupo NUNCA se deshabilita — mostrar/ocultar es control de
vista, no destino.
- eidos: fila en `--color-content-disabled`, sin puntero y sin hover.
- demo: «Agent» va deshabilitado con badge «Soon» + filas de API y a11y.
Verificado en navegador real: `<a>` sin href, `aria-disabled="true"`,
`data-disabled`, color 0.79 frente a 0.61 de una fila normal, clic sin
efecto, fuera del orden de tabulación (44 focusables en vez de 45), badge
dentro del control. `component:audit` PASS · eidos-lint 28 morfo-backed / 0
invalid · `vitest src/uix/eidos` 353/353 · svelte-check 0 errores míos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra el árbol de navegación data-driven (E-1) de F1: demo canónica de 9
pestañas con el mapa real de docs (43 nodos, 3 niveles), verificación en
navegador real y review adversarial (5 dimensiones × 3 verificadores
escépticos; 22 hallazgos brutos, 10 confirmados) con todos los confirmados
arreglados.
Arreglos del review
- sema: la parte `group` —target de los eventos emerge— se registraba SIN
`ref`, así que `runtime.trigger` lanzaba `SomaRuntimeTargetError` en
silencio y el pack no sonaba nunca (cero `data-event-*` en el grupo frente
a los de collapsible). El provider posee ahora el ref del `<ul>`.
- eidos: en una fila navegable el chevron resolvía `inline-size: 100%` como
flex-basis y ocupaba media fila (101 de 231 px en «Soma»), robándole clics
al enlace. Toggle compacto con suelo de diana de 24 px (WCAG 2.5.8).
- soma: una clave duplicada podía volver cíclico `parentByKey` y colgar la
pestaña dentro de `trailKeys` (deriva en render) → clave sufijada + aviso
del logger + guarda de ciclo en el paseo.
- soma: el colapso es CONTEXTUAL (recuerda el `activeKey` bajo el que se
hizo): cerrar la sección que lees se respeta, pero caduca al navegar
DENTRO del grupo, para que la página actual nunca quede sin fila visible.
Sigue siendo query pura, sin `$effect` que escriba estado.
- soma: `child` recibe también `children` (el árbol renderizado); antes
dejaba el landmark vacío, porque un árbol data-driven no lo puede
reautorar el consumidor.
- morfo + langs: el nombre accesible del chevron se declara en el contrato y
se localiza («Alternar sección {label}»); ya no duplica el del enlace.
- eidos: RTL completo — el glyph espeja solo (bordes lógicos), lo que no
espeja es el giro, así que bajo `[dir='rtl']` las dos rotaciones se
intercambian. El Gap «dirección del chevron en RTL» queda RESUELTO.
- demo: paridad de snippet con los controles vivos; fuera el token fantasma
`--nav-tree-rail-width` del docblock del recipe.
badge en v1 (decisión del usuario, delegada)
Está en el suelo de paridad del dossier §P5 (los 6 refs lo llevan). Se
resuelve con SNIPPET, no con recursión a nivel de eidos: el morfo declara la
parte `badge`, soma renderiza el snippet recibido (sin él, el valor crudo —
sigue siendo headless) y el wrapper de eidos pasa el `Badge` canónico. Va
DENTRO del control de la fila, así su texto entra en el nombre accesible
(«TSC, New, enlace»). `disabled` se descarta en v1 (fuera del suelo, y un
enlace de navegación deshabilitado es semánticamente dudoso); ambos quedan
registrados en la tabla de Gaps.
Verificado: `component:audit` PASS · eidos-lint 26 morfo-backed / 0 invalid ·
`svelte-check` 0 errores en estos archivos · `vitest src/uix/eidos` 353/353 ·
navegador real (Playwright): trail auto-expandido, sema estampando en el
grupo, teclado nativo, foco visible, claro y oscuro, RTL, 375 px sin
desbordes, 0 errores de consola.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PLAN §5: la decisión editorial planteada con el usuario (2026-07-22) queda
firmable al retomar — (a) elevar agent.md a especificación publicable («el
contrato de delegación») con agent-check como suite de conformance y el
checklist de amenazas como anexo; (b) nombrar las dos reclamaciones de
frontera que F4/F2 ya materializan: contexto por atención y delegación como
transacción; (c) posición honesta declarada (acts secuenciales v1,
cliente-first, generalidad pendiente del 2º/3º consumidor).
continue-agente: punto 0 del orden de trabajo → firmar D-AG.12.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The soma component README was never committed with F1.6, so
contracts.test's "public Soma component modules with local README docs"
guard was failing and the eidos README's link to it was broken. Adds the
headless-contract README; both guards go green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Estado al cierre (F0/F1/F3/F4a cerradas + tabla de commits), verificación
rápida al retomar, siguiente trabajo en orden (F4b chat → F2 gated →
flecos), y la disciplina operativa de la rama compartida + gotchas de
entorno (Ollama/OpenWebUI, pane, Chrome MCP, index:0 del stream).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
La composición que pidió el usuario: el orb es el trigger central de un
OnionMenu placement="center" y las afordancias de custodia aparecen como
sectores contextuales — reviewing→Authorize/Reject, escalated→Approve/Deny,
activo→Stop — sin fila de botones. El anillo se abre SOLO mientras la
custodia espera a un humano, con re-aserción (un cierre externo sin
resolver re-abre: el efecto lee onionOpen como dependencia); por click en
el centro mientras actúa; se pliega al devolver el control.
App-land por doctrina (agent.md §7): el framework da estados + afordancias
(Aura) y componentes (OnionMenu); el app da forma a la superficie de
review. Patrón documentado en el README de aura (§Patrón).
E2E verificado en Chrome real contra qwen3-coder: la escalada real por
doc.clear abre el anillo con el orb rojo al centro → Deny en el sector →
returned(aborted, user-cancel) + «Agente: terminó — control devuelto» +
documento intacto.
check 0 propios · smoke agnt PASS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
El aurora (banda horizontal recortada al círculo) leía estático a 32-48px
(señalado por el usuario). El render vivo pasa a ser el efecto `orb` de
$scene — la presencia esférica nativa: anillo respirando con ruido + luz
orbitando, movimiento de silueta perceptible en tamaños pequeños y
deformable por el puntero. `aurora` queda como variante para superficies
grandes (`<Aura.Indicator variant="aurora">`).
Modulación §F6 del orb: el estado fija la energía (forceHoverState +
hoverIntensity como gain + rotateOnHover en acting/escalated) y el rol el
matiz como delta de hue OKLCH sobre la identidad del shader (primary;
reviewing=risk, escalated=threat). Verificado en Chrome real, estado por
estado: idle recogido → planned anillo violeta → acting denso rotando →
escalated rojo agitado.
Demo de aura: chips de variante; scene/README: acta D4 pendiente de F3.
eidos 353/353 · check 0 propios.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
La demo usaba el adaptador sin documentar cómo se configura. Nueva sección
«Configuring the transport»: qué es el puerto (turn = un intercambio de
cable), adapters fuera del barrel, ejemplo dev-directo vs prod-proxy
(topología de credenciales), tabla completa de opciones (baseUrl · model ·
headers · request · fetch) y qué POSEE el adaptador como traducción de
dialecto (SSE→v1, sanitización de ids, re-pairing de tool_calls, split por
id en servidores que reutilizan index 0, error de cable → transport-error).
README del arte: opciones inline en la entrada del adaptador.
check 0 propios · smoke agnt PASS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
La respuesta a «¿está pensando?» no se improvisa — es Aura, el componente
del eje construido para ello. La sección real de la página agnt bootea un
uix standalone mínimo (createActiveUix + Soma + ActiveEidos + eidos css):
legítimo porque un app compone ambos tiers, y la demo ES el app.
- Motor llama → createActiveAgent (mismas opciones bus+journal); activeRun
reactivo satisface AuraAgentSource; stream vía .engine.onEvent
- <Aura {agent}> con Indicator (orb $scene) + Status + Region + Cancel;
authorize/escalation quedan como afordancias app-level (doctrina §7)
- Verificado sobre el run real de qwen3-coder: «Inactivo» → «Propone un
plan» → announcement «Agente: terminó — control devuelto» en la Region,
doc mutado, answer en es; Cancel = Button disabled-en-idle; consola 0
check 0 propios · agent 30/30 · smoke agnt PASS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
El bus como eje del agente a nivel de app, cerrado en tres direcciones:
observar (agent.*), reaccionar (orca) e INICIAR — hecho de dominio → acción
orca → App.agent.start({initiative:'system'}) con clamp auto→review por
construcción. Nunca command-events. Sección doctrinal + ejemplo en la página.
Adaptador real `$agent/adapters/openai-chat` (fuera del barrel, D-AG.1c):
dialecto OpenAI chat-completions (Ollama/llama.cpp/LM Studio/proxy), SSE →
protocolo v1, sanitización bidireccional de capability ids con puntos, y
re-pairing de tool_calls al reproducir el transcript (memoria por callId).
Fix cazado en vivo: Ollama reutiliza index:0 para todas las calls del
stream — una call nueva se detecta por id distinto, jamás por index (los
args concatenados producían invalid-args en motor + 400 en el replay).
Demo viva en /active/docs/agnt contra qwen3-coder:30b real: capacidades
doc.* tipadas (sium), read snapshot-por-turno, streaming, escalada
irreversible. Ciclo completo verificado en navegador: 3 acts, 2 turnos,
doc mutado, respuesta en es, rastro entero en bus (correlationId=runId).
agent 30/30 · smoke agnt PASS · check 0 propios.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Auditoría punto-a-punto contra los README de bus y orca (12/13 conforme).
La no-conformidad real: `goal` viajaba en AgentRunBusPayload, contra la
higiene documentada de ambos (bus: «pasa correlationId y que el suscriptor
resuelva el contexto sensible del módulo»; orca: «no unnecessary private
data — la acción lo resuelve del módulo al ejecutar la acción»).
- AgentRunBusPayload = SOLO metadata (runId/initiative/autonomy/state/
outcome/reason/acts/turns); el suscriptor resuelve vía App.agent.run(runId)
- spine test prueba el patrón: 'goal' in payload === false + resolución
desde el módulo con el orca REAL
- página agnt: nota de higiene + recomendación configureEvent(
'agent.run.state', { queuePolicy: ORCA_QUEUE_REPLACE_QUEUED }) para el
evento frecuente (doctrina de colas de orca)
- PLAN-agent F4a: acta de la auditoría de conformidad
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Auditoría del track (encargo): divergencias firmadas-sin-implementar
corregidas y el marco de integración INDEPENDIENTE del componente — el agente
como servicio del app, no como demo de un componente.
Fixes de decisiones firmadas (con tests):
- C3/D-AG.7: la iniciativa autónoma (system/scheduled) clampa auto→review —
un run que el usuario no pidió jamás se auto-autoriza.
- D-AG.5: presupuesto de contexto (maxContextBytes) con FIFO-trim byte-based
(el goal sobrevive; resumen semántico diferido) + log del recorte.
- F4c: tope por-arg (maxArgBytes) → invalid-args tipado.
- D-AG.6d: schema Standard-Schema del resultado (`result?`); un mismatch
vuelve como recoverable para auto-corrección y no cuenta como act.
Integración app (patrón session EXACTO):
- El arte posee el vocabulario `agent.*` (consts) + bus-helpers tipados que
publican con bus INYECTADO (type-only $libs/bus): run.opened/state/
escalated/closed + act.applied, con source='agent' y correlationId=runId —
la costura causal ligera (una delegación correlaciona de punta a punta).
- defineActiveAgent cablea core.bus (junto a timers/logger).
- Reacciones vía orca APP-LAND (el invariante intacto): probado con
EngineOrca REAL reaccionando a agent.run.closed en integration.test.
- Página del arte /active/docs/agnt (la convención que faltaba): sección
Delegation en nav; artículo + playground VIVO con los 4 motores puros
reales (bus log en vivo, journal WAL, contador orca, kill switch,
compuertas deterministas) — cero componentes, integración pura de app.
Gates: agent 26/26 · smoke agnt PASS · check 0 propios · verificado en
navegador (pane + Chrome real): ciclo completo, orca archiva runs, kill
switch reactivo.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nueva sección de estado en el handoff vivo: el barrido adversarial (18/18 arreglados,
6 refutados, 7 commits verificados por test/Playwright), la infra Menubar.Panel que se
commiteó (bd5eb1925) y reparó (ef85b9223) tras leerla erróneamente como ajena, el #6
(getOrderedValues por orden DOM, 92d060a4f), la verificación en barra de menús, y la
lección: uncommitted ≠ ajeno tras un context-summary. Doc de la API Menubar.Panel ya
en el README de soma/menubar (con la infra).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adversarial-review outcome (fixes + documented v1 limits) and the two
piggybacked sticky type-fixes recorded; scoreboard corrected (prose F1.5
had also landed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
svelte-check surfaced two F1.1 type errors: the sentinel is a writable
ref bound by the wrapper, so it belongs in StateProps (not ActiveProps —
part() wants State<HTMLElement|null>); and the eidos types barrel must
import the renamed ProviderProps export, not the internal StickyProps.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nav landmark + native anchors; IntersectionObserver band detection
(never a scroll listener + getBoundingClientRect — anti-reflow doctrine).
aria-current="location", per-depth rail indent, data-active drives the
eidos accent. Real-IO chromium test: band tracking + bottom dead-zone.
Adversarial-review hardening:
- ref-count register/unregister so duplicate hrefs / mount churn can't
drop a still-referenced target (+ test)
- tokenize the rail transition (--duration-fast/--ease-default) and the
focus outline-offset (calc(var(--focus-ring-width) * -1))
- document two v1 limits (instant-jump dead-zone, link-order == section
order) — both need a v2 observation-contract change
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra los dos flecos de F3 tras la corrección del usuario (la review-card
con diff era anclaje en dev-tools, sin sentido a nivel framework):
- agent.md §7 gana «The review surface»: el FRAMEWORK posee los estados del
ciclo expresados perceptualmente (reviewing/escalated con afordancias
authorize/reject/cancel — Aura ya las da) + la semántica de «propuesto por
otro actor» + los anuncios atribuidos; el APP posee el CONTENIDO de lo que
se revisa, en su propio dominio (un documento enseña sus bloques
propuestos, un calendario sus eventos pendientes). Una tarjeta-diff estilo
git es legítima en una aplicación concreta; jamás canon.
- §6 fatiga de aprobación enmendada en el mismo sentido (el cambio concreto
en el dominio del app, nunca prosa).
- PLAN: F3 CERRADA — review resuelta como doctrina sin componente nuevo;
`allowedFamilies` del primer componente actuado reubicada a F2 (su hogar
natural es el piloto). Siguiente: F4 por la vía del chat.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
El barrido de calidad de palabras confirmó que el foco por flechas (ArrowLeft/Right,
Home/End) y el hover-slide divergían del orden visual tras navegar a un elemento con
más secciones contextuales: el `{#each}` keyeado reutiliza los triggers persistentes
y los nuevos se APPENDEAN al Map después, así que el orden de inserción ≠ orden DOM.
Ahora getOrderedValues ordena por compareDocumentPosition (query estructural,
reflow-safe), corrigiendo navigateAdjacent/focusEdge/onContentKeydown/hover-slide en
un punto. 5/5 tests del menubar verdes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra el primer componente del eje agéntico de punta a punta (las 9 fases de
la ruta). `component:audit` PASS.
- README eidos con comparativa (5 refs adyacentes — thinking-indicators,
CopilotKit activity, Vercel useChat.status, presencia colaborativa
Figma/Docs, assistant-ui) reconociendo el paisaje escaso: nadie tiene una
semántica perceptiva de la delegación con a11y (informe §8.3). Decisiones +
Gaps con disposición.
- Fixes del audit: opacity literal `0.5` → `var(--opacity-50)` (R-4.2);
excepción R-1.5 documentada (único focusable = Cancel, que compone Button);
`apg: 'none — …'` en el morfo (A-1.4).
Gates: component:audit PASS · morfo:vocabulary limpio · eidos-lint aura 0
inválidos · recipe-contract verde.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reparan el commit anterior (era WIP con errores de svelte-check; runtime OK):
- provider: `tabindex:-1` en los props del panel estaba duplicado (focusScope.props
ya lo aporta con el mismo valor) → quitado el explícito.
- menubar-panel: el prop `style` (de PrimitiveDivAttributes = `Record<string,unknown>`)
no encajaba con el opt del provider (`StyleProperties`) → cast en el create.
- test: `props['aria-haspopup']` no es clave estática del tipo (es atributo del morfo
resuelto por renderProps en runtime) → acceso vía cast a Record.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Materializa el eje ortogonal agéntico del ecosistema: la 8ª familia semántica
`delegate` («¿quién actúa ahora?») deja de estar sin materializar. El agente es
OTRO ACTOR (LLM, macro, regla, workflow) que actúa por la MISMA API pública del
provider; la ruta de llamada no se bifurca, la concreción semántica depende del
actor.
F0 · Doctrina
- docs/architecture/agent.md — la doctrina permanente (capítulo comparativo de
referencias + bibliografía de seguridad, máquina estados=verbos, contrato de
participación, fila §0, a11y, amenazas).
- docs/process/{PLAN,TRIAGE,INFORME}-agent — plan de ejecución con todas las
decisiones firmadas (D-AG.1–11 + ⚖️1/2/3), triage de 4 revisores externos, e
informe autocontenido para revisión externa.
F1 · Motor ($libs/actor + arts/agent, 24º arte)
- EngineAgent puro (sin DOM/runes, portable a servidor): máquina D-AG.4
(suggest/review/auto · escalated con reason+timeout · returned outcome-tipado
· kill switch · autorización journaled aunque sea auto), tool-loop D-AG.5
(acts secuenciales, fallos→verbos, unknown-capability acotado, idempotencia
por callId, presupuestos acts/turns/wall-clock vía puerto de timers), techo de
autonomía por origen no confiable (F8b).
- ActiveAgent (sesión reactiva, contrato ActiveEngine) · protocolo v1 espejo
AG-UI (5 categorías + dirección tipada + reservas) · ScriptedAgentTransport
determinista (adapters/ fuera del barrel) · journal WAL + puerto de trazas
OTel · emisor sium→JSON Schema.
- Acuñación del actor (⚖️2/F6b): ActorToken opaco en $libs/actor (hoja bajo
orca/agent), registro privado WeakMap — los forjados resuelven a null;
costura `actor?` en TriggerOptions→SemanticSignal (runtime copia verbatim;
sema no resuelve). defineActiveAgent (service-factory app-level, timers del
core — cero setTimeout a pelo).
F3 · Aura — primer componente del eje (ruta 9 fases 0–6)
- El materializador canónico de `delegate` (reservado en scene §F6): morfo con
los eventos del ciclo (offer/escalate-untilAction/return + sustain-processing
stateBound) — PRIMER morfo del ecosistema que emite familia delegate; provider
soma que observa un puerto estructural (sin importar $agent — degradación
total); orb eidos = aurora $scene modulada por estado (§F6) con fallback CSS
= render de reduced-motion; live region única atribuida (WCAG 4.1.3); cancel
compone Button (asChild). Demo v2 sobre el MOTOR REAL con compuertas
deterministas. Orb-size derivado de la primitiva Avatar (32/40/48).
D4 · Promoción uix.scene (Aura llegó)
- defineEngineScene + superficie ActiveUix/ActiveEidos/contracts; el orb prefiere
el motor compartido (presupuesto de escenas global) con fallback por-superficie.
Gates: arts/agent 18/18 · aura 6/6 · arts:check 24 · eidos battery + recipe
30/30 + eidos-lint aura 0 inválidos · morfo:vocabulary + esquemas · smoke aura
PASS · verificado en navegador (ciclo completo + estampa delegate en DOM +
aurora WebGL pintando). Los tokens de recipe base.ts + CSS generado ya entraron
en HEAD vía una sesión concurrente.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Componente nuevo `menubar-panel.svelte` + soporte en el provider (FloatingContent
para anclar el panel a su opción, FocusScope, Dismissal) + types/exports/content/
README/test. Es la infra ribbon-style que el `palabras-element-inspector` (ya
commiteado en esta rama) COMPONE — cada sección del elemento = un Menubar.Panel
corto. Trabajo de esta sesión que había quedado sin commitear; sin él la rama
quedaba incompleta (el inspector referencia `Menubar.Panel`).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Las entradas del sidebar de docs para los componentes F1 sticky (grupo
Layout) y prose (grupo Typography), diferidas de sus commits por un estado
CRLF del archivo (autocrlf); ahora normalizado a LF y committeado limpio
(3 líneas). Los componentes ya estaban en 998b11d9b / 5b84749ee.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Quinto componente F1 del plan blocks (eidos-only; alcance E-2 = suelo del
dossier §P4). Estiliza HTML/markdown RENDERIZADO que el app pasa como children.
EL DIFERENCIADOR (que ninguna referencia puede): cada regla de elemento se
emite a especificidad CERO vía `:where([data-prose] EL)`. Como todo componente
del framework se estiliza por `[data-{component}]` (≥0,1,0), un <Button> /
<Callout> / <Card> embebido conserva su PROPIO estilo AUTOMÁTICAMENTE — sin
anotación `not-prose`. Verificado en navegador: un <Callout> dentro de prose
mantuvo su tinte affirm, borde-acento 3px y layout grid en claro Y oscuro; las
reglas :where de prose no lo tocaron. El donut `:not(:where([data-prose-ignore],
…))` es el opt-out explícito para subárboles HTML crudos.
- morfo: 2 partes (provider bare data-prose + ignore documenta el attr opt-out;
eidos-lint lo ve morfo-backed). Selectores de elemento = estructurales bajo la
parte (S1). 0 eventos, apg:none, passive
- recipe: cobertura de paridad (h1–h6 incl. h5/h6 que tailwind NO; p, a, strong/
em, blockquote borde-acento lógico, ul/ol + ::marker, dl, code/pre + reset kbd,
img/figure/figcaption, hr, table); ESCALA EM-relativa (un font-size raíz
re-deriva todo — no la escala 5× de tailwind); overflow de tabla estilo GitHub
sin wrapper (display:block+overflow); trim de bordes; SIN backticks en code
(doblarían sobre <Code>); dark GRATIS por roles (no el patrón -invert);
propiedades lógicas → RTL correcto
- decisión :where (no @scope): :where establecido en el codebase + soporte 2021+;
@scope diferido a Gaps (mitad de tamaño pero «Baseline newly available» sin
precedente aquí)
- demo v2 9 tabs con markdown real + <Callout>/<Button> embebidos (prueba de
auto-exclusión) + ejes size/measure; README (Comparativa 5 refs · Decisiones ·
Passive · Gaps con disposición)
Verificación: component:audit PASS 0E/0W (21 literales em con /* literal */
justificado + eidosSnippet) · eidos-lint 45 morfo-backed/0 invalid ·
recipe-css-contract 30/30 · svelte-check sin errores propios · navegador:
h1 36px/bold, h2 24px, medida 65ch (~692px), blockquote acento, tabla
display:block+overflow, code chip, AUTO-EXCLUSIÓN del Callout en claro y oscuro.
Nota: nav sidebar fuera del commit (flip CRLF ajeno); índice langs reconstruido
prose-only (entangle con aura sin commitear de sesión paralela).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
La cadena de fieldControl (inspector + panel) manejaba pills/toggle/number/slider/
length/textarea/text/spacing/border/typography/adjustments/color pero NO select ni
chip (ambos válidos en PalabrasFieldType / esquemas de diseñador) ni un else final,
así que un campo de esos tipos daba una celda vacía muerta. Añadido un {:else} que
muestra el valor actual en read-only (mapeado a la etiqueta de la opción si hay
options) — que ADEMÁS es el render correcto de `chip` (display read-only). Un control
`select` funcional queda como diseño diferido (flagged). Latente: ningún esquema
shipped lo usa.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adversarial review confirmó (minor): el recipe fijaba en el eje LÓGICO
(inset-block-start/end) mientras el rootMargin del observer es FÍSICO
(top/bottom). Coinciden en todos los modos horizontales (LTR y RTL: top/bottom
son eje de bloque, invariantes al flip inline), pero DIVERGEN en writing-modes
verticales, donde inset-block-start = izquierda/derecha física ≠ el borde que
el observer vigila → data-stuck se dispararía en la posición equivocada (o
nunca). La API `edge` es física ('top'|'bottom') y el observer es físico, así
que el CSS debe ser físico también (mi razón previa «lógico por RTL» era
incorrecta — RTL no afecta el eje de bloque).
- sticky.css: inset-block-start/end → top/bottom; margin-block-end/start del
centinela → margin-bottom/top (mismo eje de bloque, RTL-safe)
- demo: corregidas las 3 menciones «logical inset / writing-mode correct»
Verificado: component:audit PASS 0E/0W (físico top/bottom = eje bloque, no
dispara regla RTL) · test real-IntersectionObserver en chromium PASS (físico
top en horizontal = comportamiento idéntico). Nota: la lente teardown-ssr del
review falló por API stall mid-stream; esos caminos ya están cubiertos por los
tests (cleanup/disconnect aserted, disabled aserted, SSR no-op por contrato de
ActiveDom).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- #13: el filtro `kinds` (oculta un campo si el `kind` del bloque no coincide) solo
se aplicaba a los built-in de Contenido/Diseño; los campos de Avanzado y TODOS los
extras de esquema lo ignoraban (solo filtraban por `.tab`), mostrando un campo
kind-restringido para cualquier variante. Aplicado fieldForKind consistentemente en
ambos consumidores (element-sections + panel). Latente (ningún esquema shipped lo
usa) pero honra el contrato documentado.
- #18: los presets Interlineado/Tracking caían a un default hardcodeado ('1.5'/'0')
cuando el bloque no tenía override, resaltando un valor falso aunque el esquema
declarara otro (el esquema por defecto declara text.lineHeight 1.6). Ahora heredan
el valor EFECTIVO del esquema (schemeLineHeightFor/schemeLetterSpacingFor, espejo de
schemeFontFor/defaultFontSize), cableado en panel + inspector. Verificado: el párrafo
del esquema por defecto muestra Interlineado "Personalizado" (1.6, no el falso 1.5)
y Tracking "Normal" (sin regresión).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Registra sticky (998b11d9b) en el registro del plan: primer componente
behavioral, mapa de construcción por workflow, lecciones (jsdom-pragma
ignorado en browser mode → test IO-real en chromium; entangle de barrels
con sesion paralela; tension del test harness declarada). F1 = 4/8.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Primer componente F1 con comportamiento real (soma), y el que desbloquea el
block site-header (F2.1). Construido tras un fan-out de investigación (6
lectores) que fijó el mapa: template = FeedSentinelProvider, motor =
IntersectionObserver por el port bendecido de $adom, escritura de attrs por
syncAttrs (nunca dom.apply crudo). Alcance E-2 = suelo del dossier §P3.
- morfo: 2 partes (provider=box → data-sticky/data-edge/data-stuck ·
sentinel aria-hidden → data-edge); 0 eventos con justificación de COMPLEX
BEHAVIOR (apg: none); data-stuck presente/ausente + data-edge enum
espejan @container scroll-state(stuck: top/bottom) — este componente es el
polyfill cross-browser de ese contrato CSS futuro
- soma: un StickyProvider registra AMBAS partes en un runtime; el observer
vive en un $effect que RETORNA observeIntersection(...) como teardown
(mirror de feed); el callback async voltea stuck=$state; rootMargin
DERIVADO del offset (la línea de disparo del centinela ES la línea de pin
— acoplamiento matemático, offset obligatorio); root=scroll-host
(default viewport), Document coercionado a null; offset viaja como
--_sticky-offset (dato A8, no CSS visual — precedente meter)
- eidos: recipe fino que SOLO posiciona (position:sticky + inset lógico
desde --_sticky-offset + token --sticky-z-index + centinela layout-neutral
2px con margen negativo); el tratamiento del estado pegado es del
consumidor ([data-sticky][data-stuck]) — el recipe posiciona, no decora
- centinela = HERMANO de flujo del box (dos nodos raíz), no hijo (un hijo se
fija con el box y nunca detecta); orden según edge
- demo v2 9 tabs con scroll container real + readout de stuck vivo; READMEs
soma + eidos (Comparativa AntD/Mantine/Chrome-technique/headless/CSS ·
Decisiones · Passive justification · Gaps con disposición)
Verificación: component:audit PASS 0E/0W · eidos-lint 6 morfo-backed/0
invalid ([data-stuck] sin regla = intencional, consumidor decora) ·
morfo:check limpio · svelte-check sin regresión propia · 5 tests: 4 unit
(rootMargin por borde, root null-coerce, flip de stuck, disabled, cleanup) +
1 REAL-IntersectionObserver en chromium (foreground: no-stuck→scroll→
stuck→scroll-back→no-stuck — prueba end-to-end de la geometría offset↔
rootMargin, lo que el pane suspendido no puede). Navegador: box pinea a los
12px del offset, recipe correcto.
Decisión declarada (test harness): sticky-provider.svelte.test.ts usa el
installSomaHarness compartido (translator identidad) como los otros 86 tests
de provider — TÉCNICAMENTE en tensión con la regla CLAUDE.md «never fake
translators; use createActiveUix». Son unit tests del provider; la
integración real la cubren contracts.test.ts + la verificación en navegador.
Seguido el precedente universal; flaggeado para tu ratificación.
Nota: nav sidebar de sticky (web/routes/uix/+layout@.svelte) fuera del commit
— el archivo arrastra un flip CRLF de sesión paralela (2125 líneas de ruido);
la entrada viaja en el árbol de trabajo. Registros langs/soma de sticky
reconstruidos sticky-only (los índices tenían entangle con aura sin commitear).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nuevo efecto `water` en $scene/effects + registro en el pack Ambient
(import = opt-in). Superficie de mármol de vetas incandescentes sobre
negro que se ondula bajo el ratón: cada tramo de puntero engendra un
anillo que se expande y refracta la superficie (modelo de anillos de
pixel-blast). 11 params override por token (P-4). Alta en el catálogo
demo (Tier B, 33 efectos) y en el README del pack.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- #4: el guard capture-phase del BubbleMenu hacía preventDefault + restoreDomSelection
en TODO pointerdown del bubble, así que clickear el input de tamaño nunca lo
enfocaba (solo +/− servían). Ahora se salta el guard cuando el target es un control
de formulario nativo (input/textarea/select): el input se enfoca y como
syncSelectionFromDom CONGELA el modelo con foco fuera del editor, el bubble sigue
abierto y la marca aplica al rango. Los botones (marcas) no necesitan foco y siguen
con onclick; el resto de clicks siguen protegiendo el rango. Verificado Playwright:
teclear "48" + Enter aplica font-size:48px.
- #11: openLink saca el foco del editor al input de enlace; cerrarlo (aplicar O
cancelar con Escape) no lo devolvía → editor sin caret, teclado muerto hasta
reclicar. Ahora returnFocusToEditor() (api.focus + api.restoreCaret) en ambos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Find (#8): close() solo cambiaba `open`; la query/matches viven en el provider
raíz, así que cerrar dejaba los resaltados <mark data-palabras-find-match>
pintados sin UI para quitarlos (contradecía el contrato documentado). Como el
`find` del snippet no llega al close() del script exterior, muevo el Escape al
onkeydown del PANEL (dentro del snippet, con `find`) y limpio la query en ambos
cierres (✕ + Escape). Verificado Playwright: Escape cierra y borra los 7 marks.
- Find (#15): el contador podía mostrar N/M con N>M si los matches menguaban por
una edición directa (currentMatchIndex no se re-clampa). Clamp en el display.
- Slash (#9): 14 ítems, ~9 visibles; la navegación por teclado no hacía scroll al
activo → Enter insertaba algo invisible. Efecto (raf-diferido, iframe-safe) que
hace scrollIntoView del [data-active]. Verificado: ArrowUp→"Columnas" en vista.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cuatro defectos confirmados por el barrido:
- Panel (#5): tras clickear una tab (tabAuto=false), cambiar a un bloque sin
Contenido dejaba la tab en 'contenido' (trigger no renderiza) → panel en blanco
sin tab activa. El efecto de auto-landing ahora corrige una tab INVÁLIDA aunque
tabAuto=false (cae a la 1a disponible). Verificado Playwright: párrafo → Diseño.
- Inspector (#7): el draft de `src` derivaba `alt` de split('/')/split('.') →
basura base64 para data: URLs. Ahora se salta la derivación para data:.
- Spacing (#10): el override de enlace (localUnlink) se reseteaba por
JSON.stringify(value); dos bloques sin spacing serializan igual ('null') → el
override se filtraba entre ellos. Ahora resetea por identidad de bloque
(resetKey=api.activeBlockId), cableado en panel + inspector.
- Draft-field (#12): Enter llamaba confirm() y luego blur() que re-dispara
onblur=confirm antes de que `value` se propague → doble onCommit → 2 entradas
de historial. Guard de reentrada por el flag `editing`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tres defectos confirmados por el barrido en el mismo fichero:
- Click SECO (sin arrastrar) en un tirador de columna congelaba un ancho fijo
espurio: el guard comparaba liveWidth (float de getBoundingClientRect) contra
Math.round(startWidth), asi que cualquier columna sub-pixel disparaba el commit
(auto -> fijo) + entrada de historial. Ahora un flag `moved` (solo true si hubo
pointermove) hace del click un no-op real.
- Reflow forzado: onDragUp limpiaba el width inline y LEIA rects sync
(remeasure) → sync-after-write. Diferido via dom.measure (frame post-layout
coalescido), que ademas mide la geometria ya comprometida, no la transitoria.
- Sin pointercancel ni cleanup de desmontaje: una interrupcion (touch/pen cancel,
gesto) dejaba dragKey atascado (handles congeladas, remeasure off) + listeners
de window filtrados. Anadido onDragCancel (revierte sin commitear) + un $effect
cuyo cleanup libera un drag en vuelo al desmontar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tercera pieza F1 del plan blocks (alcance E-2, dossier §P3). Banner = tira
de anuncio de pagina; Callout = el aside del documento (split deliberado).
- morfo: 4 partes (provider role=note / icon / title / content) + texts con
TITULOS DEFAULT LOCALIZADOS por intent (note/tip/warning/caution — patron
GitHub de label visible: la semantica nunca viaja solo en color/icono);
0 eventos justificados (dismissible DIFERIDO a pasada soma+sema por la
regla de admision — dismiss ES un evento real)
- el hueco IMPORTANT resuelto con el modelo Radix, sin 5o enum: `intent`
(neutral|affirm|risk|threat — mapea NOTE/TIP/WARNING/CAUTION) + `color`
override SOLO bajo neutral (doctrina §4: intent evaluativo gana);
IMPORTANT = intent neutral + color + titulo propio (preset en la demo)
- pintura sobre la maquinaria C6/THM-2 (patron badge): forwarders por color
+ slots `_palette-track/text/solid` → el generador emite la cascada de 8
roles Y el forward presence-guarded al shared layer — 33 escalas y
colores custom con CERO CSS extra
- a11y: role=note NOMBRADO via aria-labelledby → Title SOLO mientras esta
montado (registro reactivo por context local); escalacion tipada
role=status|alert|none (el role=alert estatico de shadcn = bug de
referencia que NO copiamos); Title NO es heading (protege el outline que
escaneara anchor-nav); icono decorativo = mapa doctrinal IntentIcon
- fix cazado en navegador: el `+=` del registro leia el estado dentro del
tracking del $effect del hijo → effect_update_depth silencioso (contador
a -997, attr nunca estampado) — untrack() en register/cleanup; leccion
registrada en memoria (incidente 2 de la clase)
- recipe: grid con acento logico border-inline-start (RTL-correcto),
nesting tolerado (Docusaurus); demo v2 9 tabs con preset IMPORTANT +
PalettePicker showIntent=false; README completo
Verificacion: component:audit PASS 0E/0W · eidos-lint 8 morfo-backed/0
invalid · guards 37/37 · svelte-check 76E/51W = baseline exacto ·
navegador: «Nota»/«Atencion» localizados · risk=ambar hue 45-60 ·
IMPORTANT=plum hue 326 resuelto por shared layer · labelledby=titleId ·
cero errores de consola.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dos bugs confirmados por el barrido adversarial del editor:
1) El fantasma de respaldo + borde de requerido no se pintaban en los ITEMS de
lista: renderListItem nunca pasa por renderBlock (donde se estampa el
fallback), asi que un item sin traducir salia vacio sin la referencia del
idioma origen ni el aviso de requerido — aunque documentLangHasContent SI lo
cuenta, dejando el punto de completitud incoherente. Espejado el mismo
patron de renderBlock en el <li> via ctx.decorations.i18nFallback.
2) Un write externo a `value` que cambia activeLang se revertia solo:
syncExternalState actualizaba activeLangState pero NO opts.activeLang.current,
asi que el efecto de sync leia el prop viejo y llamaba setActiveLang(idioma
viejo). Ahora sincroniza el prop (como setActiveLang y el constructor).
Tests: render (li fallback + required flag) + provider (write externo con
activeLang distinto se honra, no revierte). 35 render + provider verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Segunda pieza F1 del plan blocks (alcance E-2 = suelo de paridad, dossier
§P3). Comparte el esqueleto de empty-state a proposito: EmptyState describe
AUSENCIA de datos, Result reporta un RESULTADO — un lenguaje de layout, dos
contratos (duplicacion consciente registrada en el README; revisable al 3er
consumidor).
- morfo: 6 partes display (provider/media/title/description/actions/extra;
extra SIN archetype — parte genuinamente propia), scope ['eidos'],
0 eventos justificados (el resultado ya OCURRIO antes de renderizar)
- status: enum de 7 = paridad AntD CON `warning` (decision firme del
dossier) y HTTP renombrados semanticos (forbidden/not-found/server-error,
nunca '404' stringly); default 'info'; data-status = attr eidos-only
- media default: compone el mapa doctrinal IntentIcon (success→fulfill ·
error→threat · warning→risk) + Info de catalogo; HTTP = codigo mono
grande NEUTRO aria-hidden (situaciones, no fallos — AntD jamas pinta 404
de rojo); children reemplazan el default entero (context local eidos-only
con getter reactivo)
- Title default h2 (vs h3 de empty-state — Result suele SER la pagina);
Actions con label→role=group; Extra alineado a inicio (detalle que lee)
- recipe: glifo display 3× xl bundle; tokens publicos
gap/actions-gap/measures + {status}-color como forwarders de rol
retintables; sin eje size (paridad AntD, gap diferido)
- demo v2 9 tabs con copy por status + entrada nav (Status) + README
(Comparativa · Decisiones · Passive justification · Gaps con disposicion)
Verificacion: component:audit PASS 0E/0W · eidos-lint 6 morfo-backed + 5
eidos-only sancionados · recipe/api/visual-attrs 37/37 · morfo:check verde
(los 7 fallos listados son deuda foranea preexistente) · svelte-check
76E/51W = baseline exacto · navegador: success=fulfill verde 84px ·
error=threat rojo · not-found=«404» mono neutro, copy conmutando, cero
errores de consola.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>