uix(navigation-menu): un <ul> no es una región, y por eso el landmark salía sin nombre
El morfo declaraba el nombre por defecto de la navegación en la parte que pinta
el <ul>, con la razón escrita al lado: «the root <nav> delegates its name to the
list it wraps». La delegación no existe. El gesto que un lector de pantalla
ofrece para saltar entre las áreas de una página llega al <nav>, y llegaba a un
<nav> ANÓNIMO — con el nombre del consumidor puesto una capa más adentro, en un
elemento que ninguna navegación por landmarks visita.
Sobrevivió tres semanas de tier porque con UNA navegación en la página un
landmark anónimo es admisible, y todas las demos del componente y todos los
blocks de sitio montan una. Apareció en cuanto el app-shell montó dos ámbitos,
que es lo que un shell hace por definición (A-111). La APG es explícita: si una
página incluye más de un landmark de navegación, cada uno lleva su nombre.
El arreglo NO es el que la ficha proponía. Su paso 1 quería que la lista
heredase el nombre por aria-labelledby; el barrido de los morfos hermanos con
defaultElement 'nav' dijo otra cosa: breadcrumb (<ol>) y nav-tree (<ul>)
declaran aria: [] en su lista y nombran sólo el <nav>. navigation-menu era el
único fuera de esa forma, así que la pareja de nombre —el aria-label con su
suppression prop-falsy ariaLabelledby, y el aria-labelledby por propRef— sube
entera a la parte Provider y la lista se queda con su aria-orientation.
En soma, la fuente ariaLabelledby sube al nivel del runtime (la forma de
breadcrumb) y el wrapper DEJA de sacar aria-label de restProps: viaja con el
resto y gana el merge por política de naming, que es lo que A-85 dejó escrito y
lo que este componente no aplicaba. Se van con ello el opt ariaLabel y el
reenvío condicional que la List hacía a mano.
Y como la regla no estaba escrita en ninguna doctrina —grep de «landmark» en
morfo.md, canon/ y guides/ daba cero—, se escribe: architecture/morfo.md §Step 4
gana «A landmark is named on ITS OWN element», y nace el censo
morfo/landmark-census.test.ts, que falla si una parte con defaultElement 'nav'
no declara un attr de nombre EN SU PARTE. Probado en rojo por mutación tres
veces: deshaciendo el arreglo (navigation-menu.provider), retirando la excepción
(palabras.breadcrumb) y vaciando el catálogo, que es la mutación que impide que
un censo sobre nada pase en verde. El ámbito es 'nav' a propósito: main, header
y footer son únicos por página, y section y form sólo son landmark cuando ya
tienen nombre.
El guard destapó de paso un segundo caso de la misma clase: la parte Breadcrumb
de palabras declara un <nav> con aria: [] y nadie la nombra, ni en soma ni en
eidos. Queda como A-116 con EXCEPCIÓN FIRMADA por el autor: palabras es el eje
de otra sesión y su rama es compartida. La tercera fila del censo obliga a
retirar la excepción el día que esa parte declare un nombre.
Verificado con el nombre COMPUTADO desde el árbol AX de Chrome por CDP, nunca
leyendo el atributo (un aria-label en el DOM prueba el atributo, no el nombre),
con servidor recién arrancado y esperando la puerta de hidratación honesta:
/blocks/app-shell/preview navigation: ['Navegación principal',
'Menú de la aplicación',
'Migas de pan'] (antes: el 2.º null)
/uix/components/navigation-menu (sin label del consumidor)
el <nav> computa 'Principal' — el default del morfo,
traducido, sobre el landmark; el <ul>, sin nombre
Gates: 420/420 en morfo+sema+soma+eidos tocados · component:audit --only
navigation-menu PASS · morfo:check PASS · svelte-check 72/62 antes y después ·
docs:check 0/0. Los avisos de prettier de los ficheros tocados ya fallaban en
HEAD (comprobado con git show HEAD:… | prettier --check); el fichero nuevo va
formateado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
# CONTINUE — tier blocks (handoff, act. 2026-08-19 · A-111 cerrada)
docs(blocks): el handoff de mañana, y las cinco filas que dejó componer la página
F2b queda cerrada entera, así que el handoff cambia de puerta: lo primero de
mañana es el repaso block a block que pediste, y va sin ficha escrita a
propósito — su forma se acuerda antes de empezar (qué orden, qué se mira, y si
lo que salga se escribe en el ledger o en el README de cada block).
Las cinco cosas que la página compuesta y cookie-consent encontraron entran al
ledger como A-106…A-110, medidas y sin tocar: Button acepta ref y no lo reenvía
—la forma exacta de A-94—, Switch no tiene parte de etiqueta y todos los
consumidores del repo repiten el mismo apaño, Dialog devuelve el foco a un nodo
muerto cuando su disparador se ha desmontado, Banner deja DOS landmarks banner en
cualquier página con cabecera, y feature-split es el único block de sección sin
.Header.
Casi las meto como A-100…A-104, que ya estaban ocupadas. El rango libre empezaba
en A-106 y el ledger tiene 110 filas, no 96: las cifras que el handoff repetía
—«96 filas, 64-19-13»— eran una foto vieja de julio. Las de ahora están contadas
sobre el fichero: 70 ARREGLADO · 24 CONFIRMADO · 13 REFUTADO · 3 DATO.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Estado**: F1 CERRADA (8/8) · F2 CERRADA (15/15) · F2b CERRADA ENTERA ·
**F3 ABIERTA — F3.1 `app-shell` CONSTRUIDO, VERIFICADO Y REHECHO**, con su fase
0 firmada (Q1– Q4) y el canon `SkipLink` nacido delante de él.
uix(banner): la tira de aviso no es la cabecera del sitio, así que deja de reclamar su landmark
Toda página con cabecera exponía DOS landmarks banner: el <header> del
site-header y la tira de aviso, que estampaba role="banner" a mano. ARIA
reserva ese rol para la cabecera DEL SITIO —identidad, búsqueda del sitio— y
dice que un documento debería marcar como mucho uno (A-109).
Las dos salidas que la ficha proponía estaban mal planteadas, y lo destapó una
revisión adversarial de tres refutadores independientes:
- «Estampar el rol ANTES de los rest props, que es la clase naming donde gana
el consumidor» contradice la doctrina que la propia ficha citaba: role es
clase CONTRATO por nombre de atributo (morfo/types.ts:811-832;
ARIA_NAMING_ATTRS contiene sólo aria-label). El runtime siempre gana ahí, y
con razón: un data-state o un role pisables son un componente que miente.
- Y el orden de estampado NUNCA fue el mecanismo. Medido: el segundo landmark
es el <header> del site-header, que no lleva atributo role ninguno — recibe
banner IMPLÍCITO por ser una <header> fuera de un elemento de sección. Un rol
pisable habría dejado quitarlo caso por caso, empujando el arreglo a cada
app, en vez de arreglarlo por defecto.
- El morfo, además, nunca declaró el rol (aria: []): era un literal del wrapper
de eidos que el README daba por contrato de la parte. Drift morfo↔eidos que
se queda sin objeto.
Lo firmado: la tira deja de reclamar el landmark. defaultElement header→section
en el morfo, fuera el role literal. Un <section> es region SÓLO cuando tiene
nombre, así que el aria-label del consumidor la hace encontrable y su ausencia
la deja como contenido plano — que es exactamente lo que el censo de landmarks
de A-111 ya sancionaba por escrito.
⚠️ SIN nombre por defecto, y esta es la corrección que la revisión me obligó a
hacer sobre mi propia propuesta: yo había planteado un default traducido
(«Aviso»), copiando el patrón de skeleton/spinner. Habría convertido TODA tira
en landmark sin salida — /temas/grafito apila cuatro y habrían salido cuatro
region con el mismo nombre, que es la clase de defecto de A-111 con otro traje.
El precedente de <nav> no transfiere: un <nav> es SIEMPRE landmark y por eso
pide nombre; un <section> lo es PORQUE lo tiene. Quien quiera una tira
encontrable, la nombra.
Derogadas por escrito las dos afirmaciones que decían lo contrario, porque
existían y estaban firmadas: el README del canon («Banner is a landmark for
page-level announcements — site header, persistent notice, system status bar»)
y el «✓ ejemplar» que docs/audit/components/banner.md le puso a ese contrato el
2026-07-07. La otra mitad de aquella decisión —no role="alert", el feedback
transitorio es de Toast— sigue en pie y se dice.
Verificado con el árbol AX de Chrome por CDP, servidor limpio:
/blocks/landing banner ['Aviso del producto','Acme'] → banner ['Acme']
+ region ['Aviso del producto']
/blocks/banner/preview banner ['Principal'] · region ['Aviso del producto']
/temas/grafito banner [] · region [] — las cuatro tiras sin nombre
dejan de ser landmarks, que es lo correcto
Píxel idéntico: la receta selecciona [data-banner], no el elemento — tira a
sangre 1280×54 con el mismo fondo, capturada y mirada. Ningún test, guard, CSS
ni consulta del repo seleccionaba por role=banner ni por el tag.
Gates: svelte-check 72/62 antes y después · vitest eidos+blocks+morfo 692/693
(el rojo es skin-media-player, ajeno y documentado) · blocks:check 0/19 ·
docs:check 0/0 (644) · morfo:check PASS en banner. Prosa re-sincronizada en 11
sitios: los README de canon, block y app-shell, la ficha de auditoría, las tres
demos, landing y su README.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**A-111, A-112 y A-109 ARREGLADAS** (el landmark anónimo de `NavigationMenu` ·
el `Toolbar.Button` que no podía decir su rango · el `Banner` que reclamaba un
landmark que no es suyo), cada una con su doctrina escrita.
docs(blocks): el handoff de mañana, y las cinco filas que dejó componer la página
F2b queda cerrada entera, así que el handoff cambia de puerta: lo primero de
mañana es el repaso block a block que pediste, y va sin ficha escrita a
propósito — su forma se acuerda antes de empezar (qué orden, qué se mira, y si
lo que salga se escribe en el ledger o en el README de cada block).
Las cinco cosas que la página compuesta y cookie-consent encontraron entran al
ledger como A-106…A-110, medidas y sin tocar: Button acepta ref y no lo reenvía
—la forma exacta de A-94—, Switch no tiene parte de etiqueta y todos los
consumidores del repo repiten el mismo apaño, Dialog devuelve el foco a un nodo
muerto cuando su disparador se ha desmontado, Banner deja DOS landmarks banner en
cualquier página con cabecera, y feature-split es el único block de sección sin
.Header.
Casi las meto como A-100…A-104, que ya estaban ocupadas. El rango libre empezaba
en A-106 y el ledger tiene 110 filas, no 96: las cifras que el handoff repetía
—«96 filas, 64-19-13»— eran una foto vieja de julio. Las de ahora están contadas
sobre el fichero: 70 ARREGLADO · 24 CONFIRMADO · 13 REFUTADO · 3 DATO.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
**19 blocks vivos**: site-header · hero · feature-grid · feature-split · pricing ·
feat(blocks): contact CERRADO — la coordinacion entra en el contrato B
El estado se le pregunta al ESQUEMA, no al Form. `form.isValid` con
`validationBehaviour: 'progressive'` significa «aun no se ha encontrado nada
mal»: un formulario vacio e intacto no tiene errores, asi que se declaraba
valido y `incomplete` era INALCANZABLE al cargar — la seccion pedia resolver la
verificacion con los tres campos vacios, justo el hueco que la doctrina existe
para cerrar. Ahora la raiz valida los valores con `validateSync` de SIUM:
sincrono y sin escribir errores (`form.validate()` habria encendido los tres
campos en rojo en el primer frame).
`state.test.ts` fija la maquina con 9 casos — primer test unitario del tier,
porque `state.ts` es su primera logica pura: el orden de prioridad, que solo
`ready` deja enviar, y que todo estado bloqueado tiene frase.
Contrato B (architecture/blocks.md): seccion «Coordination» con las tres cosas
que posee un block que coordina (estado, forma de sus datos, palabras), B-5
admite el esquema por defecto, B-7 enmendado (posee las palabras de SUS estados
como idlangref con fallback ingles) y la convencion de servicios acotada: el
traductor es el UNICO servicio sancionado. Dice tambien a quien NO aplica — los
blocks de layout no coordinan nada.
i18n por la via real: el arnes registra el namespace `blocks` en las DOS cadenas
de arranque (galeria y previews, que duplican el boot) y la demo se lee en
castellano en vez de caer al fallback ingles.
Ademas: README y pestanas de doc rehechos al reparto nuevo, ficha F2.13 y
bitacora en PLAN-blocks.md, y el control vivo que faltaba para un prop publico
— `verification` con reto / sin reto, porque omitir el prop no es lo mismo que
pasar un estado que nunca verifica.
Verificado en navegador, arco completo y las dos ramas: sin reto vacio → un
campo → tres campos (se desbloquea) → «Enviando…» → «Enviado»; con reto, con
los tres campos llenos el boton sigue bloqueado y la razon pasa a «Resuelve la
verificacion de arriba». `verifying` y `rejected` quedan cubiertos por test
unitario, no por el reto en vivo: el provider de ProofOfHuman escribe `status`
el mismo y el veredicto del app no se sostiene (hilo de canon abierto).
Gates: blocks:check verde (14 blocks) · vitest 9/9 · docs:check 0 ·
svelte-check sin errores propios · prettier limpio en los ficheros propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
testimonials · faq · stats-band · cta · newsletter · site-footer · banner · team ·
contact · content-section · logo-cloud · article-grid · cookie-consent ·
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**app-shell**. Más la página compuesta `/blocks/landing` .
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**A-95 CERRADA** con `app-shell` : solape del aviso sobre la cabecera **0px** y
hit-test dentro de la cabecera, medido (antes: 49px).
docs(blocks): el handoff de mañana, y las cinco filas que dejó componer la página
F2b queda cerrada entera, así que el handoff cambia de puerta: lo primero de
mañana es el repaso block a block que pediste, y va sin ficha escrita a
propósito — su forma se acuerda antes de empezar (qué orden, qué se mira, y si
lo que salga se escribe en el ledger o en el README de cada block).
Las cinco cosas que la página compuesta y cookie-consent encontraron entran al
ledger como A-106…A-110, medidas y sin tocar: Button acepta ref y no lo reenvía
—la forma exacta de A-94—, Switch no tiene parte de etiqueta y todos los
consumidores del repo repiten el mismo apaño, Dialog devuelve el foco a un nodo
muerto cuando su disparador se ha desmontado, Banner deja DOS landmarks banner en
cualquier página con cabecera, y feature-split es el único block de sección sin
.Header.
Casi las meto como A-100…A-104, que ya estaban ocupadas. El rango libre empezaba
en A-106 y el ledger tiene 110 filas, no 96: las cifras que el handoff repetía
—«96 filas, 64-19-13»— eran una foto vieja de julio. Las de ahora están contadas
sobre el fichero: 70 ARREGLADO · 24 CONFIRMADO · 13 REFUTADO · 3 DATO.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
## Por dónde entrar mañana
uix(navigation-menu): un <ul> no es una región, y por eso el landmark salía sin nombre
El morfo declaraba el nombre por defecto de la navegación en la parte que pinta
el <ul>, con la razón escrita al lado: «the root <nav> delegates its name to the
list it wraps». La delegación no existe. El gesto que un lector de pantalla
ofrece para saltar entre las áreas de una página llega al <nav>, y llegaba a un
<nav> ANÓNIMO — con el nombre del consumidor puesto una capa más adentro, en un
elemento que ninguna navegación por landmarks visita.
Sobrevivió tres semanas de tier porque con UNA navegación en la página un
landmark anónimo es admisible, y todas las demos del componente y todos los
blocks de sitio montan una. Apareció en cuanto el app-shell montó dos ámbitos,
que es lo que un shell hace por definición (A-111). La APG es explícita: si una
página incluye más de un landmark de navegación, cada uno lleva su nombre.
El arreglo NO es el que la ficha proponía. Su paso 1 quería que la lista
heredase el nombre por aria-labelledby; el barrido de los morfos hermanos con
defaultElement 'nav' dijo otra cosa: breadcrumb (<ol>) y nav-tree (<ul>)
declaran aria: [] en su lista y nombran sólo el <nav>. navigation-menu era el
único fuera de esa forma, así que la pareja de nombre —el aria-label con su
suppression prop-falsy ariaLabelledby, y el aria-labelledby por propRef— sube
entera a la parte Provider y la lista se queda con su aria-orientation.
En soma, la fuente ariaLabelledby sube al nivel del runtime (la forma de
breadcrumb) y el wrapper DEJA de sacar aria-label de restProps: viaja con el
resto y gana el merge por política de naming, que es lo que A-85 dejó escrito y
lo que este componente no aplicaba. Se van con ello el opt ariaLabel y el
reenvío condicional que la List hacía a mano.
Y como la regla no estaba escrita en ninguna doctrina —grep de «landmark» en
morfo.md, canon/ y guides/ daba cero—, se escribe: architecture/morfo.md §Step 4
gana «A landmark is named on ITS OWN element», y nace el censo
morfo/landmark-census.test.ts, que falla si una parte con defaultElement 'nav'
no declara un attr de nombre EN SU PARTE. Probado en rojo por mutación tres
veces: deshaciendo el arreglo (navigation-menu.provider), retirando la excepción
(palabras.breadcrumb) y vaciando el catálogo, que es la mutación que impide que
un censo sobre nada pase en verde. El ámbito es 'nav' a propósito: main, header
y footer son únicos por página, y section y form sólo son landmark cuando ya
tienen nombre.
El guard destapó de paso un segundo caso de la misma clase: la parte Breadcrumb
de palabras declara un <nav> con aria: [] y nadie la nombra, ni en soma ni en
eidos. Queda como A-116 con EXCEPCIÓN FIRMADA por el autor: palabras es el eje
de otra sesión y su rama es compartida. La tercera fila del censo obliga a
retirar la excepción el día que esa parte declare un nombre.
Verificado con el nombre COMPUTADO desde el árbol AX de Chrome por CDP, nunca
leyendo el atributo (un aria-label en el DOM prueba el atributo, no el nombre),
con servidor recién arrancado y esperando la puerta de hidratación honesta:
/blocks/app-shell/preview navigation: ['Navegación principal',
'Menú de la aplicación',
'Migas de pan'] (antes: el 2.º null)
/uix/components/navigation-menu (sin label del consumidor)
el <nav> computa 'Principal' — el default del morfo,
traducido, sobre el landmark; el <ul>, sin nombre
Gates: 420/420 en morfo+sema+soma+eidos tocados · component:audit --only
navigation-menu PASS · morfo:check PASS · svelte-check 72/62 antes y después ·
docs:check 0/0. Los avisos de prettier de los ficheros tocados ya fallaban en
HEAD (comprobado con git show HEAD:… | prettier --check); el fichero nuevo va
formateado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### 1 · ~~A-111~~ — **CERRADA 2026-08-19**. Lo que dejó, y lo que abrió
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
uix(navigation-menu): un <ul> no es una región, y por eso el landmark salía sin nombre
El morfo declaraba el nombre por defecto de la navegación en la parte que pinta
el <ul>, con la razón escrita al lado: «the root <nav> delegates its name to the
list it wraps». La delegación no existe. El gesto que un lector de pantalla
ofrece para saltar entre las áreas de una página llega al <nav>, y llegaba a un
<nav> ANÓNIMO — con el nombre del consumidor puesto una capa más adentro, en un
elemento que ninguna navegación por landmarks visita.
Sobrevivió tres semanas de tier porque con UNA navegación en la página un
landmark anónimo es admisible, y todas las demos del componente y todos los
blocks de sitio montan una. Apareció en cuanto el app-shell montó dos ámbitos,
que es lo que un shell hace por definición (A-111). La APG es explícita: si una
página incluye más de un landmark de navegación, cada uno lleva su nombre.
El arreglo NO es el que la ficha proponía. Su paso 1 quería que la lista
heredase el nombre por aria-labelledby; el barrido de los morfos hermanos con
defaultElement 'nav' dijo otra cosa: breadcrumb (<ol>) y nav-tree (<ul>)
declaran aria: [] en su lista y nombran sólo el <nav>. navigation-menu era el
único fuera de esa forma, así que la pareja de nombre —el aria-label con su
suppression prop-falsy ariaLabelledby, y el aria-labelledby por propRef— sube
entera a la parte Provider y la lista se queda con su aria-orientation.
En soma, la fuente ariaLabelledby sube al nivel del runtime (la forma de
breadcrumb) y el wrapper DEJA de sacar aria-label de restProps: viaja con el
resto y gana el merge por política de naming, que es lo que A-85 dejó escrito y
lo que este componente no aplicaba. Se van con ello el opt ariaLabel y el
reenvío condicional que la List hacía a mano.
Y como la regla no estaba escrita en ninguna doctrina —grep de «landmark» en
morfo.md, canon/ y guides/ daba cero—, se escribe: architecture/morfo.md §Step 4
gana «A landmark is named on ITS OWN element», y nace el censo
morfo/landmark-census.test.ts, que falla si una parte con defaultElement 'nav'
no declara un attr de nombre EN SU PARTE. Probado en rojo por mutación tres
veces: deshaciendo el arreglo (navigation-menu.provider), retirando la excepción
(palabras.breadcrumb) y vaciando el catálogo, que es la mutación que impide que
un censo sobre nada pase en verde. El ámbito es 'nav' a propósito: main, header
y footer son únicos por página, y section y form sólo son landmark cuando ya
tienen nombre.
El guard destapó de paso un segundo caso de la misma clase: la parte Breadcrumb
de palabras declara un <nav> con aria: [] y nadie la nombra, ni en soma ni en
eidos. Queda como A-116 con EXCEPCIÓN FIRMADA por el autor: palabras es el eje
de otra sesión y su rama es compartida. La tercera fila del censo obliga a
retirar la excepción el día que esa parte declare un nombre.
Verificado con el nombre COMPUTADO desde el árbol AX de Chrome por CDP, nunca
leyendo el atributo (un aria-label en el DOM prueba el atributo, no el nombre),
con servidor recién arrancado y esperando la puerta de hidratación honesta:
/blocks/app-shell/preview navigation: ['Navegación principal',
'Menú de la aplicación',
'Migas de pan'] (antes: el 2.º null)
/uix/components/navigation-menu (sin label del consumidor)
el <nav> computa 'Principal' — el default del morfo,
traducido, sobre el landmark; el <ul>, sin nombre
Gates: 420/420 en morfo+sema+soma+eidos tocados · component:audit --only
navigation-menu PASS · morfo:check PASS · svelte-check 72/62 antes y después ·
docs:check 0/0. Los avisos de prettier de los ficheros tocados ya fallaban en
HEAD (comprobado con git show HEAD:… | prettier --check); el fichero nuevo va
formateado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
El nombre del landmark vive ya en el `<nav>` . Medido con el árbol AX de Chrome:
`/blocks/app-shell/preview` → `navigation: ['Navegación principal', 'Menú de la
aplicación', 'Migas de pan']` (el `null` , fuera), y la demo del componente sin
label del consumidor → `'Principal'` , el default del morfo traducido sobre el
landmark. Detalle y método en `AUDIT-blocks-ledger.md` §A-111.
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
uix(navigation-menu): un <ul> no es una región, y por eso el landmark salía sin nombre
El morfo declaraba el nombre por defecto de la navegación en la parte que pinta
el <ul>, con la razón escrita al lado: «the root <nav> delegates its name to the
list it wraps». La delegación no existe. El gesto que un lector de pantalla
ofrece para saltar entre las áreas de una página llega al <nav>, y llegaba a un
<nav> ANÓNIMO — con el nombre del consumidor puesto una capa más adentro, en un
elemento que ninguna navegación por landmarks visita.
Sobrevivió tres semanas de tier porque con UNA navegación en la página un
landmark anónimo es admisible, y todas las demos del componente y todos los
blocks de sitio montan una. Apareció en cuanto el app-shell montó dos ámbitos,
que es lo que un shell hace por definición (A-111). La APG es explícita: si una
página incluye más de un landmark de navegación, cada uno lleva su nombre.
El arreglo NO es el que la ficha proponía. Su paso 1 quería que la lista
heredase el nombre por aria-labelledby; el barrido de los morfos hermanos con
defaultElement 'nav' dijo otra cosa: breadcrumb (<ol>) y nav-tree (<ul>)
declaran aria: [] en su lista y nombran sólo el <nav>. navigation-menu era el
único fuera de esa forma, así que la pareja de nombre —el aria-label con su
suppression prop-falsy ariaLabelledby, y el aria-labelledby por propRef— sube
entera a la parte Provider y la lista se queda con su aria-orientation.
En soma, la fuente ariaLabelledby sube al nivel del runtime (la forma de
breadcrumb) y el wrapper DEJA de sacar aria-label de restProps: viaja con el
resto y gana el merge por política de naming, que es lo que A-85 dejó escrito y
lo que este componente no aplicaba. Se van con ello el opt ariaLabel y el
reenvío condicional que la List hacía a mano.
Y como la regla no estaba escrita en ninguna doctrina —grep de «landmark» en
morfo.md, canon/ y guides/ daba cero—, se escribe: architecture/morfo.md §Step 4
gana «A landmark is named on ITS OWN element», y nace el censo
morfo/landmark-census.test.ts, que falla si una parte con defaultElement 'nav'
no declara un attr de nombre EN SU PARTE. Probado en rojo por mutación tres
veces: deshaciendo el arreglo (navigation-menu.provider), retirando la excepción
(palabras.breadcrumb) y vaciando el catálogo, que es la mutación que impide que
un censo sobre nada pase en verde. El ámbito es 'nav' a propósito: main, header
y footer son únicos por página, y section y form sólo son landmark cuando ya
tienen nombre.
El guard destapó de paso un segundo caso de la misma clase: la parte Breadcrumb
de palabras declara un <nav> con aria: [] y nadie la nombra, ni en soma ni en
eidos. Queda como A-116 con EXCEPCIÓN FIRMADA por el autor: palabras es el eje
de otra sesión y su rama es compartida. La tercera fila del censo obliga a
retirar la excepción el día que esa parte declare un nombre.
Verificado con el nombre COMPUTADO desde el árbol AX de Chrome por CDP, nunca
leyendo el atributo (un aria-label en el DOM prueba el atributo, no el nombre),
con servidor recién arrancado y esperando la puerta de hidratación honesta:
/blocks/app-shell/preview navigation: ['Navegación principal',
'Menú de la aplicación',
'Migas de pan'] (antes: el 2.º null)
/uix/components/navigation-menu (sin label del consumidor)
el <nav> computa 'Principal' — el default del morfo,
traducido, sobre el landmark; el <ul>, sin nombre
Gates: 420/420 en morfo+sema+soma+eidos tocados · component:audit --only
navigation-menu PASS · morfo:check PASS · svelte-check 72/62 antes y después ·
docs:check 0/0. Los avisos de prettier de los ficheros tocados ya fallaban en
HEAD (comprobado con git show HEAD:… | prettier --check); el fichero nuevo va
formateado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Tres cosas que conviene saber al retomar:
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
uix(navigation-menu): un <ul> no es una región, y por eso el landmark salía sin nombre
El morfo declaraba el nombre por defecto de la navegación en la parte que pinta
el <ul>, con la razón escrita al lado: «the root <nav> delegates its name to the
list it wraps». La delegación no existe. El gesto que un lector de pantalla
ofrece para saltar entre las áreas de una página llega al <nav>, y llegaba a un
<nav> ANÓNIMO — con el nombre del consumidor puesto una capa más adentro, en un
elemento que ninguna navegación por landmarks visita.
Sobrevivió tres semanas de tier porque con UNA navegación en la página un
landmark anónimo es admisible, y todas las demos del componente y todos los
blocks de sitio montan una. Apareció en cuanto el app-shell montó dos ámbitos,
que es lo que un shell hace por definición (A-111). La APG es explícita: si una
página incluye más de un landmark de navegación, cada uno lleva su nombre.
El arreglo NO es el que la ficha proponía. Su paso 1 quería que la lista
heredase el nombre por aria-labelledby; el barrido de los morfos hermanos con
defaultElement 'nav' dijo otra cosa: breadcrumb (<ol>) y nav-tree (<ul>)
declaran aria: [] en su lista y nombran sólo el <nav>. navigation-menu era el
único fuera de esa forma, así que la pareja de nombre —el aria-label con su
suppression prop-falsy ariaLabelledby, y el aria-labelledby por propRef— sube
entera a la parte Provider y la lista se queda con su aria-orientation.
En soma, la fuente ariaLabelledby sube al nivel del runtime (la forma de
breadcrumb) y el wrapper DEJA de sacar aria-label de restProps: viaja con el
resto y gana el merge por política de naming, que es lo que A-85 dejó escrito y
lo que este componente no aplicaba. Se van con ello el opt ariaLabel y el
reenvío condicional que la List hacía a mano.
Y como la regla no estaba escrita en ninguna doctrina —grep de «landmark» en
morfo.md, canon/ y guides/ daba cero—, se escribe: architecture/morfo.md §Step 4
gana «A landmark is named on ITS OWN element», y nace el censo
morfo/landmark-census.test.ts, que falla si una parte con defaultElement 'nav'
no declara un attr de nombre EN SU PARTE. Probado en rojo por mutación tres
veces: deshaciendo el arreglo (navigation-menu.provider), retirando la excepción
(palabras.breadcrumb) y vaciando el catálogo, que es la mutación que impide que
un censo sobre nada pase en verde. El ámbito es 'nav' a propósito: main, header
y footer son únicos por página, y section y form sólo son landmark cuando ya
tienen nombre.
El guard destapó de paso un segundo caso de la misma clase: la parte Breadcrumb
de palabras declara un <nav> con aria: [] y nadie la nombra, ni en soma ni en
eidos. Queda como A-116 con EXCEPCIÓN FIRMADA por el autor: palabras es el eje
de otra sesión y su rama es compartida. La tercera fila del censo obliga a
retirar la excepción el día que esa parte declare un nombre.
Verificado con el nombre COMPUTADO desde el árbol AX de Chrome por CDP, nunca
leyendo el atributo (un aria-label en el DOM prueba el atributo, no el nombre),
con servidor recién arrancado y esperando la puerta de hidratación honesta:
/blocks/app-shell/preview navigation: ['Navegación principal',
'Menú de la aplicación',
'Migas de pan'] (antes: el 2.º null)
/uix/components/navigation-menu (sin label del consumidor)
el <nav> computa 'Principal' — el default del morfo,
traducido, sobre el landmark; el <ul>, sin nombre
Gates: 420/420 en morfo+sema+soma+eidos tocados · component:audit --only
navigation-menu PASS · morfo:check PASS · svelte-check 72/62 antes y después ·
docs:check 0/0. Los avisos de prettier de los ficheros tocados ya fallaban en
HEAD (comprobado con git show HEAD:… | prettier --check); el fichero nuevo va
formateado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **La forma ejecutada corrige el paso 1 de la disposición**: la lista NO hereda
el nombre por `aria-labelledby` , se queda sin nombre. Los morfos hermanos con
`defaultElement: 'nav'` lo dijeron — `breadcrumb` y `nav-tree` declaran
`aria: []` en su lista. `navigation-menu` era el único fuera de esa forma.
- **Guard nuevo**: `src/uix/morfo/landmark-census.test.ts` — toda parte con
`defaultElement: 'nav'` declara un attr de nombre EN SU PARTE, probado en rojo
por mutación tres veces. Ámbito `nav` a propósito; ** `aside` es el candidato
natural a ensanchar el censo** y no se hizo sin firma.
- **Y destapó A-116**: `palabras.breadcrumb` es la misma clase (un `nav` con
`aria: []` , sin nombre en soma ni en eidos). **Excepción firmada** en el censo
— palabras es el eje de otra sesión y su rama es compartida.
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
uix(navigation-menu): un <ul> no es una región, y por eso el landmark salía sin nombre
El morfo declaraba el nombre por defecto de la navegación en la parte que pinta
el <ul>, con la razón escrita al lado: «the root <nav> delegates its name to the
list it wraps». La delegación no existe. El gesto que un lector de pantalla
ofrece para saltar entre las áreas de una página llega al <nav>, y llegaba a un
<nav> ANÓNIMO — con el nombre del consumidor puesto una capa más adentro, en un
elemento que ninguna navegación por landmarks visita.
Sobrevivió tres semanas de tier porque con UNA navegación en la página un
landmark anónimo es admisible, y todas las demos del componente y todos los
blocks de sitio montan una. Apareció en cuanto el app-shell montó dos ámbitos,
que es lo que un shell hace por definición (A-111). La APG es explícita: si una
página incluye más de un landmark de navegación, cada uno lleva su nombre.
El arreglo NO es el que la ficha proponía. Su paso 1 quería que la lista
heredase el nombre por aria-labelledby; el barrido de los morfos hermanos con
defaultElement 'nav' dijo otra cosa: breadcrumb (<ol>) y nav-tree (<ul>)
declaran aria: [] en su lista y nombran sólo el <nav>. navigation-menu era el
único fuera de esa forma, así que la pareja de nombre —el aria-label con su
suppression prop-falsy ariaLabelledby, y el aria-labelledby por propRef— sube
entera a la parte Provider y la lista se queda con su aria-orientation.
En soma, la fuente ariaLabelledby sube al nivel del runtime (la forma de
breadcrumb) y el wrapper DEJA de sacar aria-label de restProps: viaja con el
resto y gana el merge por política de naming, que es lo que A-85 dejó escrito y
lo que este componente no aplicaba. Se van con ello el opt ariaLabel y el
reenvío condicional que la List hacía a mano.
Y como la regla no estaba escrita en ninguna doctrina —grep de «landmark» en
morfo.md, canon/ y guides/ daba cero—, se escribe: architecture/morfo.md §Step 4
gana «A landmark is named on ITS OWN element», y nace el censo
morfo/landmark-census.test.ts, que falla si una parte con defaultElement 'nav'
no declara un attr de nombre EN SU PARTE. Probado en rojo por mutación tres
veces: deshaciendo el arreglo (navigation-menu.provider), retirando la excepción
(palabras.breadcrumb) y vaciando el catálogo, que es la mutación que impide que
un censo sobre nada pase en verde. El ámbito es 'nav' a propósito: main, header
y footer son únicos por página, y section y form sólo son landmark cuando ya
tienen nombre.
El guard destapó de paso un segundo caso de la misma clase: la parte Breadcrumb
de palabras declara un <nav> con aria: [] y nadie la nombra, ni en soma ni en
eidos. Queda como A-116 con EXCEPCIÓN FIRMADA por el autor: palabras es el eje
de otra sesión y su rama es compartida. La tercera fila del censo obliga a
retirar la excepción el día que esa parte declare un nombre.
Verificado con el nombre COMPUTADO desde el árbol AX de Chrome por CDP, nunca
leyendo el atributo (un aria-label en el DOM prueba el atributo, no el nombre),
con servidor recién arrancado y esperando la puerta de hidratación honesta:
/blocks/app-shell/preview navigation: ['Navegación principal',
'Menú de la aplicación',
'Migas de pan'] (antes: el 2.º null)
/uix/components/navigation-menu (sin label del consumidor)
el <nav> computa 'Principal' — el default del morfo,
traducido, sobre el landmark; el <ul>, sin nombre
Gates: 420/420 en morfo+sema+soma+eidos tocados · component:audit --only
navigation-menu PASS · morfo:check PASS · svelte-check 72/62 antes y después ·
docs:check 0/0. Los avisos de prettier de los ficheros tocados ya fallaban en
HEAD (comprobado con git show HEAD:… | prettier --check); el fichero nuevo va
formateado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
⚠️ La doctrina ganó el párrafo que no existía (`architecture/morfo.md` §Step 4,
«A landmark is named on ITS OWN element»). Antes de esto la regla no estaba
escrita en ningún sitio: grep de `landmark` en `morfo.md` , `canon/` y `guides/`
daba cero.
uix(navigation-menu): un <ul> no es una región, y por eso el landmark salía sin nombre
El morfo declaraba el nombre por defecto de la navegación en la parte que pinta
el <ul>, con la razón escrita al lado: «the root <nav> delegates its name to the
list it wraps». La delegación no existe. El gesto que un lector de pantalla
ofrece para saltar entre las áreas de una página llega al <nav>, y llegaba a un
<nav> ANÓNIMO — con el nombre del consumidor puesto una capa más adentro, en un
elemento que ninguna navegación por landmarks visita.
Sobrevivió tres semanas de tier porque con UNA navegación en la página un
landmark anónimo es admisible, y todas las demos del componente y todos los
blocks de sitio montan una. Apareció en cuanto el app-shell montó dos ámbitos,
que es lo que un shell hace por definición (A-111). La APG es explícita: si una
página incluye más de un landmark de navegación, cada uno lleva su nombre.
El arreglo NO es el que la ficha proponía. Su paso 1 quería que la lista
heredase el nombre por aria-labelledby; el barrido de los morfos hermanos con
defaultElement 'nav' dijo otra cosa: breadcrumb (<ol>) y nav-tree (<ul>)
declaran aria: [] en su lista y nombran sólo el <nav>. navigation-menu era el
único fuera de esa forma, así que la pareja de nombre —el aria-label con su
suppression prop-falsy ariaLabelledby, y el aria-labelledby por propRef— sube
entera a la parte Provider y la lista se queda con su aria-orientation.
En soma, la fuente ariaLabelledby sube al nivel del runtime (la forma de
breadcrumb) y el wrapper DEJA de sacar aria-label de restProps: viaja con el
resto y gana el merge por política de naming, que es lo que A-85 dejó escrito y
lo que este componente no aplicaba. Se van con ello el opt ariaLabel y el
reenvío condicional que la List hacía a mano.
Y como la regla no estaba escrita en ninguna doctrina —grep de «landmark» en
morfo.md, canon/ y guides/ daba cero—, se escribe: architecture/morfo.md §Step 4
gana «A landmark is named on ITS OWN element», y nace el censo
morfo/landmark-census.test.ts, que falla si una parte con defaultElement 'nav'
no declara un attr de nombre EN SU PARTE. Probado en rojo por mutación tres
veces: deshaciendo el arreglo (navigation-menu.provider), retirando la excepción
(palabras.breadcrumb) y vaciando el catálogo, que es la mutación que impide que
un censo sobre nada pase en verde. El ámbito es 'nav' a propósito: main, header
y footer son únicos por página, y section y form sólo son landmark cuando ya
tienen nombre.
El guard destapó de paso un segundo caso de la misma clase: la parte Breadcrumb
de palabras declara un <nav> con aria: [] y nadie la nombra, ni en soma ni en
eidos. Queda como A-116 con EXCEPCIÓN FIRMADA por el autor: palabras es el eje
de otra sesión y su rama es compartida. La tercera fila del censo obliga a
retirar la excepción el día que esa parte declare un nombre.
Verificado con el nombre COMPUTADO desde el árbol AX de Chrome por CDP, nunca
leyendo el atributo (un aria-label en el DOM prueba el atributo, no el nombre),
con servidor recién arrancado y esperando la puerta de hidratación honesta:
/blocks/app-shell/preview navigation: ['Navegación principal',
'Menú de la aplicación',
'Migas de pan'] (antes: el 2.º null)
/uix/components/navigation-menu (sin label del consumidor)
el <nav> computa 'Principal' — el default del morfo,
traducido, sobre el landmark; el <ul>, sin nombre
Gates: 420/420 en morfo+sema+soma+eidos tocados · component:audit --only
navigation-menu PASS · morfo:check PASS · svelte-check 72/62 antes y después ·
docs:check 0/0. Los avisos de prettier de los ficheros tocados ya fallaban en
HEAD (comprobado con git show HEAD:… | prettier --check); el fichero nuevo va
formateado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### 2 · Las filas que dejó el `app-shell` (ledger, todas nuevas)
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| Fila | Qué | Estado |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
uix(toolbar): la acción principal de un cluster ya puede decir su rango — y el gesto ya suena
`Toolbar.Button` tipaba contra el ButtonProps de SOMA ({id, disabled}): sin
variant, sin color, sin size. Un cluster de acciones donde UNA es la principal
no tenía cómo decirlo desde dentro, y la demo del app-shell dejaba «Nueva»
FUERA del toolbar como workaround documentado (A-112).
La ficha ofrecía dos salidas y mi primera recomendación fue la equivocada —
declarar el toolbar deliberadamente uniforme y consagrar el workaround. El
autor la tumbó, con razón: el eidos toolbar-button.svelte era un PASSTHROUGH
que dejaba el <button> nativo de soma, exactamente la clase de bug que
dialog-trigger (2026-06-19) y card-group-title (2026-06-27) ya pagaron, y el
precedente correcto no es menubar-trigger sino Form.Submit. Dos datos que el
primer análisis no tenía:
- El botón era MUDO. El morfo del toolbar sólo declara commit-toggle (el
group-item), así que pulsar una acción no emitía nada. Medido tras el
cambio: click → contact-activate estampado en el nodo + 8 nodos de audio,
donde antes 0.
- La uniformidad la dan los DEFAULTS, no la ausencia del eje: ghost/neutral y
el size del toolbar por contexto de eidos (toolbar/context.ts, calcado del
precedente ButtonGroup y con su misma razón: la receta del Button lee
data-size EN el nodo, un descendant selector no llega). El tipo estrecha a
SelectionVariant (solid | outline | ghost).
La forma es el Button consumer pattern entero: composición vía child de soma
(con el rename outerChild contra la recursión), y la receta CEDE el nodo —
cero cromo de botón en toolbar.css (regla 5); Link y GroupItem siguen siendo
superficie de la barra y los pinta ella. Antes de ceder, las dos recetas sobre
un mismo nodo producían un híbrido medido en el que solid/primary NO pintaba.
La doctrina que faltaba queda escrita en guides/component-guide.md §4: dos
clases de parte con forma de botón en una barra. ACCIÓN en barra (Toolbar.
Button, Form.Submit, Dialog.Trigger) compone el Button; control de SUPERFICIE
de barra (Menubar.Trigger: File, Edit) lo pinta la barra. El test: ¿significa
lo mismo fuera de la barra? Esa distinción vivía en un comentario de
menubar-trigger; el comentario ahora apunta a la doctrina.
Verificado en navegador (Playwright, servidor limpio): el nodo ES el Button
del canon (data-button + data-variant), solid/primary pinta (fondo primary,
tinta blanca), paridad exacta con el GroupItem por construcción — mismo bundle
--size-*: 36px/36px en md, 30px/30px en sm, con la herencia del size del
toolbar medida en vivo al cambiarlo. La demo del app-shell mueve «Nueva» AL
INTERIOR del cluster (workaround retirado, gap del block cerrado en su README)
y la demo del toolbar gana la acción «New» con controles vivos action variant
/ action color.
Gates: svelte-check 72/62 (base 73/62, sin regresión) · vitest eidos+blocks
470/471 (el rojo es skin-media-player, ajeno y documentado en el handoff) ·
blocks:check 0 errores / 19 blocks · docs:check 0/0. Los avisos de prettier de
los dos README ya fallaban en HEAD.
⚠️ El árbol compartido tiene ya encima trabajo NUEVO de otra sesión
(navigation-menu, PLAN-sidebar, book-deviations): este commit lleva sólo los
14 ficheros del eje A-112, verificados por lista.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
| **A-112** | ~~`Toolbar.Button` no acepta `variant`/`color`~~ — **ARREGLADO 2026-08-19, firmada (a)** : compone el `Button` del canon (defaults `ghost` /`neutral`/size del toolbar), la principal lo dice desde dentro, y gana `contact-activate` (era MUDO) | **ARREGLADO** |
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| **A-113** | La foundation emite los estilos semánticos y no los aplica a ningún elemento — Times New Roman en los 19 previews del tier | **ARREGLADO** en el reset del arnés; queda la pregunta de canon |
| **A-114** | `Sidebar.Inset` impone `overflow: auto` , y un ancestro con `overflow` se lleva el `position: sticky` de dentro | CONFIRMADO |
| **A-115** | No existe `description-list` y el panel de detalle del shell es el «primer detail-view real» que su ficha F5 pone como disparador | CONFIRMADO |
uix(navigation-menu): un <ul> no es una región, y por eso el landmark salía sin nombre
El morfo declaraba el nombre por defecto de la navegación en la parte que pinta
el <ul>, con la razón escrita al lado: «the root <nav> delegates its name to the
list it wraps». La delegación no existe. El gesto que un lector de pantalla
ofrece para saltar entre las áreas de una página llega al <nav>, y llegaba a un
<nav> ANÓNIMO — con el nombre del consumidor puesto una capa más adentro, en un
elemento que ninguna navegación por landmarks visita.
Sobrevivió tres semanas de tier porque con UNA navegación en la página un
landmark anónimo es admisible, y todas las demos del componente y todos los
blocks de sitio montan una. Apareció en cuanto el app-shell montó dos ámbitos,
que es lo que un shell hace por definición (A-111). La APG es explícita: si una
página incluye más de un landmark de navegación, cada uno lleva su nombre.
El arreglo NO es el que la ficha proponía. Su paso 1 quería que la lista
heredase el nombre por aria-labelledby; el barrido de los morfos hermanos con
defaultElement 'nav' dijo otra cosa: breadcrumb (<ol>) y nav-tree (<ul>)
declaran aria: [] en su lista y nombran sólo el <nav>. navigation-menu era el
único fuera de esa forma, así que la pareja de nombre —el aria-label con su
suppression prop-falsy ariaLabelledby, y el aria-labelledby por propRef— sube
entera a la parte Provider y la lista se queda con su aria-orientation.
En soma, la fuente ariaLabelledby sube al nivel del runtime (la forma de
breadcrumb) y el wrapper DEJA de sacar aria-label de restProps: viaja con el
resto y gana el merge por política de naming, que es lo que A-85 dejó escrito y
lo que este componente no aplicaba. Se van con ello el opt ariaLabel y el
reenvío condicional que la List hacía a mano.
Y como la regla no estaba escrita en ninguna doctrina —grep de «landmark» en
morfo.md, canon/ y guides/ daba cero—, se escribe: architecture/morfo.md §Step 4
gana «A landmark is named on ITS OWN element», y nace el censo
morfo/landmark-census.test.ts, que falla si una parte con defaultElement 'nav'
no declara un attr de nombre EN SU PARTE. Probado en rojo por mutación tres
veces: deshaciendo el arreglo (navigation-menu.provider), retirando la excepción
(palabras.breadcrumb) y vaciando el catálogo, que es la mutación que impide que
un censo sobre nada pase en verde. El ámbito es 'nav' a propósito: main, header
y footer son únicos por página, y section y form sólo son landmark cuando ya
tienen nombre.
El guard destapó de paso un segundo caso de la misma clase: la parte Breadcrumb
de palabras declara un <nav> con aria: [] y nadie la nombra, ni en soma ni en
eidos. Queda como A-116 con EXCEPCIÓN FIRMADA por el autor: palabras es el eje
de otra sesión y su rama es compartida. La tercera fila del censo obliga a
retirar la excepción el día que esa parte declare un nombre.
Verificado con el nombre COMPUTADO desde el árbol AX de Chrome por CDP, nunca
leyendo el atributo (un aria-label en el DOM prueba el atributo, no el nombre),
con servidor recién arrancado y esperando la puerta de hidratación honesta:
/blocks/app-shell/preview navigation: ['Navegación principal',
'Menú de la aplicación',
'Migas de pan'] (antes: el 2.º null)
/uix/components/navigation-menu (sin label del consumidor)
el <nav> computa 'Principal' — el default del morfo,
traducido, sobre el landmark; el <ul>, sin nombre
Gates: 420/420 en morfo+sema+soma+eidos tocados · component:audit --only
navigation-menu PASS · morfo:check PASS · svelte-check 72/62 antes y después ·
docs:check 0/0. Los avisos de prettier de los ficheros tocados ya fallaban en
HEAD (comprobado con git show HEAD:… | prettier --check); el fichero nuevo va
formateado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| **A-116** | `palabras.breadcrumb` declara `defaultElement: 'nav'` con `aria: []` y nadie la nombra: misma clase que A-111. **Excepción firmada** en el censo de landmarks, no se toca desde aquí | CONFIRMADO |
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### 3 · Y sigue pendiente lo que tú dejaste pedido
**«Repasar cada bloque»** — un repaso block a block. **No hay ficha escrita** :
su forma se acuerda antes de empezar (qué orden, qué se mira, y si lo que salga
va al ledger o al README de cada block). Pregunta antes de arrancar.
Y **F3 sigue** : el orden del plan es `app-shell` (HECHO) → `error-page` →
`user-menu` → `notifications` → `settings` → `auth` → `dashboard` → `wizard`
→ `data-table` y `kanban` . ⚠️ Recomendación registrada **sin firmar** : subir
`auth` al segundo puesto. Y ojo — `user-menu` (F3.6) y `notifications` (F3.7)
son justo las piezas que le faltan a la barra del shell, así que hacerlas
seguidas tiene un consumidor real esperando.
uix(feature-split): una sección se nombra por su encabezado, y sus filas cuelgan de él
feature-split era el único block de sección del tier sin parte .Header. Sus
filas emitían h2 por defecto, así que una sección de tres filas aportaba tres
h2 hermanos al outline y la sección misma no tenía nombre (A-110). La página
compuesta lo rodeaba a mano: encabezado escrito como primer hijo y un level={3}
repetido fila a fila.
El h2 de las filas no era un descuido: era una decisión ESCRITA en el README
del block —«.Title emits h2 by default (each row is a section-level statement
of its own)… drop it to 3 when the page already put an h2 above the rows»—. Se
deroga en sitio, y no por gusto sino porque el resto del tier declara lo
contrario: FeatureGrid.ItemTitle, las preguntas de faq y Pricing.PlanName ponen
en level 3 lo que se repite, y el h2 lo lleva el Header.
Nace FeatureSplit.Header, calcado de Testimonials.Header (Box con medida +
Motion + Stack) y sin contexto, porque este block no tiene eje align que
heredar. Y .Title baja su default de 2 a 3. La forma queda coherente en los dos
casos, que es lo que la hace preferible a las alternativas: con .Header el h2 es
suyo y las filas cuelgan; sin .Header la app ya puso el h2 encima y las filas
cuelgan igual. La tercera vía —un contexto donde el Header marcase su presencia
y el Title derivara su nivel— daba el mismo resultado a cambio de un hijo
escribiendo en el contexto del padre, que es el patrón que ya costó un bucle de
$effect en este repo. Por un solo bit, no compensa.
Verificado en navegador, outline leído del DOM:
/blocks/feature-split/preview h2 «Del evento crudo a la decisión»
h3 ×3 (las filas) ← antes: h2 ×3, sin nombre
/blocks/landing h2 + h3 ×2, dentro de un outline de página
que ya no salta de nivel
La demo compone su encabezado y LandingSite deja de hacerlo a mano: fuera el
Stack escrito a pelo y fuera los dos level={3}. Header medido en su sitio (x
144, 768 de ancho: la misma medida que la copia de las filas).
Gates: svelte-check 72/62 antes y después · vitest src/uix/blocks 36/36 ·
blocks:check verde (19 blocks, 147 ficheros) · docs:check 0/0.
Primera fila del ledger arreglada DENTRO del tier, por acotación del eje: las
tres anteriores de la sesión (A-111, A-112, A-109) eran todas de canon.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### Lo que dejó la sesión de A-110 (2026-08-19)
**Primera fila arreglada DENTRO del tier** — decisión tuya de acotar el eje a
`src/uix/blocks/` + `web/routes/blocks/` , después de tres cierres seguidos que
fueron todos de canon.
`FeatureSplit` gana su `.Header` (era el único block de sección sin ella) y
`.Title` baja de `h2` a `h3` , como declaran los cuatro hermanos. ⚠️ El `h2` de
las filas **era una decisión escrita en el README** , no un descuido: queda
derogada en sitio. Medido: el preview pasa de tres `h2` hermanos sin nombre de
sección a `h2` + tres `h3` , y `/blocks/landing` deja de componer el encabezado
a mano y de bajar cada fila a `level={3}` .
uix(blocks-demos): la vista previa nace con el estado vivo de ActiveUix, no con los defaults
El iframe de la vista previa es OTRO documento con OTRO runtime (BootUix corre
su propio createActiveUix), así que el estado de la sección no cruza el marco:
la rama de 375/768 se abría SIEMPRE en claro, y en 9 demos siempre en LTR, con
la topbar en oscuro y RTL delante (A-29). Dentro del documento los blocks
siempre atendieron a ActiveUix — el shell conduce el framework por
uix.prefs.setIntent y el modeSource de eidos —; lo que faltaba era la única
serialización que existe entre dos runtimes: la URL.
El arreglo va en el ARNÉS, no demo a demo. BlockDemo lee el estado VIVO por la
superficie sancionada del framework — readActiveUixPrefsSlot(uix.prefs,
'direction'|'language')?.get(), las lecturas por dimensión que son rune-backed
(contador $state por dimensión), y eidos.getThemeContext().mode, que lee el
modeSource del shell — y compone la URL final: los props del BLOCK los pone el
demo, los EJES los pone el arnés. El {#key} y el enlace «abrir ↗» pasan a la
URL resuelta, así que mover un toggle global re-monta el iframe con los params
nuevos y el enlace abre lo que estás viendo.
Retirados los 11 controles «dir (solo la vista previa)» de banner, contact,
cta, feature-grid, feature-split, hero, newsletter, site-footer, site-header,
stats-band y team: eran el eje de sección repetido por demo — la regla del
arnés dice que tema, idioma y dirección los da el shell — y no tocaban prefs,
sólo escribían la URL. El shell queda como único dueño de los ejes; una
precedencia demo-gana habría dejado el toggle global inerte en esas páginas.
Dos correcciones a la ficha: testimonials ya derivaba su URL (el literal
desnudo quedó atrás en F2b), y el alcance real era el sistémico que la propia
ficha apuntaba — mode= no viajaba en NINGUNO de los 19 demos y dir= faltaba en
9. La pata lang sigue siendo inerte salvo en contact (blocks.* registrado),
pero viaja igual: es un eje del shell.
Verificado con la topbar REAL de la sección, no con params a mano: en oscuro +
RTL, el iframe de pricing a 375 arranca base-dark + dir=rtl (tinta
oklch(0.9491 0 0)) y el de hero — que tenía control local — hereda igual; en el
estado por defecto los dos vuelven a base-light + ltr. La URL sale con los
props del block primero y mode/dir/lang detrás.
Gates: svelte-check 72/62 antes y después · blocks:check 0/19 · docs:check
0/0. Huella: BlockDemo +31, cada demo −18 (−192/+39 en total).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
**A-29 CERRADA** (el arnés serializa el estado VIVO de ActiveUix a la URL del
preview — `readActiveUixPrefsSlot` + `getThemeContext().mode` — y los 11
controles `dir` locales se retiran: el shell es el único dueño de los ejes).
⚠️ Costó dos propuestas malas ANTES de leer `ActiveUix` : un contexto paralelo
`_lib/axes.ts` y una precedencia demo-gana que dejaba el toggle global inerte
en 10 páginas. La lección es la pregunta 2 del protocolo, otra vez.
**Lo que queda dentro de la frontera**, por si entras por aquí: A-11
(demos que redeclaran tipos del canon) ·
uix(feature-split): una sección se nombra por su encabezado, y sus filas cuelgan de él
feature-split era el único block de sección del tier sin parte .Header. Sus
filas emitían h2 por defecto, así que una sección de tres filas aportaba tres
h2 hermanos al outline y la sección misma no tenía nombre (A-110). La página
compuesta lo rodeaba a mano: encabezado escrito como primer hijo y un level={3}
repetido fila a fila.
El h2 de las filas no era un descuido: era una decisión ESCRITA en el README
del block —«.Title emits h2 by default (each row is a section-level statement
of its own)… drop it to 3 when the page already put an h2 above the rows»—. Se
deroga en sitio, y no por gusto sino porque el resto del tier declara lo
contrario: FeatureGrid.ItemTitle, las preguntas de faq y Pricing.PlanName ponen
en level 3 lo que se repite, y el h2 lo lleva el Header.
Nace FeatureSplit.Header, calcado de Testimonials.Header (Box con medida +
Motion + Stack) y sin contexto, porque este block no tiene eje align que
heredar. Y .Title baja su default de 2 a 3. La forma queda coherente en los dos
casos, que es lo que la hace preferible a las alternativas: con .Header el h2 es
suyo y las filas cuelgan; sin .Header la app ya puso el h2 encima y las filas
cuelgan igual. La tercera vía —un contexto donde el Header marcase su presencia
y el Title derivara su nivel— daba el mismo resultado a cambio de un hijo
escribiendo en el contexto del padre, que es el patrón que ya costó un bucle de
$effect en este repo. Por un solo bit, no compensa.
Verificado en navegador, outline leído del DOM:
/blocks/feature-split/preview h2 «Del evento crudo a la decisión»
h3 ×3 (las filas) ← antes: h2 ×3, sin nombre
/blocks/landing h2 + h3 ×2, dentro de un outline de página
que ya no salta de nivel
La demo compone su encabezado y LandingSite deja de hacerlo a mano: fuera el
Stack escrito a pelo y fuera los dos level={3}. Header medido en su sitio (x
144, 768 de ancho: la misma medida que la copia de las filas).
Gates: svelte-check 72/62 antes y después · vitest src/uix/blocks 36/36 ·
blocks:check verde (19 blocks, 147 ficheros) · docs:check 0/0.
Primera fila del ledger arreglada DENTRO del tier, por acotación del eje: las
tres anteriores de la sesión (A-111, A-112, A-109) eran todas de canon.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
A-71 (el arnés pinta el block antes del `h1` ) · A-96 (`affixOffset` sin control
vivo) · A-74 (anclas muertas en la demo de feature-split) · A-53 · A-62 · A-65 ·
A-75 · A-48 · A-60 · A-79 (SOSP) · A-55 (medida, pero el autor la ve centrada) ·
A-92 (sólo doc de motion). Fuera de la frontera y por tanto CONGELADAS mientras
el eje sea «sólo bloques»: A-106, A-107, A-108, A-114, A-115, A-116 (canon) y
A-09, A-47, A-67, cuya ficha sitúa el arreglo en canon.
uix(banner): la tira de aviso no es la cabecera del sitio, así que deja de reclamar su landmark
Toda página con cabecera exponía DOS landmarks banner: el <header> del
site-header y la tira de aviso, que estampaba role="banner" a mano. ARIA
reserva ese rol para la cabecera DEL SITIO —identidad, búsqueda del sitio— y
dice que un documento debería marcar como mucho uno (A-109).
Las dos salidas que la ficha proponía estaban mal planteadas, y lo destapó una
revisión adversarial de tres refutadores independientes:
- «Estampar el rol ANTES de los rest props, que es la clase naming donde gana
el consumidor» contradice la doctrina que la propia ficha citaba: role es
clase CONTRATO por nombre de atributo (morfo/types.ts:811-832;
ARIA_NAMING_ATTRS contiene sólo aria-label). El runtime siempre gana ahí, y
con razón: un data-state o un role pisables son un componente que miente.
- Y el orden de estampado NUNCA fue el mecanismo. Medido: el segundo landmark
es el <header> del site-header, que no lleva atributo role ninguno — recibe
banner IMPLÍCITO por ser una <header> fuera de un elemento de sección. Un rol
pisable habría dejado quitarlo caso por caso, empujando el arreglo a cada
app, en vez de arreglarlo por defecto.
- El morfo, además, nunca declaró el rol (aria: []): era un literal del wrapper
de eidos que el README daba por contrato de la parte. Drift morfo↔eidos que
se queda sin objeto.
Lo firmado: la tira deja de reclamar el landmark. defaultElement header→section
en el morfo, fuera el role literal. Un <section> es region SÓLO cuando tiene
nombre, así que el aria-label del consumidor la hace encontrable y su ausencia
la deja como contenido plano — que es exactamente lo que el censo de landmarks
de A-111 ya sancionaba por escrito.
⚠️ SIN nombre por defecto, y esta es la corrección que la revisión me obligó a
hacer sobre mi propia propuesta: yo había planteado un default traducido
(«Aviso»), copiando el patrón de skeleton/spinner. Habría convertido TODA tira
en landmark sin salida — /temas/grafito apila cuatro y habrían salido cuatro
region con el mismo nombre, que es la clase de defecto de A-111 con otro traje.
El precedente de <nav> no transfiere: un <nav> es SIEMPRE landmark y por eso
pide nombre; un <section> lo es PORQUE lo tiene. Quien quiera una tira
encontrable, la nombra.
Derogadas por escrito las dos afirmaciones que decían lo contrario, porque
existían y estaban firmadas: el README del canon («Banner is a landmark for
page-level announcements — site header, persistent notice, system status bar»)
y el «✓ ejemplar» que docs/audit/components/banner.md le puso a ese contrato el
2026-07-07. La otra mitad de aquella decisión —no role="alert", el feedback
transitorio es de Toast— sigue en pie y se dice.
Verificado con el árbol AX de Chrome por CDP, servidor limpio:
/blocks/landing banner ['Aviso del producto','Acme'] → banner ['Acme']
+ region ['Aviso del producto']
/blocks/banner/preview banner ['Principal'] · region ['Aviso del producto']
/temas/grafito banner [] · region [] — las cuatro tiras sin nombre
dejan de ser landmarks, que es lo correcto
Píxel idéntico: la receta selecciona [data-banner], no el elemento — tira a
sangre 1280×54 con el mismo fondo, capturada y mirada. Ningún test, guard, CSS
ni consulta del repo seleccionaba por role=banner ni por el tag.
Gates: svelte-check 72/62 antes y después · vitest eidos+blocks+morfo 692/693
(el rojo es skin-media-player, ajeno y documentado) · blocks:check 0/19 ·
docs:check 0/0 (644) · morfo:check PASS en banner. Prosa re-sincronizada en 11
sitios: los README de canon, block y app-shell, la ficha de auditoría, las tres
demos, landing y su README.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### Lo que dejó la sesión de A-109 (2026-08-19)
**El `Banner` deja de reclamar el landmark `banner` **: `<section>` sin rol —
`region` sólo cuando el consumidor la nombra. Y las DOS salidas que la ficha
proponía estaban mal planteadas; la revisión adversarial (tres refutadores) es
lo que lo destapó:
- **`role` es clase CONTRATO** (`morfo/types.ts:811-832`), así que «estampar el
rol antes de los rest props para que el consumidor lo pise» contradice la
doctrina que la propia ficha citaba.
- **El orden de estampado nunca fue el mecanismo**: el segundo landmark es el
`<header>` del `site-header` , que **no lleva `role`** — lo recibe implícito.
- **El morfo nunca declaró el rol** (`aria: []`): era un literal del wrapper que
el README daba por contrato. Drift morfo↔eidos, ahora sin objeto.
- ⚠️ **Y mi primera versión del arreglo llevaba un nombre por defecto traducido,
que era un error**: un default convierte TODA tira en landmark sin salida —
`/temas/grafito` apila cuatro y habrían salido cuatro `region` homónimas (la
clase de A-111). El precedente de `nav` no transfiere: un `<nav>` SIEMPRE es
landmark; un `<section>` lo es **porque** tiene nombre.
- **Derogados por escrito** la decisión del README del canon («Banner is a
landmark… site header, persistent notice, system status bar») y el «✓
ejemplar» de `docs/audit/components/banner.md` . La otra mitad —no
`role="alert"` — sigue.
Medido (AX por CDP): `/blocks/landing` pasa de `banner: ['Aviso del producto',
'Acme']` a `banner: ['Acme']` + `region: ['Aviso del producto']` ;
`/temas/grafito` deja de exponer landmarks de tira. Píxel idéntico (la receta va
por `[data-banner]` ). 11 sitios de prosa re-sincronizados.
uix(toolbar): la acción principal de un cluster ya puede decir su rango — y el gesto ya suena
`Toolbar.Button` tipaba contra el ButtonProps de SOMA ({id, disabled}): sin
variant, sin color, sin size. Un cluster de acciones donde UNA es la principal
no tenía cómo decirlo desde dentro, y la demo del app-shell dejaba «Nueva»
FUERA del toolbar como workaround documentado (A-112).
La ficha ofrecía dos salidas y mi primera recomendación fue la equivocada —
declarar el toolbar deliberadamente uniforme y consagrar el workaround. El
autor la tumbó, con razón: el eidos toolbar-button.svelte era un PASSTHROUGH
que dejaba el <button> nativo de soma, exactamente la clase de bug que
dialog-trigger (2026-06-19) y card-group-title (2026-06-27) ya pagaron, y el
precedente correcto no es menubar-trigger sino Form.Submit. Dos datos que el
primer análisis no tenía:
- El botón era MUDO. El morfo del toolbar sólo declara commit-toggle (el
group-item), así que pulsar una acción no emitía nada. Medido tras el
cambio: click → contact-activate estampado en el nodo + 8 nodos de audio,
donde antes 0.
- La uniformidad la dan los DEFAULTS, no la ausencia del eje: ghost/neutral y
el size del toolbar por contexto de eidos (toolbar/context.ts, calcado del
precedente ButtonGroup y con su misma razón: la receta del Button lee
data-size EN el nodo, un descendant selector no llega). El tipo estrecha a
SelectionVariant (solid | outline | ghost).
La forma es el Button consumer pattern entero: composición vía child de soma
(con el rename outerChild contra la recursión), y la receta CEDE el nodo —
cero cromo de botón en toolbar.css (regla 5); Link y GroupItem siguen siendo
superficie de la barra y los pinta ella. Antes de ceder, las dos recetas sobre
un mismo nodo producían un híbrido medido en el que solid/primary NO pintaba.
La doctrina que faltaba queda escrita en guides/component-guide.md §4: dos
clases de parte con forma de botón en una barra. ACCIÓN en barra (Toolbar.
Button, Form.Submit, Dialog.Trigger) compone el Button; control de SUPERFICIE
de barra (Menubar.Trigger: File, Edit) lo pinta la barra. El test: ¿significa
lo mismo fuera de la barra? Esa distinción vivía en un comentario de
menubar-trigger; el comentario ahora apunta a la doctrina.
Verificado en navegador (Playwright, servidor limpio): el nodo ES el Button
del canon (data-button + data-variant), solid/primary pinta (fondo primary,
tinta blanca), paridad exacta con el GroupItem por construcción — mismo bundle
--size-*: 36px/36px en md, 30px/30px en sm, con la herencia del size del
toolbar medida en vivo al cambiarlo. La demo del app-shell mueve «Nueva» AL
INTERIOR del cluster (workaround retirado, gap del block cerrado en su README)
y la demo del toolbar gana la acción «New» con controles vivos action variant
/ action color.
Gates: svelte-check 72/62 (base 73/62, sin regresión) · vitest eidos+blocks
470/471 (el rojo es skin-media-player, ajeno y documentado en el handoff) ·
blocks:check 0 errores / 19 blocks · docs:check 0/0. Los avisos de prettier de
los dos README ya fallaban en HEAD.
⚠️ El árbol compartido tiene ya encima trabajo NUEVO de otra sesión
(navigation-menu, PLAN-sidebar, book-deviations): este commit lleva sólo los
14 ficheros del eje A-112, verificados por lista.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
### Lo que dejó la sesión de A-112 (2026-08-19, mismo día que A-111)
**Firmada la salida (a) tras tumbar el autor mi primera recomendación (b)** —
y la corrección es la lección: (b) «declarar el toolbar uniforme» habría
consagrado el bug de passthrough documentado (el eidos `toolbar-button.svelte`
dejaba el `<button>` nativo de soma) y dejado el gesto MUDO. La ficha §A-112
lleva el detalle; lo ejecutado:
- `Toolbar.Button` **compone el `Button` del canon** vía `child` (consumer
pattern): defaults `ghost` /`neutral`/el `size` del toolbar por contexto de
eidos (`toolbar/context.ts`, precedente ButtonGroup); tipo estrechado a
`SelectionVariant` . La receta CEDE el nodo (cero cromo de botón); `Link` y
`GroupItem` siguen siendo superficie de la barra.
- **El gesto suena**: `contact-activate` estampado + 8 nodos de audio al click,
antes 0 (el morfo del toolbar sólo declara `commit-toggle` ).
- **Doctrina nueva en `guides/component-guide.md` §4**: dos clases de parte con
forma de botón en una barra — ACCIÓN en barra (compone `Button` :
Toolbar.Button, Form.Submit, Dialog.Trigger) vs control de SUPERFICIE de
barra (la pinta la barra: Menubar.Trigger). El test: ¿significaría lo mismo
fuera de la barra?
- **La demo del app-shell mueve «Nueva» AL INTERIOR del cluster** (el
workaround de la ficha, retirado) y la demo del toolbar gana la acción «New»
con controles vivos `action variant` / `action color` .
- Herencia de tamaño medida en vivo: toolbar `sm` → botones `sm` (30px, paridad
exacta con GroupItem por el bundle `--size-*` compartido).
uix(navigation-menu): un <ul> no es una región, y por eso el landmark salía sin nombre
El morfo declaraba el nombre por defecto de la navegación en la parte que pinta
el <ul>, con la razón escrita al lado: «the root <nav> delegates its name to the
list it wraps». La delegación no existe. El gesto que un lector de pantalla
ofrece para saltar entre las áreas de una página llega al <nav>, y llegaba a un
<nav> ANÓNIMO — con el nombre del consumidor puesto una capa más adentro, en un
elemento que ninguna navegación por landmarks visita.
Sobrevivió tres semanas de tier porque con UNA navegación en la página un
landmark anónimo es admisible, y todas las demos del componente y todos los
blocks de sitio montan una. Apareció en cuanto el app-shell montó dos ámbitos,
que es lo que un shell hace por definición (A-111). La APG es explícita: si una
página incluye más de un landmark de navegación, cada uno lleva su nombre.
El arreglo NO es el que la ficha proponía. Su paso 1 quería que la lista
heredase el nombre por aria-labelledby; el barrido de los morfos hermanos con
defaultElement 'nav' dijo otra cosa: breadcrumb (<ol>) y nav-tree (<ul>)
declaran aria: [] en su lista y nombran sólo el <nav>. navigation-menu era el
único fuera de esa forma, así que la pareja de nombre —el aria-label con su
suppression prop-falsy ariaLabelledby, y el aria-labelledby por propRef— sube
entera a la parte Provider y la lista se queda con su aria-orientation.
En soma, la fuente ariaLabelledby sube al nivel del runtime (la forma de
breadcrumb) y el wrapper DEJA de sacar aria-label de restProps: viaja con el
resto y gana el merge por política de naming, que es lo que A-85 dejó escrito y
lo que este componente no aplicaba. Se van con ello el opt ariaLabel y el
reenvío condicional que la List hacía a mano.
Y como la regla no estaba escrita en ninguna doctrina —grep de «landmark» en
morfo.md, canon/ y guides/ daba cero—, se escribe: architecture/morfo.md §Step 4
gana «A landmark is named on ITS OWN element», y nace el censo
morfo/landmark-census.test.ts, que falla si una parte con defaultElement 'nav'
no declara un attr de nombre EN SU PARTE. Probado en rojo por mutación tres
veces: deshaciendo el arreglo (navigation-menu.provider), retirando la excepción
(palabras.breadcrumb) y vaciando el catálogo, que es la mutación que impide que
un censo sobre nada pase en verde. El ámbito es 'nav' a propósito: main, header
y footer son únicos por página, y section y form sólo son landmark cuando ya
tienen nombre.
El guard destapó de paso un segundo caso de la misma clase: la parte Breadcrumb
de palabras declara un <nav> con aria: [] y nadie la nombra, ni en soma ni en
eidos. Queda como A-116 con EXCEPCIÓN FIRMADA por el autor: palabras es el eje
de otra sesión y su rama es compartida. La tercera fila del censo obliga a
retirar la excepción el día que esa parte declare un nombre.
Verificado con el nombre COMPUTADO desde el árbol AX de Chrome por CDP, nunca
leyendo el atributo (un aria-label en el DOM prueba el atributo, no el nombre),
con servidor recién arrancado y esperando la puerta de hidratación honesta:
/blocks/app-shell/preview navigation: ['Navegación principal',
'Menú de la aplicación',
'Migas de pan'] (antes: el 2.º null)
/uix/components/navigation-menu (sin label del consumidor)
el <nav> computa 'Principal' — el default del morfo,
traducido, sobre el landmark; el <ul>, sin nombre
Gates: 420/420 en morfo+sema+soma+eidos tocados · component:audit --only
navigation-menu PASS · morfo:check PASS · svelte-check 72/62 antes y después ·
docs:check 0/0. Los avisos de prettier de los ficheros tocados ya fallaban en
HEAD (comprobado con git show HEAD:… | prettier --check); el fichero nuevo va
formateado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### Lo que dejó la sesión de A-111 (2026-08-19, un commit)
Siete ficheros del canon y tres de proceso. Nada del tier `blocks` se tocó: el
defecto vivía una capa más abajo, como las 8 de 12 de la fase 7.
- `morfo/components/navigation-menu.ts` — la pareja de nombre sube de `List` a
`Provider` ; la `List` conserva sólo `aria-orientation` .
- `soma/components/navigation-menu/` — la fuente `ariaLabelledby` sube al nivel
del runtime; el wrapper deja de sacar `aria-label` de restProps (viaja con el
resto y gana el merge, A-85); fuera el opt `ariaLabel` y el reenvío de la
`List` ; test del bag reescrito (nombre en el provider, `not.toHaveProperty`
en la lista, suppression por `aria-labelledby` ).
- `morfo/landmark-census.test.ts` (nuevo) + `architecture/morfo.md` §Step 4.
- READMEs de soma y eidos del componente; ledger §A-111 y §A-116 nuevas.
**Gates**: 420/420 en morfo+sema+soma+eidos tocados · `component:audit --only
navigation-menu` PASS · `morfo:check` PASS (las 6 rojas —color-field, combobox,
fab, gradient-builder, menu-dial, palabras— son ajenas: el check valida
`data-*` , que esta sesión no toca) · `svelte-check` **72/62 antes y después** ·
`docs:check` 0/0 (640 docs) · prettier: los 4 avisos de ficheros tocados ya
fallaban en HEAD (verificado con `git show HEAD:… | prettier --check` ), el
fichero nuevo va formateado.
⚠️ **Método que valió la pena** : el nombre se midió COMPUTADO, desde el árbol AX
de Chrome por CDP, nunca leyendo el atributo — un `aria-label` en el DOM prueba
el atributo, no el nombre. Y el guard se probó en rojo por mutación tres veces;
una de ellas (vaciar el catálogo) es la que impide que un censo sobre nada pase
en verde.
### Lo que dejó la sesión del `app-shell` (2026-08-19)
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Ocho commits, de `9fd54c80f` a `edf0639ee` . **Sin push.**
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Fase 0 firmada** en `PLAN-blocks.md` §F3.1 — Q1 el block NO cablea servicios
· Q2 dos modelos de scroll por prop · Q3 la cabecera dentro del inset · Q4
canon `SkipLink` + uno por región montada.
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **`SkipLink` (canon nuevo)** — eidos-native, 1 parte, 0 eventos, posee sus
palabras por ROL de landmark, peldaño de z propio (950).
- **LOS DOS ÁMBITOS, y es la corrección de fondo del día**: la barra lleva la
APLICACIÓN (su menú general, buscar, avisos, cuenta) y no se mueve; el RAÍL
lleva el CONTEXTO (este proyecto, esta tabla) y cambia entero al cambiar de
contexto. Yo había escrito que un menú en la barra sería «una segunda
navegación»: es al revés, dos navegaciones de ámbito distinto son lo correcto
(Atlassian `TopNav` +`SideNav`, Polaris `TopBar` +`Navigation`). El rastro va en
la cabecera de la PÁGINA, no en la barra.
- **Cuatro trampas de layout medidas**, escritas en el README del block:
`minHeight` es un suelo y no un techo · un grid que sólo declara filas tiene
una columna implícita `auto` que se encoge · `Box` declara
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
`flex-grow/shrink/basis` y pisa al `flex` de quien lo envuelve · un ancestro
con `overflow` se lleva el `sticky` (A-114).
- ⚠️ **Dos averías del ARNÉS que este block destapó, las dos arregladas** : los
previews llevaban los 8px de margen del navegador (así que NINGÚN block se
enseñaba a sangre: `x: 8, w: 1264` a 1280, la página compuesta incluida) y no
anclaban la tipografía del tema (A-113). **Las medidas de anchura anteriores
al 2026-08-19 llevan el sesgo de los 8px.**
- **La demo se rehizo dos veces, y la lección vale para todo el tier**: primero
estaba escrita a mano (`< div style > `, `16rem` clavado, `font-size` en un
`<strong>` ) y luego, ya sin literales, seguía sin usar los componentes que
SIGNIFICAN lo que mostraba. Ahora: `Metrics` para los KPI (con la valencia
desacoplada), `Feed` para bandeja y actividad (`role="feed"` + `article` +
`aria-posinset` ), `Toolbar` para los clusters, `NavigationMenu` para el menú
de la aplicación, y la tipografía por los estilos SEMÁNTICOS del tema
(`Text style="label|caption|body"`, `Heading level` + `style` ), nunca por un
tamaño elegido a ojo.
- **Lo que NO se ejecutó y sigue sobre la mesa**: la **app de referencia**
(`web/routes/blocks/app/`, `kind: 'page'` ) — la primera raíz de composición en
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
modo **attach** del repo (`createActiveApp` → `attachActiveUix` → `<Uix>` →
`ActiveEidos` con sus fuentes → `createActivePrefsDomProjection` →
`setActiveApp` /`setBus`/`setPermsContext`). Hoy **ningún** shell del repo hace
docs(blocks): el shell dejó cinco filas, y una explica por qué nadie la había visto
Handoff reescrito para mañana y las cinco filas que abrió el `app-shell`
fichadas en el ledger (A-111…A-115). La puerta de entrada de mañana es la
primera.
**A-111 — `NavigationMenu` deja su landmark ANÓNIMO.** El `aria-label` del
consumidor se reenvía al `<ul>` de la parte `List` y el `<nav>` se queda sin
nombre; el morfo lo declara así a propósito («the root delegates its name to the
list it wraps»). Pero un `<ul>` no es una región: quien navega por landmarks ve
el `<nav>`, y lo ve anónimo. Con UNA navegación en la página es admisible —y por
eso lleva tres semanas de tier sin salir—; con dos ámbitos incumple la APG, y un
shell de aplicación monta dos por definición. Medido en el preview:
`navigation: ['Navegación principal', null, 'Migas de pan']`.
La ficha lleva la forma propuesta en cuatro pasos: mover el default de naming de
la `List` al `Provider` y que la lista apunte al root con `aria-labelledby`;
precedencia de clase «naming» (el consumidor gana si dice algo); un guard que
falle si un componente con `defaultElement: 'nav'` no puede recibir nombre en su
root; y re-medir el censo. Toca morfo + soma, así que va con el aviso de mirar
antes si la sesión del `sidebar` sigue viva ahí.
Las otras cuatro: **A-112** `Toolbar.Button` tipa contra el `ButtonProps` de
soma y no acepta `variant`/`color` · **A-113** la foundation emite los estilos
semánticos y no los aplica a ningún elemento —Times New Roman en los 19
previews—, ARREGLADA en el reset del arnés pero con la pregunta de canon abierta
· **A-114** `Sidebar.Inset` impone `overflow: auto` y se lleva el `sticky` de
dentro · **A-115** no existe `description-list` y el panel de detalle del shell
es el «primer detail-view real» que su propia ficha F5 pone como disparador.
El handoff recoge además la corrección de fondo del día (la barra y el raíl son
dos ÁMBITOS, no la misma navegación en dos tamaños), las dos averías del arnés
que este block destapó, y lo que NO se ejecutó: la app de referencia en modo
attach, que sigue siendo el único sitio donde el ecosistema entero se
demostraría cableado.
docs:check 0/0 (639).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
nada de eso: los siete arrancan `createActiveUix` standalone.
uix(toolbar): la acción principal de un cluster ya puede decir su rango — y el gesto ya suena
`Toolbar.Button` tipaba contra el ButtonProps de SOMA ({id, disabled}): sin
variant, sin color, sin size. Un cluster de acciones donde UNA es la principal
no tenía cómo decirlo desde dentro, y la demo del app-shell dejaba «Nueva»
FUERA del toolbar como workaround documentado (A-112).
La ficha ofrecía dos salidas y mi primera recomendación fue la equivocada —
declarar el toolbar deliberadamente uniforme y consagrar el workaround. El
autor la tumbó, con razón: el eidos toolbar-button.svelte era un PASSTHROUGH
que dejaba el <button> nativo de soma, exactamente la clase de bug que
dialog-trigger (2026-06-19) y card-group-title (2026-06-27) ya pagaron, y el
precedente correcto no es menubar-trigger sino Form.Submit. Dos datos que el
primer análisis no tenía:
- El botón era MUDO. El morfo del toolbar sólo declara commit-toggle (el
group-item), así que pulsar una acción no emitía nada. Medido tras el
cambio: click → contact-activate estampado en el nodo + 8 nodos de audio,
donde antes 0.
- La uniformidad la dan los DEFAULTS, no la ausencia del eje: ghost/neutral y
el size del toolbar por contexto de eidos (toolbar/context.ts, calcado del
precedente ButtonGroup y con su misma razón: la receta del Button lee
data-size EN el nodo, un descendant selector no llega). El tipo estrecha a
SelectionVariant (solid | outline | ghost).
La forma es el Button consumer pattern entero: composición vía child de soma
(con el rename outerChild contra la recursión), y la receta CEDE el nodo —
cero cromo de botón en toolbar.css (regla 5); Link y GroupItem siguen siendo
superficie de la barra y los pinta ella. Antes de ceder, las dos recetas sobre
un mismo nodo producían un híbrido medido en el que solid/primary NO pintaba.
La doctrina que faltaba queda escrita en guides/component-guide.md §4: dos
clases de parte con forma de botón en una barra. ACCIÓN en barra (Toolbar.
Button, Form.Submit, Dialog.Trigger) compone el Button; control de SUPERFICIE
de barra (Menubar.Trigger: File, Edit) lo pinta la barra. El test: ¿significa
lo mismo fuera de la barra? Esa distinción vivía en un comentario de
menubar-trigger; el comentario ahora apunta a la doctrina.
Verificado en navegador (Playwright, servidor limpio): el nodo ES el Button
del canon (data-button + data-variant), solid/primary pinta (fondo primary,
tinta blanca), paridad exacta con el GroupItem por construcción — mismo bundle
--size-*: 36px/36px en md, 30px/30px en sm, con la herencia del size del
toolbar medida en vivo al cambiarlo. La demo del app-shell mueve «Nueva» AL
INTERIOR del cluster (workaround retirado, gap del block cerrado en su README)
y la demo del toolbar gana la acción «New» con controles vivos action variant
/ action color.
Gates: svelte-check 72/62 (base 73/62, sin regresión) · vitest eidos+blocks
470/471 (el rojo es skin-media-player, ajeno y documentado en el handoff) ·
blocks:check 0 errores / 19 blocks · docs:check 0/0. Los avisos de prettier de
los dos README ya fallaban en HEAD.
⚠️ El árbol compartido tiene ya encima trabajo NUEVO de otra sesión
(navigation-menu, PLAN-sidebar, book-deviations): este commit lleva sólo los
14 ficheros del eje A-112, verificados por lista.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- **CERRADO (2026-08-19), ya no está en manos de nadie**: las dos navegaciones
del shell dicen lo mismo ante el mismo gesto. `Sidebar.MenuButton` dejó de ser
mudo (eje del `Sidebar` , `f6d5fa159` ) y `NavigationMenu.Link` dejó de emitir
`commit-select` al navegar (T-1): los dos declaran `contact-activate` anclado
en el control pulsado + `shift-navigate` en la superficie que cruza.
## ⚠️ LO PRIMERO: la fuente viva es el LEDGER, no el documento del 2026-08-01
feat(blocks): el guard vigila la frontera, y siete filas del ledger caen medidas
FASE 5 DEL SANEAMIENTO — `blocks:check` deja de vigilar solo la forma del tier
y pasa a vigilar su frontera, su documentacion y su catalogo. Tres reglas, cada
una con su fixture negativo en el selfTest():
1. Lista blanca de importaciones (B-4). La frontera dura 1 era prosa; ahora es
codigo: $uix, los arts publicos, $libs/forms, svelte y los relativos del
propio block. Los arts se DERIVAN de src/arts/*, no se listan a mano — una
lista escrita se queda atras el dia que aterriza un art. El tier ya cumplia:
cero violaciones al encenderla.
2. README completo (B-9) + declaracion de landmark (B-8). Salieron 7 de 15 sin
ella; escritas leyendo la fuente de cada block, no la plantilla.
3. Ficha en el catalogo de demos (B-9), en los DOS sentidos: un block que el
catalogo no publica es invisible para el rail, y un slug publicado sin block
detras es un enlace muerto.
De paso el escaner deja de leer los comentarios como codigo: los index.ts
documentan su uso con un `// import { Cta } from '$blocks/cta'`, y una lista
blanca que lee prosa habria empezado a acusar a los ejemplos.
Verificado EN ROJO sobre el arbol real, no solo contra los fixtures: un zod y un
$libs/days metidos en hero/types.ts salen con su linea exacta mientras el mismo
import dentro de un comentario NO salta; el catalogo falla en las dos
direcciones; y al romper isAllowedSpec a proposito el self-test aborta el guard
en vez de dar verde.
ARREGLOS DEL CONTRASTE DOCTRINAL — siete filas, cada una con medicion o gate:
- A-88 team: `height="100%"` en el Stack del Member. El div que pinta Motion es
un bloque pelado, asi que el Stack se quedaba a altura de contenido y el
`margin-top:auto` de los enlaces repartia CERO. Medido: la desviacion entre
filas de iconos pasa de 20px a 0, y el margen reparte 20,297px.
- A-94/A-28 (feature-grid, team, testimonials): los tres declaraban su tipo como
`= AutoGridProps` y ponian {...rest} ANTES de las props que fijan, asi que un
consumidor podia escribir align="start" y no obtener nada. Cerrados con
Pick<AutoGridProps, ...>, que conserva los tipos exactos del canon.
- A-84 feature-grid: el eje `align` llega a las partes por contexto, como en
team, en vez de que el typedoc instruya al app a repetirlo. Medido: con
align=center el align-items computado es center en cabecera Y celdas.
- A-19 faq: `marginX="auto"` en el Header — Box no centra solo. Medido: el Box
de 768px resuelve margin-inline 248px/248px donde antes daba 0.
- A-27 testimonials y A-86 pricing: el typedoc decia `soft` donde el codigo hace
`outline`, y el ejemplo de la puerta de entrada no compilaba.
- A-91 banner (fila nueva): el block hereda el eje intent/color que el canon
declara fuera del sistema abierto en su propio typedoc, sin registrarlo.
LEDGER — dos lecciones de instrumento escritas dentro, porque ambas produjeron
hallazgos falsos publicados:
1. El dev server sirve codigo ANTERIOR a HEAD si reusa cache de Vite. Tres filas
(A-36, A-69, A-85) se dieron por vivas estando arregladas. El delator fue
aritmetico: las duraciones medidas eran EXACTAMENTE las que el comentario del
arreglo cita como estado anterior.
2. Contar nodos WebAudio no es medir sonido: la sintesis crea dos osciladores
por earcon, un sound pack de muestras cambia la aritmetica entera, y crear
nodos no es sonar. El contador responde "hubo actividad" y nada mas.
Y el error de metodo del que ambas son sintoma, tambien registrado: se fue a
re-medir A-67 sin leer su ficha, que ya traia los gains del catalogo, el empate
de applyDominance y la regla de CANON.md incumplida — mejor razonado que la nota
que lo sustituia.
Gates: blocks:check verde (15 blocks / 115 ficheros) · svelte-check 69 errores /
57 avisos = linea base exacta, ninguno en lo tocado · docs:check 0/0 · prettier
limpio en todo lo del commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**`docs/process/AUDIT-blocks-ledger.md`** — filas con id estable desde `A-01` .
El documento del 2026-08-01 se conserva (sus reclamaciones y evidencias son
válidas y el ledger las indexa) pero **su clasificación NO vale** : tenía los
veredictos desemparejados de sus hallazgos por un join por POSICIÓN, y el journal
del workflow era de sesión. Se reprodujo en vivo al re-verificar — el journal
devuelve los resultados en un orden distinto al de entrada.
uix(blocks-demos): la vista previa nace con el estado vivo de ActiveUix, no con los defaults
El iframe de la vista previa es OTRO documento con OTRO runtime (BootUix corre
su propio createActiveUix), así que el estado de la sección no cruza el marco:
la rama de 375/768 se abría SIEMPRE en claro, y en 9 demos siempre en LTR, con
la topbar en oscuro y RTL delante (A-29). Dentro del documento los blocks
siempre atendieron a ActiveUix — el shell conduce el framework por
uix.prefs.setIntent y el modeSource de eidos —; lo que faltaba era la única
serialización que existe entre dos runtimes: la URL.
El arreglo va en el ARNÉS, no demo a demo. BlockDemo lee el estado VIVO por la
superficie sancionada del framework — readActiveUixPrefsSlot(uix.prefs,
'direction'|'language')?.get(), las lecturas por dimensión que son rune-backed
(contador $state por dimensión), y eidos.getThemeContext().mode, que lee el
modeSource del shell — y compone la URL final: los props del BLOCK los pone el
demo, los EJES los pone el arnés. El {#key} y el enlace «abrir ↗» pasan a la
URL resuelta, así que mover un toggle global re-monta el iframe con los params
nuevos y el enlace abre lo que estás viendo.
Retirados los 11 controles «dir (solo la vista previa)» de banner, contact,
cta, feature-grid, feature-split, hero, newsletter, site-footer, site-header,
stats-band y team: eran el eje de sección repetido por demo — la regla del
arnés dice que tema, idioma y dirección los da el shell — y no tocaban prefs,
sólo escribían la URL. El shell queda como único dueño de los ejes; una
precedencia demo-gana habría dejado el toggle global inerte en esas páginas.
Dos correcciones a la ficha: testimonials ya derivaba su URL (el literal
desnudo quedó atrás en F2b), y el alcance real era el sistémico que la propia
ficha apuntaba — mode= no viajaba en NINGUNO de los 19 demos y dir= faltaba en
9. La pata lang sigue siendo inerte salvo en contact (blocks.* registrado),
pero viaja igual: es un eje del shell.
Verificado con la topbar REAL de la sección, no con params a mano: en oscuro +
RTL, el iframe de pricing a 375 arranca base-dark + dir=rtl (tinta
oklch(0.9491 0 0)) y el de hero — que tenía control local — hereda igual; en el
estado por defecto los dos vuelven a base-light + ltr. La URL sale con los
props del block primero y mode/dir/lang detrás.
Gates: svelte-check 72/62 antes y después · blocks:check 0/19 · docs:check
0/0. Huella: BlockDemo +31, cada demo −18 (−192/+39 en total).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
Estado del ledger (2026-08-19): **77 ARREGLADO · 23 CONFIRMADO · 13 REFUTADO ·
uix(navigation-menu): un <ul> no es una región, y por eso el landmark salía sin nombre
El morfo declaraba el nombre por defecto de la navegación en la parte que pinta
el <ul>, con la razón escrita al lado: «the root <nav> delegates its name to the
list it wraps». La delegación no existe. El gesto que un lector de pantalla
ofrece para saltar entre las áreas de una página llega al <nav>, y llegaba a un
<nav> ANÓNIMO — con el nombre del consumidor puesto una capa más adentro, en un
elemento que ninguna navegación por landmarks visita.
Sobrevivió tres semanas de tier porque con UNA navegación en la página un
landmark anónimo es admisible, y todas las demos del componente y todos los
blocks de sitio montan una. Apareció en cuanto el app-shell montó dos ámbitos,
que es lo que un shell hace por definición (A-111). La APG es explícita: si una
página incluye más de un landmark de navegación, cada uno lleva su nombre.
El arreglo NO es el que la ficha proponía. Su paso 1 quería que la lista
heredase el nombre por aria-labelledby; el barrido de los morfos hermanos con
defaultElement 'nav' dijo otra cosa: breadcrumb (<ol>) y nav-tree (<ul>)
declaran aria: [] en su lista y nombran sólo el <nav>. navigation-menu era el
único fuera de esa forma, así que la pareja de nombre —el aria-label con su
suppression prop-falsy ariaLabelledby, y el aria-labelledby por propRef— sube
entera a la parte Provider y la lista se queda con su aria-orientation.
En soma, la fuente ariaLabelledby sube al nivel del runtime (la forma de
breadcrumb) y el wrapper DEJA de sacar aria-label de restProps: viaja con el
resto y gana el merge por política de naming, que es lo que A-85 dejó escrito y
lo que este componente no aplicaba. Se van con ello el opt ariaLabel y el
reenvío condicional que la List hacía a mano.
Y como la regla no estaba escrita en ninguna doctrina —grep de «landmark» en
morfo.md, canon/ y guides/ daba cero—, se escribe: architecture/morfo.md §Step 4
gana «A landmark is named on ITS OWN element», y nace el censo
morfo/landmark-census.test.ts, que falla si una parte con defaultElement 'nav'
no declara un attr de nombre EN SU PARTE. Probado en rojo por mutación tres
veces: deshaciendo el arreglo (navigation-menu.provider), retirando la excepción
(palabras.breadcrumb) y vaciando el catálogo, que es la mutación que impide que
un censo sobre nada pase en verde. El ámbito es 'nav' a propósito: main, header
y footer son únicos por página, y section y form sólo son landmark cuando ya
tienen nombre.
El guard destapó de paso un segundo caso de la misma clase: la parte Breadcrumb
de palabras declara un <nav> con aria: [] y nadie la nombra, ni en soma ni en
eidos. Queda como A-116 con EXCEPCIÓN FIRMADA por el autor: palabras es el eje
de otra sesión y su rama es compartida. La tercera fila del censo obliga a
retirar la excepción el día que esa parte declare un nombre.
Verificado con el nombre COMPUTADO desde el árbol AX de Chrome por CDP, nunca
leyendo el atributo (un aria-label en el DOM prueba el atributo, no el nombre),
con servidor recién arrancado y esperando la puerta de hidratación honesta:
/blocks/app-shell/preview navigation: ['Navegación principal',
'Menú de la aplicación',
'Migas de pan'] (antes: el 2.º null)
/uix/components/navigation-menu (sin label del consumidor)
el <nav> computa 'Principal' — el default del morfo,
traducido, sobre el landmark; el <ul>, sin nombre
Gates: 420/420 en morfo+sema+soma+eidos tocados · component:audit --only
navigation-menu PASS · morfo:check PASS · svelte-check 72/62 antes y después ·
docs:check 0/0. Los avisos de prettier de los ficheros tocados ya fallaban en
HEAD (comprobado con git show HEAD:… | prettier --check); el fichero nuevo va
formateado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
3 DATO** (116 filas: las 90 originales + A-91…A-116). Cero
feat(blocks): el guard vigila la frontera, y siete filas del ledger caen medidas
FASE 5 DEL SANEAMIENTO — `blocks:check` deja de vigilar solo la forma del tier
y pasa a vigilar su frontera, su documentacion y su catalogo. Tres reglas, cada
una con su fixture negativo en el selfTest():
1. Lista blanca de importaciones (B-4). La frontera dura 1 era prosa; ahora es
codigo: $uix, los arts publicos, $libs/forms, svelte y los relativos del
propio block. Los arts se DERIVAN de src/arts/*, no se listan a mano — una
lista escrita se queda atras el dia que aterriza un art. El tier ya cumplia:
cero violaciones al encenderla.
2. README completo (B-9) + declaracion de landmark (B-8). Salieron 7 de 15 sin
ella; escritas leyendo la fuente de cada block, no la plantilla.
3. Ficha en el catalogo de demos (B-9), en los DOS sentidos: un block que el
catalogo no publica es invisible para el rail, y un slug publicado sin block
detras es un enlace muerto.
De paso el escaner deja de leer los comentarios como codigo: los index.ts
documentan su uso con un `// import { Cta } from '$blocks/cta'`, y una lista
blanca que lee prosa habria empezado a acusar a los ejemplos.
Verificado EN ROJO sobre el arbol real, no solo contra los fixtures: un zod y un
$libs/days metidos en hero/types.ts salen con su linea exacta mientras el mismo
import dentro de un comentario NO salta; el catalogo falla en las dos
direcciones; y al romper isAllowedSpec a proposito el self-test aborta el guard
en vez de dar verde.
ARREGLOS DEL CONTRASTE DOCTRINAL — siete filas, cada una con medicion o gate:
- A-88 team: `height="100%"` en el Stack del Member. El div que pinta Motion es
un bloque pelado, asi que el Stack se quedaba a altura de contenido y el
`margin-top:auto` de los enlaces repartia CERO. Medido: la desviacion entre
filas de iconos pasa de 20px a 0, y el margen reparte 20,297px.
- A-94/A-28 (feature-grid, team, testimonials): los tres declaraban su tipo como
`= AutoGridProps` y ponian {...rest} ANTES de las props que fijan, asi que un
consumidor podia escribir align="start" y no obtener nada. Cerrados con
Pick<AutoGridProps, ...>, que conserva los tipos exactos del canon.
- A-84 feature-grid: el eje `align` llega a las partes por contexto, como en
team, en vez de que el typedoc instruya al app a repetirlo. Medido: con
align=center el align-items computado es center en cabecera Y celdas.
- A-19 faq: `marginX="auto"` en el Header — Box no centra solo. Medido: el Box
de 768px resuelve margin-inline 248px/248px donde antes daba 0.
- A-27 testimonials y A-86 pricing: el typedoc decia `soft` donde el codigo hace
`outline`, y el ejemplo de la puerta de entrada no compilaba.
- A-91 banner (fila nueva): el block hereda el eje intent/color que el canon
declara fuera del sistema abierto en su propio typedoc, sin registrarlo.
LEDGER — dos lecciones de instrumento escritas dentro, porque ambas produjeron
hallazgos falsos publicados:
1. El dev server sirve codigo ANTERIOR a HEAD si reusa cache de Vite. Tres filas
(A-36, A-69, A-85) se dieron por vivas estando arregladas. El delator fue
aritmetico: las duraciones medidas eran EXACTAMENTE las que el comentario del
arreglo cita como estado anterior.
2. Contar nodos WebAudio no es medir sonido: la sintesis crea dos osciladores
por earcon, un sound pack de muestras cambia la aritmetica entera, y crear
nodos no es sonar. El contador responde "hubo actividad" y nada mas.
Y el error de metodo del que ambas son sintoma, tambien registrado: se fue a
re-medir A-67 sin leer su ficha, que ya traia los gains del catalogo, el empate
de applyDominance y la regla de CANON.md incumplida — mejor razonado que la nota
que lo sustituia.
Gates: blocks:check verde (15 blocks / 115 ficheros) · svelte-check 69 errores /
57 avisos = linea base exacta, ninguno en lo tocado · docs:check 0/0 · prettier
limpio en todo lo del commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
pendientes: todas están medidas. La tasa real de refutación fue del **13%** , no
del 28%.
**La regla que hay que respetar al escribir en él: unión por `id` , SIEMPRE.**
Ningún proceso vuelve a casar dos listas por posición. Un verificador que no
devuelve su id no escribe fila.
## El hallazgo grande de la re-verificación
**De las 12 ALTA de percepción, 8 tenían la causa raíz en el CANON, no en los
blocks.** Los blocks componen bien; lo que estaba roto era el arco perceptivo del
canon. Todos arreglados ya (ver abajo), pero la lección se queda: cuando una
auditoría de un tier acusa al tier, comprueba en qué capa vive el defecto.
## Lo hecho en la sesión del 2026-08-06 (commit `866089a40`)
**Canon** — `Link` (la prop `color` era inerte en `subtle` /`plain`, el hover
clavaba primary, el `:active` quedaba tapado) · `Card` (prometía `BoxProps` sin
aplicarlos: `height="100%"` era un atributo inerte; ahora el tipo dice la verdad y
el eje de tamaño va por `buildSizeStyle` , helper compartido) · `Form.Submit` y
`Form.Reset` (componen el `Button` del canon, así que la acción principal de un
formulario recupera el `contact-activate` en el gesto; y el `aria-label` genérico
deja de pisar el texto propio — WCAG 2.5.3 para todo consumidor, verificado con el
AX tree de Chrome) · `Field` (dejaba de duplicar `commit-submit` dentro de un
`Form` ) · `NavigationMenu` (el `commit-select` se mueve al `Link` , el despliegue
habla como `emerge-open/close` en vez de fingir una selección por hover, y el pack
casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia
base, 10× lo diseñado) · `Badge` (el ✕ compone `IconButton` ; la altura del chip
pasa a ser la del control) · **la fundación** : `[data-on]` arrastra la propiedad
`color` , no solo las variables.
**Blocks** — hero 1.61:1 → 6.61:1 · contact separa `incomplete` de `invalid` (el
camino de error por campo era inalcanzable por construcción) y su frase llega a la
AT · site-header ya no congela la página al cruzar el breakpoint · site-footer
emite su commit · pricing no se vacía · cta llega a sangre de verdad · newsletter
COORDINA.
**Doctrina** — la frontera dura 1 nombra `$libs/forms` ; el contrato B admite un
**segundo servicio, el anunciador** (`uix.announce`): un block que posee las
palabras de sus estados tiene que poder decirlas.
docs(process): el handoff dice donde estamos y por donde se entra
El documento seguia contando la sesion del 2026-08-10: «fase 7, 7 de 15»,
«quedan 7 en este orden», el ledger a 51/27/12 y unas puertas «SIN COMMITEAR».
Nada de eso era cierto ya, y un handoff rancio es peor que ninguno porque se
lee como si fuera el estado.
Puesto al dia:
- **Fase 7 CERRADA 15/15.** Ninguno de los 15 tiene un defecto de composicion:
lo que salio vive una capa mas abajo (canon) o en las FICHAS del ledger. Se
conserva escrito COMO se llego ahi, porque me equivoque en el camino —
declare «conformes» siete blocks de los que solo habia medido un eje, y se
corrigio cuando el pregunto si habia terminado. Un barrido no es la rejilla.
- **Ledger a 64 ARREGLADO / 19 CONFIRMADO / 13 REFUTADO** sobre 96 filas.
- **§Qué queda reescrita como puerta de entrada**, agrupando las 19 vivas por lo
que hace falta para cerrarlas en vez de por severidad: tres bloqueadas por
decision suya (A-67 al eje de sema con la puerta ya elegida · A-47, que por
doctrina es un VALOR de configuracion y no un cambio de CSS por componente ·
A-09, cuya disposicion apagaria la deteccion live), una bloqueada por una
pieza que no existe (A-95 espera a `app-shell`, F3.1), y quince de trabajo
normal — con el aviso de que A-55 no es un defecto visible (1,6% del ancho) y
de que A-65/A-75 son el mismo patron.
- **Las dos sesiones del 16/17** con sus dos commits (`4caf1100a` el renombrado
del eje de tamaño, `d02021aee` los dos arreglos de canon), incluido el residuo
del 35,4% de A-68, que es peor que mi estimacion y queda escrito como tal.
- **Puertas al parar**, con la advertencia de siempre reforzada: la cifra de
`svelte-check` SE MUEVE con otras sesiones vivas en el arbol (72, 74, 75, 76,
77 y 80 en dias distintos), asi que se mide justo antes y justo despues del
cambio, nunca contra un numero recordado. Y los 8 rojos vivos se nombran uno a
uno como ajenos, con el commit que los introduce.
Y tres errores de METODO de estas sesiones, que es lo que un handoff existe para
que no se repita:
1. Medir un default en una demo que lo pisa no es medir un default.
2. El panel del navegador oculto suspende el IntersectionObserver, no solo el
rAF — una medicion dio «contadores congelados» que era la suspension, no el
arreglo. La via fiable es Playwright headless.
3. `Motion` es `once: true`: empujar algo bajo el pliegue DESPUES de cargar no
lo hace invisible, su observador ya disparo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## Fase 7 — contraste doctrinal de los 15 — **CERRADA 15/15**
feat(blocks): el guard vigila la frontera, y siete filas del ledger caen medidas
FASE 5 DEL SANEAMIENTO — `blocks:check` deja de vigilar solo la forma del tier
y pasa a vigilar su frontera, su documentacion y su catalogo. Tres reglas, cada
una con su fixture negativo en el selfTest():
1. Lista blanca de importaciones (B-4). La frontera dura 1 era prosa; ahora es
codigo: $uix, los arts publicos, $libs/forms, svelte y los relativos del
propio block. Los arts se DERIVAN de src/arts/*, no se listan a mano — una
lista escrita se queda atras el dia que aterriza un art. El tier ya cumplia:
cero violaciones al encenderla.
2. README completo (B-9) + declaracion de landmark (B-8). Salieron 7 de 15 sin
ella; escritas leyendo la fuente de cada block, no la plantilla.
3. Ficha en el catalogo de demos (B-9), en los DOS sentidos: un block que el
catalogo no publica es invisible para el rail, y un slug publicado sin block
detras es un enlace muerto.
De paso el escaner deja de leer los comentarios como codigo: los index.ts
documentan su uso con un `// import { Cta } from '$blocks/cta'`, y una lista
blanca que lee prosa habria empezado a acusar a los ejemplos.
Verificado EN ROJO sobre el arbol real, no solo contra los fixtures: un zod y un
$libs/days metidos en hero/types.ts salen con su linea exacta mientras el mismo
import dentro de un comentario NO salta; el catalogo falla en las dos
direcciones; y al romper isAllowedSpec a proposito el self-test aborta el guard
en vez de dar verde.
ARREGLOS DEL CONTRASTE DOCTRINAL — siete filas, cada una con medicion o gate:
- A-88 team: `height="100%"` en el Stack del Member. El div que pinta Motion es
un bloque pelado, asi que el Stack se quedaba a altura de contenido y el
`margin-top:auto` de los enlaces repartia CERO. Medido: la desviacion entre
filas de iconos pasa de 20px a 0, y el margen reparte 20,297px.
- A-94/A-28 (feature-grid, team, testimonials): los tres declaraban su tipo como
`= AutoGridProps` y ponian {...rest} ANTES de las props que fijan, asi que un
consumidor podia escribir align="start" y no obtener nada. Cerrados con
Pick<AutoGridProps, ...>, que conserva los tipos exactos del canon.
- A-84 feature-grid: el eje `align` llega a las partes por contexto, como en
team, en vez de que el typedoc instruya al app a repetirlo. Medido: con
align=center el align-items computado es center en cabecera Y celdas.
- A-19 faq: `marginX="auto"` en el Header — Box no centra solo. Medido: el Box
de 768px resuelve margin-inline 248px/248px donde antes daba 0.
- A-27 testimonials y A-86 pricing: el typedoc decia `soft` donde el codigo hace
`outline`, y el ejemplo de la puerta de entrada no compilaba.
- A-91 banner (fila nueva): el block hereda el eje intent/color que el canon
declara fuera del sistema abierto en su propio typedoc, sin registrarlo.
LEDGER — dos lecciones de instrumento escritas dentro, porque ambas produjeron
hallazgos falsos publicados:
1. El dev server sirve codigo ANTERIOR a HEAD si reusa cache de Vite. Tres filas
(A-36, A-69, A-85) se dieron por vivas estando arregladas. El delator fue
aritmetico: las duraciones medidas eran EXACTAMENTE las que el comentario del
arreglo cita como estado anterior.
2. Contar nodos WebAudio no es medir sonido: la sintesis crea dos osciladores
por earcon, un sound pack de muestras cambia la aritmetica entera, y crear
nodos no es sonar. El contador responde "hubo actividad" y nada mas.
Y el error de metodo del que ambas son sintoma, tambien registrado: se fue a
re-medir A-67 sin leer su ficha, que ya traia los gains del catalogo, el empate
de applyDominance y la regla de CANON.md incumplida — mejor razonado que la nota
que lo sustituia.
Gates: blocks:check verde (15 blocks / 115 ficheros) · svelte-check 69 errores /
57 avisos = linea base exacta, ninguno en lo tocado · docs:check 0/0 · prettier
limpio en todo lo del commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Encargo del usuario: contrastar cada block con la documentación del sistema **a
nivel de cada componente que compone y en conjunto**, siguiendo la guía de
creación de blocks y **con la composición como estrategia válida** , comprobando
las directrices de eidos, sema, morfo y soma — más **motion** , que faltaba en la
rejilla y detectó él preguntando si había leído su doctrina. No la había leído;
el primer veredicto sobre coreografía salió sin ella y hubo que retirarlo.
**Detalle completo, rejilla de siete ejes y fichas: `AUDIT-blocks-ledger.md`
§Fase 7.** Aquí sólo el estado y cómo seguir.
### ⚠️ Lo primero: el instrumento mentía
Las mediciones en navegador del 2026-08-09 se hicieron contra un dev server con
caché de Vite **anterior a arreglos ya commiteados** . Al re-medir con servidor
limpio cayeron **A-36, A-69 y A-85** . El delator fue aritmético: las duraciones
medidas (7867/3567 ms, ratio 2.21) eran EXACTAMENTE las que el comentario del
arreglo cita como estado anterior. **Servidor recién arrancado antes de medir** ,
y si un número reproduce el «antes» documentado con demasiada exactitud,
sospechar del instrumento antes que del código.
docs(process): el handoff dice donde estamos y por donde se entra
El documento seguia contando la sesion del 2026-08-10: «fase 7, 7 de 15»,
«quedan 7 en este orden», el ledger a 51/27/12 y unas puertas «SIN COMMITEAR».
Nada de eso era cierto ya, y un handoff rancio es peor que ninguno porque se
lee como si fuera el estado.
Puesto al dia:
- **Fase 7 CERRADA 15/15.** Ninguno de los 15 tiene un defecto de composicion:
lo que salio vive una capa mas abajo (canon) o en las FICHAS del ledger. Se
conserva escrito COMO se llego ahi, porque me equivoque en el camino —
declare «conformes» siete blocks de los que solo habia medido un eje, y se
corrigio cuando el pregunto si habia terminado. Un barrido no es la rejilla.
- **Ledger a 64 ARREGLADO / 19 CONFIRMADO / 13 REFUTADO** sobre 96 filas.
- **§Qué queda reescrita como puerta de entrada**, agrupando las 19 vivas por lo
que hace falta para cerrarlas en vez de por severidad: tres bloqueadas por
decision suya (A-67 al eje de sema con la puerta ya elegida · A-47, que por
doctrina es un VALOR de configuracion y no un cambio de CSS por componente ·
A-09, cuya disposicion apagaria la deteccion live), una bloqueada por una
pieza que no existe (A-95 espera a `app-shell`, F3.1), y quince de trabajo
normal — con el aviso de que A-55 no es un defecto visible (1,6% del ancho) y
de que A-65/A-75 son el mismo patron.
- **Las dos sesiones del 16/17** con sus dos commits (`4caf1100a` el renombrado
del eje de tamaño, `d02021aee` los dos arreglos de canon), incluido el residuo
del 35,4% de A-68, que es peor que mi estimacion y queda escrito como tal.
- **Puertas al parar**, con la advertencia de siempre reforzada: la cifra de
`svelte-check` SE MUEVE con otras sesiones vivas en el arbol (72, 74, 75, 76,
77 y 80 en dias distintos), asi que se mide justo antes y justo despues del
cambio, nunca contra un numero recordado. Y los 8 rojos vivos se nombran uno a
uno como ajenos, con el commit que los introduce.
Y tres errores de METODO de estas sesiones, que es lo que un handoff existe para
que no se repita:
1. Medir un default en una demo que lo pisa no es medir un default.
2. El panel del navegador oculto suspende el IntersectionObserver, no solo el
rAF — una medicion dio «contadores congelados» que era la suspension, no el
arreglo. La via fiable es Playwright headless.
3. `Motion` es `once: true`: empujar algo bajo el pliegue DESPUES de cargar no
lo hace invisible, su observador ya disparo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### Estado: los 15 con la rejilla completa
feat(blocks): el guard vigila la frontera, y siete filas del ledger caen medidas
FASE 5 DEL SANEAMIENTO — `blocks:check` deja de vigilar solo la forma del tier
y pasa a vigilar su frontera, su documentacion y su catalogo. Tres reglas, cada
una con su fixture negativo en el selfTest():
1. Lista blanca de importaciones (B-4). La frontera dura 1 era prosa; ahora es
codigo: $uix, los arts publicos, $libs/forms, svelte y los relativos del
propio block. Los arts se DERIVAN de src/arts/*, no se listan a mano — una
lista escrita se queda atras el dia que aterriza un art. El tier ya cumplia:
cero violaciones al encenderla.
2. README completo (B-9) + declaracion de landmark (B-8). Salieron 7 de 15 sin
ella; escritas leyendo la fuente de cada block, no la plantilla.
3. Ficha en el catalogo de demos (B-9), en los DOS sentidos: un block que el
catalogo no publica es invisible para el rail, y un slug publicado sin block
detras es un enlace muerto.
De paso el escaner deja de leer los comentarios como codigo: los index.ts
documentan su uso con un `// import { Cta } from '$blocks/cta'`, y una lista
blanca que lee prosa habria empezado a acusar a los ejemplos.
Verificado EN ROJO sobre el arbol real, no solo contra los fixtures: un zod y un
$libs/days metidos en hero/types.ts salen con su linea exacta mientras el mismo
import dentro de un comentario NO salta; el catalogo falla en las dos
direcciones; y al romper isAllowedSpec a proposito el self-test aborta el guard
en vez de dar verde.
ARREGLOS DEL CONTRASTE DOCTRINAL — siete filas, cada una con medicion o gate:
- A-88 team: `height="100%"` en el Stack del Member. El div que pinta Motion es
un bloque pelado, asi que el Stack se quedaba a altura de contenido y el
`margin-top:auto` de los enlaces repartia CERO. Medido: la desviacion entre
filas de iconos pasa de 20px a 0, y el margen reparte 20,297px.
- A-94/A-28 (feature-grid, team, testimonials): los tres declaraban su tipo como
`= AutoGridProps` y ponian {...rest} ANTES de las props que fijan, asi que un
consumidor podia escribir align="start" y no obtener nada. Cerrados con
Pick<AutoGridProps, ...>, que conserva los tipos exactos del canon.
- A-84 feature-grid: el eje `align` llega a las partes por contexto, como en
team, en vez de que el typedoc instruya al app a repetirlo. Medido: con
align=center el align-items computado es center en cabecera Y celdas.
- A-19 faq: `marginX="auto"` en el Header — Box no centra solo. Medido: el Box
de 768px resuelve margin-inline 248px/248px donde antes daba 0.
- A-27 testimonials y A-86 pricing: el typedoc decia `soft` donde el codigo hace
`outline`, y el ejemplo de la puerta de entrada no compilaba.
- A-91 banner (fila nueva): el block hereda el eje intent/color que el canon
declara fuera del sistema abierto en su propio typedoc, sin registrarlo.
LEDGER — dos lecciones de instrumento escritas dentro, porque ambas produjeron
hallazgos falsos publicados:
1. El dev server sirve codigo ANTERIOR a HEAD si reusa cache de Vite. Tres filas
(A-36, A-69, A-85) se dieron por vivas estando arregladas. El delator fue
aritmetico: las duraciones medidas eran EXACTAMENTE las que el comentario del
arreglo cita como estado anterior.
2. Contar nodos WebAudio no es medir sonido: la sintesis crea dos osciladores
por earcon, un sound pack de muestras cambia la aritmetica entera, y crear
nodos no es sonar. El contador responde "hubo actividad" y nada mas.
Y el error de metodo del que ambas son sintoma, tambien registrado: se fue a
re-medir A-67 sin leer su ficha, que ya traia los gains del catalogo, el empate
de applyDominance y la regla de CANON.md incumplida — mejor razonado que la nota
que lo sustituia.
Gates: blocks:check verde (15 blocks / 115 ficheros) · svelte-check 69 errores /
57 avisos = linea base exacta, ninguno en lo tocado · docs:check 0/0 · prettier
limpio en todo lo del commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
docs(process): el handoff dice donde estamos y por donde se entra
El documento seguia contando la sesion del 2026-08-10: «fase 7, 7 de 15»,
«quedan 7 en este orden», el ledger a 51/27/12 y unas puertas «SIN COMMITEAR».
Nada de eso era cierto ya, y un handoff rancio es peor que ninguno porque se
lee como si fuera el estado.
Puesto al dia:
- **Fase 7 CERRADA 15/15.** Ninguno de los 15 tiene un defecto de composicion:
lo que salio vive una capa mas abajo (canon) o en las FICHAS del ledger. Se
conserva escrito COMO se llego ahi, porque me equivoque en el camino —
declare «conformes» siete blocks de los que solo habia medido un eje, y se
corrigio cuando el pregunto si habia terminado. Un barrido no es la rejilla.
- **Ledger a 64 ARREGLADO / 19 CONFIRMADO / 13 REFUTADO** sobre 96 filas.
- **§Qué queda reescrita como puerta de entrada**, agrupando las 19 vivas por lo
que hace falta para cerrarlas en vez de por severidad: tres bloqueadas por
decision suya (A-67 al eje de sema con la puerta ya elegida · A-47, que por
doctrina es un VALOR de configuracion y no un cambio de CSS por componente ·
A-09, cuya disposicion apagaria la deteccion live), una bloqueada por una
pieza que no existe (A-95 espera a `app-shell`, F3.1), y quince de trabajo
normal — con el aviso de que A-55 no es un defecto visible (1,6% del ancho) y
de que A-65/A-75 son el mismo patron.
- **Las dos sesiones del 16/17** con sus dos commits (`4caf1100a` el renombrado
del eje de tamaño, `d02021aee` los dos arreglos de canon), incluido el residuo
del 35,4% de A-68, que es peor que mi estimacion y queda escrito como tal.
- **Puertas al parar**, con la advertencia de siempre reforzada: la cifra de
`svelte-check` SE MUEVE con otras sesiones vivas en el arbol (72, 74, 75, 76,
77 y 80 en dias distintos), asi que se mide justo antes y justo despues del
cambio, nunca contra un numero recordado. Y los 8 rojos vivos se nombran uno a
uno como ajenos, con el commit que los introduce.
Y tres errores de METODO de estas sesiones, que es lo que un handoff existe para
que no se repita:
1. Medir un default en una demo que lo pisa no es medir un default.
2. El panel del navegador oculto suspende el IntersectionObserver, no solo el
rAF — una medicion dio «contadores congelados» que era la suspension, no el
arreglo. La via fiable es Playwright headless.
3. `Motion` es `once: true`: empujar algo bajo el pliegue DESPUES de cargar no
lo hace invisible, su observador ya disparo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Ninguno de los 15 tiene un defecto de composición.** Componen canon y lo
componen bien; todo lo que salió vive una capa más abajo (canon) o en las FICHAS
del propio ledger. Ese es el resultado de la fase, y conviene no perderlo: la
auditoría original acusaba al tier.
feat(blocks): el guard vigila la frontera, y siete filas del ledger caen medidas
FASE 5 DEL SANEAMIENTO — `blocks:check` deja de vigilar solo la forma del tier
y pasa a vigilar su frontera, su documentacion y su catalogo. Tres reglas, cada
una con su fixture negativo en el selfTest():
1. Lista blanca de importaciones (B-4). La frontera dura 1 era prosa; ahora es
codigo: $uix, los arts publicos, $libs/forms, svelte y los relativos del
propio block. Los arts se DERIVAN de src/arts/*, no se listan a mano — una
lista escrita se queda atras el dia que aterriza un art. El tier ya cumplia:
cero violaciones al encenderla.
2. README completo (B-9) + declaracion de landmark (B-8). Salieron 7 de 15 sin
ella; escritas leyendo la fuente de cada block, no la plantilla.
3. Ficha en el catalogo de demos (B-9), en los DOS sentidos: un block que el
catalogo no publica es invisible para el rail, y un slug publicado sin block
detras es un enlace muerto.
De paso el escaner deja de leer los comentarios como codigo: los index.ts
documentan su uso con un `// import { Cta } from '$blocks/cta'`, y una lista
blanca que lee prosa habria empezado a acusar a los ejemplos.
Verificado EN ROJO sobre el arbol real, no solo contra los fixtures: un zod y un
$libs/days metidos en hero/types.ts salen con su linea exacta mientras el mismo
import dentro de un comentario NO salta; el catalogo falla en las dos
direcciones; y al romper isAllowedSpec a proposito el self-test aborta el guard
en vez de dar verde.
ARREGLOS DEL CONTRASTE DOCTRINAL — siete filas, cada una con medicion o gate:
- A-88 team: `height="100%"` en el Stack del Member. El div que pinta Motion es
un bloque pelado, asi que el Stack se quedaba a altura de contenido y el
`margin-top:auto` de los enlaces repartia CERO. Medido: la desviacion entre
filas de iconos pasa de 20px a 0, y el margen reparte 20,297px.
- A-94/A-28 (feature-grid, team, testimonials): los tres declaraban su tipo como
`= AutoGridProps` y ponian {...rest} ANTES de las props que fijan, asi que un
consumidor podia escribir align="start" y no obtener nada. Cerrados con
Pick<AutoGridProps, ...>, que conserva los tipos exactos del canon.
- A-84 feature-grid: el eje `align` llega a las partes por contexto, como en
team, en vez de que el typedoc instruya al app a repetirlo. Medido: con
align=center el align-items computado es center en cabecera Y celdas.
- A-19 faq: `marginX="auto"` en el Header — Box no centra solo. Medido: el Box
de 768px resuelve margin-inline 248px/248px donde antes daba 0.
- A-27 testimonials y A-86 pricing: el typedoc decia `soft` donde el codigo hace
`outline`, y el ejemplo de la puerta de entrada no compilaba.
- A-91 banner (fila nueva): el block hereda el eje intent/color que el canon
declara fuera del sistema abierto en su propio typedoc, sin registrarlo.
LEDGER — dos lecciones de instrumento escritas dentro, porque ambas produjeron
hallazgos falsos publicados:
1. El dev server sirve codigo ANTERIOR a HEAD si reusa cache de Vite. Tres filas
(A-36, A-69, A-85) se dieron por vivas estando arregladas. El delator fue
aritmetico: las duraciones medidas eran EXACTAMENTE las que el comentario del
arreglo cita como estado anterior.
2. Contar nodos WebAudio no es medir sonido: la sintesis crea dos osciladores
por earcon, un sound pack de muestras cambia la aritmetica entera, y crear
nodos no es sonar. El contador responde "hubo actividad" y nada mas.
Y el error de metodo del que ambas son sintoma, tambien registrado: se fue a
re-medir A-67 sin leer su ficha, que ya traia los gains del catalogo, el empate
de applyDominance y la regla de CANON.md incumplida — mejor razonado que la nota
que lo sustituia.
Gates: blocks:check verde (15 blocks / 115 ficheros) · svelte-check 69 errores /
57 avisos = linea base exacta, ninguno en lo tocado · docs:check 0/0 · prettier
limpio en todo lo del commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
docs(process): el handoff dice donde estamos y por donde se entra
El documento seguia contando la sesion del 2026-08-10: «fase 7, 7 de 15»,
«quedan 7 en este orden», el ledger a 51/27/12 y unas puertas «SIN COMMITEAR».
Nada de eso era cierto ya, y un handoff rancio es peor que ninguno porque se
lee como si fuera el estado.
Puesto al dia:
- **Fase 7 CERRADA 15/15.** Ninguno de los 15 tiene un defecto de composicion:
lo que salio vive una capa mas abajo (canon) o en las FICHAS del ledger. Se
conserva escrito COMO se llego ahi, porque me equivoque en el camino —
declare «conformes» siete blocks de los que solo habia medido un eje, y se
corrigio cuando el pregunto si habia terminado. Un barrido no es la rejilla.
- **Ledger a 64 ARREGLADO / 19 CONFIRMADO / 13 REFUTADO** sobre 96 filas.
- **§Qué queda reescrita como puerta de entrada**, agrupando las 19 vivas por lo
que hace falta para cerrarlas en vez de por severidad: tres bloqueadas por
decision suya (A-67 al eje de sema con la puerta ya elegida · A-47, que por
doctrina es un VALOR de configuracion y no un cambio de CSS por componente ·
A-09, cuya disposicion apagaria la deteccion live), una bloqueada por una
pieza que no existe (A-95 espera a `app-shell`, F3.1), y quince de trabajo
normal — con el aviso de que A-55 no es un defecto visible (1,6% del ancho) y
de que A-65/A-75 son el mismo patron.
- **Las dos sesiones del 16/17** con sus dos commits (`4caf1100a` el renombrado
del eje de tamaño, `d02021aee` los dos arreglos de canon), incluido el residuo
del 35,4% de A-68, que es peor que mi estimacion y queda escrito como tal.
- **Puertas al parar**, con la advertencia de siempre reforzada: la cifra de
`svelte-check` SE MUEVE con otras sesiones vivas en el arbol (72, 74, 75, 76,
77 y 80 en dias distintos), asi que se mide justo antes y justo despues del
cambio, nunca contra un numero recordado. Y los 8 rojos vivos se nombran uno a
uno como ajenos, con el commit que los introduce.
Y tres errores de METODO de estas sesiones, que es lo que un handoff existe para
que no se repita:
1. Medir un default en una demo que lo pisa no es medir un default.
2. El panel del navegador oculto suspende el IntersectionObserver, no solo el
rAF — una medicion dio «contadores congelados» que era la suspension, no el
arreglo. La via fiable es Playwright headless.
3. `Motion` es `once: true`: empujar algo bajo el pliegue DESPUES de cargar no
lo hace invisible, su observador ya disparo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
⚠️ Cómo se llegó aquí importa, porque me equivoqué en el camino: durante días
declaré «conformes» siete blocks de los que sólo había medido UN eje (dónde cae
el `{...rest}` y quién estampa `data-stagger` ). Un barrido no es la rejilla.
Se corrigió en el ledger y en este handoff cuando él preguntó «¿ya has
terminado?». Lo que el contraste produjo:
feat(blocks): el guard vigila la frontera, y siete filas del ledger caen medidas
FASE 5 DEL SANEAMIENTO — `blocks:check` deja de vigilar solo la forma del tier
y pasa a vigilar su frontera, su documentacion y su catalogo. Tres reglas, cada
una con su fixture negativo en el selfTest():
1. Lista blanca de importaciones (B-4). La frontera dura 1 era prosa; ahora es
codigo: $uix, los arts publicos, $libs/forms, svelte y los relativos del
propio block. Los arts se DERIVAN de src/arts/*, no se listan a mano — una
lista escrita se queda atras el dia que aterriza un art. El tier ya cumplia:
cero violaciones al encenderla.
2. README completo (B-9) + declaracion de landmark (B-8). Salieron 7 de 15 sin
ella; escritas leyendo la fuente de cada block, no la plantilla.
3. Ficha en el catalogo de demos (B-9), en los DOS sentidos: un block que el
catalogo no publica es invisible para el rail, y un slug publicado sin block
detras es un enlace muerto.
De paso el escaner deja de leer los comentarios como codigo: los index.ts
documentan su uso con un `// import { Cta } from '$blocks/cta'`, y una lista
blanca que lee prosa habria empezado a acusar a los ejemplos.
Verificado EN ROJO sobre el arbol real, no solo contra los fixtures: un zod y un
$libs/days metidos en hero/types.ts salen con su linea exacta mientras el mismo
import dentro de un comentario NO salta; el catalogo falla en las dos
direcciones; y al romper isAllowedSpec a proposito el self-test aborta el guard
en vez de dar verde.
ARREGLOS DEL CONTRASTE DOCTRINAL — siete filas, cada una con medicion o gate:
- A-88 team: `height="100%"` en el Stack del Member. El div que pinta Motion es
un bloque pelado, asi que el Stack se quedaba a altura de contenido y el
`margin-top:auto` de los enlaces repartia CERO. Medido: la desviacion entre
filas de iconos pasa de 20px a 0, y el margen reparte 20,297px.
- A-94/A-28 (feature-grid, team, testimonials): los tres declaraban su tipo como
`= AutoGridProps` y ponian {...rest} ANTES de las props que fijan, asi que un
consumidor podia escribir align="start" y no obtener nada. Cerrados con
Pick<AutoGridProps, ...>, que conserva los tipos exactos del canon.
- A-84 feature-grid: el eje `align` llega a las partes por contexto, como en
team, en vez de que el typedoc instruya al app a repetirlo. Medido: con
align=center el align-items computado es center en cabecera Y celdas.
- A-19 faq: `marginX="auto"` en el Header — Box no centra solo. Medido: el Box
de 768px resuelve margin-inline 248px/248px donde antes daba 0.
- A-27 testimonials y A-86 pricing: el typedoc decia `soft` donde el codigo hace
`outline`, y el ejemplo de la puerta de entrada no compilaba.
- A-91 banner (fila nueva): el block hereda el eje intent/color que el canon
declara fuera del sistema abierto en su propio typedoc, sin registrarlo.
LEDGER — dos lecciones de instrumento escritas dentro, porque ambas produjeron
hallazgos falsos publicados:
1. El dev server sirve codigo ANTERIOR a HEAD si reusa cache de Vite. Tres filas
(A-36, A-69, A-85) se dieron por vivas estando arregladas. El delator fue
aritmetico: las duraciones medidas eran EXACTAMENTE las que el comentario del
arreglo cita como estado anterior.
2. Contar nodos WebAudio no es medir sonido: la sintesis crea dos osciladores
por earcon, un sound pack de muestras cambia la aritmetica entera, y crear
nodos no es sonar. El contador responde "hubo actividad" y nada mas.
Y el error de metodo del que ambas son sintoma, tambien registrado: se fue a
re-medir A-67 sin leer su ficha, que ya traia los gains del catalogo, el empate
de applyDominance y la regla de CANON.md incumplida — mejor razonado que la nota
que lo sustituia.
Gates: blocks:check verde (15 blocks / 115 ficheros) · svelte-check 69 errores /
57 avisos = linea base exacta, ninguno en lo tocado · docs:check 0/0 · prettier
limpio en todo lo del commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Cinco disposiciones corregidas** — A-55 (mecanismo falso: el desvío lo causa
el ancho que resta el `Close` , no unos márgenes auto que miden `0px` ), A-09 (su
arreglo apagaría la detección live), A-68 (no es arreglable desde el block sin
romper B-6), A-60 (correcta, y con el camino que nombra el propio `AutoGrid` ),
A-61 (su segunda mitad ya la cerró `uix.announce` ).
- **Tres filas nuevas** — A-91 (`banner` hereda el eje `intent` /`color` sin
registrarlo), A-92 (una restricción del modelo de cascada que vive en un
comentario de `pricing` y falta en §D.13 de la doctrina de motion), A-93
(`Motion` no expone su momento «visto», que es lo que desbloquearía A-68).
⚠️ Al retomar: **la composición es estrategia válida** (norma del usuario). Un
block que sólo coloca canon no es defectuoso por serlo; sólo lo es si al componer
se salta un contrato o reinventa un primitivo. Y el eje motion se contrasta con
§D.11 (la cascada ES `[data-stagger]` + preset + regla de fundación — modelo
CERRADO, no un atajo), §D.13(1) hijos directos, §D.13(2) ENTER-ONLY, y §2
KNOWN-FRAGILE (firma y stagger sobre UN mismo nodo se pisan).
docs(process): el handoff dice donde estamos y por donde se entra
El documento seguia contando la sesion del 2026-08-10: «fase 7, 7 de 15»,
«quedan 7 en este orden», el ledger a 51/27/12 y unas puertas «SIN COMMITEAR».
Nada de eso era cierto ya, y un handoff rancio es peor que ninguno porque se
lee como si fuera el estado.
Puesto al dia:
- **Fase 7 CERRADA 15/15.** Ninguno de los 15 tiene un defecto de composicion:
lo que salio vive una capa mas abajo (canon) o en las FICHAS del ledger. Se
conserva escrito COMO se llego ahi, porque me equivoque en el camino —
declare «conformes» siete blocks de los que solo habia medido un eje, y se
corrigio cuando el pregunto si habia terminado. Un barrido no es la rejilla.
- **Ledger a 64 ARREGLADO / 19 CONFIRMADO / 13 REFUTADO** sobre 96 filas.
- **§Qué queda reescrita como puerta de entrada**, agrupando las 19 vivas por lo
que hace falta para cerrarlas en vez de por severidad: tres bloqueadas por
decision suya (A-67 al eje de sema con la puerta ya elegida · A-47, que por
doctrina es un VALOR de configuracion y no un cambio de CSS por componente ·
A-09, cuya disposicion apagaria la deteccion live), una bloqueada por una
pieza que no existe (A-95 espera a `app-shell`, F3.1), y quince de trabajo
normal — con el aviso de que A-55 no es un defecto visible (1,6% del ancho) y
de que A-65/A-75 son el mismo patron.
- **Las dos sesiones del 16/17** con sus dos commits (`4caf1100a` el renombrado
del eje de tamaño, `d02021aee` los dos arreglos de canon), incluido el residuo
del 35,4% de A-68, que es peor que mi estimacion y queda escrito como tal.
- **Puertas al parar**, con la advertencia de siempre reforzada: la cifra de
`svelte-check` SE MUEVE con otras sesiones vivas en el arbol (72, 74, 75, 76,
77 y 80 en dias distintos), asi que se mide justo antes y justo despues del
cambio, nunca contra un numero recordado. Y los 8 rojos vivos se nombran uno a
uno como ajenos, con el commit que los introduce.
Y tres errores de METODO de estas sesiones, que es lo que un handoff existe para
que no se repita:
1. Medir un default en una demo que lo pisa no es medir un default.
2. El panel del navegador oculto suspende el IntersectionObserver, no solo el
rAF — una medicion dio «contadores congelados» que era la suspension, no el
arreglo. La via fiable es Playwright headless.
3. `Motion` es `once: true`: empujar algo bajo el pliegue DESPUES de cargar no
lo hace invisible, su observador ya disparo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## Lo hecho en las sesiones del 2026-08-16/17 (commits `4caf1100a`, `d02021aee`)
### 1 · El eje de tamaño dice de QUIÉN es (`4caf1100a`)
`size` significaba dos cosas en el tier: el aire de la `Section` en trece blocks
y la altura de la tira en `banner` — misma prop, distinta escala (`BannerSize`
no tiene `xl` ). Renombrado: `size` →**`sectionSize`** (13), `container` →
**`containerSize`** (14), `minColumnWidth` →**`minChildWidth`** en `site-footer` .
`size` queda libre y sólo lo usa `banner` , donde SÍ es el tamaño del block.
**La regla vive ya en `architecture/blocks.md` §Conventions**: un block que
reenvía el eje de tamaño de una pieza interna lo nombra `{pieza}Size` , y un eje
que el canon ya nombra se reenvía CON SU NOMBRE.
Método que funcionó y conviene repetir: **renombrar los TIPOS primero** y dejar
que `svelte-check` señale cada consumidor. Cazó los cinco call sites que
quedaban. ⚠️ Y cazó un error mío: el primer patrón era demasiado ancho y renombró
`size` en siete sub-partes donde ese `size` es el del `Card` o el `Text` que
envuelven. Revertidas antes de seguir.
### 2 · Dos animadores que no se hablaban y un cluster que no envolvía (`d02021aee`)
**A-99 + A-98 — `Group` no aplicaba NINGUNO de los dos defaults que su README
promete** (`align: center`, `wrap: wrap` ): nunca se los pasaba a `Flex` , que cae
a `nowrap` +`stretch`. De ahí que las acciones de `hero` no apilaran a 375px.
Arreglado en `group.svelte` con el mecanismo que ya usaba para `direction` , y con
`attached` invirtiendo a `nowrap` (el recipe cuadra radios y solapa bordes
asumiendo UNA línea). El censo decidió la forma: ** `ButtonGroup` es el único
`Group` en columna de todo el ecosistema**, así que su `align="stretch"` vive en
su call site y no como default condicional. Medido: `hero` pasa a 2 líneas y la
acción secundaria de 124px truncados a 156,9px.
**A-68 + A-93 — los contadores corrían detrás de `opacity: 0` .** `CountUp` trae
su propio IO (threshold 0) y `Motion` usa otro (0.1 / − 10%); un IO no mira la
opacidad. `Motion` publica ya su momento visto por contexto
(`motion/context.ts`, calcado de `cascade/context.ts` ) y `stats-band-value` lo
pasa a `startWhen` . Medido en la ventana que discrimina (banda a 6px dentro del
viewport): contadores a 0/0/0 en la llegada y tras 2,6 s, donde antes acababan
en 12.121/330/47. Al revelarse, la primera cifra al **0%** — antes 97,8%. **A-69
intacta**: aterrizaje a 1781 ms, dispersión 0.
⚠️ **Residuo documentado** : las cifras que el stagger revela después llegan al
16,8% y **35,4%** . Estimé «≤10%» y me equivoqué — el muelle es sobreamortiguado
y cubre mucho recorrido al principio. Es el precio del aterrizaje conjunto que
A-69 firmó; no baja sin reabrirla.
### 3 · Tres errores de método de estas sesiones (no los repitas)
1. **Medir un default en una demo que lo pisa no es medir un default.** Escribí
en la ficha de A-99 que «el `align` sí se cumple, luego el recipe carga». El
`center` salía del control de la propia demo, que lo pasa explícito.
2. **El panel del navegador oculto suspende el IntersectionObserver** , no sólo
el rAF. Una primera medición de A-68 dio «contadores congelados» y era la
suspensión, no el arreglo: con el panel oculto el IO de `CountUp` tampoco
dispara, así que el resultado no distinguía una cosa de la otra. La vía
fiable es **Playwright headless** (como las fichas originales), resolviendo
`playwright` desde el `package.json` del repo si la sonda vive en el scratchpad.
3. ** `Motion` es `once: true` .** Empujar la banda bajo el pliegue DESPUÉS de
cargar no sirve: su observador ya disparó. El espaciador tiene que existir en
el primer pintado (`addInitScript` con una hoja de estilo).
feat(blocks): el guard vigila la frontera, y siete filas del ledger caen medidas
FASE 5 DEL SANEAMIENTO — `blocks:check` deja de vigilar solo la forma del tier
y pasa a vigilar su frontera, su documentacion y su catalogo. Tres reglas, cada
una con su fixture negativo en el selfTest():
1. Lista blanca de importaciones (B-4). La frontera dura 1 era prosa; ahora es
codigo: $uix, los arts publicos, $libs/forms, svelte y los relativos del
propio block. Los arts se DERIVAN de src/arts/*, no se listan a mano — una
lista escrita se queda atras el dia que aterriza un art. El tier ya cumplia:
cero violaciones al encenderla.
2. README completo (B-9) + declaracion de landmark (B-8). Salieron 7 de 15 sin
ella; escritas leyendo la fuente de cada block, no la plantilla.
3. Ficha en el catalogo de demos (B-9), en los DOS sentidos: un block que el
catalogo no publica es invisible para el rail, y un slug publicado sin block
detras es un enlace muerto.
De paso el escaner deja de leer los comentarios como codigo: los index.ts
documentan su uso con un `// import { Cta } from '$blocks/cta'`, y una lista
blanca que lee prosa habria empezado a acusar a los ejemplos.
Verificado EN ROJO sobre el arbol real, no solo contra los fixtures: un zod y un
$libs/days metidos en hero/types.ts salen con su linea exacta mientras el mismo
import dentro de un comentario NO salta; el catalogo falla en las dos
direcciones; y al romper isAllowedSpec a proposito el self-test aborta el guard
en vez de dar verde.
ARREGLOS DEL CONTRASTE DOCTRINAL — siete filas, cada una con medicion o gate:
- A-88 team: `height="100%"` en el Stack del Member. El div que pinta Motion es
un bloque pelado, asi que el Stack se quedaba a altura de contenido y el
`margin-top:auto` de los enlaces repartia CERO. Medido: la desviacion entre
filas de iconos pasa de 20px a 0, y el margen reparte 20,297px.
- A-94/A-28 (feature-grid, team, testimonials): los tres declaraban su tipo como
`= AutoGridProps` y ponian {...rest} ANTES de las props que fijan, asi que un
consumidor podia escribir align="start" y no obtener nada. Cerrados con
Pick<AutoGridProps, ...>, que conserva los tipos exactos del canon.
- A-84 feature-grid: el eje `align` llega a las partes por contexto, como en
team, en vez de que el typedoc instruya al app a repetirlo. Medido: con
align=center el align-items computado es center en cabecera Y celdas.
- A-19 faq: `marginX="auto"` en el Header — Box no centra solo. Medido: el Box
de 768px resuelve margin-inline 248px/248px donde antes daba 0.
- A-27 testimonials y A-86 pricing: el typedoc decia `soft` donde el codigo hace
`outline`, y el ejemplo de la puerta de entrada no compilaba.
- A-91 banner (fila nueva): el block hereda el eje intent/color que el canon
declara fuera del sistema abierto en su propio typedoc, sin registrarlo.
LEDGER — dos lecciones de instrumento escritas dentro, porque ambas produjeron
hallazgos falsos publicados:
1. El dev server sirve codigo ANTERIOR a HEAD si reusa cache de Vite. Tres filas
(A-36, A-69, A-85) se dieron por vivas estando arregladas. El delator fue
aritmetico: las duraciones medidas eran EXACTAMENTE las que el comentario del
arreglo cita como estado anterior.
2. Contar nodos WebAudio no es medir sonido: la sintesis crea dos osciladores
por earcon, un sound pack de muestras cambia la aritmetica entera, y crear
nodos no es sonar. El contador responde "hubo actividad" y nada mas.
Y el error de metodo del que ambas son sintoma, tambien registrado: se fue a
re-medir A-67 sin leer su ficha, que ya traia los gains del catalogo, el empate
de applyDominance y la regla de CANON.md incumplida — mejor razonado que la nota
que lo sustituia.
Gates: blocks:check verde (15 blocks / 115 ficheros) · svelte-check 69 errores /
57 avisos = linea base exacta, ninguno en lo tocado · docs:check 0/0 · prettier
limpio en todo lo del commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## Lo hecho en la sesión del 2026-08-09 — fase 5, el guard endurecido
`blocks:check` deja de vigilar sólo la forma del tier y pasa a vigilar su
frontera, su documentación y su catálogo. Tres reglas, cada una con fixture
negativo en el `selfTest()` :
1. **Lista blanca de importaciones (B-4)** — la frontera dura 1 era prosa; ahora
es código. Permitido: `$uix` , los arts públicos, `$libs/forms` , `svelte` /
`svelte/elements` y los relativos del propio block. **Los arts se DERIVAN de
`src/arts/*` **, no se listan a mano — una lista escrita se queda atrás el día
que aterriza un art (y este proyecto ya pagó dos veces esa lección). El tier
ya cumplía: cero violaciones al encenderla. Cierra la disposición de A-05.
2. **README completo (B-9) + declaración de landmark (B-8)** — las cuatro
secciones de la plantilla y un párrafo `**Landmark + headings**` . Al
encenderla salieron **7 de 15** sin declaración: los tres de A-14 más
`banner` , `content-section` , `cta` , `newsletter` y `site-footer` , que
nombraban «landmark» en una celda de tabla pero no declaraban nada. Escritas
leyendo la fuente de cada uno. **A-14 → ARREGLADO.**
3. **Ficha en `_lib/catalog.ts` (B-9)** — el árbol y el catálogo tienen que
coincidir en los DOS sentidos: un block que el catálogo no publica es
invisible para el raíl, y un slug publicado sin block detrás es un enlace
muerto.
De paso, **el escáner deja de leer los comentarios como código** : los `index.ts`
documentan su uso con un `// import { Cta } from '$blocks/cta'` , y una lista
blanca que lee prosa habría empezado a acusar a los ejemplos. (No era teórico:
esos 15 comentarios ya entraban en el escaneo de B-10 y sólo se salvaban porque
cada ejemplo cita su propio block.)
**Verificado en ROJO sobre el árbol real, no sólo contra los fixtures** — que es
la única prueba de que una regla nueva no es decorativa: un `zod` y un
`$libs/days` metidos en `hero/types.ts` salen con su línea exacta mientras el
mismo import dentro de un comentario NO salta; el catálogo falla en las dos
direcciones (`shipped: false` sobre un block vivo, slug fantasma); y al romper
`isAllowedSpec` a propósito el self-test aborta el guard en vez de dar verde.
## Qué queda, por orden
docs(process): el handoff dice donde estamos y por donde se entra
El documento seguia contando la sesion del 2026-08-10: «fase 7, 7 de 15»,
«quedan 7 en este orden», el ledger a 51/27/12 y unas puertas «SIN COMMITEAR».
Nada de eso era cierto ya, y un handoff rancio es peor que ninguno porque se
lee como si fuera el estado.
Puesto al dia:
- **Fase 7 CERRADA 15/15.** Ninguno de los 15 tiene un defecto de composicion:
lo que salio vive una capa mas abajo (canon) o en las FICHAS del ledger. Se
conserva escrito COMO se llego ahi, porque me equivoque en el camino —
declare «conformes» siete blocks de los que solo habia medido un eje, y se
corrigio cuando el pregunto si habia terminado. Un barrido no es la rejilla.
- **Ledger a 64 ARREGLADO / 19 CONFIRMADO / 13 REFUTADO** sobre 96 filas.
- **§Qué queda reescrita como puerta de entrada**, agrupando las 19 vivas por lo
que hace falta para cerrarlas en vez de por severidad: tres bloqueadas por
decision suya (A-67 al eje de sema con la puerta ya elegida · A-47, que por
doctrina es un VALOR de configuracion y no un cambio de CSS por componente ·
A-09, cuya disposicion apagaria la deteccion live), una bloqueada por una
pieza que no existe (A-95 espera a `app-shell`, F3.1), y quince de trabajo
normal — con el aviso de que A-55 no es un defecto visible (1,6% del ancho) y
de que A-65/A-75 son el mismo patron.
- **Las dos sesiones del 16/17** con sus dos commits (`4caf1100a` el renombrado
del eje de tamaño, `d02021aee` los dos arreglos de canon), incluido el residuo
del 35,4% de A-68, que es peor que mi estimacion y queda escrito como tal.
- **Puertas al parar**, con la advertencia de siempre reforzada: la cifra de
`svelte-check` SE MUEVE con otras sesiones vivas en el arbol (72, 74, 75, 76,
77 y 80 en dias distintos), asi que se mide justo antes y justo despues del
cambio, nunca contra un numero recordado. Y los 8 rojos vivos se nombran uno a
uno como ajenos, con el commit que los introduce.
Y tres errores de METODO de estas sesiones, que es lo que un handoff existe para
que no se repita:
1. Medir un default en una demo que lo pisa no es medir un default.
2. El panel del navegador oculto suspende el IntersectionObserver, no solo el
rAF — una medicion dio «contadores congelados» que era la suspension, no el
arreglo. La via fiable es Playwright headless.
3. `Motion` es `once: true`: empujar algo bajo el pliegue DESPUES de cargar no
lo hace invisible, su observador ya disparo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Las 19 filas CONFIRMADO vivas**, agrupadas por lo que hace falta para cerrarlas.
Cada una lleva mecanismo y evidencia en su ficha del ledger — **léela antes de
re-medirla** (perdí una sesión re-midiendo A-67 sin leer la suya, que ya tenía el
análisis completo).
### a) Bloqueadas por decisión TUYA — no las toco sin tu firma
| Fila | Qué hay que decidir |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **A-67** `faq` | Dos earcons simultáneos al cambiar de pregunta. Puerta ya elegida en la ficha: un nuance `emerge-collapse-swap` (morfo + provider + pack). **Cae en el eje de sema** , así que se ejecuta en una sesión suya, no aquí. |
| **A-47** `cta` (ALTA) | Anillo de foco. Por doctrina es **un eje de configuración** : se endurece con un VALOR en `color.focus.ring` , «never a per-component CSS change» — que es justo lo que hacía el intento revertido en `e468e764b` . |
| **A-09** `stats-band` | Su disposición APAGARÍA la detección live: el contrato exige `value` para detectarla. El defecto real es el anuncio crudo en `metrics-value.svelte:40` , que es canon. |
docs(process): el handoff dice donde estamos y por donde se entra
El documento seguia contando la sesion del 2026-08-10: «fase 7, 7 de 15»,
«quedan 7 en este orden», el ledger a 51/27/12 y unas puertas «SIN COMMITEAR».
Nada de eso era cierto ya, y un handoff rancio es peor que ninguno porque se
lee como si fuera el estado.
Puesto al dia:
- **Fase 7 CERRADA 15/15.** Ninguno de los 15 tiene un defecto de composicion:
lo que salio vive una capa mas abajo (canon) o en las FICHAS del ledger. Se
conserva escrito COMO se llego ahi, porque me equivoque en el camino —
declare «conformes» siete blocks de los que solo habia medido un eje, y se
corrigio cuando el pregunto si habia terminado. Un barrido no es la rejilla.
- **Ledger a 64 ARREGLADO / 19 CONFIRMADO / 13 REFUTADO** sobre 96 filas.
- **§Qué queda reescrita como puerta de entrada**, agrupando las 19 vivas por lo
que hace falta para cerrarlas en vez de por severidad: tres bloqueadas por
decision suya (A-67 al eje de sema con la puerta ya elegida · A-47, que por
doctrina es un VALOR de configuracion y no un cambio de CSS por componente ·
A-09, cuya disposicion apagaria la deteccion live), una bloqueada por una
pieza que no existe (A-95 espera a `app-shell`, F3.1), y quince de trabajo
normal — con el aviso de que A-55 no es un defecto visible (1,6% del ancho) y
de que A-65/A-75 son el mismo patron.
- **Las dos sesiones del 16/17** con sus dos commits (`4caf1100a` el renombrado
del eje de tamaño, `d02021aee` los dos arreglos de canon), incluido el residuo
del 35,4% de A-68, que es peor que mi estimacion y queda escrito como tal.
- **Puertas al parar**, con la advertencia de siempre reforzada: la cifra de
`svelte-check` SE MUEVE con otras sesiones vivas en el arbol (72, 74, 75, 76,
77 y 80 en dias distintos), asi que se mide justo antes y justo despues del
cambio, nunca contra un numero recordado. Y los 8 rojos vivos se nombran uno a
uno como ajenos, con el commit que los introduce.
Y tres errores de METODO de estas sesiones, que es lo que un handoff existe para
que no se repita:
1. Medir un default en una demo que lo pisa no es medir un default.
2. El panel del navegador oculto suspende el IntersectionObserver, no solo el
rAF — una medicion dio «contadores congelados» que era la suspension, no el
arreglo. La via fiable es Playwright headless.
3. `Motion` es `once: true`: empujar algo bajo el pliegue DESPUES de cargar no
lo hace invisible, su observador ya disparo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### b) ~~Bloqueada por una pieza que no existe~~ — CERRADA 2026-08-19
docs(process): el handoff dice donde estamos y por donde se entra
El documento seguia contando la sesion del 2026-08-10: «fase 7, 7 de 15»,
«quedan 7 en este orden», el ledger a 51/27/12 y unas puertas «SIN COMMITEAR».
Nada de eso era cierto ya, y un handoff rancio es peor que ninguno porque se
lee como si fuera el estado.
Puesto al dia:
- **Fase 7 CERRADA 15/15.** Ninguno de los 15 tiene un defecto de composicion:
lo que salio vive una capa mas abajo (canon) o en las FICHAS del ledger. Se
conserva escrito COMO se llego ahi, porque me equivoque en el camino —
declare «conformes» siete blocks de los que solo habia medido un eje, y se
corrigio cuando el pregunto si habia terminado. Un barrido no es la rejilla.
- **Ledger a 64 ARREGLADO / 19 CONFIRMADO / 13 REFUTADO** sobre 96 filas.
- **§Qué queda reescrita como puerta de entrada**, agrupando las 19 vivas por lo
que hace falta para cerrarlas en vez de por severidad: tres bloqueadas por
decision suya (A-67 al eje de sema con la puerta ya elegida · A-47, que por
doctrina es un VALOR de configuracion y no un cambio de CSS por componente ·
A-09, cuya disposicion apagaria la deteccion live), una bloqueada por una
pieza que no existe (A-95 espera a `app-shell`, F3.1), y quince de trabajo
normal — con el aviso de que A-55 no es un defecto visible (1,6% del ancho) y
de que A-65/A-75 son el mismo patron.
- **Las dos sesiones del 16/17** con sus dos commits (`4caf1100a` el renombrado
del eje de tamaño, `d02021aee` los dos arreglos de canon), incluido el residuo
del 35,4% de A-68, que es peor que mi estimacion y queda escrito como tal.
- **Puertas al parar**, con la advertencia de siempre reforzada: la cifra de
`svelte-check` SE MUEVE con otras sesiones vivas en el arbol (72, 74, 75, 76,
77 y 80 en dias distintos), asi que se mide justo antes y justo despues del
cambio, nunca contra un numero recordado. Y los 8 rojos vivos se nombran uno a
uno como ajenos, con el commit que los introduce.
Y tres errores de METODO de estas sesiones, que es lo que un handoff existe para
que no se repita:
1. Medir un default en una demo que lo pisa no es medir un default.
2. El panel del navegador oculto suspende el IntersectionObserver, no solo el
rAF — una medicion dio «contadores congelados» que era la suspension, no el
arreglo. La via fiable es Playwright headless.
3. `Motion` es `once: true`: empujar algo bajo el pliegue DESPUES de cargar no
lo hace invisible, su observador ya disparo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- ~~**A-95** `banner` ~~ **ARREGLADA** : la pieza era `app-shell` (F3.1) y ya
existe. Solape 0px y hit-test dentro de la cabecera, medido. Lo que sigue
siendo cierto: un `affix="top"` FUERA de un shell sigue tapando una cabecera
pegada — es el apilamiento firmado, y el README de `banner` es donde se dice.
docs(process): el handoff dice donde estamos y por donde se entra
El documento seguia contando la sesion del 2026-08-10: «fase 7, 7 de 15»,
«quedan 7 en este orden», el ledger a 51/27/12 y unas puertas «SIN COMMITEAR».
Nada de eso era cierto ya, y un handoff rancio es peor que ninguno porque se
lee como si fuera el estado.
Puesto al dia:
- **Fase 7 CERRADA 15/15.** Ninguno de los 15 tiene un defecto de composicion:
lo que salio vive una capa mas abajo (canon) o en las FICHAS del ledger. Se
conserva escrito COMO se llego ahi, porque me equivoque en el camino —
declare «conformes» siete blocks de los que solo habia medido un eje, y se
corrigio cuando el pregunto si habia terminado. Un barrido no es la rejilla.
- **Ledger a 64 ARREGLADO / 19 CONFIRMADO / 13 REFUTADO** sobre 96 filas.
- **§Qué queda reescrita como puerta de entrada**, agrupando las 19 vivas por lo
que hace falta para cerrarlas en vez de por severidad: tres bloqueadas por
decision suya (A-67 al eje de sema con la puerta ya elegida · A-47, que por
doctrina es un VALOR de configuracion y no un cambio de CSS por componente ·
A-09, cuya disposicion apagaria la deteccion live), una bloqueada por una
pieza que no existe (A-95 espera a `app-shell`, F3.1), y quince de trabajo
normal — con el aviso de que A-55 no es un defecto visible (1,6% del ancho) y
de que A-65/A-75 son el mismo patron.
- **Las dos sesiones del 16/17** con sus dos commits (`4caf1100a` el renombrado
del eje de tamaño, `d02021aee` los dos arreglos de canon), incluido el residuo
del 35,4% de A-68, que es peor que mi estimacion y queda escrito como tal.
- **Puertas al parar**, con la advertencia de siempre reforzada: la cifra de
`svelte-check` SE MUEVE con otras sesiones vivas en el arbol (72, 74, 75, 76,
77 y 80 en dias distintos), asi que se mide justo antes y justo despues del
cambio, nunca contra un numero recordado. Y los 8 rojos vivos se nombran uno a
uno como ajenos, con el commit que los introduce.
Y tres errores de METODO de estas sesiones, que es lo que un handoff existe para
que no se repita:
1. Medir un default en una demo que lo pisa no es medir un default.
2. El panel del navegador oculto suspende el IntersectionObserver, no solo el
rAF — una medicion dio «contadores congelados» que era la suspension, no el
arreglo. La via fiable es Playwright headless.
3. `Motion` es `once: true`: empujar algo bajo el pliegue DESPUES de cargar no
lo hace invisible, su observador ya disparo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
docs(blocks): el handoff de mañana, y las cinco filas que dejó componer la página
F2b queda cerrada entera, así que el handoff cambia de puerta: lo primero de
mañana es el repaso block a block que pediste, y va sin ficha escrita a
propósito — su forma se acuerda antes de empezar (qué orden, qué se mira, y si
lo que salga se escribe en el ledger o en el README de cada block).
Las cinco cosas que la página compuesta y cookie-consent encontraron entran al
ledger como A-106…A-110, medidas y sin tocar: Button acepta ref y no lo reenvía
—la forma exacta de A-94—, Switch no tiene parte de etiqueta y todos los
consumidores del repo repiten el mismo apaño, Dialog devuelve el foco a un nodo
muerto cuando su disparador se ha desmontado, Banner deja DOS landmarks banner en
cualquier página con cabecera, y feature-split es el único block de sección sin
.Header.
Casi las meto como A-100…A-104, que ya estaban ocupadas. El rango libre empezaba
en A-106 y el ledger tiene 110 filas, no 96: las cifras que el handoff repetía
—«96 filas, 64-19-13»— eran una foto vieja de julio. Las de ahora están contadas
sobre el fichero: 70 ARREGLADO · 24 CONFIRMADO · 13 REFUTADO · 3 DATO.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
### c) Las 5 que dejó la página compuesta (F2b-D, 2026-08-18)
Todas medidas, todas CONFIRMADO, ninguna tocada — salen de componer 14 blocks en
un documento y de construir `cookie-consent` :
| Fila | Qué |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **A-106** `Button` (canon) | Acepta `ref` en su tipo y NUNCA lo reenvía: `bind:ref` tipa bien y no ata nada. **Forma exacta de A-94.** |
docs(blocks): el handoff de mañana, y las cinco filas que dejó componer la página
F2b queda cerrada entera, así que el handoff cambia de puerta: lo primero de
mañana es el repaso block a block que pediste, y va sin ficha escrita a
propósito — su forma se acuerda antes de empezar (qué orden, qué se mira, y si
lo que salga se escribe en el ledger o en el README de cada block).
Las cinco cosas que la página compuesta y cookie-consent encontraron entran al
ledger como A-106…A-110, medidas y sin tocar: Button acepta ref y no lo reenvía
—la forma exacta de A-94—, Switch no tiene parte de etiqueta y todos los
consumidores del repo repiten el mismo apaño, Dialog devuelve el foco a un nodo
muerto cuando su disparador se ha desmontado, Banner deja DOS landmarks banner en
cualquier página con cabecera, y feature-split es el único block de sección sin
.Header.
Casi las meto como A-100…A-104, que ya estaban ocupadas. El rango libre empezaba
en A-106 y el ledger tiene 110 filas, no 96: las cifras que el handoff repetía
—«96 filas, 64-19-13»— eran una foto vieja de julio. Las de ahora están contadas
sobre el fichero: 70 ARREGLADO · 24 CONFIRMADO · 13 REFUTADO · 3 DATO.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
| **A-107** `Switch` (canon) | El morfo no tiene parte de etiqueta: la etiqueta visible es hermana y no es diana de clic. Todos los consumidores del repo repiten el mismo apaño. |
| **A-108** `Dialog` (canon) | Devuelve el foco al nodo que lo tenía al abrir; si el disparador se desmontó, acaba en el `body` — y la trampa sigue armada un frame más. |
| **A-109** `Banner` (canon) | DOS landmarks `banner` en toda página con cabecera; el `role` se estampa DESPUÉS de los rest props. |
| **A-110** `feature-split` | Único block de sección sin `.Header` : sus filas emiten `h2` y la sección se queda sin nombre. |
docs(blocks): el handoff de mañana, y las cinco filas que dejó componer la página
F2b queda cerrada entera, así que el handoff cambia de puerta: lo primero de
mañana es el repaso block a block que pediste, y va sin ficha escrita a
propósito — su forma se acuerda antes de empezar (qué orden, qué se mira, y si
lo que salga se escribe en el ledger o en el README de cada block).
Las cinco cosas que la página compuesta y cookie-consent encontraron entran al
ledger como A-106…A-110, medidas y sin tocar: Button acepta ref y no lo reenvía
—la forma exacta de A-94—, Switch no tiene parte de etiqueta y todos los
consumidores del repo repiten el mismo apaño, Dialog devuelve el foco a un nodo
muerto cuando su disparador se ha desmontado, Banner deja DOS landmarks banner en
cualquier página con cabecera, y feature-split es el único block de sección sin
.Header.
Casi las meto como A-100…A-104, que ya estaban ocupadas. El rango libre empezaba
en A-106 y el ledger tiene 110 filas, no 96: las cifras que el handoff repetía
—«96 filas, 64-19-13»— eran una foto vieja de julio. Las de ahora están contadas
sobre el fichero: 70 ARREGLADO · 24 CONFIRMADO · 13 REFUTADO · 3 DATO.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
### d) Las 15 restantes — trabajo normal
docs(process): el handoff dice donde estamos y por donde se entra
El documento seguia contando la sesion del 2026-08-10: «fase 7, 7 de 15»,
«quedan 7 en este orden», el ledger a 51/27/12 y unas puertas «SIN COMMITEAR».
Nada de eso era cierto ya, y un handoff rancio es peor que ninguno porque se
lee como si fuera el estado.
Puesto al dia:
- **Fase 7 CERRADA 15/15.** Ninguno de los 15 tiene un defecto de composicion:
lo que salio vive una capa mas abajo (canon) o en las FICHAS del ledger. Se
conserva escrito COMO se llego ahi, porque me equivoque en el camino —
declare «conformes» siete blocks de los que solo habia medido un eje, y se
corrigio cuando el pregunto si habia terminado. Un barrido no es la rejilla.
- **Ledger a 64 ARREGLADO / 19 CONFIRMADO / 13 REFUTADO** sobre 96 filas.
- **§Qué queda reescrita como puerta de entrada**, agrupando las 19 vivas por lo
que hace falta para cerrarlas en vez de por severidad: tres bloqueadas por
decision suya (A-67 al eje de sema con la puerta ya elegida · A-47, que por
doctrina es un VALOR de configuracion y no un cambio de CSS por componente ·
A-09, cuya disposicion apagaria la deteccion live), una bloqueada por una
pieza que no existe (A-95 espera a `app-shell`, F3.1), y quince de trabajo
normal — con el aviso de que A-55 no es un defecto visible (1,6% del ancho) y
de que A-65/A-75 son el mismo patron.
- **Las dos sesiones del 16/17** con sus dos commits (`4caf1100a` el renombrado
del eje de tamaño, `d02021aee` los dos arreglos de canon), incluido el residuo
del 35,4% de A-68, que es peor que mi estimacion y queda escrito como tal.
- **Puertas al parar**, con la advertencia de siempre reforzada: la cifra de
`svelte-check` SE MUEVE con otras sesiones vivas en el arbol (72, 74, 75, 76,
77 y 80 en dias distintos), asi que se mide justo antes y justo despues del
cambio, nunca contra un numero recordado. Y los 8 rojos vivos se nombran uno a
uno como ajenos, con el commit que los introduce.
Y tres errores de METODO de estas sesiones, que es lo que un handoff existe para
que no se repita:
1. Medir un default en una demo que lo pisa no es medir un default.
2. El panel del navegador oculto suspende el IntersectionObserver, no solo el
rAF — una medicion dio «contadores congelados» que era la suspension, no el
arreglo. La via fiable es Playwright headless.
3. `Motion` es `once: true`: empujar algo bajo el pliegue DESPUES de cargar no
lo hace invisible, su observador ya disparo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
`A-11` site-header · `A-29` pricing+testimonials · `A-48` banner · `A-53` hero ·
`A-55` banner · `A-60` site-footer · `A-61` contact · `A-62` team · `A-65`
pricing · `A-71` site-footer · `A-74` feature-split · `A-75` pricing · `A-79`
site-footer (SOSP) · `A-92` pricing · `A-96` banner.
⚠️ Dos avisos sobre este grupo: **A-55 no es un defecto visible** (20,5px = 1,6%
del ancho; él lo ve centrado y tiene razón), y **A-65/A-75 son el mismo patrón**
— re-emitir sobre un control que ya emite.
docs(blocks): el handoff de mañana, y las cinco filas que dejó componer la página
F2b queda cerrada entera, así que el handoff cambia de puerta: lo primero de
mañana es el repaso block a block que pediste, y va sin ficha escrita a
propósito — su forma se acuerda antes de empezar (qué orden, qué se mira, y si
lo que salga se escribe en el ledger o en el README de cada block).
Las cinco cosas que la página compuesta y cookie-consent encontraron entran al
ledger como A-106…A-110, medidas y sin tocar: Button acepta ref y no lo reenvía
—la forma exacta de A-94—, Switch no tiene parte de etiqueta y todos los
consumidores del repo repiten el mismo apaño, Dialog devuelve el foco a un nodo
muerto cuando su disparador se ha desmontado, Banner deja DOS landmarks banner en
cualquier página con cabecera, y feature-split es el único block de sección sin
.Header.
Casi las meto como A-100…A-104, que ya estaban ocupadas. El rango libre empezaba
en A-106 y el ledger tiene 110 filas, no 96: las cifras que el handoff repetía
—«96 filas, 64-19-13»— eran una foto vieja de julio. Las de ahora están contadas
sobre el fichero: 70 ARREGLADO · 24 CONFIRMADO · 13 REFUTADO · 3 DATO.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
### e) La ola F2b — CERRADA ENTERA 2026-08-18
docs(blocks): la variedad se firma antes de construirla — F2b en cuatro tramos
El usuario señaló que el dossier de referencias nos dejaba muy por debajo en
variedad. El contraste fila a fila de su §P1 contra los 15 blocks dio 6 filas
hechas y 4 resueltas por composición: lo que quedaba pendiente eran DECISIONES
de julio, no medidas. Ninguna de las ocho variantes era un hallazgo nuevo —
todas estaban ya en el README de su block esperando una firma.
Pero ocho variantes no cierran la brecha que se ve, que es de CARDINALIDAD, y
el método para esa ya estaba escrito en el dossier (P6.1: demostrar la
equivalencia variante-a-variante en vez de competir en número de dumps). La
primera versión de la sección no lo trajo y se reescribió entera el mismo día.
F2b queda con cuatro tramos: A las variantes de suelo, B la matriz de
equivalencia por block —el entregable central, con su plantilla y el guard
encendido al final para no dejar 18 READMEs en rojo durante la ola—, C el
catálogo que se reabre y D la página compuesta.
La regla de forma gana el término que faltaba y que más trabajo hace: una
variante alcanzable componiendo es una RECETA demostrada, cero código. Sin él
cada fila de las referencias parece pedir un layout nuevo y el tier engorda por
imitación.
Tres decisiones de catálogo: logo-cloud vuelve (se difirió sobre una premisa
que las refs desmienten, y su valor propio es la tinta monocroma derivada de
tokens, que nadie más puede dar); blog entra por la misma puerta que team;
cookie-consent entra con doctrina propia, porque la ley europea vive en el TIPO
— rechazar al mismo nivel que aceptar, no esenciales apagadas por construcción.
onboarding NO entra: es un wizard con otro nombre, y duplicar función es la
inflación que este modelo existe para evitar.
Y el cierre de F2 deja de mentir: cerrada EN UNIDADES. La página compuesta que
exigía nunca se construyó, así que el claim de que los blocks ensamblan sin
fricción se declara sin demostrar en vez de darse por probado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**La fuente es `PLAN-blocks.md` §F2b** — no dupliques aquí sus tablas. Cuatro
blocks(pricing): un plan solo no se sostenía — la fila necesitaba un tope
Segunda fila de la ola F2b, y la que existía para comprobar una sospecha. El
README llevaba desde julio diciendo que el plan único «ya lo cubre el layout»,
diferido por eso. La ficha exigía medirlo antes de darlo por gratis, y no lo
cubría.
La causa está en la lista de pistas, no en cuántos planes hay: la fila es
repeat(auto-fill, minmax(17rem, 1fr)), y auto-fill RESERVA toda pista que quepa,
la ocupe alguien o no. Medido a 1280 sobre 992px de fila, un plan solo daba una
tarjeta de 315px pegada al borde inicial con 677px de vacío al lado; fijar
columns a 1 solo cambiaba eso por una tarjeta de 992px, que es un cartel. Meterlo
en un Container sm tampoco: a 640px vuelven a caber dos pistas.
Lo que faltaba era un tope sobre la FILA, y la parte no lo dejaba pasar: sus
props están cerradas a propósito desde A-94, porque el envoltorio esparce el
resto antes de las props que fija y un width del consumidor tipaba y se
descartaba en silencio. Así que se abre nombrando: maxWidth entra en el tipo y
viaja explícito al AutoGrid, que es exactamente lo que A-94 dejó dicho — el tipo
dice lo que la parte honra. El marginX auto va con él sin condición: sin tope la
fila ya llena la medida y auto no resuelve a nada.
El número lo pone la app. Un block que horneara uno estaría decidiendo cuánto
mide un plan; la demo pasa 28rem para uno y 44rem para dos, que es lo que las
referencias le dan a cada arreglo, y gana un control vivo de recuento que también
viaja en la URL de la vista previa.
Medido después: un plan 448px centrado, dos planes 704px en dos pistas de 340
centradas, y tres planes 992px idénticos a antes — el tope es opt-in y no toca el
caso que ya existía. A 375 es inerte, porque allí una sola pista ya toma el
ancho.
Queda anotada la lección en el README y en el handoff, porque no es de este
block: una fila «diferida» puede ser una medición equivocada y no un
aplazamiento. Nadie había mirado pricing con menos de tres planes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
tramos, en orden: **A** variantes de suelo (~~V4 hero `form` ~~ ✅ · ~~V8
blocks(faq): la lista estática, y un tipo que deja de prometer lo que no da
La brecha del dossier para este block, y la mayoría del formato: seis de siete en
Tailwind Plus no son acordeón, sino una rejilla de preguntas y respuestas siempre
abiertas.
El eje vive en la LISTA, no en la raíz. Primero porque la lista es lo único que
cambia —la cabecera se lee igual—, y segundo porque esa colocación es la que
permite que el tipo diga la verdad: pasa a ser una unión discriminada. Bajo
acordeón atraviesa toda la superficie del Accordion del canon; bajo lista no se
ofrece ninguna de esas props, porque una rejilla estática no tiene estado abierto
que enlazar, nada que colapsar ni modo simple o múltiple. Ofrecer una superficie y
descartarla en silencio es justo lo que A-94 dejó dicho que no se hace, y aquí es
donde iba a morder después.
El ítem lee la disposición del contexto: no puede discriminar sobre la unión de la
lista sin obligar a la app a repetir el arreglo en cada pregunta. Su value y su
disabled quedan documentados como del acordeón, la misma convención que el fondo
del hero y la media del cta.
Y bajo lista la pregunta es un encabezado de verdad, porque ahí ES el encabezado
de su respuesta; bajo acordeón el canon la mete dentro del disparador y esa
semántica es suya, así que el block no inventa una segunda.
Medido a 1280: en acordeón, cinco disparadores, ningún encabezado, medida de
setecientos sesenta y ocho y respuestas ocultas hasta pulsar; en lista, ningún
disparador, cinco encabezados, tres pistas de trescientos nueve, medida de mil
veinticuatro y todas las respuestas visibles sin tocar nada. A 375 cae a una
columna conservando ambas cosas. El acordeón queda idéntico.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
single-price~~ ✅ · ~~V5 cta split~~ ✅ · ~~V2 testimonials spotlight~~ ✅ ·
blocks(pricing): la tabla de comparación, y las palabras que una marca no dice
La brecha recurrente del dossier en pricing, y la última fila del primer tramo de
la ola.
Es una PARTE y no un hermano porque tiene que obedecer al periodo de la sección, y
sólo una parte puede leer ese contexto: B-10 le prohíbe a un hermano importar el
block que lo posee. Vivir dentro de la sección es además lo que permite que la app
meta el precio en una celda y cambie con el conmutador, sin que esta página cablee
nada.
La app construye la tabla y el block la compone. B-5 admite datos justo donde el
canon compuesto ya es data-driven, y la tabla del canon lo es: exige la instancia
que devuelve createTable. Así que la parte no inventa un formato de matriz propio
ni traduce nada de ida y vuelta, que es el acoplamiento que esa regla existe para
evitar. Las tarjetas y la tabla conviven, como en las referencias, alimentadas por
el mismo array de planes.
Dos cosas que el plan había esbozado y que construirlas demostró equivocadas, y
quedan escritas en vez de calladas. No hay un Record de ejes visuales por plan:
acentuar una columna es componer canon, que es justo lo que ya permite el snippet
de cabecera, y ese Record habría sido una segunda fuente de lo que la app declara
en su plan. Y el block no fija la columna: el motor es del app, y meter mano ahí
sería el block decidiendo cómo se comporta la tabla de otro.
Lo que sí posee es lo que la app no puede: el nombre accesible de la tabla y las
palabras que una marca de visto o de guion no dice en voz alta. Ese glifo lleva
todo el significado de la celda y es silencio para un lector de pantalla, así que
va oculto y la frase viaja en su etiqueta, desde un mapa exhaustivo con respaldo
en inglés y su test en las dos direcciones.
Y absorbe el cast del canon siendo genérico: la tabla tipa su ranura sin
parámetro mientras el motor sí lo devuelve, así que todo consumidor castea,
empezando por la demo del propio componente. Aquí se castea una vez a la entrada y
se deshace a la salida, para que el snippet de celda devuelva la fila con la forma
del app.
Medido a 1280 y 375: ocho filas por cuatro columnas, tabla nombrada, marcas que se
anuncian, y en móvil la tabla desplaza con la columna de características quieta y
la última columna alcanzable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
~~V3 faq-lista~~ ✅ · ~~V1 `Pricing.Compare`~~ ✅ — **TRAMO A CERRADO 6/6 el
blocks(tramo B): las quince matrices, y el guard que ya las exige
El entregable central de la ola queda cerrado. Cada block declara ahora, en su
propio README, qué composición nuestra alcanza cada variante que shippean las
referencias, con la receta exacta, dónde se ha visto y en qué estado queda. Y la
regla que gobierna la tabla no admite promesas: una receta que no se ha visto en
el navegador no entra.
El saldo es lo que la ola buscaba desde que se abrió la pregunta. Unas noventa y
cinco filas cubiertas con receta y prueba. Cuatro superaciones con nombre, que
ninguna referencia puede expresar porque shippean markup estático: el plan actual
que no se puede elegir, las dos secciones que explican por qué un envío está
bloqueado, y las cifras que cuentan respetando la preferencia de movimiento. Una
veintena de gaps, cada uno con su nombre: la mitad son recetas que existen y no se
han enseñado, que es trabajo de demo, y la otra mitad candidatos de canon que ya
conocíamos. Y ocho «no se ofrece» con su motivo escrito, que es una postura del
sistema y no un agujero — un carrusel en la apertura es movimiento automático sin
control de pausa, un mapa es una dependencia externa, filtrar preguntas es estado
de datos del app.
La primera tabla justificó el tramo entero. El hero declaraba desde julio que el
mockup de móvil estaba hecho, y la demo sólo había enseñado el cromo de navegador.
El primitivo ofrece tres. Se añadió el control, se midió, y sólo entonces se
escribió la fila: una receta declarada no es una receta demostrada, y separarlas
es exactamente para lo que este tramo existía.
El guard exige ya la sección, encendido al final como mandaba el plan —hacerlo
antes habría dejado quince readmes en rojo toda la ola— y con su fixture negativo.
Cambiar la regla hizo caer tres fixtures antiguos, que es el self-test haciendo su
trabajo, y se actualizaron. Probado en rojo sobre el árbol real: quitándole la
sección a team sale su línea exacta, y restaurada vuelve a verde.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
2026-08-17; entra por el tramo B**) · **B** ✅ **CERRADO 2026-08-17** (15 matrices + guard encendido y probado en rojo) — la matriz de
docs(blocks): la variedad se firma antes de construirla — F2b en cuatro tramos
El usuario señaló que el dossier de referencias nos dejaba muy por debajo en
variedad. El contraste fila a fila de su §P1 contra los 15 blocks dio 6 filas
hechas y 4 resueltas por composición: lo que quedaba pendiente eran DECISIONES
de julio, no medidas. Ninguna de las ocho variantes era un hallazgo nuevo —
todas estaban ya en el README de su block esperando una firma.
Pero ocho variantes no cierran la brecha que se ve, que es de CARDINALIDAD, y
el método para esa ya estaba escrito en el dossier (P6.1: demostrar la
equivalencia variante-a-variante en vez de competir en número de dumps). La
primera versión de la sección no lo trajo y se reescribió entera el mismo día.
F2b queda con cuatro tramos: A las variantes de suelo, B la matriz de
equivalencia por block —el entregable central, con su plantilla y el guard
encendido al final para no dejar 18 READMEs en rojo durante la ola—, C el
catálogo que se reabre y D la página compuesta.
La regla de forma gana el término que faltaba y que más trabajo hace: una
variante alcanzable componiendo es una RECETA demostrada, cero código. Sin él
cada fila de las referencias parece pedir un layout nuevo y el tier engorda por
imitación.
Tres decisiones de catálogo: logo-cloud vuelve (se difirió sobre una premisa
que las refs desmienten, y su valor propio es la tinta monocroma derivada de
tokens, que nadie más puede dar); blog entra por la misma puerta que team;
cookie-consent entra con doctrina propia, porque la ley europea vive en el TIPO
— rechazar al mismo nivel que aceptar, no esenciales apagadas por construcción.
onboarding NO entra: es un wizard con otro nombre, y duplicar función es la
inflación que este modelo existe para evitar.
Y el cierre de F2 deja de mentir: cerrada EN UNIDADES. La página compuesta que
exigía nunca se construyó, así que el claim de que los blocks ensamblan sin
fricción se declara sin demostrar en vez de darse por probado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
equivalencia variante-a-variante en el README de cada block, con recetas
demostradas en demo (el entregable central: responde a la brecha de
blocks(cookie-consent): la ley europea como tipo, y las dos salidas al mismo peso
Cierra el tramo C de F2b. El único block cuya fase 0 fue jurídica antes que
visual: las referencias shippean el aviso como markup, y el markup no puede
impedir las tres cosas que lo vuelven ilegal — un rechazo debilitado,
categorías premarcadas, y cerrar tratado como consentir.
Aquí son el tipo y el constructor. `essential: true` hace inescribible una UI
que ofrezca apagarlo; `initialConsent()` no admite semilla, así que una primera
pregunta no puede llegar marcada; `onDecision` es obligatoria, así que un aviso
que no informa de vuelta no se puede montar. Las cinco reglas están numeradas en
consent.ts y afirmadas por 8 tests, porque son justo lo que un refactor rompe en
silencio mientras todo sigue pintando bien.
Desviación de la ficha, y a favor: UNA llamada requerida `onDecision(consent,
via)` en vez de `onAcceptAll` + `onRejectAll`. Con dos callbacks el consumidor
podía pasar un no-op a «rechazar»; aquí el camino de rechazo lo renderiza el
block y el API no expone knob para degradarlo. Medido: las dos acciones tienen
idéntico relleno, tinta, borde, tamaño, peso y alto — 5.96:1 en claro, 8.79:1 en
oscuro.
Tres cosas se arreglaron por mirar, no por deducir. La barra era un `Card`
outline y midió 1.00:1 contra la página: se disolvía en ella, así que va al
plano `overlay` del sistema, el mismo que estampa cada superficie flotante del
canon. A 375 las tres acciones envolvían con «rechazar» en una fila y «aceptar»
en la siguiente, leyéndose como dos botones ajenos justo donde la pareja más
importa: ahora son un `Group`, y el markup lo dice. Y el panel devolvía el foco
a un nodo muerto — se devuelve aquí, un frame después de que la trampa se
desarme, con un pestillo para que el asentamiento del montaje no le robe el foco
a nadie al cargar.
Tres gaps de canon salieron al componerlo, todos en el README: `Button` acepta
`ref` y no lo reenvía (forma de A-94), `Switch` no tiene parte de etiqueta, y el
`Dialog` restaura el foco a un nodo muerto cuando su disparador se desmontó.
El instrumento mintió antes que el código, otra vez: el pane del navegador no
compone frames, así que la animación de salida no termina y un diálogo cerrado
sigue pintado — el foco medido allí era ficción. Y `fillStyle` no normaliza
oklch, así que todo color leído del estilo computado se volvió negro y dio
1.11:1 sobre texto perfectamente legible. Las cifras de esta ficha se miden por
píxel, en Chrome real.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
cardinalidad sin competir en dumps) · **C** ✅ **CERRADO 2026-08-18** (~~`logo-cloud`~~ ✅ con el eje `data-ink` en la fundación · ~~`blog`~~ ✅ como ** `article-grid` ** — el nombre se decidió en su fase 0 — · ~~`cookie-consent`~~ ✅ con su fase 0 legal: las 5 reglas son el TIPO y 8 tests, y **una sola llamada requerida `onDecision(consent, via)` en vez de las dos de la ficha** — es MÁS fuerte, porque el camino de rechazo lo renderiza el block y no el consumidor, que con dos callbacks podía pasar un no-op) **C** tres blocks nuevos — `logo-cloud`
docs(blocks): la variedad se firma antes de construirla — F2b en cuatro tramos
El usuario señaló que el dossier de referencias nos dejaba muy por debajo en
variedad. El contraste fila a fila de su §P1 contra los 15 blocks dio 6 filas
hechas y 4 resueltas por composición: lo que quedaba pendiente eran DECISIONES
de julio, no medidas. Ninguna de las ocho variantes era un hallazgo nuevo —
todas estaban ya en el README de su block esperando una firma.
Pero ocho variantes no cierran la brecha que se ve, que es de CARDINALIDAD, y
el método para esa ya estaba escrito en el dossier (P6.1: demostrar la
equivalencia variante-a-variante en vez de competir en número de dumps). La
primera versión de la sección no lo trajo y se reescribió entera el mismo día.
F2b queda con cuatro tramos: A las variantes de suelo, B la matriz de
equivalencia por block —el entregable central, con su plantilla y el guard
encendido al final para no dejar 18 READMEs en rojo durante la ola—, C el
catálogo que se reabre y D la página compuesta.
La regla de forma gana el término que faltaba y que más trabajo hace: una
variante alcanzable componiendo es una RECETA demostrada, cero código. Sin él
cada fila de las referencias parece pedir un layout nuevo y el tier engorda por
imitación.
Tres decisiones de catálogo: logo-cloud vuelve (se difirió sobre una premisa
que las refs desmienten, y su valor propio es la tinta monocroma derivada de
tokens, que nadie más puede dar); blog entra por la misma puerta que team;
cookie-consent entra con doctrina propia, porque la ley europea vive en el TIPO
— rechazar al mismo nivel que aceptar, no esenciales apagadas por construcción.
onboarding NO entra: es un wizard con otro nombre, y duplicar función es la
inflación que este modelo existe para evitar.
Y el cierre de F2 deja de mentir: cerrada EN UNIDADES. La página compuesta que
exigía nunca se construyó, así que el claim de que los blocks ensamblan sin
fricción se declara sin demostrar en vez de darse por probado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
(reabierto: las refs lo shippean estático), `blog` , `cookie-consent` (fase 0
legal profunda; la ley vive en el TIPO) — y `onboarding` NO entra (receta de
blocks(landing): las piezas se prueban juntas, y ahí sale lo que ninguna enseñaba sola
Cierra el tramo D y con él F2b entera. Una landing verosímil con 14 de los 18
blocks, a sangre y sin marco de demo, porque el claim del tier —que sus blocks
ensamblan sin fricción— es indemostrable block a block: el outline de
encabezados sólo existe cruzando secciones, dos landmarks sólo chocan cuando hay
dos, y una costura vertical sólo aparece cuando algo se pone debajo.
Lo que dice la medida: un h1 y 22 encabezados sin un solo salto de nivel; ningún
landmark repetido sin nombre; 0px de hueco entre secciones vecinas —el aire es
siempre el padding de cada Section, costuras de 112 a 192px—; cuatro de las cinco
cascadas esperando a que su sección entre en viewport; un solo commit-submit en
el formulario; setenta tabulaciones sin una trampa de foco y un drawer que a
375px devuelve el foco a su disparador.
A-95 vuelve confirmado, ahora con cifras: con affix="top" la tira y la cabecera
solapan 49px —la altura entera de la cabecera— y el hit-test en su centro cae en
la tira, así que la navegación es inalcanzable. La pieza que falta sigue siendo
app-shell. La página compone la tira en flujo, que es lo correcto, y deja el
escenario del defecto detrás de ?banner=affix para poder medirlo en vez de
citarlo.
Un defecto arreglado, y era de los que importan: Newsletter.Reason medía 2,33:1
sobre el panel mientras la nota a su lado medía 8,5:1. Es la frase que explica
por qué la acción está bloqueada —la que la doctrina de coordinación obliga a
tener— y no se podía leer: la doctrina cumplida en la estructura y derrotada en
la percepción. El block ya tenía el mecanismo y ya lo había escrito para su nota
(«más callado por TAMAÑO, no por tinta»); su razón no lo seguía. Ahora 8,83:1
sobre el panel, y muted fuera de él.
Dos gaps nuevos, ninguno tocado aquí: los DOS landmarks banner —el canon estampa
role="banner" DESPUÉS de sus rest props, así que nombrar ambos es todo lo que
puede hacer la app, y esta página lo hace— y feature-split sin parte .Header, el
único block de sección que no la tiene: sin ella emite tres h2 hermanos y la
sección se queda sin nombre.
El catálogo aprendió a publicar páginas: kind: 'page' en BlockEntry y el guard lo
salta en las dos direcciones, con self-test propio probado por mutación.
Y una lección de instrumento que costó un susto: una captura fullPage no hace
scroll, así que toda cascada bajo el pliegue se queda en opacidad 0 y la imagen
sale EN BLANCO. La primera medida dijo «la landing está vacía». Saltar de una vez
al final del documento tampoco vale: las secciones pasan de debajo a encima en un
frame y el observador nunca las ve entrar. Se recorre en pasos.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
`wizard` ) · **D** ✅ **CERRADO 2026-08-18** — `web/routes/blocks/landing/` , 14 de 18 blocks a sangre, medidas en su propio README. **F2b QUEDA CERRADA ENTERA.** Lo que dejó: A-95 re-confirmado con cifras (solape 49px = la cabecera entera; falta `app-shell` F3.1) · `Newsletter.Reason` arreglado (2,33 → 8,83:1 sobre el panel: la frase del bloqueo no se leía) · dos gaps nuevos (los DOS landmarks `banner` del canon · `feature-split` sin `.Header` , el único block de sección sin ella) · el catálogo aprendió `kind: 'page'` con guard probado por mutación. ⚠️ **medir una página con motion de viewport** : `fullPage` no hace scroll y sale EN BLANCO, y saltar al final tampoco dispara el observador — se recorre en pasos.
docs(blocks): la variedad se firma antes de construirla — F2b en cuatro tramos
El usuario señaló que el dossier de referencias nos dejaba muy por debajo en
variedad. El contraste fila a fila de su §P1 contra los 15 blocks dio 6 filas
hechas y 4 resueltas por composición: lo que quedaba pendiente eran DECISIONES
de julio, no medidas. Ninguna de las ocho variantes era un hallazgo nuevo —
todas estaban ya en el README de su block esperando una firma.
Pero ocho variantes no cierran la brecha que se ve, que es de CARDINALIDAD, y
el método para esa ya estaba escrito en el dossier (P6.1: demostrar la
equivalencia variante-a-variante en vez de competir en número de dumps). La
primera versión de la sección no lo trajo y se reescribió entera el mismo día.
F2b queda con cuatro tramos: A las variantes de suelo, B la matriz de
equivalencia por block —el entregable central, con su plantilla y el guard
encendido al final para no dejar 18 READMEs en rojo durante la ola—, C el
catálogo que se reabre y D la página compuesta.
La regla de forma gana el término que faltaba y que más trabajo hace: una
variante alcanzable componiendo es una RECETA demostrada, cero código. Sin él
cada fila de las referencias parece pedir un layout nuevo y el tier engorda por
imitación.
Tres decisiones de catálogo: logo-cloud vuelve (se difirió sobre una premisa
que las refs desmienten, y su valor propio es la tinta monocroma derivada de
tokens, que nadie más puede dar); blog entra por la misma puerta que team;
cookie-consent entra con doctrina propia, porque la ley europea vive en el TIPO
— rechazar al mismo nivel que aceptar, no esenciales apagadas por construcción.
onboarding NO entra: es un wizard con otro nombre, y duplicar función es la
inflación que este modelo existe para evitar.
Y el cierre de F2 deja de mentir: cerrada EN UNIDADES. La página compuesta que
exigía nunca se construyó, así que el claim de que los blocks ensamblan sin
fricción se declara sin demostrar en vez de darse por probado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Las 4 filas que la sección «Deuda a decisión tuya» de este handoff listaba
llevan ahí su firma (V1 parte, no hermano · V2/V3 `layout` · V4 slot). V6/V7
(stats-band split-with-image y timeline) salieron a F5: el dossier declara ese
block «Paridad OK».
⚠️ **Lo que dejó V4 y sirve para las que vienen** :
- **Dos gaps de canon** (README del hero §«Found while composing»):
`Form.Submit` no reenvía al `Button` ni `intent` ni eje de anchura — el botón
no llena su celda cuando la fila colapsa, y `newsletter` mide lo mismo, así
que es del canon, no del block. El `justify` de `Grid` es `justify-content` ,
no `justify-items` : no se arregla desde fuera. La demo usa `style` , que sí se
reenvía.
- **El contexto de inversión mordió por segunda vez**: bajo `background` , un
`Text` sin `color` explícito mide **1.62:1** ; con `on-solid` , 10:1. Bajo
`background` la tinta se pone explícita SIEMPRE.
- **La sonda de contraste**: mide sobre PÍXELES PINTADOS, nunca parseando
colores. Dos sondas dieron cifras seguras y falsas antes (un parser rgb sobre
un sistema que emite `oklch` , y `canvas.fillStyle` , que tampoco normaliza
oklch aquí → medía NEGRO contra todo: 20.46:1 en claro y 1.11:1 en oscuro,
ficción las dos). Control de sanidad: la etiqueta del submit tiene que salir
a **5.18:1** , la cifra que el repo ya documenta para `primary` . Vive en
`scratchpad/v4-final.mjs` .
- **El preview cambia de tema por `?mode=dark` **, no por `prefers-color-scheme`
ni tocando `data-theme` a mano (el boot es el dueño; forzar el atributo NO
repinta y da dos medidas idénticas que parecen «no hay diferencia»).
blocks(pricing): un plan solo no se sostenía — la fila necesitaba un tope
Segunda fila de la ola F2b, y la que existía para comprobar una sospecha. El
README llevaba desde julio diciendo que el plan único «ya lo cubre el layout»,
diferido por eso. La ficha exigía medirlo antes de darlo por gratis, y no lo
cubría.
La causa está en la lista de pistas, no en cuántos planes hay: la fila es
repeat(auto-fill, minmax(17rem, 1fr)), y auto-fill RESERVA toda pista que quepa,
la ocupe alguien o no. Medido a 1280 sobre 992px de fila, un plan solo daba una
tarjeta de 315px pegada al borde inicial con 677px de vacío al lado; fijar
columns a 1 solo cambiaba eso por una tarjeta de 992px, que es un cartel. Meterlo
en un Container sm tampoco: a 640px vuelven a caber dos pistas.
Lo que faltaba era un tope sobre la FILA, y la parte no lo dejaba pasar: sus
props están cerradas a propósito desde A-94, porque el envoltorio esparce el
resto antes de las props que fija y un width del consumidor tipaba y se
descartaba en silencio. Así que se abre nombrando: maxWidth entra en el tipo y
viaja explícito al AutoGrid, que es exactamente lo que A-94 dejó dicho — el tipo
dice lo que la parte honra. El marginX auto va con él sin condición: sin tope la
fila ya llena la medida y auto no resuelve a nada.
El número lo pone la app. Un block que horneara uno estaría decidiendo cuánto
mide un plan; la demo pasa 28rem para uno y 44rem para dos, que es lo que las
referencias le dan a cada arreglo, y gana un control vivo de recuento que también
viaja en la URL de la vista previa.
Medido después: un plan 448px centrado, dos planes 704px en dos pistas de 340
centradas, y tres planes 992px idénticos a antes — el tope es opt-in y no toca el
caso que ya existía. A 375 es inerte, porque allí una sola pista ya toma el
ancho.
Queda anotada la lección en el README y en el handoff, porque no es de este
block: una fila «diferida» puede ser una medición equivocada y no un
aplazamiento. Nadie había mirado pricing con menos de tres planes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
⚠️ **Y lo que dejó V8, que vale para TODA la ola** : **una fila «diferida» puede
ser una medición equivocada, no un aplazamiento.** El README de `pricing` decía
desde julio que el plan único «ya lo cubre el layout» y era falso — la rejilla
es `auto-fill` y RESERVA las pistas que caben aunque nadie las ocupe (un plan
solo: card de 315px con 677px de vacío al lado). Nadie había mirado ese block
con menos de tres planes. Antes de dar por buena una disposición vieja de
cualquier README, mídela.
blocks(testimonials): una cita sola no va en una tarjeta
La brecha que el dossier le apuntaba a este block: la cita única es la variante
modal de todas las referencias y el grid la minoría, dos de ocho en Tailwind Plus.
Entra como disposición, no como hermano. La anatomía no cambia —cita, autor,
avatar—, sólo cómo se colocan, y el precedente es el fondo del hero, que también
redefine lo que hacen sus partes. El hermano se reabre sólo si algún día pide
rotación o carrusel, que es otro componente.
Viaja por contexto de configuración, el patrón del align de feature-grid y team,
no la coordinación de pricing. Y viaja porque el spotlight no es una piel sobre
las mismas cajas: la rejilla deja de serlo y de estampar el ritmo estructural —que
es para una FILA, no para una cita—, el ítem pierde la tarjeta, la cita sube de
tamaño y se centra, y el autor pasa debajo, donde ya no hay una hilera de caras
que alinear. Pedirle a la app que repita esa decisión en cinco partes es el
agujero que A-84 cerró en feature-grid.
Enmarcar una cita sola la hace parecer un ítem de una lista que no está ahí, así
que ahí no hay tarjeta y el tipo lo dice, en vez de aceptar una superficie que
luego descarta. La cita se centra con un párrafo de verdad: sobre una caja en
línea el centrado no aplica, y sería mentira.
Slot nuevo para la marca de quien firma, en las dos disposiciones: es lo que hace
que una cita se lea como evidencia y no como una opinión, y es la pieza que ponen
todas las referencias.
Medido: en grid, cinco tarjetas, veinte píxeles, medida de mil veinticuatro y
ritmo escalonado; en spotlight, cero tarjetas, una cita de veintiocho centrada en
párrafo, medida de setecientos sesenta y ocho, autor centrado y sin escalonado; a
375 la cita cae a veinticuatro. El grid queda idéntico a antes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
⚠️ **Y dos cosas del árbol COMPARTIDO, medidas el 2026-08-17** : otra sesión está
a medias con un componente ** `background` ** (toca `morfo/` , `langs/` y
`eidos/generated` ), así que `svelte-check` da 73 con cosas que no son del tier —
la base real sin nadie es **60** . Y **no hagas `git stash --include-untracked`
aquí**: arrastra su trabajo sin commitear. Si necesitas una base, mide por
fichero.
docs(blocks): la variedad se firma antes de construirla — F2b en cuatro tramos
El usuario señaló que el dossier de referencias nos dejaba muy por debajo en
variedad. El contraste fila a fila de su §P1 contra los 15 blocks dio 6 filas
hechas y 4 resueltas por composición: lo que quedaba pendiente eran DECISIONES
de julio, no medidas. Ninguna de las ocho variantes era un hallazgo nuevo —
todas estaban ya en el README de su block esperando una firma.
Pero ocho variantes no cierran la brecha que se ve, que es de CARDINALIDAD, y
el método para esa ya estaba escrito en el dossier (P6.1: demostrar la
equivalencia variante-a-variante en vez de competir en número de dumps). La
primera versión de la sección no lo trajo y se reescribió entera el mismo día.
F2b queda con cuatro tramos: A las variantes de suelo, B la matriz de
equivalencia por block —el entregable central, con su plantilla y el guard
encendido al final para no dejar 18 READMEs en rojo durante la ola—, C el
catálogo que se reabre y D la página compuesta.
La regla de forma gana el término que faltaba y que más trabajo hace: una
variante alcanzable componiendo es una RECETA demostrada, cero código. Sin él
cada fila de las referencias parece pedir un layout nuevo y el tier engorda por
imitación.
Tres decisiones de catálogo: logo-cloud vuelve (se difirió sobre una premisa
que las refs desmienten, y su valor propio es la tinta monocroma derivada de
tokens, que nadie más puede dar); blog entra por la misma puerta que team;
cookie-consent entra con doctrina propia, porque la ley europea vive en el TIPO
— rechazar al mismo nivel que aceptar, no esenciales apagadas por construcción.
onboarding NO entra: es un wizard con otro nombre, y duplicar función es la
inflación que este modelo existe para evitar.
Y el cierre de F2 deja de mentir: cerrada EN UNIDADES. La página compuesta que
exigía nunca se construyó, así que el claim de que los blocks ensamblan sin
fricción se declara sin demostrar en vez de darse por probado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### e) Cabos y fases
docs(process): el handoff dice donde estamos y por donde se entra
El documento seguia contando la sesion del 2026-08-10: «fase 7, 7 de 15»,
«quedan 7 en este orden», el ledger a 51/27/12 y unas puertas «SIN COMMITEAR».
Nada de eso era cierto ya, y un handoff rancio es peor que ninguno porque se
lee como si fuera el estado.
Puesto al dia:
- **Fase 7 CERRADA 15/15.** Ninguno de los 15 tiene un defecto de composicion:
lo que salio vive una capa mas abajo (canon) o en las FICHAS del ledger. Se
conserva escrito COMO se llego ahi, porque me equivoque en el camino —
declare «conformes» siete blocks de los que solo habia medido un eje, y se
corrigio cuando el pregunto si habia terminado. Un barrido no es la rejilla.
- **Ledger a 64 ARREGLADO / 19 CONFIRMADO / 13 REFUTADO** sobre 96 filas.
- **§Qué queda reescrita como puerta de entrada**, agrupando las 19 vivas por lo
que hace falta para cerrarlas en vez de por severidad: tres bloqueadas por
decision suya (A-67 al eje de sema con la puerta ya elegida · A-47, que por
doctrina es un VALOR de configuracion y no un cambio de CSS por componente ·
A-09, cuya disposicion apagaria la deteccion live), una bloqueada por una
pieza que no existe (A-95 espera a `app-shell`, F3.1), y quince de trabajo
normal — con el aviso de que A-55 no es un defecto visible (1,6% del ancho) y
de que A-65/A-75 son el mismo patron.
- **Las dos sesiones del 16/17** con sus dos commits (`4caf1100a` el renombrado
del eje de tamaño, `d02021aee` los dos arreglos de canon), incluido el residuo
del 35,4% de A-68, que es peor que mi estimacion y queda escrito como tal.
- **Puertas al parar**, con la advertencia de siempre reforzada: la cifra de
`svelte-check` SE MUEVE con otras sesiones vivas en el arbol (72, 74, 75, 76,
77 y 80 en dias distintos), asi que se mide justo antes y justo despues del
cambio, nunca contra un numero recordado. Y los 8 rojos vivos se nombran uno a
uno como ajenos, con el commit que los introduce.
Y tres errores de METODO de estas sesiones, que es lo que un handoff existe para
que no se repita:
1. Medir un default en una demo que lo pisa no es medir un default.
2. El panel del navegador oculto suspende el IntersectionObserver, no solo el
rAF — una medicion dio «contadores congelados» que era la suspension, no el
arreglo. La via fiable es Playwright headless.
3. `Motion` es `once: true`: empujar algo bajo el pliegue DESPUES de cargar no
lo hace invisible, su observador ya disparo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
1. **Tres cabos concretos** :
- Un **desbordamiento de texto en las cards del card-group** que el usuario ve
y que NO se reprodujo a 1280 en claro ni en oscuro. Falta el ancho de ventana.
- Los **hermanos del `Field`** (`password-field`, `search-field` , `textarea` )
declaran su propio `commit-submit` copiando el patrón: pueden duplicar igual
dentro de un `Form` . Se comprueba en un comando con la sonda (abajo).
docs(blocks): la variedad se firma antes de construirla — F2b en cuatro tramos
El usuario señaló que el dossier de referencias nos dejaba muy por debajo en
variedad. El contraste fila a fila de su §P1 contra los 15 blocks dio 6 filas
hechas y 4 resueltas por composición: lo que quedaba pendiente eran DECISIONES
de julio, no medidas. Ninguna de las ocho variantes era un hallazgo nuevo —
todas estaban ya en el README de su block esperando una firma.
Pero ocho variantes no cierran la brecha que se ve, que es de CARDINALIDAD, y
el método para esa ya estaba escrito en el dossier (P6.1: demostrar la
equivalencia variante-a-variante en vez de competir en número de dumps). La
primera versión de la sección no lo trajo y se reescribió entera el mismo día.
F2b queda con cuatro tramos: A las variantes de suelo, B la matriz de
equivalencia por block —el entregable central, con su plantilla y el guard
encendido al final para no dejar 18 READMEs en rojo durante la ola—, C el
catálogo que se reabre y D la página compuesta.
La regla de forma gana el término que faltaba y que más trabajo hace: una
variante alcanzable componiendo es una RECETA demostrada, cero código. Sin él
cada fila de las referencias parece pedir un layout nuevo y el tier engorda por
imitación.
Tres decisiones de catálogo: logo-cloud vuelve (se difirió sobre una premisa
que las refs desmienten, y su valor propio es la tinta monocroma derivada de
tokens, que nadie más puede dar); blog entra por la misma puerta que team;
cookie-consent entra con doctrina propia, porque la ley europea vive en el TIPO
— rechazar al mismo nivel que aceptar, no esenciales apagadas por construcción.
onboarding NO entra: es un wizard con otro nombre, y duplicar función es la
inflación que este modelo existe para evitar.
Y el cierre de F2 deja de mentir: cerrada EN UNIDADES. La página compuesta que
exigía nunca se construyó, así que el claim de que los blocks ensamblan sin
fricción se declara sin demostrar en vez de darse por probado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **La página compuesta** dejó de ser un cabo suelto: es el tramo D de F2b.
2. **F3 del plan original** (10 blocks de aplicación), ya con orden recomendado
en el plan: ** `app-shell` PRIMERO** (desbloquea A-95 y F4.1); `data-table` y
`kanban` al final por su fase 0 obligada.
## ⚠️ El sonido CAMBIÓ DE MODELO el mismo día — léelo antes de tocar un pack
Mientras esta sesión trabajaba, la de sema rehízo el sonido dos veces. Modelo
vigente (`CONTINUE-sema-audit.md` §3.0-bis):
```
nombre = per-emit ?? cascada ?? pack ?? morfo ?? familia[verbo] ?? familia.default
sonido = pack[`${nombre}.${intent}`] ?? pack[nombre] ?? nada
```
- **`soundTuning()`, `SOUND_TUNINGS` y `sound()` están RETIRADOS**, con guard
propio (`sounds-grammar.test.ts`). El sonido ya no se modula: **se elige** . Un
intent selecciona OTRO sonido entero, no doblega el mismo.
- **El sonido lo decide el mapa, por VERBO** (`SEMA_MAP.families[f].sounds`), así
que **un pack escribe una regla sólo cuando DIFIERE del default** : de ~165
reglas a 30 en 71 packs. Un pack migrado es casi sólo selectores.
- **`allowedTargets`** en el morfo declara a qué partes puede viajar el estampado.
- Guard de **distinguibilidad** : dos nombres han de diferir en ≥2 ejes.
**Qué significa para la cola de este handoff**: nada se invalida — lo pendiente es
QUÉ evento se emite y SOBRE QUÉ parte, no cómo suena. Lo que cambia es el método:
si al arreglar un confirmado hiciera falta tocar el sonido, ya no se afina, se
NOMBRA; y si el nombre no existe, se registra en el catálogo antes de usarlo.
El pack de `navigation-menu` de esta sesión ilustra el encaje: la otra sesión lo
migró encima y **las tres reglas sobrevivieron sin su `sound:`** — la aportación
era a qué parte apunta cada una, y eso es ortogonal al modelo de sonido.
## La sonda de sema — `G:/tmp/sonda-sema.mjs`
Herramienta nueva, y la forma de contestar «¿esto suena?» sin discutirlo:
```bash
node G:/tmp/sonda-sema.mjs < url > < enter | click | hover > [selector]
```
Dice, por gesto, cuántos nodos de audio se crean y qué eventos se estampan con su
familia e intent. Con ella se midió el doble `commit-submit` del `Field` (2
eventos a 11 ms y 4 nodos de audio → 1 y 2) y el mudo del `NavigationMenu.Link` .
⚠️ Sesgo conocido: asocia los metadatos por ventana temporal de 30 ms, así que
puede atribuir el `intent` de un evento vecino. El `contact-activate` NO lleva
intent (morfo de Button, cap. 22 §11).
feat(blocks): `contact` — el primer block que COORDINA (estado, datos y palabras)
WIP declarado: el block funciona y renderiza, pero el arco interactivo completo
no está verificado todavía. Lo que falta está en `CONTINUE-blocks.md`, en orden.
## El cambio de doctrina
Decisión del usuario, textual: «si al final es una lista de componentes… el bloque
es coordinación, estado, y data también». El motivo se veía en este mismo block:
con el estado fuera, cada `disabled` era una expresión distinta montada en el punto
de uso, y aparecían huecos — un envío que no se podía hacer y nadie decía por qué.
Eso cambia lo firmado en `docs/architecture/blocks.md` (B-5/B-7, D-BLK). Queda
ANOTADO como pendiente de escribir en el contrato: propagarlo sin actualizarlo
sería deriva.
## Lo que ahora posee el block
- **La máquina de estado** (`state.ts`): `incomplete · unverified · verifying ·
rejected · ready · sending · sent`, derivada de la validez del `Form`, del
veredicto de verificación y del envío. Con dos registros EXHAUSTIVOS —
`CONTACT_REASON` y `CONTACT_ACTION`— que hacen imposible un estado mudo: no se
puede añadir un estado que bloquee el envío sin darle su frase, porque el
`Record` obliga.
- **La forma de los datos** (`schema.ts`): nombre · correo · mensaje, con las
etiquetas como idlangref. El app lo sustituye pasando su `form` + `schema`.
- **Las palabras**, por el traductor (`#?blocks.contact.…|fallback`), la misma
puerta que usa el canon para sus `texts:`.
Partes nuevas: `Contact.Fields` (los tres campos del esquema propio),
`Contact.Submit` (bloqueo Y etiqueta desde el estado, nunca desde una expresión en
el punto de uso) y `Contact.Reason` (la frase del estado). `Contact.Form` toma el
handle del contexto en vez de pedirlo otra vez.
La demo adelgaza en consecuencia: se le cayeron el esquema, las etiquetas, el
`disabled`, el flag de envío y la frase. Le queda lo que es de un app — las
palabras de la página, los datos de contacto, el reto que elige y qué significa
«enviar».
## De paso, dos arreglos de composición
- El `TextArea` iba dentro de `Field.Control` y salían DOS marcos: pinta su propia
carcasa (borde, radio y relleno son de su recipe). `Field.Control` es la carcasa
del `Field.Input` desnudo.
- El `Form.ErrorSummary` vacío dejaba una caja naranja: su raíz pinta borde de
riesgo en cuanto el form es inválido, aunque la lista esté vacía.
## Dos hilos de canon abiertos (medidos, sin causa raíz)
- Con `progressive`/`onSubmit`, un envío inválido bloquea y mueve el foco pero no
expone mensajes; con `onChange` sí, pero saltan los tres campos al escribir en
uno. La máquina tapa el agujero de cara al usuario; el hueco sigue.
- El provider de `ProofOfHuman` escribe `status` él mismo, así que el veredicto que
devuelve el app no se sostiene y los slots `stage*` no renderizaron nunca.
Gates: `blocks:check` verde (14 blocks) · `svelte-check` sin errores propios ·
prettier limpio. Verificado que la sección renderiza (section · form · 3 campos ·
submit · cero errores de página) en un dev server limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
⚠️⚠️ **Y su límite, que el handoff de sema documenta como lección cara** : contar
nodos de audio **no es medir la salida** . Sirve para «¿emite o no emite?», que es
para lo que se usó aquí. Para «¿suena como debe?» hay que renderizar el grafo
offline (`OfflineAudioContext`) y medir el pico de muestra — ahí se descubrió que
una firma «silenciada» salía a − 13.9 dBFS.
feat(blocks): `contact` — el primer block que COORDINA (estado, datos y palabras)
WIP declarado: el block funciona y renderiza, pero el arco interactivo completo
no está verificado todavía. Lo que falta está en `CONTINUE-blocks.md`, en orden.
## El cambio de doctrina
Decisión del usuario, textual: «si al final es una lista de componentes… el bloque
es coordinación, estado, y data también». El motivo se veía en este mismo block:
con el estado fuera, cada `disabled` era una expresión distinta montada en el punto
de uso, y aparecían huecos — un envío que no se podía hacer y nadie decía por qué.
Eso cambia lo firmado en `docs/architecture/blocks.md` (B-5/B-7, D-BLK). Queda
ANOTADO como pendiente de escribir en el contrato: propagarlo sin actualizarlo
sería deriva.
## Lo que ahora posee el block
- **La máquina de estado** (`state.ts`): `incomplete · unverified · verifying ·
rejected · ready · sending · sent`, derivada de la validez del `Form`, del
veredicto de verificación y del envío. Con dos registros EXHAUSTIVOS —
`CONTACT_REASON` y `CONTACT_ACTION`— que hacen imposible un estado mudo: no se
puede añadir un estado que bloquee el envío sin darle su frase, porque el
`Record` obliga.
- **La forma de los datos** (`schema.ts`): nombre · correo · mensaje, con las
etiquetas como idlangref. El app lo sustituye pasando su `form` + `schema`.
- **Las palabras**, por el traductor (`#?blocks.contact.…|fallback`), la misma
puerta que usa el canon para sus `texts:`.
Partes nuevas: `Contact.Fields` (los tres campos del esquema propio),
`Contact.Submit` (bloqueo Y etiqueta desde el estado, nunca desde una expresión en
el punto de uso) y `Contact.Reason` (la frase del estado). `Contact.Form` toma el
handle del contexto en vez de pedirlo otra vez.
La demo adelgaza en consecuencia: se le cayeron el esquema, las etiquetas, el
`disabled`, el flag de envío y la frase. Le queda lo que es de un app — las
palabras de la página, los datos de contacto, el reto que elige y qué significa
«enviar».
## De paso, dos arreglos de composición
- El `TextArea` iba dentro de `Field.Control` y salían DOS marcos: pinta su propia
carcasa (borde, radio y relleno son de su recipe). `Field.Control` es la carcasa
del `Field.Input` desnudo.
- El `Form.ErrorSummary` vacío dejaba una caja naranja: su raíz pinta borde de
riesgo en cuanto el form es inválido, aunque la lista esté vacía.
## Dos hilos de canon abiertos (medidos, sin causa raíz)
- Con `progressive`/`onSubmit`, un envío inválido bloquea y mueve el foco pero no
expone mensajes; con `onChange` sí, pero saltan los tres campos al escribir en
uno. La máquina tapa el agujero de cara al usuario; el hueco sigue.
- El provider de `ProofOfHuman` escribe `status` él mismo, así que el veredicto que
devuelve el app no se sostiene y los slots `stage*` no renderizaron nunca.
Gates: `blocks:check` verde (14 blocks) · `svelte-check` sin errores propios ·
prettier limpio. Verificado que la sección renderiza (section · form · 3 campos ·
submit · cero errores de página) en un dev server limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
> ⚠️ **CAMBIO DE DOCTRINA (decisión del usuario, 2026-07-31).** Lee la sección
> «La doctrina cambió» antes de tocar nada: un block ya no es solo colocación.
feat(blocks): contact CERRADO — la coordinacion entra en el contrato B
El estado se le pregunta al ESQUEMA, no al Form. `form.isValid` con
`validationBehaviour: 'progressive'` significa «aun no se ha encontrado nada
mal»: un formulario vacio e intacto no tiene errores, asi que se declaraba
valido y `incomplete` era INALCANZABLE al cargar — la seccion pedia resolver la
verificacion con los tres campos vacios, justo el hueco que la doctrina existe
para cerrar. Ahora la raiz valida los valores con `validateSync` de SIUM:
sincrono y sin escribir errores (`form.validate()` habria encendido los tres
campos en rojo en el primer frame).
`state.test.ts` fija la maquina con 9 casos — primer test unitario del tier,
porque `state.ts` es su primera logica pura: el orden de prioridad, que solo
`ready` deja enviar, y que todo estado bloqueado tiene frase.
Contrato B (architecture/blocks.md): seccion «Coordination» con las tres cosas
que posee un block que coordina (estado, forma de sus datos, palabras), B-5
admite el esquema por defecto, B-7 enmendado (posee las palabras de SUS estados
como idlangref con fallback ingles) y la convencion de servicios acotada: el
traductor es el UNICO servicio sancionado. Dice tambien a quien NO aplica — los
blocks de layout no coordinan nada.
i18n por la via real: el arnes registra el namespace `blocks` en las DOS cadenas
de arranque (galeria y previews, que duplican el boot) y la demo se lee en
castellano en vez de caer al fallback ingles.
Ademas: README y pestanas de doc rehechos al reparto nuevo, ficha F2.13 y
bitacora en PLAN-blocks.md, y el control vivo que faltaba para un prop publico
— `verification` con reto / sin reto, porque omitir el prop no es lo mismo que
pasar un estado que nunca verifica.
Verificado en navegador, arco completo y las dos ramas: sin reto vacio → un
campo → tres campos (se desbloquea) → «Enviando…» → «Enviado»; con reto, con
los tres campos llenos el boton sigue bloqueado y la razon pasa a «Resuelve la
verificacion de arriba». `verifying` y `rejected` quedan cubiertos por test
unitario, no por el reto en vivo: el provider de ProofOfHuman escribe `status`
el mismo y el veredicto del app no se sostiene (hilo de canon abierto).
Gates: blocks:check verde (14 blocks) · vitest 9/9 · docs:check 0 ·
svelte-check sin errores propios · prettier limpio en los ficheros propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
> Ya está ESCRITA en el contrato (`architecture/blocks.md` §«Coordination»).
feat(blocks): F2.3 `feature-grid` — el primer block compound del tier
`<FeatureGrid>` + `.Header` + `.Items` + `.Item` + `.ItemIcon`/`.ItemTitle`/
`.ItemText`: una cabecera sobre una rejilla responsive de features
(icono · título · texto). Es la sección que responde «qué hace», la nº1 tras el
hero.
Primer block COMPOUND del tier, y aplica la regla de forma que fijamos: el
`.Item` se REPITE (el app mapea sobre N features) → gana sub-componentes, donde
`hero`/`site-header` usan slots de snippet (partes fijas de layout). Las partes
no coordinan —sin contexto ni estado entre ellas—: la rejilla es del padre, las
celdas del app. `.Items` es el envoltorio honesto de la rejilla (`AutoGrid`), que
deja la cabecera fuera sin un `grid-column: 1/-1` a pelo.
- El block coloca (Section · Container · AutoGrid · Surface · Heading · Text); el
app pone todo el contenido por children (B-7).
- `.Items` fluido por `minChildWidth` (tantas columnas como quepan) o `columns`
fijas; `.Item` `align` start/center; `.ItemIcon` chip `Surface`; `.ItemTitle`
`Heading` h3; `.ItemText` `Text` apagado.
- `align` de sección (center/start) coloca la cabecera coherente con las columnas.
Dos cosas encontradas al construir, resueltas:
- **Los sub-componentes de bloque extienden los props del componente canon que
envuelven** (`BoxProps`, `StackProps`, `HeadingProps`…), **no
`HTMLAttributes`**: el `style: string|null` del atributo HTML crudo choca con
el `style: string` del canon al hacer spread (+ "union type too complex").
- **`.ItemIcon` por defecto `solid`, no `soft`**: el soft-primary en claro es
casi blanco (oklch 0.99) → el chip era invisible; solid da el chip con glifo
on-solid (la tinta de contraste la pone `Surface`).
Demo (`web/routes/blocks/feature-grid/`): full-bleed + ruta `preview`, con
control de columnas (fluido/2/3/4), align, nº de items y dir; 6 features con
iconos del canon.
Hueco a decisión del usuario (en los Gaps del block): el **feature-split/
alternante** (texto junto a un screenshot, lados alternos) — la brecha nº1 del
dossier — es otra disposición (filas de 2 columnas, no rejilla de iconos):
probablemente un block hermano `feature-split`. Presentado, no resuelto.
Verificado en navegador (Playwright, módulos frescos): center/start × claro/
oscuro × LTR/RTL, columnas fluidas y fijas, 3/4/6 items, chips visibles.
`blocks:check` verde (3 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
(El plan fijó 14; `feature-split` se añadió como block hermano con
scope-approval, así que el tier tiene 15 y el denominador honesto es 15.)
fix(blocks): lo que la auditoría encontró DENTRO del tier
Una auditoría multi-agente (7 dimensiones doctrinales + refutación adversarial)
confirmó 32 hallazgos sobre la pasada de calidad. Esto corrige los que caen
dentro de `blocks/`; la deuda de fuera queda REGISTRADA, no tocada
(`PLAN-blocks-quality.md` §6).
- **B-7 roto en `pricing`**: el block traía tres strings castellanas propias como
defaults del toggle — dos visibles y un nombre accesible. Hardcodeaba un idioma
DENTRO del framework y esquivaba `langs`, en el mismo block cuyo README predica
«cero strings propias». Ahora los tres son props REQUERIDAS y las pone la app.
- **El block pintaba**: `pricing.Plan` metía `box-shadow` por `style=` porque
`Card` no expone elevación. Retirado; el hueco queda registrado como candidato
a canon en vez de falseado.
- **El escalonado estaba roto y el comentario mentía**: puse `spring-pop` (driver
JS) en los planes, y un driver JS no lee el `animation-delay` donde vive el
stagger — entraban TODOS a la vez mientras el comentario afirmaba lo contrario.
Vuelto al preset CSS: verificado, índices 0,1,2 a 70ms.
- **Comentario falso en la demo del hero**: atribuía al `intent` visual una
consecuencia sonora/háptica que ningún camino produce (el morfo del Button dice
explícitamente que `contact-activate` NO lleva intent). Reescrito.
- **Composition maps mentían**: los 6 blocks que ahora componen `Motion` no lo
decían; el hero seguía diciendo `Heading` (es `Display`), sin `Backdrop` ni
`decor`, y declaraba DIFERIDO un preset de cromo de media que ya está shipeado
(`Mockup`). Al día, sin filas duplicadas ni datos rancios (el chip de
feature-grid es `solid`, no `soft`).
- **Handoff y plan**: el plan seguía prescribiendo un `<Reveal>` que esta misma
sesión creó y retiró; el handoff daba `feature-split` por «sin decidir» estando
hecho, y los gates con números falsos.
Verificado: `blocks:check` verde (7) · `svelte-check` sin errores propios · en el
navegador, pricing escalona de nuevo, las etiquetas vienen de la app y ninguna
tarjeta pinta sombra.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
Commiteado en `alpha-0.1-dir-prefs` (`866089a40` + este handoff). SIN PUSH.
feat(blocks): F2.3 `feature-grid` — el primer block compound del tier
`<FeatureGrid>` + `.Header` + `.Items` + `.Item` + `.ItemIcon`/`.ItemTitle`/
`.ItemText`: una cabecera sobre una rejilla responsive de features
(icono · título · texto). Es la sección que responde «qué hace», la nº1 tras el
hero.
Primer block COMPOUND del tier, y aplica la regla de forma que fijamos: el
`.Item` se REPITE (el app mapea sobre N features) → gana sub-componentes, donde
`hero`/`site-header` usan slots de snippet (partes fijas de layout). Las partes
no coordinan —sin contexto ni estado entre ellas—: la rejilla es del padre, las
celdas del app. `.Items` es el envoltorio honesto de la rejilla (`AutoGrid`), que
deja la cabecera fuera sin un `grid-column: 1/-1` a pelo.
- El block coloca (Section · Container · AutoGrid · Surface · Heading · Text); el
app pone todo el contenido por children (B-7).
- `.Items` fluido por `minChildWidth` (tantas columnas como quepan) o `columns`
fijas; `.Item` `align` start/center; `.ItemIcon` chip `Surface`; `.ItemTitle`
`Heading` h3; `.ItemText` `Text` apagado.
- `align` de sección (center/start) coloca la cabecera coherente con las columnas.
Dos cosas encontradas al construir, resueltas:
- **Los sub-componentes de bloque extienden los props del componente canon que
envuelven** (`BoxProps`, `StackProps`, `HeadingProps`…), **no
`HTMLAttributes`**: el `style: string|null` del atributo HTML crudo choca con
el `style: string` del canon al hacer spread (+ "union type too complex").
- **`.ItemIcon` por defecto `solid`, no `soft`**: el soft-primary en claro es
casi blanco (oklch 0.99) → el chip era invisible; solid da el chip con glifo
on-solid (la tinta de contraste la pone `Surface`).
Demo (`web/routes/blocks/feature-grid/`): full-bleed + ruta `preview`, con
control de columnas (fluido/2/3/4), align, nº de items y dir; 6 features con
iconos del canon.
Hueco a decisión del usuario (en los Gaps del block): el **feature-split/
alternante** (texto junto a un screenshot, lados alternos) — la brecha nº1 del
dossier — es otra disposición (filas de 2 columnas, no rejilla de iconos):
probablemente un block hermano `feature-split`. Presentado, no resuelto.
Verificado en navegador (Playwright, módulos frescos): center/start × claro/
oscuro × LTR/RTL, columnas fluidas y fijas, 3/4/6 items, chips visibles.
`blocks:check` verde (3 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Dónde vive cada cosa:
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se
razonó con el usuario y con el paisaje de referencia. Compound se reserva para
partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un
hero son cinco slots de layout que no se hablan entre sí y que el root arregla,
así que snippets. Además el campo entero shippea marketing como copy-paste
plano —nadie aplica compound a una sección—, y los slots por zona son la
historia de personalización que distingue al tier del copy-paste.
`<Hero layout="center|split|background" level container size>` +
`eyebrow/title/description/actions/media/background/children`.
- El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el
`id` que nombra el `<section aria-labelledby>` y el nivel del encabezado,
mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son
contenido libre (ahí los componentes del canon SON la API).
- `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de
dos columnas (una sola sin media), apila en estrecho.
- **`background` (cover)** — añadido por scope-approval del usuario: la media a
sangre detrás de la copy, con velo de contraste (`--color-overlay` a
`--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>`
justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre
tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin
`z-index`.
Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para
anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover
dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish
aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la
paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`).
Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow`
no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la
usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el
README del block y en el handoff para revisar el cableado de `--box-flex`.
Verificado en navegador (Playwright headless, módulos frescos): center/split/
background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado
(región con `aria-labelledby` que resuelve al título). `blocks:check` verde
(2 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Plan y bitácora por block**: `docs/process/PLAN-blocks.md` (fichas §F2.x +
registro cronológico al final, con el detalle de cada cierre).
- **Contrato del tier**: `docs/architecture/blocks.md` (B-1..B-11, D-BLK).
- **Suelo de paridad**: `docs/process/RESEARCH-blocks-references.md` (el dossier).
- **Deuda de canon congelada**: `docs/process/PLAN-blocks-quality.md` §6.
- **Decisiones por block**: el README de cada uno (`src/uix/blocks/{kebab}/`).
docs(blocks): registro de F2.1, doctrina de demo del tier y handoff
Documentación al día de lo que se cerró hoy y handoff para retomar mañana.
- `PLAN-blocks.md` §7: **F2.1 `site-header` HECHA** con sus siete commits, la
fase 0 contra el dossier §P1, el landmark que le faltaba a `NavigationMenu`
(arreglado en el canon, no parcheado en el block), el hueco del CTA que
navega y parece botón (registrado, no falseado) y el defecto de framework
que destapó la demo.
- `theming/changelog.md` §46: las 44 variables de cascada de `Box` dejan de
heredarse. Un `Section` regalaba su padding a cada descendiente —la galería
arrastraba ~300px de aire desde F0— y los hijos heredaban anchos y `display`
ajenos. `@property { inherits: false }`, radio verificado sin regresiones.
Lección: una variable que un componente escribe para SÍ MISMO debe declararse
`inherits: false`; si no, deja de ser un prop y se vuelve un contagio.
- `architecture/blocks.md` B-9: la demo de un block se construye sobre el
harness compartido y el block se enseña A SANGRE — nunca dentro de un marco
con relleno ni de una caja con scroll, porque eso cambia lo que el block
hace.
- `src/uix/blocks/README.md`: anatomía de la demo (harness, `{Name}Site`, ruta
`preview`, `DocRow`, catálogo único, ejes en el shell).
- `CONTINUE-blocks.md` (nuevo): handoff — qué toca (F2.2 `hero`), la plantilla
de ficheros para copiar, las reglas que ya costaron sangre (a sangre, iframe
solo para anchos de dispositivo, cada prop un control, nada de backticks en
`<Text>`, ojo con las variables que heredan), la deuda declarada que es
decisión del usuario y el estado exacto de los gates.
Gates al parar: `blocks:check` verde · `svelte-check` 73 errores, todos deuda
ajena (0 propios) · `vitest src/uix/eidos` 353/353 · `contracts.test` con los
3 fallos ajenos conocidos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
---
feat(blocks): `contact` — el primer block que COORDINA (estado, datos y palabras)
WIP declarado: el block funciona y renderiza, pero el arco interactivo completo
no está verificado todavía. Lo que falta está en `CONTINUE-blocks.md`, en orden.
## El cambio de doctrina
Decisión del usuario, textual: «si al final es una lista de componentes… el bloque
es coordinación, estado, y data también». El motivo se veía en este mismo block:
con el estado fuera, cada `disabled` era una expresión distinta montada en el punto
de uso, y aparecían huecos — un envío que no se podía hacer y nadie decía por qué.
Eso cambia lo firmado en `docs/architecture/blocks.md` (B-5/B-7, D-BLK). Queda
ANOTADO como pendiente de escribir en el contrato: propagarlo sin actualizarlo
sería deriva.
## Lo que ahora posee el block
- **La máquina de estado** (`state.ts`): `incomplete · unverified · verifying ·
rejected · ready · sending · sent`, derivada de la validez del `Form`, del
veredicto de verificación y del envío. Con dos registros EXHAUSTIVOS —
`CONTACT_REASON` y `CONTACT_ACTION`— que hacen imposible un estado mudo: no se
puede añadir un estado que bloquee el envío sin darle su frase, porque el
`Record` obliga.
- **La forma de los datos** (`schema.ts`): nombre · correo · mensaje, con las
etiquetas como idlangref. El app lo sustituye pasando su `form` + `schema`.
- **Las palabras**, por el traductor (`#?blocks.contact.…|fallback`), la misma
puerta que usa el canon para sus `texts:`.
Partes nuevas: `Contact.Fields` (los tres campos del esquema propio),
`Contact.Submit` (bloqueo Y etiqueta desde el estado, nunca desde una expresión en
el punto de uso) y `Contact.Reason` (la frase del estado). `Contact.Form` toma el
handle del contexto en vez de pedirlo otra vez.
La demo adelgaza en consecuencia: se le cayeron el esquema, las etiquetas, el
`disabled`, el flag de envío y la frase. Le queda lo que es de un app — las
palabras de la página, los datos de contacto, el reto que elige y qué significa
«enviar».
## De paso, dos arreglos de composición
- El `TextArea` iba dentro de `Field.Control` y salían DOS marcos: pinta su propia
carcasa (borde, radio y relleno son de su recipe). `Field.Control` es la carcasa
del `Field.Input` desnudo.
- El `Form.ErrorSummary` vacío dejaba una caja naranja: su raíz pinta borde de
riesgo en cuanto el form es inválido, aunque la lista esté vacía.
## Dos hilos de canon abiertos (medidos, sin causa raíz)
- Con `progressive`/`onSubmit`, un envío inválido bloquea y mueve el foco pero no
expone mensajes; con `onChange` sí, pero saltan los tres campos al escribir en
uno. La máquina tapa el agujero de cara al usuario; el hueco sigue.
- El provider de `ProofOfHuman` escribe `status` él mismo, así que el veredicto que
devuelve el app no se sostiene y los slots `stage*` no renderizaron nunca.
Gates: `blocks:check` verde (14 blocks) · `svelte-check` sin errores propios ·
prettier limpio. Verificado que la sección renderiza (section · form · 3 campos ·
submit · cero errores de página) en un dev server limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## La doctrina cambió: un block coordina, tiene estado y trae datos
Decisión del usuario, textual: ** «si al final es una lista de componentes… el
bloque es coordinación, estado, y data también»**. El motivo, con el `contact`
delante: cuando el estado vive fuera, cada `disabled` es una expresión distinta
montada en el punto de uso y aparecen **huecos** — un envío que no se puede hacer
y nadie dice por qué.
Lo que cambia respecto de lo firmado en `docs/architecture/blocks.md` (B-5/B-7 y
D-BLK: «todo el contenido por snippets, cero cadenas propias, sin servicios»):
1. **El block posee el estado de su sección.** Una máquina única y EXHAUSTIVA,
derivada, en contexto. Sus partes la leen; ninguna la recalcula ni inventa un
`disabled` .
2. **El block trae la forma de sus datos** (el esquema por defecto). El app lo
sustituye pasando el suyo.
3. **El block habla** , con idlangrefs por el traductor
(`#?blocks.< block > .< clave > |fallback`), la misma puerta que usa el canon para
sus `texts:` . Fallback en inglés; el app traduce registrando `blocks.*` .
feat(blocks): contact CERRADO — la coordinacion entra en el contrato B
El estado se le pregunta al ESQUEMA, no al Form. `form.isValid` con
`validationBehaviour: 'progressive'` significa «aun no se ha encontrado nada
mal»: un formulario vacio e intacto no tiene errores, asi que se declaraba
valido y `incomplete` era INALCANZABLE al cargar — la seccion pedia resolver la
verificacion con los tres campos vacios, justo el hueco que la doctrina existe
para cerrar. Ahora la raiz valida los valores con `validateSync` de SIUM:
sincrono y sin escribir errores (`form.validate()` habria encendido los tres
campos en rojo en el primer frame).
`state.test.ts` fija la maquina con 9 casos — primer test unitario del tier,
porque `state.ts` es su primera logica pura: el orden de prioridad, que solo
`ready` deja enviar, y que todo estado bloqueado tiene frase.
Contrato B (architecture/blocks.md): seccion «Coordination» con las tres cosas
que posee un block que coordina (estado, forma de sus datos, palabras), B-5
admite el esquema por defecto, B-7 enmendado (posee las palabras de SUS estados
como idlangref con fallback ingles) y la convencion de servicios acotada: el
traductor es el UNICO servicio sancionado. Dice tambien a quien NO aplica — los
blocks de layout no coordinan nada.
i18n por la via real: el arnes registra el namespace `blocks` en las DOS cadenas
de arranque (galeria y previews, que duplican el boot) y la demo se lee en
castellano en vez de caer al fallback ingles.
Ademas: README y pestanas de doc rehechos al reparto nuevo, ficha F2.13 y
bitacora en PLAN-blocks.md, y el control vivo que faltaba para un prop publico
— `verification` con reto / sin reto, porque omitir el prop no es lo mismo que
pasar un estado que nunca verifica.
Verificado en navegador, arco completo y las dos ramas: sin reto vacio → un
campo → tres campos (se desbloquea) → «Enviando…» → «Enviado»; con reto, con
los tres campos llenos el boton sigue bloqueado y la razon pasa a «Resuelve la
verificacion de arriba». `verifying` y `rejected` quedan cubiertos por test
unitario, no por el reto en vivo: el provider de ProofOfHuman escribe `status`
el mismo y el veredicto del app no se sostiene (hilo de canon abierto).
Gates: blocks:check verde (14 blocks) · vitest 9/9 · docs:check 0 ·
svelte-check sin errores propios · prettier limpio en los ficheros propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**ESCRITO** (2026-07-31): `architecture/blocks.md` gana la sección
«Coordination», B-5 admite que el block traiga la FORMA de sus datos, B-7 queda
enmendado (posee las palabras de SUS estados, como idlangref con fallback
inglés) y la convención de servicios se acota. La sección dice también a quién NO
aplica.
**Enmendado el 2026-08-06**: los servicios sancionados son DOS — el traductor,
que resuelve las palabras, y el anunciador (`uix.announce`), que las entrega a la
tecnología asistiva. Entró por la misma razón que el primero: un block que posee
las palabras de sus estados tiene que poder decirlas, y montar una región viva
propia re-implementa `Announce` .
feat(blocks): `contact` — el primer block que COORDINA (estado, datos y palabras)
WIP declarado: el block funciona y renderiza, pero el arco interactivo completo
no está verificado todavía. Lo que falta está en `CONTINUE-blocks.md`, en orden.
## El cambio de doctrina
Decisión del usuario, textual: «si al final es una lista de componentes… el bloque
es coordinación, estado, y data también». El motivo se veía en este mismo block:
con el estado fuera, cada `disabled` era una expresión distinta montada en el punto
de uso, y aparecían huecos — un envío que no se podía hacer y nadie decía por qué.
Eso cambia lo firmado en `docs/architecture/blocks.md` (B-5/B-7, D-BLK). Queda
ANOTADO como pendiente de escribir en el contrato: propagarlo sin actualizarlo
sería deriva.
## Lo que ahora posee el block
- **La máquina de estado** (`state.ts`): `incomplete · unverified · verifying ·
rejected · ready · sending · sent`, derivada de la validez del `Form`, del
veredicto de verificación y del envío. Con dos registros EXHAUSTIVOS —
`CONTACT_REASON` y `CONTACT_ACTION`— que hacen imposible un estado mudo: no se
puede añadir un estado que bloquee el envío sin darle su frase, porque el
`Record` obliga.
- **La forma de los datos** (`schema.ts`): nombre · correo · mensaje, con las
etiquetas como idlangref. El app lo sustituye pasando su `form` + `schema`.
- **Las palabras**, por el traductor (`#?blocks.contact.…|fallback`), la misma
puerta que usa el canon para sus `texts:`.
Partes nuevas: `Contact.Fields` (los tres campos del esquema propio),
`Contact.Submit` (bloqueo Y etiqueta desde el estado, nunca desde una expresión en
el punto de uso) y `Contact.Reason` (la frase del estado). `Contact.Form` toma el
handle del contexto en vez de pedirlo otra vez.
La demo adelgaza en consecuencia: se le cayeron el esquema, las etiquetas, el
`disabled`, el flag de envío y la frase. Le queda lo que es de un app — las
palabras de la página, los datos de contacto, el reto que elige y qué significa
«enviar».
## De paso, dos arreglos de composición
- El `TextArea` iba dentro de `Field.Control` y salían DOS marcos: pinta su propia
carcasa (borde, radio y relleno son de su recipe). `Field.Control` es la carcasa
del `Field.Input` desnudo.
- El `Form.ErrorSummary` vacío dejaba una caja naranja: su raíz pinta borde de
riesgo en cuanto el form es inválido, aunque la lista esté vacía.
## Dos hilos de canon abiertos (medidos, sin causa raíz)
- Con `progressive`/`onSubmit`, un envío inválido bloquea y mueve el foco pero no
expone mensajes; con `onChange` sí, pero saltan los tres campos al escribir en
uno. La máquina tapa el agujero de cara al usuario; el hueco sigue.
- El provider de `ProofOfHuman` escribe `status` él mismo, así que el veredicto que
devuelve el app no se sostiene y los slots `stage*` no renderizaron nunca.
Gates: `blocks:check` verde (14 blocks) · `svelte-check` sin errores propios ·
prettier limpio. Verificado que la sección renderiza (section · form · 3 campos ·
submit · cero errores de página) en un dev server limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
docs(blocks): cada block declara su posicion bajo la doctrina de coordinacion
Y `content-section.Media` pasa a seguir al texto por defecto.
DEFAULT: `.Media` era `width='wide'`, asi que una figura se salia de la columna
SALVO que dijeras lo contrario. Al reves de lo que hacen todas las referencias
(en `@tailwindcss/typography` una imagen dentro de `prose` queda capada a la
medida y salirse exige el truco del `translate`; en `markdown-body` de GitHub,
`max-width: 100%` de la columna). Ahora el default es `measure` — la figura
sigue al texto y el escape se PIDE. Cambiado en los cuatro sitios que decian lo
contrario: la parte, la ruta `preview`, el estado inicial del control de la demo
(una demo que pisa el default en silencio ensena algo que el block no hace solo)
y la doc. Verificado: 528 = ancho del texto exacto a 1011/1280/1920, y `wide` /
`full` siguen funcionando cuando se piden.
DOCUMENTACION: los 15 READMEs declaran ahora su posicion bajo §«Coordination»,
y el mapa real no es uniforme —
- Coordinan: `contact` (maquina + esquema + palabras) y `pricing` (el periodo
por contexto). Pero en pricing nada se puede BLOQUEAR, asi que no necesita
maquina ni palabras: coordinar no es siempre una maquina.
- Comparten CONFIGURACION, no estado: `team` (`align`) y `content-section` (la
medida).
- Costura bindable hacia el canon: `faq` (`value` → `Accordion`) y
`site-header` (`mobileOpen` → `Drawer`). Reenviar no es poseer.
- No poseen nada: banner · cta · feature-grid · feature-split · hero ·
site-footer · stats-band · testimonials. Decirlo explicitamente es el punto:
inventarle estado a un block que no coordina nada es el error contrario.
- `newsletter` queda como CANDIDATO ABIERTO: coloca un envio que puede quedar
bloqueado sin que nada diga por que — el `disabled` lo monta el app en el
punto de uso — que es exactamente el hueco que cerro `contact`. Es anterior a
la doctrina (block del 30-07, doctrina del 31-07). Registrado, no asumido: se
toca con decision del usuario.
El README del tier apunta a la doctrina y el handoff recoge el mapa completo.
Gates: blocks:check verde (15) · docs:check 0 · svelte-check sin errores en lo
tocado · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Alcance acordado**: `contact` primero como prueba de la forma — **HECHO** .
**Revisión documental de los 15 HECHA** (2026-08-01): cada README declara ahora
su posición bajo la doctrina, y el mapa real salió así —
- **Coordinan**: `contact` (máquina + esquema + palabras) · `pricing` (el periodo
por contexto, pero nada se puede BLOQUEAR ahí, así que no necesita máquina ni
palabras: coordinar no es siempre una máquina).
- **Comparten configuración, no estado**: `team` (`align`) · `content-section`
(la medida).
- **Costura bindable hacia el canon**: `faq` (`value` → `Accordion` ) ·
`site-header` (`mobileOpen` → `Drawer` ). Reenviar no es poseer.
- **No poseen nada**: banner · cta · feature-grid · feature-split · hero ·
site-footer · stats-band · testimonials.
- ✅ ** `newsletter` COORDINA desde 2026-08-06** — era el candidato abierto de esta
lista y ya está cerrado. Máquina de cinco estados (`incomplete · invalid ·
ready · sending · sent`), dos `Record` exhaustivos de razón y acción, y
`Newsletter.Submit` / `Newsletter.Reason` como PARTES que leen el contexto: la
regla de forma del tier aplicada, no una excepción — compound se gana cuando las
partes repiten o **coordinan** . `state.test.ts` con 8 casos.
feat(blocks): contact CERRADO — la coordinacion entra en el contrato B
El estado se le pregunta al ESQUEMA, no al Form. `form.isValid` con
`validationBehaviour: 'progressive'` significa «aun no se ha encontrado nada
mal»: un formulario vacio e intacto no tiene errores, asi que se declaraba
valido y `incomplete` era INALCANZABLE al cargar — la seccion pedia resolver la
verificacion con los tres campos vacios, justo el hueco que la doctrina existe
para cerrar. Ahora la raiz valida los valores con `validateSync` de SIUM:
sincrono y sin escribir errores (`form.validate()` habria encendido los tres
campos en rojo en el primer frame).
`state.test.ts` fija la maquina con 9 casos — primer test unitario del tier,
porque `state.ts` es su primera logica pura: el orden de prioridad, que solo
`ready` deja enviar, y que todo estado bloqueado tiene frase.
Contrato B (architecture/blocks.md): seccion «Coordination» con las tres cosas
que posee un block que coordina (estado, forma de sus datos, palabras), B-5
admite el esquema por defecto, B-7 enmendado (posee las palabras de SUS estados
como idlangref con fallback ingles) y la convencion de servicios acotada: el
traductor es el UNICO servicio sancionado. Dice tambien a quien NO aplica — los
blocks de layout no coordinan nada.
i18n por la via real: el arnes registra el namespace `blocks` en las DOS cadenas
de arranque (galeria y previews, que duplican el boot) y la demo se lee en
castellano en vez de caer al fallback ingles.
Ademas: README y pestanas de doc rehechos al reparto nuevo, ficha F2.13 y
bitacora en PLAN-blocks.md, y el control vivo que faltaba para un prop publico
— `verification` con reto / sin reto, porque omitir el prop no es lo mismo que
pasar un estado que nunca verifica.
Verificado en navegador, arco completo y las dos ramas: sin reto vacio → un
campo → tres campos (se desbloquea) → «Enviando…» → «Enviado»; con reto, con
los tres campos llenos el boton sigue bloqueado y la razon pasa a «Resuelve la
verificacion de arriba». `verifying` y `rejected` quedan cubiertos por test
unitario, no por el reto en vivo: el provider de ProofOfHuman escribe `status`
el mismo y el veredicto del app no se sostiene (hilo de canon abierto).
Gates: blocks:check verde (14 blocks) · vitest 9/9 · docs:check 0 ·
svelte-check sin errores propios · prettier limpio en los ficheros propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
⚠️ Al revisarlos, el listón es el que dejó `contact` : **el estado se deriva de
una fuente y las partes lo leen**; si un estado puede bloquear algo, tiene frase
por `Record` exhaustivo. Y **no inventes estado donde no lo hay** — un block de
layout que no coordina nada se queda como está.
feat(blocks): `contact` — el primer block que COORDINA (estado, datos y palabras)
WIP declarado: el block funciona y renderiza, pero el arco interactivo completo
no está verificado todavía. Lo que falta está en `CONTINUE-blocks.md`, en orden.
## El cambio de doctrina
Decisión del usuario, textual: «si al final es una lista de componentes… el bloque
es coordinación, estado, y data también». El motivo se veía en este mismo block:
con el estado fuera, cada `disabled` era una expresión distinta montada en el punto
de uso, y aparecían huecos — un envío que no se podía hacer y nadie decía por qué.
Eso cambia lo firmado en `docs/architecture/blocks.md` (B-5/B-7, D-BLK). Queda
ANOTADO como pendiente de escribir en el contrato: propagarlo sin actualizarlo
sería deriva.
## Lo que ahora posee el block
- **La máquina de estado** (`state.ts`): `incomplete · unverified · verifying ·
rejected · ready · sending · sent`, derivada de la validez del `Form`, del
veredicto de verificación y del envío. Con dos registros EXHAUSTIVOS —
`CONTACT_REASON` y `CONTACT_ACTION`— que hacen imposible un estado mudo: no se
puede añadir un estado que bloquee el envío sin darle su frase, porque el
`Record` obliga.
- **La forma de los datos** (`schema.ts`): nombre · correo · mensaje, con las
etiquetas como idlangref. El app lo sustituye pasando su `form` + `schema`.
- **Las palabras**, por el traductor (`#?blocks.contact.…|fallback`), la misma
puerta que usa el canon para sus `texts:`.
Partes nuevas: `Contact.Fields` (los tres campos del esquema propio),
`Contact.Submit` (bloqueo Y etiqueta desde el estado, nunca desde una expresión en
el punto de uso) y `Contact.Reason` (la frase del estado). `Contact.Form` toma el
handle del contexto en vez de pedirlo otra vez.
La demo adelgaza en consecuencia: se le cayeron el esquema, las etiquetas, el
`disabled`, el flag de envío y la frase. Le queda lo que es de un app — las
palabras de la página, los datos de contacto, el reto que elige y qué significa
«enviar».
## De paso, dos arreglos de composición
- El `TextArea` iba dentro de `Field.Control` y salían DOS marcos: pinta su propia
carcasa (borde, radio y relleno son de su recipe). `Field.Control` es la carcasa
del `Field.Input` desnudo.
- El `Form.ErrorSummary` vacío dejaba una caja naranja: su raíz pinta borde de
riesgo en cuanto el form es inválido, aunque la lista esté vacía.
## Dos hilos de canon abiertos (medidos, sin causa raíz)
- Con `progressive`/`onSubmit`, un envío inválido bloquea y mueve el foco pero no
expone mensajes; con `onChange` sí, pero saltan los tres campos al escribir en
uno. La máquina tapa el agujero de cara al usuario; el hueco sigue.
- El provider de `ProofOfHuman` escribe `status` él mismo, así que el veredicto que
devuelve el app no se sostiene y los slots `stage*` no renderizaron nunca.
Gates: `blocks:check` verde (14 blocks) · `svelte-check` sin errores propios ·
prettier limpio. Verificado que la sección renderiza (section · form · 3 campos ·
submit · cero errores de página) en un dev server limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
---
feat(blocks): contact CERRADO — la coordinacion entra en el contrato B
El estado se le pregunta al ESQUEMA, no al Form. `form.isValid` con
`validationBehaviour: 'progressive'` significa «aun no se ha encontrado nada
mal»: un formulario vacio e intacto no tiene errores, asi que se declaraba
valido y `incomplete` era INALCANZABLE al cargar — la seccion pedia resolver la
verificacion con los tres campos vacios, justo el hueco que la doctrina existe
para cerrar. Ahora la raiz valida los valores con `validateSync` de SIUM:
sincrono y sin escribir errores (`form.validate()` habria encendido los tres
campos en rojo en el primer frame).
`state.test.ts` fija la maquina con 9 casos — primer test unitario del tier,
porque `state.ts` es su primera logica pura: el orden de prioridad, que solo
`ready` deja enviar, y que todo estado bloqueado tiene frase.
Contrato B (architecture/blocks.md): seccion «Coordination» con las tres cosas
que posee un block que coordina (estado, forma de sus datos, palabras), B-5
admite el esquema por defecto, B-7 enmendado (posee las palabras de SUS estados
como idlangref con fallback ingles) y la convencion de servicios acotada: el
traductor es el UNICO servicio sancionado. Dice tambien a quien NO aplica — los
blocks de layout no coordinan nada.
i18n por la via real: el arnes registra el namespace `blocks` en las DOS cadenas
de arranque (galeria y previews, que duplican el boot) y la demo se lee en
castellano en vez de caer al fallback ingles.
Ademas: README y pestanas de doc rehechos al reparto nuevo, ficha F2.13 y
bitacora en PLAN-blocks.md, y el control vivo que faltaba para un prop publico
— `verification` con reto / sin reto, porque omitir el prop no es lo mismo que
pasar un estado que nunca verifica.
Verificado en navegador, arco completo y las dos ramas: sin reto vacio → un
campo → tres campos (se desbloquea) → «Enviando…» → «Enviado»; con reto, con
los tres campos llenos el boton sigue bloqueado y la razon pasa a «Resuelve la
verificacion de arriba». `verifying` y `rejected` quedan cubiertos por test
unitario, no por el reto en vivo: el provider de ProofOfHuman escribe `status`
el mismo y el veredicto del app no se sostiene (hilo de canon abierto).
Gates: blocks:check verde (14 blocks) · vitest 9/9 · docs:check 0 ·
svelte-check sin errores propios · prettier limpio en los ficheros propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## F2.13 `contact` — CERRADO (2026-07-31)
Los seis puntos que quedaban están hechos: arco verificado en navegador · README
rehecho · pestañas de doc al día · namespace `blocks` registrado en el arnés ·
ficha §F2.13 + bitácora en `PLAN-blocks.md` · contrato B actualizado.
**Un defecto real, encontrado al verificar**: la máquina leía `form.isValid` ,
que con `progressive` significa «aún no se ha encontrado nada mal» — un
formulario vacío e intacto no tiene errores, así que se declaraba válido y
`incomplete` era INALCANZABLE al cargar: la sección pedía resolver la
verificación con los tres campos vacíos. Justo el hueco que la doctrina existe
para cerrar. Se arregla preguntando al ESQUEMA (`validateSync` de SIUM: síncrono
y sin escribir errores; `form.validate()` habría encendido los tres campos en
rojo al cargar). Fijado con `state.test.ts` — 9 casos, **primer test unitario
del tier**, porque `state.ts` es su primera lógica pura.
Añadido de paso el control vivo que faltaba para un prop público:
`verification` con reto / sin reto. **Omitir el prop NO es pasar `idle`** : sin
él la sección no tiene paso de verificación; con un estado que nunca llega a
`verified` tienes una verificación que no pasa, que es otra cosa.
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
feat(blocks): `contact` — el primer block que COORDINA (estado, datos y palabras)
WIP declarado: el block funciona y renderiza, pero el arco interactivo completo
no está verificado todavía. Lo que falta está en `CONTINUE-blocks.md`, en orden.
## El cambio de doctrina
Decisión del usuario, textual: «si al final es una lista de componentes… el bloque
es coordinación, estado, y data también». El motivo se veía en este mismo block:
con el estado fuera, cada `disabled` era una expresión distinta montada en el punto
de uso, y aparecían huecos — un envío que no se podía hacer y nadie decía por qué.
Eso cambia lo firmado en `docs/architecture/blocks.md` (B-5/B-7, D-BLK). Queda
ANOTADO como pendiente de escribir en el contrato: propagarlo sin actualizarlo
sería deriva.
## Lo que ahora posee el block
- **La máquina de estado** (`state.ts`): `incomplete · unverified · verifying ·
rejected · ready · sending · sent`, derivada de la validez del `Form`, del
veredicto de verificación y del envío. Con dos registros EXHAUSTIVOS —
`CONTACT_REASON` y `CONTACT_ACTION`— que hacen imposible un estado mudo: no se
puede añadir un estado que bloquee el envío sin darle su frase, porque el
`Record` obliga.
- **La forma de los datos** (`schema.ts`): nombre · correo · mensaje, con las
etiquetas como idlangref. El app lo sustituye pasando su `form` + `schema`.
- **Las palabras**, por el traductor (`#?blocks.contact.…|fallback`), la misma
puerta que usa el canon para sus `texts:`.
Partes nuevas: `Contact.Fields` (los tres campos del esquema propio),
`Contact.Submit` (bloqueo Y etiqueta desde el estado, nunca desde una expresión en
el punto de uso) y `Contact.Reason` (la frase del estado). `Contact.Form` toma el
handle del contexto en vez de pedirlo otra vez.
La demo adelgaza en consecuencia: se le cayeron el esquema, las etiquetas, el
`disabled`, el flag de envío y la frase. Le queda lo que es de un app — las
palabras de la página, los datos de contacto, el reto que elige y qué significa
«enviar».
## De paso, dos arreglos de composición
- El `TextArea` iba dentro de `Field.Control` y salían DOS marcos: pinta su propia
carcasa (borde, radio y relleno son de su recipe). `Field.Control` es la carcasa
del `Field.Input` desnudo.
- El `Form.ErrorSummary` vacío dejaba una caja naranja: su raíz pinta borde de
riesgo en cuanto el form es inválido, aunque la lista esté vacía.
## Dos hilos de canon abiertos (medidos, sin causa raíz)
- Con `progressive`/`onSubmit`, un envío inválido bloquea y mueve el foco pero no
expone mensajes; con `onChange` sí, pero saltan los tres campos al escribir en
uno. La máquina tapa el agujero de cara al usuario; el hueco sigue.
- El provider de `ProofOfHuman` escribe `status` él mismo, así que el veredicto que
devuelve el app no se sostiene y los slots `stage*` no renderizaron nunca.
Gates: `blocks:check` verde (14 blocks) · `svelte-check` sin errores propios ·
prettier limpio. Verificado que la sección renderiza (section · form · 3 campos ·
submit · cero errores de página) en un dev server limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
⚠️ **Ojo con el dev server de otra sesión** : el de `:5173` sirvió un módulo VACÍO
para `blocks/contact/index.ts` (500 «does not provide an export named Contact»)
porque tenía el grafo caducado tras crear ficheros nuevos. Arrancado uno propio en
otro puerto, la página va perfecta. Si mañana ves ese 500, es eso: no persigas el
código.
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
feat(blocks): `contact` — el primer block que COORDINA (estado, datos y palabras)
WIP declarado: el block funciona y renderiza, pero el arco interactivo completo
no está verificado todavía. Lo que falta está en `CONTINUE-blocks.md`, en orden.
## El cambio de doctrina
Decisión del usuario, textual: «si al final es una lista de componentes… el bloque
es coordinación, estado, y data también». El motivo se veía en este mismo block:
con el estado fuera, cada `disabled` era una expresión distinta montada en el punto
de uso, y aparecían huecos — un envío que no se podía hacer y nadie decía por qué.
Eso cambia lo firmado en `docs/architecture/blocks.md` (B-5/B-7, D-BLK). Queda
ANOTADO como pendiente de escribir en el contrato: propagarlo sin actualizarlo
sería deriva.
## Lo que ahora posee el block
- **La máquina de estado** (`state.ts`): `incomplete · unverified · verifying ·
rejected · ready · sending · sent`, derivada de la validez del `Form`, del
veredicto de verificación y del envío. Con dos registros EXHAUSTIVOS —
`CONTACT_REASON` y `CONTACT_ACTION`— que hacen imposible un estado mudo: no se
puede añadir un estado que bloquee el envío sin darle su frase, porque el
`Record` obliga.
- **La forma de los datos** (`schema.ts`): nombre · correo · mensaje, con las
etiquetas como idlangref. El app lo sustituye pasando su `form` + `schema`.
- **Las palabras**, por el traductor (`#?blocks.contact.…|fallback`), la misma
puerta que usa el canon para sus `texts:`.
Partes nuevas: `Contact.Fields` (los tres campos del esquema propio),
`Contact.Submit` (bloqueo Y etiqueta desde el estado, nunca desde una expresión en
el punto de uso) y `Contact.Reason` (la frase del estado). `Contact.Form` toma el
handle del contexto en vez de pedirlo otra vez.
La demo adelgaza en consecuencia: se le cayeron el esquema, las etiquetas, el
`disabled`, el flag de envío y la frase. Le queda lo que es de un app — las
palabras de la página, los datos de contacto, el reto que elige y qué significa
«enviar».
## De paso, dos arreglos de composición
- El `TextArea` iba dentro de `Field.Control` y salían DOS marcos: pinta su propia
carcasa (borde, radio y relleno son de su recipe). `Field.Control` es la carcasa
del `Field.Input` desnudo.
- El `Form.ErrorSummary` vacío dejaba una caja naranja: su raíz pinta borde de
riesgo en cuanto el form es inválido, aunque la lista esté vacía.
## Dos hilos de canon abiertos (medidos, sin causa raíz)
- Con `progressive`/`onSubmit`, un envío inválido bloquea y mueve el foco pero no
expone mensajes; con `onChange` sí, pero saltan los tres campos al escribir en
uno. La máquina tapa el agujero de cara al usuario; el hueco sigue.
- El provider de `ProofOfHuman` escribe `status` él mismo, así que el veredicto que
devuelve el app no se sostiene y los slots `stage*` no renderizaron nunca.
Gates: `blocks:check` verde (14 blocks) · `svelte-check` sin errores propios ·
prettier limpio. Verificado que la sección renderiza (section · form · 3 campos ·
submit · cero errores de página) en un dev server limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## Dos hilos de canon abiertos (medidos, sin causa raíz)
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
feat(blocks): `contact` — el primer block que COORDINA (estado, datos y palabras)
WIP declarado: el block funciona y renderiza, pero el arco interactivo completo
no está verificado todavía. Lo que falta está en `CONTINUE-blocks.md`, en orden.
## El cambio de doctrina
Decisión del usuario, textual: «si al final es una lista de componentes… el bloque
es coordinación, estado, y data también». El motivo se veía en este mismo block:
con el estado fuera, cada `disabled` era una expresión distinta montada en el punto
de uso, y aparecían huecos — un envío que no se podía hacer y nadie decía por qué.
Eso cambia lo firmado en `docs/architecture/blocks.md` (B-5/B-7, D-BLK). Queda
ANOTADO como pendiente de escribir en el contrato: propagarlo sin actualizarlo
sería deriva.
## Lo que ahora posee el block
- **La máquina de estado** (`state.ts`): `incomplete · unverified · verifying ·
rejected · ready · sending · sent`, derivada de la validez del `Form`, del
veredicto de verificación y del envío. Con dos registros EXHAUSTIVOS —
`CONTACT_REASON` y `CONTACT_ACTION`— que hacen imposible un estado mudo: no se
puede añadir un estado que bloquee el envío sin darle su frase, porque el
`Record` obliga.
- **La forma de los datos** (`schema.ts`): nombre · correo · mensaje, con las
etiquetas como idlangref. El app lo sustituye pasando su `form` + `schema`.
- **Las palabras**, por el traductor (`#?blocks.contact.…|fallback`), la misma
puerta que usa el canon para sus `texts:`.
Partes nuevas: `Contact.Fields` (los tres campos del esquema propio),
`Contact.Submit` (bloqueo Y etiqueta desde el estado, nunca desde una expresión en
el punto de uso) y `Contact.Reason` (la frase del estado). `Contact.Form` toma el
handle del contexto en vez de pedirlo otra vez.
La demo adelgaza en consecuencia: se le cayeron el esquema, las etiquetas, el
`disabled`, el flag de envío y la frase. Le queda lo que es de un app — las
palabras de la página, los datos de contacto, el reto que elige y qué significa
«enviar».
## De paso, dos arreglos de composición
- El `TextArea` iba dentro de `Field.Control` y salían DOS marcos: pinta su propia
carcasa (borde, radio y relleno son de su recipe). `Field.Control` es la carcasa
del `Field.Input` desnudo.
- El `Form.ErrorSummary` vacío dejaba una caja naranja: su raíz pinta borde de
riesgo en cuanto el form es inválido, aunque la lista esté vacía.
## Dos hilos de canon abiertos (medidos, sin causa raíz)
- Con `progressive`/`onSubmit`, un envío inválido bloquea y mueve el foco pero no
expone mensajes; con `onChange` sí, pero saltan los tres campos al escribir en
uno. La máquina tapa el agujero de cara al usuario; el hueco sigue.
- El provider de `ProofOfHuman` escribe `status` él mismo, así que el veredicto que
devuelve el app no se sostiene y los slots `stage*` no renderizaron nunca.
Gates: `blocks:check` verde (14 blocks) · `svelte-check` sin errores propios ·
prettier limpio. Verificado que la sección renderiza (section · form · 3 campos ·
submit · cero errores de página) en un dev server limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
1. ** `Form` /SIUM**: con `progressive` /`onSubmit`, un envío inválido bloquea y
mueve el foco pero **no expone mensajes** (`form.errors` vacío). Con `onChange`
sí aparecen —y traducidos—, pero saltan en los tres campos al escribir en uno;
con `onBlur` aparecen desde la carga. La máquina de estado del block TAPA el
agujero de cara al usuario (el botón dice qué falta), pero el hueco sigue ahí.
2. ** `ProofOfHuman` **: el provider de soma escribe `status` él mismo
(`writableActive`), así que el veredicto que devuelve el app tras consultar a su
servidor no se sostiene, y los slots `stage*` no renderizaron en ningún estado.
feat(blocks): contact CERRADO — la coordinacion entra en el contrato B
El estado se le pregunta al ESQUEMA, no al Form. `form.isValid` con
`validationBehaviour: 'progressive'` significa «aun no se ha encontrado nada
mal»: un formulario vacio e intacto no tiene errores, asi que se declaraba
valido y `incomplete` era INALCANZABLE al cargar — la seccion pedia resolver la
verificacion con los tres campos vacios, justo el hueco que la doctrina existe
para cerrar. Ahora la raiz valida los valores con `validateSync` de SIUM:
sincrono y sin escribir errores (`form.validate()` habria encendido los tres
campos en rojo en el primer frame).
`state.test.ts` fija la maquina con 9 casos — primer test unitario del tier,
porque `state.ts` es su primera logica pura: el orden de prioridad, que solo
`ready` deja enviar, y que todo estado bloqueado tiene frase.
Contrato B (architecture/blocks.md): seccion «Coordination» con las tres cosas
que posee un block que coordina (estado, forma de sus datos, palabras), B-5
admite el esquema por defecto, B-7 enmendado (posee las palabras de SUS estados
como idlangref con fallback ingles) y la convencion de servicios acotada: el
traductor es el UNICO servicio sancionado. Dice tambien a quien NO aplica — los
blocks de layout no coordinan nada.
i18n por la via real: el arnes registra el namespace `blocks` en las DOS cadenas
de arranque (galeria y previews, que duplican el boot) y la demo se lee en
castellano en vez de caer al fallback ingles.
Ademas: README y pestanas de doc rehechos al reparto nuevo, ficha F2.13 y
bitacora en PLAN-blocks.md, y el control vivo que faltaba para un prop publico
— `verification` con reto / sin reto, porque omitir el prop no es lo mismo que
pasar un estado que nunca verifica.
Verificado en navegador, arco completo y las dos ramas: sin reto vacio → un
campo → tres campos (se desbloquea) → «Enviando…» → «Enviado»; con reto, con
los tres campos llenos el boton sigue bloqueado y la razon pasa a «Resuelve la
verificacion de arriba». `verifying` y `rejected` quedan cubiertos por test
unitario, no por el reto en vivo: el provider de ProofOfHuman escribe `status`
el mismo y el veredicto del app no se sostiene (hilo de canon abierto).
Gates: blocks:check verde (14 blocks) · vitest 9/9 · docs:check 0 ·
svelte-check sin errores propios · prettier limpio en los ficheros propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
El camino «rechazado» no es contable hoy desde el app. **Consecuencia medida en
`contact` **: sus estados `verifying` y `rejected` quedan cubiertos por test
unitario de la máquina, no por el reto en vivo.
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
---
## La regla de forma del tier (no la re-decidas)
Compound **solo** si las partes:
- **SE REPITEN** — el app mapea sobre N (`feature-grid.Item`,
`testimonials.Item` , `pricing.Plan` , `faq.Item` , `stats-band.Stat` ,
`site-footer.Column` ), o
- **COORDINAN** — se hablan por contexto (`pricing.Switch` ↔ `PlanPrice` ).
Si no hacen ninguna de las dos: **slots de snippet** en la raíz (`hero`, `cta` ,
`newsletter` , y la marca / alta / social / legal / extra de `site-footer` ). El
plan original dibujaba varias de esas como partes; la desviación está registrada
en cada README.
---
## Los 8 hallazgos de canon que dejó el tier (congelados)
Restricción del usuario en vigor: **no tocar componentes ni librerías fuera de
`src/uix/blocks/` y `web/routes/blocks/` **. Están todos medidos en navegador
y listados en `PLAN-blocks-quality.md` §6 (F15– F22) — más los de motion (A1, A2,
A4, C7, C8, C10, D12, E14) de la pasada de calidad.
docs(blocks): registro de F2.1, doctrina de demo del tier y handoff
Documentación al día de lo que se cerró hoy y handoff para retomar mañana.
- `PLAN-blocks.md` §7: **F2.1 `site-header` HECHA** con sus siete commits, la
fase 0 contra el dossier §P1, el landmark que le faltaba a `NavigationMenu`
(arreglado en el canon, no parcheado en el block), el hueco del CTA que
navega y parece botón (registrado, no falseado) y el defecto de framework
que destapó la demo.
- `theming/changelog.md` §46: las 44 variables de cascada de `Box` dejan de
heredarse. Un `Section` regalaba su padding a cada descendiente —la galería
arrastraba ~300px de aire desde F0— y los hijos heredaban anchos y `display`
ajenos. `@property { inherits: false }`, radio verificado sin regresiones.
Lección: una variable que un componente escribe para SÍ MISMO debe declararse
`inherits: false`; si no, deja de ser un prop y se vuelve un contagio.
- `architecture/blocks.md` B-9: la demo de un block se construye sobre el
harness compartido y el block se enseña A SANGRE — nunca dentro de un marco
con relleno ni de una caja con scroll, porque eso cambia lo que el block
hace.
- `src/uix/blocks/README.md`: anatomía de la demo (harness, `{Name}Site`, ruta
`preview`, `DocRow`, catálogo único, ejes en el shell).
- `CONTINUE-blocks.md` (nuevo): handoff — qué toca (F2.2 `hero`), la plantilla
de ficheros para copiar, las reglas que ya costaron sangre (a sangre, iframe
solo para anchos de dispositivo, cada prop un control, nada de backticks en
`<Text>`, ojo con las variables que heredan), la deuda declarada que es
decisión del usuario y el estado exacto de los gates.
Gates al parar: `blocks:check` verde · `svelte-check` 73 errores, todos deuda
ajena (0 propios) · `vitest src/uix/eidos` 353/353 · `contracts.test` con los
3 fallos ajenos conocidos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| # | Qué |
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| F15 | `Surface variant='soft'` **no acota un panel** : su track queda a 0.002 de luminancia del fondo de página en claro, y `Surface` no tiene borde. `Card outline` sí acota pero no acepta `gradient` → «panel sosegado con borde» no tiene primitivo. |
| F16 | La ranura `contrast` de la paleta es **blanca en todo escalón sólido** , así que un lienzo de luminancia media deja el cuerpo bajo AA: `primary` 5.18 · `indigo` 5.21 · `plum` 4.75 pasan; `neutral` 3.32 · `secondary` /`slate` 3.30 · `teal` 3.07 fallan en claro. |
| F17 | ** `Text align` es inerte por defecto**: renderiza un `span` y `text-align` no hace nada sobre caja inline. Hay que pedir `as="p"` . |
| F18 | **La fundación de eidos no trae reset de modelo de caja y lo asume del app.** Bajo `content-box` , `[data-field-control]` (`inline-size:100%` + padding) mide 30px más que su contenedor. Resuelto en app-land: `web/routes/blocks/_lib/reset.css` . |
| F19 | ** `onValidSubmit` /`onInvalidSubmit` son no-op silenciosos** si se pasa un `form` ya construido: el componente solo los reenvía al `createForm` que hace él mismo. El handler va SIEMPRE en `createForm` . |
| F20 | Los mensajes de SIUM son **idlangref** (`#?sium.errors.email\|…`): resolver con `uix.langs.t(issue.message, issue.params)` . La demo de docs del `Form` los parte a mano y por eso siempre salen en inglés. |
| F21 | **Los primitivos de layout no pueden cambiar de elemento** : `Text` /`Heading` aceptan `as` , pero `Box` —y `Stack` /`Flex`/`Grid`/`Group`/`Wrap`/`Container`/`Section`— es un `<div>` fijo. Una columna de enlaces no puede ser `ul` /`li`. |
| F22 | **Un `Select` controlado muestra el VALOR crudo hasta abrirse una vez** : las etiquetas las registran los `Select.Item` al montarse y el `Content` portaleado está cerrado. Rodeo: el `child` de `Select.Value` . |
Y dos huecos anteriores que siguen abiertos: ** `Box` /`Surface` `flex` /`grow` no
hacen crecer a un hijo flex** (se rodea con tracks `1fr` de `Grid` ) y ** `Group` no
apila** (usa `Flex direction={{ base: 'column', sm: 'row' }}` ; `hero` todavía
compone sus acciones con `Group` ).
---
## La plantilla ya existe — cópiala, no la reinventes
docs(blocks): registro de F2.1, doctrina de demo del tier y handoff
Documentación al día de lo que se cerró hoy y handoff para retomar mañana.
- `PLAN-blocks.md` §7: **F2.1 `site-header` HECHA** con sus siete commits, la
fase 0 contra el dossier §P1, el landmark que le faltaba a `NavigationMenu`
(arreglado en el canon, no parcheado en el block), el hueco del CTA que
navega y parece botón (registrado, no falseado) y el defecto de framework
que destapó la demo.
- `theming/changelog.md` §46: las 44 variables de cascada de `Box` dejan de
heredarse. Un `Section` regalaba su padding a cada descendiente —la galería
arrastraba ~300px de aire desde F0— y los hijos heredaban anchos y `display`
ajenos. `@property { inherits: false }`, radio verificado sin regresiones.
Lección: una variable que un componente escribe para SÍ MISMO debe declararse
`inherits: false`; si no, deja de ser un prop y se vuelve un contagio.
- `architecture/blocks.md` B-9: la demo de un block se construye sobre el
harness compartido y el block se enseña A SANGRE — nunca dentro de un marco
con relleno ni de una caja con scroll, porque eso cambia lo que el block
hace.
- `src/uix/blocks/README.md`: anatomía de la demo (harness, `{Name}Site`, ruta
`preview`, `DocRow`, catálogo único, ejes en el shell).
- `CONTINUE-blocks.md` (nuevo): handoff — qué toca (F2.2 `hero`), la plantilla
de ficheros para copiar, las reglas que ya costaron sangre (a sangre, iframe
solo para anchos de dispositivo, cada prop un control, nada de backticks en
`<Text>`, ojo con las variables que heredan), la deuda declarada que es
decisión del usuario y el estado exacto de los gates.
Gates al parar: `blocks:check` verde · `svelte-check` 73 errores, todos deuda
ajena (0 propios) · `vitest src/uix/eidos` 353/353 · `contracts.test` con los
3 fallos ajenos conocidos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
```text
src/uix/blocks/{kebab}/
├── README.md # Función · Mapa de composición · Decisiones · Gaps
├── index.ts # export del compound + tipos
├── types.ts # props (todo contenido entra por snippets, B-5/B-7)
└── {kebab}.svelte # composición: solo componentes del canon, sin CSS
web/routes/blocks/{kebab}/
├── +page.svelte # BlockDemo + controles vivos + pestañas de doc
├── {Name}Site.svelte # el block dentro de contenido REAL de producto
└── preview/
├── +layout@.svelte # `@` resetea el layout: la vista previa es su página
└── +page.svelte # sirve {Name}Site leyendo la URL
```
Y luego: marcar `shipped: true` en `web/routes/blocks/_lib/catalog.ts` (raíl y
galería leen esa única fuente) y `npm run blocks:check` .
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
---
docs(blocks): registro de F2.1, doctrina de demo del tier y handoff
Documentación al día de lo que se cerró hoy y handoff para retomar mañana.
- `PLAN-blocks.md` §7: **F2.1 `site-header` HECHA** con sus siete commits, la
fase 0 contra el dossier §P1, el landmark que le faltaba a `NavigationMenu`
(arreglado en el canon, no parcheado en el block), el hueco del CTA que
navega y parece botón (registrado, no falseado) y el defecto de framework
que destapó la demo.
- `theming/changelog.md` §46: las 44 variables de cascada de `Box` dejan de
heredarse. Un `Section` regalaba su padding a cada descendiente —la galería
arrastraba ~300px de aire desde F0— y los hijos heredaban anchos y `display`
ajenos. `@property { inherits: false }`, radio verificado sin regresiones.
Lección: una variable que un componente escribe para SÍ MISMO debe declararse
`inherits: false`; si no, deja de ser un prop y se vuelve un contagio.
- `architecture/blocks.md` B-9: la demo de un block se construye sobre el
harness compartido y el block se enseña A SANGRE — nunca dentro de un marco
con relleno ni de una caja con scroll, porque eso cambia lo que el block
hace.
- `src/uix/blocks/README.md`: anatomía de la demo (harness, `{Name}Site`, ruta
`preview`, `DocRow`, catálogo único, ejes en el shell).
- `CONTINUE-blocks.md` (nuevo): handoff — qué toca (F2.2 `hero`), la plantilla
de ficheros para copiar, las reglas que ya costaron sangre (a sangre, iframe
solo para anchos de dispositivo, cada prop un control, nada de backticks en
`<Text>`, ojo con las variables que heredan), la deuda declarada que es
decisión del usuario y el estado exacto de los gates.
Gates al parar: `blocks:check` verde · `svelte-check` 73 errores, todos deuda
ajena (0 propios) · `vitest src/uix/eidos` 353/353 · `contracts.test` con los
3 fallos ajenos conocidos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
## Reglas que ya costaron sangre (no las re-aprendas)
- **El block se enseña A SANGRE en la página.** Nada entre el block y el borde:
ni marco con relleno, ni caja con scroll, ni cromo pegajoso encima. Medido: un
`Card` desplazaba 21px un header con `offset: 0` (su recipe pinta con
`--card-padding-*` , que `padding={0}` de la capa Box no alcanza), y un
`position: sticky` dentro de un div con scroll es un comportamiento que nadie
vive. **Si un block se ancla a algo, se mide contra lo que se anclará en
producción.**
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Verifica esperando la CONDICIÓN, nunca un timeout fijo.** Con `Form` +`Field`+
SIUM el dev server tarda ~2,5s en hidratar y un screenshot temprano fotografía
el panel todavía invisible (`data-animation-pending` puesto). Esperar a que ese
atributo desaparezca es la diferencia entre verificar y reportar un bug que no
existe.
feat(blocks): `banner` — el aviso de arriba, y tres defectos del canon que no existían
F2.11. El block más FINO del tier a propósito: cuando el canon ya tiene la pieza,
el block es colocación y nada más. Reenvía la superficie entera del `Banner` con
`Omit<BannerProps, 'children'>` —sin re-declarar `intent`/`variant`/`size` ni
estrecharlos—, lo mete en columna con `Container` (`width="100%"` +
`paddingX={0}`: a sangre por fuera, en columna por dentro), envuelve la fila con
`Wrap` en vez de aplastarla, y cablea `Banner.Close` solo si llega `onDismiss`.
La visibilidad NO es suya: es la decisión que ya tomó el componente
(«composición, no un booleano `dismissible`»), así que el app envuelve en su
`{#if}`. Un block que guardara ese estado sería una segunda fuente de verdad.
Tampoco emite sema: el canon declara cero eventos para `Banner` a propósito, y un
aviso persistente sin descarte es tan legítimo como uno descartable.
La demo lo enseña **encima del `site-header` de verdad**, con página para
desplazarse. Un aviso dentro de un recuadro no se parece a un aviso.
## El hallazgo que la fase 0 anticipó
`Banner` estampa `role="banner"` DESPUÉS de sus rest props, así que no se puede
relajar a una región normal. Con el `site-header` en la misma página —que es la
única colocación que shipean las referencias— quedan **dos landmarks `banner`**.
Medido en la vista previa: dos. Lo único que puede hacer un app hoy es nombrarlos
con `aria-label`, y eso hace la demo.
## Y una lección de método que costó una sesión
Reporté tres «defectos del canon» —`aria-label` ausente en `Banner.Close`, `type`
desaparecido de todo `Button`, `IconButton` tragándose el `onclick`— y **los tres
eran falsos**. La causa era la misma: medir demasiado pronto.
Los `data-variant` / `data-size` los escribe el componente de eidos al renderizar,
así que están desde el primer frame; `type`, `aria-label` y los handlers los
aplica la **runtime del morfo en un efecto posterior**. A ~1s: `type` ausente en
3 de 3 botones y ningún clic disparando. A ~6s: `type="button"`,
`aria-label="Descartar"` y el descarte funcionando.
La espera válida es un atributo que solo pueda haber puesto la runtime
—`waitForFunction(() => boton.hasAttribute('type'))`—, no que el nodo exista ni
que se vea. La regla queda afinada en `CONTINUE-blocks.md`, que ya avisaba de
esperar la condición y no el reloj: fallé al elegir la condición.
De paso, un aviso de soma que sí era real y era mío: `Tabs.List` sin nombre
accesible en `BlockDemo.svelte`. Afectaba a las 12 demos del tier.
Verificado en navegador: los 4 intents, los 3 tratamientos, `descartable` sí/no
(con `no` el botón deja de renderizarse, no se oculta), claro/oscuro/RTL y 420px,
la tira siempre por encima de la cabecera, cero desbordamiento horizontal, el
descarte con la página recolocándose, y los controles de la demo moviendo la
vista previa en línea. Gates: `blocks:check` verde (12 blocks) · `svelte-check`
sin errores propios · `docs:check` sin errores míos · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Y la condición tiene que ser algo que escriba la RUNTIME del morfo, no el
render.** Los `data-variant` / `data-size` los pone el componente de eidos al
renderizar, así que están desde el primer frame; `type` , `aria-label` y los
handlers los aplica la runtime en un efecto POSTERIOR. Esperar a que exista el
nodo (o a que se vea) no basta: a ~1s medí `type` ausente en TODOS los `Button`
del árbol y ningún `onclick` disparando, y a ~6s los mismos botones tenían
`type="button"` , `aria-label="Descartar"` y el clic funcionando. Reporté tres
feat(blocks): contact CERRADO — la coordinacion entra en el contrato B
El estado se le pregunta al ESQUEMA, no al Form. `form.isValid` con
`validationBehaviour: 'progressive'` significa «aun no se ha encontrado nada
mal»: un formulario vacio e intacto no tiene errores, asi que se declaraba
valido y `incomplete` era INALCANZABLE al cargar — la seccion pedia resolver la
verificacion con los tres campos vacios, justo el hueco que la doctrina existe
para cerrar. Ahora la raiz valida los valores con `validateSync` de SIUM:
sincrono y sin escribir errores (`form.validate()` habria encendido los tres
campos en rojo en el primer frame).
`state.test.ts` fija la maquina con 9 casos — primer test unitario del tier,
porque `state.ts` es su primera logica pura: el orden de prioridad, que solo
`ready` deja enviar, y que todo estado bloqueado tiene frase.
Contrato B (architecture/blocks.md): seccion «Coordination» con las tres cosas
que posee un block que coordina (estado, forma de sus datos, palabras), B-5
admite el esquema por defecto, B-7 enmendado (posee las palabras de SUS estados
como idlangref con fallback ingles) y la convencion de servicios acotada: el
traductor es el UNICO servicio sancionado. Dice tambien a quien NO aplica — los
blocks de layout no coordinan nada.
i18n por la via real: el arnes registra el namespace `blocks` en las DOS cadenas
de arranque (galeria y previews, que duplican el boot) y la demo se lee en
castellano en vez de caer al fallback ingles.
Ademas: README y pestanas de doc rehechos al reparto nuevo, ficha F2.13 y
bitacora en PLAN-blocks.md, y el control vivo que faltaba para un prop publico
— `verification` con reto / sin reto, porque omitir el prop no es lo mismo que
pasar un estado que nunca verifica.
Verificado en navegador, arco completo y las dos ramas: sin reto vacio → un
campo → tres campos (se desbloquea) → «Enviando…» → «Enviado»; con reto, con
los tres campos llenos el boton sigue bloqueado y la razon pasa a «Resuelve la
verificacion de arriba». `verifying` y `rejected` quedan cubiertos por test
unitario, no por el reto en vivo: el provider de ProofOfHuman escribe `status`
el mismo y el veredicto del app no se sostiene (hilo de canon abierto).
Gates: blocks:check verde (14 blocks) · vitest 9/9 · docs:check 0 ·
svelte-check sin errores propios · prettier limpio en los ficheros propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
«defectos del canon» que no existían.
- **MATIZ CARO (2026-07-31): que lo escriba la runtime NO basta — tiene que no
existir en el SSR.** Esperé `button[type="submit"]` con atributo `type` como
puerta de hidratación en `contact` , y **ese atributo viene ya en el HTML del
servidor**: la espera se cumplía sobre el documento estático y yo tecleaba
antes de que Svelte tomara los inputs. Resultado: los valores entraban en el
DOM, `form.values` seguía vacío, y pasé varias rondas persiguiendo un fallo del
block que no existía (llegué a «arreglar» un `$derived` que estaba bien; lo
revertí al medirlo). **La puerta honesta** : algo que solo pueda haber escrito
el cliente — aquí `<style id="uix-blocks-display">` , que pone un `$effect` de
`BootUix` — más un ida y vuelta reactivo real antes de dar por hidratado.
- **Si instrumentas con `console.log` y no lo ves en el navegador, míralo en el
SERVIDOR.** Un `console.log` dentro de un `$derived` sale por el stdout del dev
server durante el SSR. Ver el log SOLO ahí es la prueba de que el componente no
está corriendo en cliente — fue lo que delató el diagnóstico anterior.
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Playwright headless SÍ sirve para foco y teclado.** El pane suspendido no
(congela rAF y pierde `activeElement` ), pero un `page.keyboard.type()` real
dispara `:placeholder-shown` y `:focus-within` : así se verificó que la etiqueta
flotante del `Field` sube al borde. `page.fill()` usa el setter nativo y NO los
dispara. Un script del scratchpad debe importar
`file:///G:/dev/svelte/vicen/node_modules/playwright/index.mjs` (no resuelve
`playwright` por ruta relativa).
docs(blocks): registro de F2.1, doctrina de demo del tier y handoff
Documentación al día de lo que se cerró hoy y handoff para retomar mañana.
- `PLAN-blocks.md` §7: **F2.1 `site-header` HECHA** con sus siete commits, la
fase 0 contra el dossier §P1, el landmark que le faltaba a `NavigationMenu`
(arreglado en el canon, no parcheado en el block), el hueco del CTA que
navega y parece botón (registrado, no falseado) y el defecto de framework
que destapó la demo.
- `theming/changelog.md` §46: las 44 variables de cascada de `Box` dejan de
heredarse. Un `Section` regalaba su padding a cada descendiente —la galería
arrastraba ~300px de aire desde F0— y los hijos heredaban anchos y `display`
ajenos. `@property { inherits: false }`, radio verificado sin regresiones.
Lección: una variable que un componente escribe para SÍ MISMO debe declararse
`inherits: false`; si no, deja de ser un prop y se vuelve un contagio.
- `architecture/blocks.md` B-9: la demo de un block se construye sobre el
harness compartido y el block se enseña A SANGRE — nunca dentro de un marco
con relleno ni de una caja con scroll, porque eso cambia lo que el block
hace.
- `src/uix/blocks/README.md`: anatomía de la demo (harness, `{Name}Site`, ruta
`preview`, `DocRow`, catálogo único, ejes en el shell).
- `CONTINUE-blocks.md` (nuevo): handoff — qué toca (F2.2 `hero`), la plantilla
de ficheros para copiar, las reglas que ya costaron sangre (a sangre, iframe
solo para anchos de dispositivo, cada prop un control, nada de backticks en
`<Text>`, ojo con las variables que heredan), la deuda declarada que es
decisión del usuario y el estado exacto de los gates.
Gates al parar: `blocks:check` verde · `svelte-check` 73 errores, todos deuda
ajena (0 propios) · `vitest src/uix/eidos` 353/353 · `contracts.test` con los
3 fallos ajenos conocidos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
- **Los anchos de dispositivo (375/768) van por la ruta `preview` en iframe**, y
es opt-in: en dev, dos documentos sin empaquetar a la vez agotan las
conexiones del navegador (`ERR_INSUFFICIENT_RESOURCES` mata las DOS páginas).
- **Cada prop público, un control vivo** en la demo; los ejes de sección (tema,
idioma, dirección, densidad) ya los da el shell, no los repitas.
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Un slot que se apaga se pasa como `undefined` , no se renderiza vacío.**
Declara el snippet arriba y pásalo por prop
(`signup={activo ? banda : undefined}`): así el `{#if}` del block quita también
su hueco. Y **cuidado con el sombreado** : `{#snippet signup()}` pisa un prop
llamado `signup` dentro del componente.
docs(blocks): registro de F2.1, doctrina de demo del tier y handoff
Documentación al día de lo que se cerró hoy y handoff para retomar mañana.
- `PLAN-blocks.md` §7: **F2.1 `site-header` HECHA** con sus siete commits, la
fase 0 contra el dossier §P1, el landmark que le faltaba a `NavigationMenu`
(arreglado en el canon, no parcheado en el block), el hueco del CTA que
navega y parece botón (registrado, no falseado) y el defecto de framework
que destapó la demo.
- `theming/changelog.md` §46: las 44 variables de cascada de `Box` dejan de
heredarse. Un `Section` regalaba su padding a cada descendiente —la galería
arrastraba ~300px de aire desde F0— y los hijos heredaban anchos y `display`
ajenos. `@property { inherits: false }`, radio verificado sin regresiones.
Lección: una variable que un componente escribe para SÍ MISMO debe declararse
`inherits: false`; si no, deja de ser un prop y se vuelve un contagio.
- `architecture/blocks.md` B-9: la demo de un block se construye sobre el
harness compartido y el block se enseña A SANGRE — nunca dentro de un marco
con relleno ni de una caja con scroll, porque eso cambia lo que el block
hace.
- `src/uix/blocks/README.md`: anatomía de la demo (harness, `{Name}Site`, ruta
`preview`, `DocRow`, catálogo único, ejes en el shell).
- `CONTINUE-blocks.md` (nuevo): handoff — qué toca (F2.2 `hero`), la plantilla
de ficheros para copiar, las reglas que ya costaron sangre (a sangre, iframe
solo para anchos de dispositivo, cada prop un control, nada de backticks en
`<Text>`, ojo con las variables que heredan), la deuda declarada que es
decisión del usuario y el estado exacto de los gates.
Gates al parar: `blocks:check` verde · `svelte-check` 73 errores, todos deuda
ajena (0 propios) · `vitest src/uix/eidos` 353/353 · `contracts.test` con los
3 fallos ajenos conocidos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
- **Nada de backticks de markdown dentro de `<Text>` **: se ven literales. Lo que
es código va en `<Code>` .
- **Ojo con las variables de layout que heredan.** `justify` de `Group` se
hereda a los clusters anidados (pon `justify` explícito) y las de `Box` YA no
heredan desde 2026-07-23 (`docs/theming/changelog.md` §46).
- **Un hueco del canon se registra, no se falsea.** Si al componer falta algo,
va a los Gaps del block como candidato a canon y la demo usa lo que hay.
feat(blocks): content-section cierra F2 — la rejilla de escape, y las escalas tipograficas salen de Text
El ultimo block de sitio, y el que mas cerca estuvo de ser un envoltorio:
`Section` + `Container` + `Prose` no habria anadido nada, porque `Prose` ya trae
su medida de lectura. Lo que falta en el ecosistema es el ESCAPE — dentro de un
`Container` nada puede ser mas ancho que el, asi que una figura a sangre hay que
sacarla del articulo y ponerla de hermana, y el orden de lectura se rompe.
La seccion es una rejilla de 5 pistas (canalon · flanco · MEDIDA · flanco ·
canalon) y cada parte dice hasta donde llega: `measure` col 3, `wide` col 2/5,
`full` col 1/-1. Todas las longitudes son tokens del sistema (`--measure-*`,
`--container-width-*`, `--container-padding-inline`). `.Body` y `.Media` son
compound porque SE REPITEN; la cabecera son slots de snippet. `.Body` apaga la
medida de `Prose`: dos duenos del mismo ancho se pelean y ganaria el mas
estrecho en silencio.
Los tres ejes son RESPONSIVE (`measure`, `wide`, y el `width` de cada parte),
resueltos con `eidos.resolve()` — el mismo camino que usan los primitivos de
eidos, asi que los unicos breakpoints en juego son los canonicos. Es lo que un
valor unico no puede decir: `{ base: 'full', md: 'wide' }` = foto a sangre en
movil y contenida de `md` arriba.
CANON: un componente no es libreria de otro. `Heading` importaba CINCO escalas
tipograficas de `Text` (`TextTracking`, `TextLeading`, `TextWrap`,
`TextNumeric`, `TextMeasure`) y al tipar este block repeti la violacion. Las
cinco se mueven a `eidos/lib/types.ts`, que es donde su propio encabezado dice
que van y donde ya esta documentado el precedente del mismo fallo (`ColorRole`
re-escrito a mano que derivo a 8 miembros contra 9). Sin shim de compatibilidad:
`text` y `heading` las importan de la libreria.
Cuatro defectos que solo dijo el navegador:
1. `gap` separa tambien las COLUMNAS y una parte que las cruza se lleva esos
huecos encima — `wide` media 1088 en vez de los 1024 del contenedor con el
que debe alinearse, y a 420px `full` llegaba a 532 dentro de una rejilla de
404 y hacia scrollear la pagina. Van `rowGap` + `columnGap={0}`.
2. Una pista `min(medida, 100%)` ignora sus propios canalones: a 420px la
central se quedaba los 404 enteros y la rejilla se iba a 436.
3. Un token de container es un ancho EXTERIOR: con `--container-width-lg` a
pelo la figura `wide` sobresalia 16px por cada lado respecto al texto de la
seccion hermana de debajo, a 1280/1440/1920. La pista ancha resta ahora
`2 * --container-padding-inline` (992 en `lg`) y el desfase es 0. Lo cazo el
usuario mirando la pantalla: yo habia medido el block contra si mismo, no
contra la pagina.
4. `100%` significa algo distinto dentro de cada parte — en `measure`/`wide` ya
es una pista, en `full` es la rejilla entera con canalones —, asi que capar
el pie igual en los dos casos lo dejaba a 340 contra una columna de 372 en
uno y a 404 en el otro.
Verificado en navegador: barrido 375→1920 sin desbordamiento; alineacion
pie↔prosa identica en 3 viewports × 3 medidas × 3 anchuras (18/18); figura
`wide` a ras del texto de la seccion hermana en 1280/1440/1920; y el valor
responsive cambia exactamente en el `md` canonico (a sangre a 767, contenida a
768). Claro, oscuro, 420px y 1920 mirados, y la geometria confirmada tambien en
el Chrome del usuario.
Semantica propia `figure`/`figcaption` (no interactivos → estructura de
documento, B-8) con el hueco senalado: el canon no tiene primitiva `Figure`.
Contexto de CONFIGURACION, no de estado — es un block de layout y no coordina
nada.
Gates: blocks:check verde (15 blocks) · svelte-check sin errores en lo tocado ·
eidos 28/29 suites (la que falla es `audio-player` sin morfo, del hilo de audio,
ajena) · docs:check 0 · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **`gap` en un `Grid` separa también las COLUMNAS, y una parte que las cruza se
lleva esos huecos encima** (`content-section`, medido): con `gap={8}` y cinco
pistas, la parte que abarca 2/5 medía 1088 en vez de los 1024 del contenedor
con el que debía alinearse, y la de 1/-1 llegaba a 532 dentro de una rejilla de
404 y hacía scrollear la página a 420px. Si las columnas son un instrumento de
medida y no cosas puestas al lado: `rowGap` + `columnGap={0}` .
- **Y `100%` dentro de una pista no es `100%` de la rejilla.** Una pista
`min(medida, 100%)` se come sus propios canalones (a 420px la central se
quedaba los 404 enteros y la rejilla se iba a 436), y algo anidado dentro de una
parte ve el ancho de SU pista, no el de la rejilla — capar igual en los dos
casos descuadra uno de los dos. Los dos se cazaron midiendo, no leyendo.
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Los números de layout se miden.** Tres defaults de `site-footer` (`container`,
la razón de la rejilla, el gap de columnas) salieron mal a la primera y solo el
navegador lo dijo. Si un default decide cuántas cosas caben en una fila, se
comprueba con el contenido real.
---
## Deuda del arnés de demos (mía, sin tocar)
- La galería (`web/routes/blocks/+layout@.svelte`) **arranca UIX en línea** en vez
de usar `_lib/BootUix.svelte` , que es lo que usan los previews: la misma cadena
escrita dos veces, y el motivo de que `reset.css` haya que importarlo en los dos
sitios.
- En `BlockDemo` , a ~1400px, el conmutador de anchos de dispositivo se solapa con
el párrafo que lo precede. Idéntico en los 11 blocks → es del arnés, no de un
block.
---
docs(blocks): la variedad se firma antes de construirla — F2b en cuatro tramos
El usuario señaló que el dossier de referencias nos dejaba muy por debajo en
variedad. El contraste fila a fila de su §P1 contra los 15 blocks dio 6 filas
hechas y 4 resueltas por composición: lo que quedaba pendiente eran DECISIONES
de julio, no medidas. Ninguna de las ocho variantes era un hallazgo nuevo —
todas estaban ya en el README de su block esperando una firma.
Pero ocho variantes no cierran la brecha que se ve, que es de CARDINALIDAD, y
el método para esa ya estaba escrito en el dossier (P6.1: demostrar la
equivalencia variante-a-variante en vez de competir en número de dumps). La
primera versión de la sección no lo trajo y se reescribió entera el mismo día.
F2b queda con cuatro tramos: A las variantes de suelo, B la matriz de
equivalencia por block —el entregable central, con su plantilla y el guard
encendido al final para no dejar 18 READMEs en rojo durante la ola—, C el
catálogo que se reabre y D la página compuesta.
La regla de forma gana el término que faltaba y que más trabajo hace: una
variante alcanzable componiendo es una RECETA demostrada, cero código. Sin él
cada fila de las referencias parece pedir un layout nuevo y el tier engorda por
imitación.
Tres decisiones de catálogo: logo-cloud vuelve (se difirió sobre una premisa
que las refs desmienten, y su valor propio es la tinta monocroma derivada de
tokens, que nadie más puede dar); blog entra por la misma puerta que team;
cookie-consent entra con doctrina propia, porque la ley europea vive en el TIPO
— rechazar al mismo nivel que aceptar, no esenciales apagadas por construcción.
onboarding NO entra: es un wizard con otro nombre, y duplicar función es la
inflación que este modelo existe para evitar.
Y el cierre de F2 deja de mentir: cerrada EN UNIDADES. La página compuesta que
exigía nunca se construyó, así que el claim de que los blocks ensamblan sin
fricción se declara sin demostrar en vez de darse por probado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## Deuda a decisión tuya — SALDADA 2026-08-17
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
docs(blocks): la variedad se firma antes de construirla — F2b en cuatro tramos
El usuario señaló que el dossier de referencias nos dejaba muy por debajo en
variedad. El contraste fila a fila de su §P1 contra los 15 blocks dio 6 filas
hechas y 4 resueltas por composición: lo que quedaba pendiente eran DECISIONES
de julio, no medidas. Ninguna de las ocho variantes era un hallazgo nuevo —
todas estaban ya en el README de su block esperando una firma.
Pero ocho variantes no cierran la brecha que se ve, que es de CARDINALIDAD, y
el método para esa ya estaba escrito en el dossier (P6.1: demostrar la
equivalencia variante-a-variante en vez de competir en número de dumps). La
primera versión de la sección no lo trajo y se reescribió entera el mismo día.
F2b queda con cuatro tramos: A las variantes de suelo, B la matriz de
equivalencia por block —el entregable central, con su plantilla y el guard
encendido al final para no dejar 18 READMEs en rojo durante la ola—, C el
catálogo que se reabre y D la página compuesta.
La regla de forma gana el término que faltaba y que más trabajo hace: una
variante alcanzable componiendo es una RECETA demostrada, cero código. Sin él
cada fila de las referencias parece pedir un layout nuevo y el tier engorda por
imitación.
Tres decisiones de catálogo: logo-cloud vuelve (se difirió sobre una premisa
que las refs desmienten, y su valor propio es la tinta monocroma derivada de
tokens, que nadie más puede dar); blog entra por la misma puerta que team;
cookie-consent entra con doctrina propia, porque la ley europea vive en el TIPO
— rechazar al mismo nivel que aceptar, no esenciales apagadas por construcción.
onboarding NO entra: es un wizard con otro nombre, y duplicar función es la
inflación que este modelo existe para evitar.
Y el cierre de F2 deja de mentir: cerrada EN UNIDADES. La página compuesta que
exigía nunca se construyó, así que el claim de que los blocks ensamblan sin
fricción se declara sin demostrar en vez de darse por probado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Las cuatro se firmaron en `PLAN-blocks.md` §F2b (tramo A) y ya no bloquean:
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
docs(blocks): la variedad se firma antes de construirla — F2b en cuatro tramos
El usuario señaló que el dossier de referencias nos dejaba muy por debajo en
variedad. El contraste fila a fila de su §P1 contra los 15 blocks dio 6 filas
hechas y 4 resueltas por composición: lo que quedaba pendiente eran DECISIONES
de julio, no medidas. Ninguna de las ocho variantes era un hallazgo nuevo —
todas estaban ya en el README de su block esperando una firma.
Pero ocho variantes no cierran la brecha que se ve, que es de CARDINALIDAD, y
el método para esa ya estaba escrito en el dossier (P6.1: demostrar la
equivalencia variante-a-variante en vez de competir en número de dumps). La
primera versión de la sección no lo trajo y se reescribió entera el mismo día.
F2b queda con cuatro tramos: A las variantes de suelo, B la matriz de
equivalencia por block —el entregable central, con su plantilla y el guard
encendido al final para no dejar 18 READMEs en rojo durante la ola—, C el
catálogo que se reabre y D la página compuesta.
La regla de forma gana el término que faltaba y que más trabajo hace: una
variante alcanzable componiendo es una RECETA demostrada, cero código. Sin él
cada fila de las referencias parece pedir un layout nuevo y el tier engorda por
imitación.
Tres decisiones de catálogo: logo-cloud vuelve (se difirió sobre una premisa
que las refs desmienten, y su valor propio es la tinta monocroma derivada de
tokens, que nadie más puede dar); blog entra por la misma puerta que team;
cookie-consent entra con doctrina propia, porque la ley europea vive en el TIPO
— rechazar al mismo nivel que aceptar, no esenciales apagadas por construcción.
onboarding NO entra: es un wizard con otro nombre, y duplicar función es la
inflación que este modelo existe para evitar.
Y el cierre de F2 deja de mentir: cerrada EN UNIDADES. La página compuesta que
exigía nunca se construyó, así que el claim de que los blocks ensamblan sin
fricción se declara sin demostrar en vez de darse por probado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
1. **Tabla de comparación** de features× planes → ** `Pricing.Compare` **, PARTE
del block (el toggle de periodo vive en su contexto y B-10 impide que un
hermano lo lea). NO es el hermano `pricing-table` que esta lista sugería.
2. **Cita en spotlight** → `layout="spotlight"` en `testimonials` .
3. **Lista estática 2/3 columnas** → `layout="list"` en `faq` , con unión
discriminada en los tipos (A-94).
4. ** `form-in-hero` ** → slot `form` del hero.
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Resueltas ya: feature-split/alternante (F2.3b, con `Mockup` ) · hero con fondo
cover (layout `background` ) · CTA que navega y parece botón (el `child` de
`Button` entrega un snippet `content` ; `Button` sigue sin `href` y `Link` sigue
poseyendo la navegación — doctrina en el README de eidos Button §«CTA que
navega»).
docs(blocks): registro de F2.1, doctrina de demo del tier y handoff
Documentación al día de lo que se cerró hoy y handoff para retomar mañana.
- `PLAN-blocks.md` §7: **F2.1 `site-header` HECHA** con sus siete commits, la
fase 0 contra el dossier §P1, el landmark que le faltaba a `NavigationMenu`
(arreglado en el canon, no parcheado en el block), el hueco del CTA que
navega y parece botón (registrado, no falseado) y el defecto de framework
que destapó la demo.
- `theming/changelog.md` §46: las 44 variables de cascada de `Box` dejan de
heredarse. Un `Section` regalaba su padding a cada descendiente —la galería
arrastraba ~300px de aire desde F0— y los hijos heredaban anchos y `display`
ajenos. `@property { inherits: false }`, radio verificado sin regresiones.
Lección: una variable que un componente escribe para SÍ MISMO debe declararse
`inherits: false`; si no, deja de ser un prop y se vuelve un contagio.
- `architecture/blocks.md` B-9: la demo de un block se construye sobre el
harness compartido y el block se enseña A SANGRE — nunca dentro de un marco
con relleno ni de una caja con scroll, porque eso cambia lo que el block
hace.
- `src/uix/blocks/README.md`: anatomía de la demo (harness, `{Name}Site`, ruta
`preview`, `DocRow`, catálogo único, ejes en el shell).
- `CONTINUE-blocks.md` (nuevo): handoff — qué toca (F2.2 `hero`), la plantilla
de ficheros para copiar, las reglas que ya costaron sangre (a sangre, iframe
solo para anchos de dispositivo, cada prop un control, nada de backticks en
`<Text>`, ojo con las variables que heredan), la deuda declarada que es
decisión del usuario y el estado exacto de los gates.
Gates al parar: `blocks:check` verde · `svelte-check` 73 errores, todos deuda
ajena (0 propios) · `vitest src/uix/eidos` 353/353 · `contracts.test` con los
3 fallos ajenos conocidos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
---
docs(blocks): registro de F2.1, doctrina de demo del tier y handoff
Documentación al día de lo que se cerró hoy y handoff para retomar mañana.
- `PLAN-blocks.md` §7: **F2.1 `site-header` HECHA** con sus siete commits, la
fase 0 contra el dossier §P1, el landmark que le faltaba a `NavigationMenu`
(arreglado en el canon, no parcheado en el block), el hueco del CTA que
navega y parece botón (registrado, no falseado) y el defecto de framework
que destapó la demo.
- `theming/changelog.md` §46: las 44 variables de cascada de `Box` dejan de
heredarse. Un `Section` regalaba su padding a cada descendiente —la galería
arrastraba ~300px de aire desde F0— y los hijos heredaban anchos y `display`
ajenos. `@property { inherits: false }`, radio verificado sin regresiones.
Lección: una variable que un componente escribe para SÍ MISMO debe declararse
`inherits: false`; si no, deja de ser un prop y se vuelve un contagio.
- `architecture/blocks.md` B-9: la demo de un block se construye sobre el
harness compartido y el block se enseña A SANGRE — nunca dentro de un marco
con relleno ni de una caja con scroll, porque eso cambia lo que el block
hace.
- `src/uix/blocks/README.md`: anatomía de la demo (harness, `{Name}Site`, ruta
`preview`, `DocRow`, catálogo único, ejes en el shell).
- `CONTINUE-blocks.md` (nuevo): handoff — qué toca (F2.2 `hero`), la plantilla
de ficheros para copiar, las reglas que ya costaron sangre (a sangre, iframe
solo para anchos de dispositivo, cada prop un control, nada de backticks en
`<Text>`, ojo con las variables que heredan), la deuda declarada que es
decisión del usuario y el estado exacto de los gates.
Gates al parar: `blocks:check` verde · `svelte-check` 73 errores, todos deuda
ajena (0 propios) · `vitest src/uix/eidos` 353/353 · `contracts.test` con los
3 fallos ajenos conocidos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
docs(process): el handoff dice donde estamos y por donde se entra
El documento seguia contando la sesion del 2026-08-10: «fase 7, 7 de 15»,
«quedan 7 en este orden», el ledger a 51/27/12 y unas puertas «SIN COMMITEAR».
Nada de eso era cierto ya, y un handoff rancio es peor que ninguno porque se
lee como si fuera el estado.
Puesto al dia:
- **Fase 7 CERRADA 15/15.** Ninguno de los 15 tiene un defecto de composicion:
lo que salio vive una capa mas abajo (canon) o en las FICHAS del ledger. Se
conserva escrito COMO se llego ahi, porque me equivoque en el camino —
declare «conformes» siete blocks de los que solo habia medido un eje, y se
corrigio cuando el pregunto si habia terminado. Un barrido no es la rejilla.
- **Ledger a 64 ARREGLADO / 19 CONFIRMADO / 13 REFUTADO** sobre 96 filas.
- **§Qué queda reescrita como puerta de entrada**, agrupando las 19 vivas por lo
que hace falta para cerrarlas en vez de por severidad: tres bloqueadas por
decision suya (A-67 al eje de sema con la puerta ya elegida · A-47, que por
doctrina es un VALOR de configuracion y no un cambio de CSS por componente ·
A-09, cuya disposicion apagaria la deteccion live), una bloqueada por una
pieza que no existe (A-95 espera a `app-shell`, F3.1), y quince de trabajo
normal — con el aviso de que A-55 no es un defecto visible (1,6% del ancho) y
de que A-65/A-75 son el mismo patron.
- **Las dos sesiones del 16/17** con sus dos commits (`4caf1100a` el renombrado
del eje de tamaño, `d02021aee` los dos arreglos de canon), incluido el residuo
del 35,4% de A-68, que es peor que mi estimacion y queda escrito como tal.
- **Puertas al parar**, con la advertencia de siempre reforzada: la cifra de
`svelte-check` SE MUEVE con otras sesiones vivas en el arbol (72, 74, 75, 76,
77 y 80 en dias distintos), asi que se mide justo antes y justo despues del
cambio, nunca contra un numero recordado. Y los 8 rojos vivos se nombran uno a
uno como ajenos, con el commit que los introduce.
Y tres errores de METODO de estas sesiones, que es lo que un handoff existe para
que no se repita:
1. Medir un default en una demo que lo pisa no es medir un default.
2. El panel del navegador oculto suspende el IntersectionObserver, no solo el
rAF — una medicion dio «contadores congelados» que era la suspension, no el
arreglo. La via fiable es Playwright headless.
3. `Motion` es `once: true`: empujar algo bajo el pliegue DESPUES de cargar no
lo hace invisible, su observador ya disparo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## Estado de gates al parar (2026-08-17 — TODO COMMITEADO Y PUSHEADO)
- `npm run blocks:check` — **verde, 0 errores sobre 15 blocks / 115 ficheros** .
- `npm run docs:check` — **0 errores, 0 avisos** (629 docs).
- `svelte-check` : **72 errores / 59 avisos** , medido justo antes y justo después
de cada cambio — misma cifra, cero regresión, ninguno en lo tocado. ⚠️ Esa
cifra SE MUEVE: con otras sesiones vivas en el mismo árbol se midió 72, 74,
75, 76, 77 y 80 en días distintos. Mídela justo antes y justo después de tu
cambio, **nunca contra un número recordado** .
- Suite completa: **8 rojos en 3 ficheros, todos AJENOS** —
`contracts.test.ts` (6), `eidos/lint.test.ts` y `soma-attr-audit.test.ts`
nombran `waveform` / `skin-media-player` / `radio-group` , que entran por
`bf12b8a57` y `68a48a440` (sesión del media-player, componentes sin morfo), más
un timeout de `engine-orca` que huele a flaky. Ninguno cita nada del tier.
- `prettier` : los avisos que quedan son de ficheros **CRLF preexistentes**
(`motion.svelte`, `motion.css` , `motion/types.ts` , `motion/README.md` ,
`AUDIT-blocks-ledger.md` ) — ya fallaban en HEAD y formatearlos produciría un
diff del fichero entero. Se dejan como están, a propósito.
## Estado de gates al parar (2026-08-09, fase 5 — histórico)
- `npm run blocks:check` verde con las tres reglas nuevas, 15 blocks / 114
ficheros · `vitest src/uix/blocks/` 20/20 · `vitest src/uix/eidos
src/uix/blocks` 410/410 · `docs:check` 0/0 (618 docs) · `svelte-check` 74/54.
## ⚠️ El árbol está COMPARTIDO — cuidado al commitear
El commit `866089a40` incluye ficheros que **ya venían modificados** cuando
empezó la sesión: había trabajo sin commitear de otras sesiones sobre `blocks/` y
`eidos/` , y al editar encima no hay forma de separarlo por fichero. Lo que sí se
respetó: `morfo/{calendar,pagination,rating-group,toolbar,tree-view,schema,types}` ,
`sema/components/textarea.ts` y `soma/tree-view` quedaron FUERA por ser de otra
sesión.
Antes de tocar sema o morfo, mira si hay otra sesión viva ahí. En este mismo día,
una arregló el emparejamiento de las reglas (`closest()`) y el renombrado que mató
el prefijo `form.` en las claves de sonido, y otra (ésta) cambió a qué parte
apuntan los eventos. Se complementaron por suerte, no por diseño.
docs(blocks): auditoria del tier congelada — 29 confirmados, 50 sin verificar
Auditoria de los 15 blocks con 50 agentes: dos dimensiones en paralelo —doctrina
(contrato B + regla de coordinacion, por lectura) y percepcion (sema en vivo en
navegador, interaccion por interaccion)— y despues un verificador ESCEPTICO por
hallazgo, con el encargo de refutarlo y la lista de falsos positivos conocidos
del repo.
90 en bruto → 40 verificados → 29 CONFIRMADOS + 11 refutados (28%)
Ese 28% es la razon de existir de la fase adversarial: sin ella habrian entrado
once acusaciones falsas.
⚠️ 50 hallazgos quedaron SIN VERIFICAR, 18 de ellos marcados ALTA. El tope de 40
lo puso mi script, no el trabajo. No son menos graves: estan sin comprobar.
`AUDIT-blocks-2026-08-01.md` es la UNICA copia — el journal del workflow era de
sesion y ya no existe. Incluye los refutados con su motivo para que nadie los
vuelva a reportar.
Lo gordo confirmado:
- `contact` importa `$libs/forms`, fuera de la lista de la frontera dura 1.
- El nombre accesible del submit de `contact` esta congelado en «Enviar» en los
cinco estados: `Form.Submit` impone su `aria-label`, asi que las palabras de
estado que el block existe para poseer NO llegan al lector de pantalla. El
block cumple su promesa solo a la vista.
- `site-header` congela la pagina: con el cajon abierto, cruzar el `breakpoint`
esconde cajon/overlay/trigger con `display:none` pero deja el Drawer abierto y
el scroll del body bloqueado. Reproducido con swipe tactil real rotando un
telefono; la unica salida es Escape, que en tactil no existe.
- `hero` en `layout="background"`: CTA secundaria a 1.78:1, bajo AA, y el token
que la demo escribe a mano para arreglarlo es inerte.
- `height="100%"` sobre `Card` es INERTE (atributo HTML, no prop) en `pricing` y
`testimonials`: las tarjetas no igualan alto mientras README y demo afirman lo
contrario.
- B-8 sin declarar (landmark + jerarquia de encabezados) en seis READMEs.
La dimension CRUZADA —los 15 en una pagina compuesta— NO se ejecuto, y es ademas
la condicion de cierre de F2 del plan.
La auditoria confirmo su propia premisa: la capa perceptiva no se habia ejercido
NUNCA porque la galeria arrancaba sin packs de sema desde F0, y ahi esta lo mas
feo (todavia sin verificar): el nav del header emitiendo `commit-select`+`affirm`
con earcon audible al pasar el RATON por encima, un `Enter` en `newsletter`
emitiendo dos `commit-submit` con intents contradictorios, y 4 de 7 elementos del
header mudos.
El handoff `CONTINUE-blocks.md` arranca ahora por la auditoria, con el orden
recomendado (verificar los 50 → arreglar por severidad → pagina compuesta →
catalogo → v2) y la deuda declarada del arnes, que triplica la cadena de arranque.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
---
## Sesión 2026-08-01 — lo que cambió
- **`content-section` HECHO** → F2 cerrada 15/15. Rejilla de escape de 5 pistas
(`measure` col 3 · `wide` col 2/5 · `full` col 1/-1), los tres ejes RESPONSIVE
por `eidos.resolve()` , y `.Media` con default ** `measure` **: la figura sigue al
texto y salirse se PIDE, como en todas las referencias.
- **CANON tocado por orden del usuario**: un componente no es librería de otro. Las
cinco escalas tipográficas (`TextTracking`, `TextLeading` , `TextWrap` ,
`TextNumeric` , `TextMeasure` ) salieron de `components/text/types.ts` a
`eidos/lib/types.ts` . `Heading` ya las importaba de `Text` desde antes.
- **La galería EMITE**: arrancaba sin packs de sema desde F0 — el motor estampaba
`data-event-*` y nada era audible. Ahora `events: { sound, haptic, components }`
con la lista DERIVADA del barrel (`Object.values`), no escrita a mano: la lista a
mano del otro arnés se quedó atrás dos veces.
- **Los 15 READMEs declaran su posición** bajo la doctrina de coordinación. El mapa
no es uniforme y está en la sección de arriba.
### Lecciones de verificación que costaron esta sesión
1. **La puerta de hidratación no puede ser un atributo que el SSR ya manda.** Usé
el `type` del submit y la espera se cumplía sobre el documento estático: tecleé
antes de hidratar, los valores entraron en el DOM, `form.values` siguió vacío, y
perseguí un fallo inexistente varias rondas. La puerta honesta es algo que solo
escriba el cliente (`style#uix-blocks-display`).
2. **Medir el block contra SÍ MISMO no basta.** El defecto de los 16px de
`content-section` solo apareció al poner una sección normal debajo y comparar
bordes. Los 15 blocks están verificados cada uno contra sí mismo.
3. **Inspeccionar en una pestaña de fondo miente** : `visibilityState: 'hidden'`
suspende el rAF y deja `data-animation-pending` con `opacity: 0` , que parece un
fallo de visibilidad grave y no lo es.
4. **Screenshot Y MIRAR.** Generé la captura en oscuro y nunca la abrí.