docs(blocks): A-95 deja de ser «bloqueada por una pieza que no existe»

La pieza existe desde ayer. La fila pasa a ARREGLADO con sus cifras: solape 0px
entre el aviso y la cabecera, hit-test dentro de la cabecera, documento = 720 =
el viewport. Lo que la cierra no es un número mejor sino haber quitado la
pregunta que tumbó todas las propuestas anteriores («¿y si tenemos banners a
diferentes alturas?»): el aviso entra en flujo como una fila, así que su altura
es la que sea.

⚠️ Sigue siendo cierto lo que la ficha decía del `Affix`: fuera de un shell, un
aviso con `affix="top"` sigue tapando una cabecera pegada. Es el apilamiento
firmado, no un defecto.

A-109 gana disposición escrita (es del canon: el `role="banner"` va después de
los rest props; dos salidas, y se decide en una sesión del canon).

El handoff cambia de puerta: F3 está abierta y `app-shell` hecho. Queda escrito
lo que la sesión NO llegó a ejecutar — la app de referencia en modo attach, que
sigue siendo el único sitio donde el ecosistema entero se demuestra cableado, y
hoy no lo hace ninguno de los siete shells del repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent 6a53c3dc99
commit 9333acd661

@ -27,7 +27,7 @@ Detail lives in the linked doc, never copied here.
> **Stage 2 CLOSED (2026-07-20) — no solver needed.** The 6 decisions were
> locked (D1 band · D2 measured-pass · D3 flip-only · D4 opt-in · D5 runtime-first
> · D6 now), then **execution disproved the premise**: the template morph does not
> *compute* text contrast, it **inherits** it (steps 11/12 copy the donor's L-curve
> _compute_ text contrast, it **inherits** it (steps 11/12 copy the donor's L-curve
> verbatim; L-driven contrast is ~chroma-invariant under gamut-mapping), so any
> scale generated from a §40-compliant donor library clears the ratified text
> floors **by construction**. Verified on three banks — authored base, leave-one-out
@ -119,7 +119,7 @@ far, to seed its scope:
verdict, 2026-07-07, audit B.1): segment height derives from the SIZE axis
at the FIELD family level — `field` recipe declares `segment-height` with
size-scoped declarations (`calc(var(--field-control-height-{k}) -
var(--space-2))`; the TSC `size:` scope axis exists, unused so far), and
var(--space-2))`; the TSC `size:` scope axis exists, unused so far), and
the per-component copies die (each x-field today re-declares the whole
`height-{xs..xl}` family the field recipe already has). **MANDATED by the
B.1-(i) verdict**: date/time/color-field MUST incorporate the Field
@ -164,7 +164,7 @@ the vocabulary gained the alpha `surface`/`surface-hover` + `hover` slots.
never a scissor; expression waits animation-finish after the hold, capped by
an absolute `MAX_EXPRESSION_WAIT_MS`, never derived from the hold) is recorded
in [`decisions/book-deviations.md`](./decisions/book-deviations.md) D.12 as a
candidate **editorial appendix** for the book (*Diseñando lo que ocurre*,
candidate **editorial appendix** for the book (_Diseñando lo que ocurre_,
HOMOGENEIZADO edition) — the book gives qualitative regions and this is the
materialization doctrine worth feeding back to the source.
@ -240,6 +240,14 @@ navigation tree); parity-floor = v1 scope policy; F2 = **14** site blocks
(+banner · team · contact · content-section); distribution registered as §9;
⌘K stays app-land.
**F3 STARTED 2026-08-19** — `app-shell` shipped with a signed phase 0 (four
decisions: the block wires no services, two scroll models by prop, the top bar
inside the inset, generated bypass links) and a new canon component in front of
it, `SkipLink` (WCAG 2.4.1). It closes ledger row **A-95**, the one thing the
tier could not fix from inside any block: with the shell owning the page's
heights, an in-flow notice and a pinned header overlap by **0px** (measured;
before: 49px, with the hit test landing on the strip).
**Deps**: `scheduler` block gated on `chronos` landing; chat-room block
explicitly NOT here (canon `chat-*` family, §7 above).
@ -282,7 +290,7 @@ same encoder:
- **Codabar · MSI · Pharmacode** — the niche tail JsBarcode still ships. Pure
table work: each is a symbology module + its vectors.
- **GS1-128 (FNC1 + Application Identifier parsing)** — a *profile* of Code 128,
- **GS1-128 (FNC1 + Application Identifier parsing)** — a _profile_ of Code 128,
not a new encoder: the FNC1 code value plus an AI grammar so the HRI can print
the bracketed form. The natural pair for a logistics-facing app, and the same
shape the ISBN profile already established.

@ -138,7 +138,7 @@ Columna **or.** = de qué sección del documento viejo salió la fila (`C` confi
| A-92 | `pricing` | doctrina | BAJA | F7 | Una restricción estructural del modelo de cascada (sólo los presets CSS participan del stagger) vive en un comentario del block y falta en §D.13 de la doctrina… | `docs/theming/motion.md` | CONFIRMADO |
| A-93 | `stats-band` | doctrina | MEDIA | F7 | `Motion` no expone su momento «visto» (privado + `data-animation-pending`), así que un block no puede sincronizarse con el revelado sin violar B-6… | `src/uix/eidos/components/motion/motion.svelte` | ARREGLADO |
| A-94 | `feature-grid · team · testimonials` | doctrina | MEDIA | F7 | Los tres declaran `= AutoGridProps` y ponen `{...rest}` antes de props fijadas: el consumidor tipa `align`/`width` y se descartan en silencio (A-28 ×3)… | `src/uix/blocks/testimonials/types.ts` | ARREGLADO |
| A-95 | `banner` | doctrina | MEDIA | F7 | Con `affix="top"` la tira fijada tapa la cabecera pegada y la navegación queda inalcanzable; la pieza que falta es la que posee las alturas de página (`app-shell`)… | `web/routes/blocks/banner/BannerSite.svelte` | CONFIRMADO |
| A-95 | `banner` | doctrina | MEDIA | F7 | Con `affix="top"` la tira fijada tapa la cabecera pegada y la navegación queda inalcanzable; la pieza que falta es la que posee las alturas de página (`app-shell`)… | `web/routes/blocks/banner/BannerSite.svelte` | ARREGLADO |
| A-96 | `banner` | doctrina | BAJA | F7 | `affixOffset` es prop público sin control vivo en la demo, contra la regla «cada prop público, un control»… | `web/routes/blocks/banner/+page.svelte` | CONFIRMADO |
| A-97 | `feature-grid + 6 blocks` | percepcion | MEDIA | F7 | El texto `color="muted"` mide 3,70:1 en claro — bajo AA de cuerpo — en 8 sitios de 7 blocks; el hallazgo estaba enterrado en la ficha de A-70, que es REFUTADO… | `src/uix/blocks/feature-grid/feature-grid-item-text.svelte` | REFUTADO |
| A-98 | `hero` | percepcion | MEDIA | F7 | Las acciones no apilan en móvil: `Group` es row/nowrap y a 375px los dos botones ocupan 315 de 327px. Síntoma de [A-99]; cae sin tocar el block… | `src/uix/blocks/hero/hero.svelte` | ARREGLADO |
@ -368,7 +368,7 @@ Los trece restantes, por filas vivas primero: `pricing` · `faq` · `site-header
### A-95 — CONFIRMADO · `banner` · doctrina · MEDIA
### A-95 — ARREGLADO · `banner` · doctrina · MEDIA
**Mecanismo** — Con `affix="top"` la tira fijada TAPA la cabecera pegada, y la navegación del sitio queda inalcanzable mientras el aviso siga visible. Medido en `/blocks/banner/preview?affix=top` a 1280×700 con la página desplazada 800px: la tira ocupa `0..54` y el `<header>` del `site-header` `0..31` —se solapan—, y `document.elementFromPoint` en el centro del header devuelve LA TIRA. El apilamiento en sí es correcto y está firmado: `Affix` monta en el peldaño `--viewport-placement-z` = 150, «encima de un sticky header, debajo de menús y diálogos», y el escenario lo dice («las dos compiten por el mismo borde — por eso el aviso va por encima del cromo»). El escenario ya compensa con `padding-block-start: 3.5rem` en la página, pero eso empuja el CONTENIDO: un sticky con `top: 0` se sigue anclando donde está la tira.
@ -380,6 +380,26 @@ Los trece restantes, por filas vivas primero: `pricing` · `faq` · `site-header
**Disposición** — **NO se arregla en `banner` ni en `site-header`**: la pieza que falta es la que POSEE las alturas de página y propaga los offsets, y en el plan del tier es el block `app-shell` (F3.1, sin construir). Hasta que exista, cualquier solución en el banner es un número acordado con otro nombre. Lo que sí procede ya, y es documental: que el README del block diga que `affix="top"` no está pensado para convivir con una cabecera pegada — para eso, hasta que haya shell, el app agrupa aviso y cabecera en un solo `Sticky` (⚠️ esa alternativa está RAZONADA, no medida).
**CERRADA 2026-08-19 — la pieza existe.** El block `app-shell` (F3.1) se
construyó y con él el solape desaparece por CONSTRUCCIÓN, no por compensación:
bajo `scroll="main"` el shell no fija nada. Medido en
`/blocks/app-shell/preview?scroll=main&banner=true` a 1280×720 con la tira
puesta: el aviso ocupa `[0, 54]`, la cabecera `[54, 85]`, **solape 0px**, y
`document.elementFromPoint` en el centro de la cabecera devuelve LA CABECERA.
El documento mide 720 = el viewport, así que tampoco hay página que se desplace
por debajo del cromo.
Lo que lo resuelve no es un número mejor sino quitar la pregunta que el autor
hizo y que tumbó todas las propuestas anteriores («¿y si tenemos banners a
diferentes alturas?»): el aviso entra EN FLUJO como una fila de la rejilla, así
que su altura es la que sea — una línea, dos, o dos avisos — y la cabecera se
ancla debajo sin que nadie declare nada.
⚠️ Sigue siendo cierto lo que la ficha decía del `Affix`: un aviso con
`affix="top"` FUERA de un shell sigue tapando una cabecera pegada, y eso no es
un defecto sino el apilamiento firmado. El README de `banner` sigue siendo el
sitio donde decirlo.
### A-96 — CONFIRMADO · `banner` · doctrina · BAJA
@ -1979,6 +1999,16 @@ un block que no posee nada.
**Evidencia** — `/blocks/landing`: `querySelectorAll('header,[role=banner]')` devuelve dos y, sin `aria-label` en la tira, uno queda anónimo en el árbol de accesibilidad. `src/uix/eidos/components/banner/banner.svelte:41`.
**Disposición (escrita 2026-08-19, con `app-shell` delante)** — Es del CANON y
sigue abierta: mientras `banner.svelte` estampe `role="banner"` después de los
rest props, ni un block ni una app pueden quitarlo. Lo que el shell aporta es
que la página deja de necesitar el apaño para OTRA cosa: sus regiones se nombran
solas, así que el único landmark sin nombre posible es el segundo `banner`. Dos
salidas, y no son excluyentes: (a) canon — estampar el `role` ANTES de los rest
props, que es la clase de precedencia «naming» donde el consumidor gana
(`reference_aria_two_class_precedence_a85`); (b) app — nombrar los dos, que es
lo único disponible hoy. Se decide en una sesión del canon, no del tier.
### A-110 — CONFIRMADO · `feature-split` · doctrina · MEDIA
**Mecanismo** — es el único block de sección sin parte `.Header`: `feature-grid`, `testimonials`, `faq` y `pricing` la tienen. Sus `Row` emiten `Title` a `level={2}` por defecto, así que una sección de tres filas aporta **tres `h2` hermanos** y la sección misma no tiene nombre. Un app puede componer el encabezado a mano como primer hijo (el root renderiza `{@render children()}` dentro de un `Stack`) y bajar las filas a `level={3}` —lo que hace la página compuesta—, pero eso es reconstruir a mano lo que el resto del tier declara.

@ -1,34 +1,81 @@
# CONTINUE — tier blocks (handoff, act. 2026-08-18)
# CONTINUE — tier blocks (handoff, act. 2026-08-19)
**Estado: F1 CERRADA (8/8) · F2 CERRADA EN UNIDADES (15/15) · saneamiento
CERRADO ENTERO · fase 5 (guard endurecido) HECHA · fase 7 (contraste doctrinal)
CERRADA 15/15 · renombrado del eje de tamaño HECHO · A-99/A-98/A-93/A-68
ARREGLADAS · F2b (ola de variedad) ✅ CERRADA ENTERA 2026-08-18, los cuatro
tramos — y con su tramo D queda cerrado también el cierre que F2 debía y no
cumplió: la página compuesta.**
CERRADA 15/15 · F2b (ola de variedad) CERRADA ENTERA con su página compuesta ·
**F3 ABIERTA: F3.1 `app-shell` CONSTRUIDO Y VERIFICADO 2026-08-19**, con su
fase 0 firmada (Q1–Q4) y el canon `SkipLink` nacido delante de él.**
**18 blocks vivos**: site-header · hero · feature-grid · feature-split · pricing ·
**19 blocks vivos**: site-header · hero · feature-grid · feature-split · pricing ·
testimonials · faq · stats-band · cta · newsletter · site-footer · banner · team ·
contact · content-section · **logo-cloud** · **article-grid** ·
**cookie-consent**. Más **una página compuesta**: `/blocks/landing`, con 14 de
ellos.
contact · content-section · logo-cloud · article-grid · cookie-consent ·
**app-shell**. Más **una página compuesta**: `/blocks/landing`, con 14 de ellos.
**A-95 CERRADA** (la única fila que estaba bloqueada por una pieza inexistente):
con `app-shell` el solape del aviso sobre la cabecera es **0px** y el hit-test
cae en la cabecera, medido. La ficha del ledger lleva las cifras.
---
## Por dónde entrar mañana
**Lo que TÚ dejaste pedido, y va primero: «repasar cada bloque».** Un repaso
block a block, con el tier ya completo y la página compuesta como banco de
pruebas. **No hay ficha escrita para ese repaso**: su forma se acuerda antes de
empezar — qué orden, qué se mira en cada uno, y si lo que salga se escribe en el
ledger o en el README de cada block. Pregunta antes de arrancar.
Detrás, por orden: [§Qué queda, por orden](#qué-queda-por-orden) — las **24 filas
CONFIRMADO** del ledger (110 filas: 70 ARREGLADO · 24 CONFIRMADO · 13 REFUTADO ·
3 DATO), de las que **5 nacieron el 2026-08-18** en la página compuesta.
⚠️ Las cifras «96 filas / 64-19-13» que este handoff traía eran una foto vieja.
Las de arriba están contadas hoy sobre el fichero.
**Sigue pendiente lo que TÚ dejaste pedido: «repasar cada bloque»** — un repaso
block a block con el tier de sitio completo. **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 ahora hay una segunda puerta, abierta ayer: F3 sigue.** El orden del plan es
`app-shell` (HECHO) → luego, por coste creciente, `error-page` → `user-menu` →
`notifications` → `settings` → `auth` → `dashboard` → `wizard` → `data-table` y
`kanban`. ⚠️ **Recomendación registrada, sin firmar por ti**: subir **`auth`** al
segundo puesto — es lo que a un producto real le falta de verdad, y su ficha ya
tiene anotado lo que hay que resolver en su fase 0 (cuatro vistas frente a las
seis del dominio; y dos hilos de canon abiertos que la esperan: `Form`/SIUM con
`progressive` no expone mensajes, y `ProofOfHuman` escribe su propio `status`).
### Lo que dejó la sesión del `app-shell` (2026-08-19)
Tres commits: `docs(blocks)` (la fase 0), `uix(skip-link)` (canon nuevo) y
`blocks(app-shell)`.
- **Fase 0 firmada, en `PLAN-blocks.md` §F3.1** — Q1 el block NO cablea
servicios (la integración del ecosistema se demuestra en una app de
referencia, no dentro del block) · Q2 dos modelos de scroll por prop · Q3 la
cabecera dentro del inset (el provider del `Sidebar` ES la fila flex) · Q4
canon `SkipLink` + uno por región montada.
- **`SkipLink` (canon)** — eidos-native, 1 parte, 0 eventos. Posee sus palabras
por ROL de landmark; peldaño propio de z (950, el más alto de la escalera
estática, porque un empate lo rompe el orden del DOM y este enlace es el
primer elemento).
- **Cuatro trampas de layout medidas**, todas 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
`flex-grow/shrink/basis` y pisa al `flex` de la receta de quien lo envuelve ·
**un ancestro con `overflow` se lleva el `position: sticky` de dentro** (por
eso el shell pone su propia caja en vez de `Sidebar.Inset`).
- **Una avería del ARNÉS, no del block**: los previews del tier llevaban los 8px
de margen por defecto del navegador, así que NINGÚN block se enseñaba a
sangre (`x: 8, w: 1264` a 1280), contra la regla dura del tier — la página
compuesta incluida, cuyo README cita cifras a sangre. Arreglado en
`_lib/reset.css`. ⚠️ **Las medidas de anchura tomadas antes del 2026-08-19
llevan ese sesgo.**
- **Gaps de canon que abrió, anotados en el README del block**: el eje `layout`
del `Sidebar` (provider ≠ fila) para una topbar a todo lo ancho, que pedirá
`docs-shell` · `Sidebar.Inset` debería dejar elegir si es contenedor de
scroll · el `Sidebar` no publica su ancho actual · F21 (los primitivos de
layout no cambian de elemento) obliga a dos declaraciones en línea sobre
`<main>` y `<aside>`.
- **Lo que la fase 0 NO ejecutó y sigue sobre la mesa**: la **app de referencia**
(`web/routes/blocks/app/`, `kind: 'page'`) — la primera raíz de composición en
modo attach del repo (`createActiveApp` → `attachActiveUix` → `<Uix>` →
`ActiveEidos` con sus fuentes → `createActivePrefsDomProjection` →
`setActiveApp`/`setBus`/`setPermsContext`). Hoy **ningún** shell del repo hace
nada de eso: los siete arrancan `createActiveUix` standalone y cada uno
reescribe a mano su `localStorage` y su `Set<listener>` de modo. Era el tramo
F3 del plan de la sesión y no se llegó.
Detrás, por orden: [§Qué queda, por orden](#qué-queda-por-orden) — las filas
CONFIRMADO del ledger (110 filas; **A-95 pasó a ARREGLADO el 2026-08-19**).
## ⚠️ LO PRIMERO: la fuente viva es el LEDGER, no el documento del 2026-08-01
@ -240,33 +287,31 @@ 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. |
| 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. |
### b) Bloqueada por una pieza que no existe
### b) ~~Bloqueada por una pieza que no existe~~ — CERRADA 2026-08-19
- **A-95** `banner` — con `affix="top"` la tira tapa la cabecera pegada. No se
arregla en ninguno de los dos blocks: falta la pieza que POSEE las alturas de
página. Mantine lo resuelve en su `AppShell` (`header={{ height }}`); nuestro
equivalente es el block **`app-shell`, F3.1, sin construir**. El plan ordena
ya F3 con `app-shell` PRIMERO por esto (y porque F4.1 lo especializa); la
página compuesta (F2b tramo D) es donde A-95 se re-mide.
- ~~**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.
### 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.** |
| 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.** |
| **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. |
| **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. |
### d) Las 15 restantes — trabajo normal
@ -715,7 +760,7 @@ navega»).
- `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.
src/uix/blocks` 410/410 · `docs:check` 0/0 (618 docs) · `svelte-check` 74/54.
## ⚠️ El árbol está COMPARTIDO — cuidado al commitear

Loading…
Cancel
Save

Powered by TurnKey Linux.