astra
alpha-0.1-background
alpha-0.1-dir-prefs
alpha-0.1-sec-dom
menubar-v4-safe
active-uix
morfo-runtime
morfo-driven-soma
semantuix
glm-5
main
sium-v1.0
${ noResults }
390 Commits (2de1718c3bfc3bf8a3844bc90aee8af11a3e5531)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
2de1718c3b |
refactor(langs): words se pliega en palabras — y el nombre sale del corpus
El pack words desaparece: sus strings de motor entran en palabrasLangs y las guardas (ACTIVE_DEV_TRACK, WIP_TRACKS) dejan de listar 'words'. Las menciones en comentarios de recetas, READMEs, SPECs, demos y docs de proceso pasan a Palabras. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
39d3790803 |
feat(blocks): contact (F2.13) — el bloque de contacto completo
Compound contact.* (header/reason/form/fields/submit/details) con schema y contexto propios, README y ruta de preview; marcado shipped en el catálogo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
e09170f9da |
feat(uix): Menubar.Panel — una entrada puede abrir un panel (role=dialog)
Nueva parte Panel en el morfo (archetype content, role dialog, opcional) y aria-haspopup pasa a propRef: 'menu' cuando la entrada despliega un Content, 'dialog' cuando despliega un Panel. Wrapper eidos Menubar.Panel + receta, y la demo monta el mega-menú/form-panel anclado a la barra o al trigger. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
ae7e3462e5 |
feat(barcode): el ISBN es un PERFIL de entrada sobre EAN-13, no una simbología
Un código de barras de ISBN ES un EAN-13: ISO 2108 mete los libros en el mismo espacio GTIN-13 que todo lo demás bajo los prefijos Bookland 978/979 — por eso el mismo lector lee una novela y una lata de garbanzos. Así que `isbn` NO entra como noveno valor de `symbology`: declararlo haría que `data-symbology` afirmara un estándar que no existe. Entra como perfil de entrada sobre `ean13`, que es la forma que adoptan las implementaciones serias. - **Separadores tolerados** en las simbologías NUMÉRICAS: un ISBN viaja con guiones y un GTIN con espacios. Code 128 y Code 39 quedan fuera a propósito — ahí un guión es dato codificable y quitarlo cambiaría la cadena en silencio. - **ISBN-10 → Bookland**: un valor de 10 caracteres en `ean13` se lee como ISBN-10, se le valida SU dígito de control (pesos 10…1, mod 11, con `X` valiendo 10) y se convierte con prefijo 978 y mod-10 recalculado. La validación previa es la parte que importa: sin ella, un ISBN-10 mal tecleado se convierte en un EAN-13 perfectamente válido que apunta a OTRO libro — el modo de fallo más caro del dominio. Falla con `invalid-check-digit`. - **La hyphenación NO es nuestra** y queda documentada como tal: las posiciones de los guiones no se calculan desde el número, dependen de las tablas de rangos de la International ISBN Agency, que son dato versionado con fecha de caducidad. Meterlas aquí pondría un reloj de mantenimiento dentro de una librería de cero dependencias. Si algún día se pinta la línea `ISBN 978-…` sobre las barras, el string con guiones lo trae el consumidor. Y un defecto que el ISBN destapó, anterior a él: **el nombre accesible mentía**. `aria-label` anunciaba el valor CRUDO mientras el símbolo codificaba otro — pasaba ya con cualquier EAN al que se le añade el dígito de control, y con el ITF-14, y con Code 39 al mayusculizar. Ahora anuncia lo que el símbolo lleva de verdad. - Vectores publicados en los tests: `0-306-40615-2` → `978-0-306-40615-7`, y los dos casos de control `X` (`0-8044-2957-X`, `0-9752298-0-X`). - `D-1.2` estaba ROTO en el commit anterior y no lo vi: prettier parte la unión `type Tab` a 102 caracteres y el audit exige la forma de UNA línea que documenta la guía de demos. Restaurada con `prettier-ignore`; el audit vuelve a PASS con 0 errores. - `F-1.4` se rompió al escribir la sección ISBN: mencionaba `## Gaps` en línea y el regex del audit engancha esa primera aparición. Reescrito. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
75017e5515 |
feat(blocks): `team` — las personas, y un contexto que sirve para una sola cosa
F2.12. Compound porque `Member` SE REPITE (el app mapea sobre N), como
`feature-grid`. Pero es el primero del tier cuyo **contexto existe para una sola
cosa**: el `align` de la sección viaja a cada `Member` y a su fila de enlaces, así
que la foto, el nombre, el cargo y los enlaces comparten eje sin que el app repita
la alineación en cada tarjeta. Un `align` explícito en una parte siempre gana.
Esa es la diferencia con `feature-grid`, cuyos ítems solo se repiten y por eso no
llevan contexto: **la regla de forma se aplica parte por parte, no block por
block**.
## Dos decisiones de forma
- **Sin `.MemberAvatar`.** El plan la dibujaba; no tendría nada que añadir sobre
`<Avatar size radius>` y escondería su API (`Image`, `Fallback`, los estados de
carga). El app compone el `Avatar` del canon directamente dentro del `Member`.
Se envuelve lo que el block DEFAULTEA —tipografía, retícula, eje—, no lo que
solo reenvía.
- **El nombre es un `Heading level={3}`** apagado con `size="sm"`: una persona en
una retícula tiene nombre, y las referencias lo marcan igual. El nivel es
estructura del documento; el tamaño, tipografía. Así un lector de pantalla puede
saltar de persona a persona.
Y un detalle que separa cumplir de sobresalir: la fila de enlaces va pegada al
fondo de la tarjeta (`marginTop: auto`). Con biografías de distinto largo las filas
quedaban a alturas distintas y la retícula se leía descuadrada; sin biografías las
tarjetas ya miden lo mismo y la regla no cambia nada.
## Encontrado al verificar
**Un `columns` fijo no colapsa.** A 420px, cuatro columnas dejan celdas de ~90px
con el nombre partido en tres líneas y el avatar desbordando. El default del block
es fluido (`minChildWidth`), así que el fallo era de la demo: ahora pasa
`columns={{ base: 2, md: N }}` — para eso están los props responsivos. Anotado en
el README.
Verificado en navegador, esperando el atributo que pone la runtime del morfo y no
el reloj: 2/3/4 columnas, `align` center/start propagándose por contexto al eje de
cada miembro, biografía sí/no, claro/oscuro/RTL y 420px, seis avatares, doce
botones de icono **con nombre propio por persona** («Escribir a Ada Lovelace», no
«correo»), escalonado estructural con índices 0,1,2…, cero desbordamiento
horizontal y cero errores de página. Gates: `blocks:check` verde (13 blocks) ·
`svelte-check` sin errores propios · `docs:check` 0 · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
c57b66dc92 |
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 |
|
|
08ee18a8e1 |
fix(sound): `decode()` — la puerta que faltaba, y el estudio deja de abrir su contexto
A-1 de la auditoría (`PLAN-sound-engine` §16): `analyze.ts` hacía `new Ctor()` propio para decodificar el `.wav` importado. Medido inyectando un WAV real por el `input[type=file]`: **1 → 2 contextos**. Transitorio —se cerraba al acabar el decode— pero es exactamente el antipatrón que el art existe para impedir, en la misma página que usé como prueba de la invariante. Y el aviso de «segundo contexto» no lo vio, porque sólo cuenta los contextos que crea el art (A-2). No era un despiste de esa página: **la superficie del art no ofrecía la operación**. `preload(urls)` decodifica y esconde los `AudioBuffer` en una caché privada, así que quien quiere las MUESTRAS —un waveform leyendo picos, un analizador midiendo un fichero— no tenía puerta y se abría la suya. - **`EngineSound.decode(data): Promise<AudioBuffer | null>`**. Segunda puerta, simétrica de `context`: una para quien quiere un GRAFO, otra para quien quiere las MUESTRAS. Las dos llevan al mismo contexto. - **No pasa por `getOrCreateContext()`**: decodificar funciona sobre un contexto suspendido, y `resume()` fuera de un gesto puede quedarse pendiente para siempre — un `decode()` que se espera desde la UI no puede colgar de eso. Se añade `ensureContext()`, crear sin resumir. - **No cachea** (los bytes crudos no tienen clave; la caché por URL es de `preload`) y **devuelve `null` en vez de lanzar**. - **Desviación declarada** de lo que §16.2 proponía: NO se añade el acceso a los buffers cacheados. Hoy no tiene consumidor —`Waveform` no existe— y añadir superficie sin consumidor es justo lo que §16.1 le reprocha al art. - `analyze.ts` toma el motor por puerto estructural; `AnalyzePanel` lo lee de `getActiveUix()`, que es el idioma de la casa, no prop-drilling. MEDIDO en Chrome: importar un WAV crea ahora **1 contexto** en vez de 2, y el earcon posterior reutiliza ése. Y sigue analizando bien: un tono de 440 Hz a 8 kHz, decodificado sobre el contexto compartido a 44,1 kHz, se mide como **441 Hz** — el resampleo no falsea la medida. Guards nuevos en `engine-sound.test.ts`: decodifica sobre el contexto compartido SIN llamar a `resume()`, un segundo `decode` no crea otro contexto, y devuelve `null` con bytes indecodificables o sin contexto. Verificación: art + sema 17 suites / 201 tests · `check` en la baseline exacta (73 errores, 0 propios). Quedan abiertos A-2 (el aviso es ciego a los contextos crudos), A-3 (S-1 no está cerrado), A-4 (opciones del canal tiradas en silencio) y A-5 (`gainScale` en el master compartido) — los tres últimos cambian comportamiento público y esperan decisión. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
be3cbf680e |
fix(sound): la invariante de un solo AudioContext, cerrada en attach y medida
`PLAN-sound-engine` daba por cableada la propiedad del motor «en los dos modos de arranque». En attach no lo estaba: `defineUixServices` registraba `motion` y `scene` junto a `dom` pero NO `sound`, y `defineEngineSemantic` no reenviaba `soundEngine`. Una app en attach con sonido activo tenía el motor privado del canal MÁS `uix.sound` — dos motores, y dos contextos en cuanto el reproductor primase el suyo. `attachActiveUix` DOCUMENTABA el footgun en un comentario en vez de cerrarlo, mientras `contracts.ts` ya prometía `singleContextPerDocument`. - `defineUixServices` declara `sound` junto a `motion` / `scene`, y `defineEngineSemantic` lo toma como `serviceDependencies: ['dom', 'sound']` y lo reenvía. Degrada igual que `dom`: sin el servicio declarado llega `undefined` y el canal vuelve a crear el suyo (construcción directa y tests). - La nota de `attachActiveUix` pasa de describir el footgun a describir el cierre, y deja escrito el único caso que sigue vivo: un esquema de servicios a mano que declare `events` sin `sound`. - Fila `events` de `contracts.ts` con `serviceDependencies`, y el assert DERIVA de la tabla en vez de hardcodearla — que es lo que su propia doctrina pide. - El estudio de sema (`/temas/sema`) enchufa `uix.sound`. Era la única página capaz de ejercitar la inyección y no lo hacía. MEDIDO en Chrome, no deducido. Con el motor privado, cada edición del draft reconstruye el `EngineSemantic`, el `dispose()` del canal cierra el contexto y el siguiente disparo abre otro: 9 ediciones dejaron **10 contextos creados y 9 cerrados**, perdiendo además la caché de samples decodificados en cada una. Con el motor compartido: **1 contexto, 0 cierres**, y 24 osciladores construidos en 6 ciclos — el earcon sigue vivo. Dos guards, y los dos VISTOS FALLAR sin el cableado. Es la lección de la retirada de F2, donde una medición vacua se presentó como prueba: - `active-uix.svelte.test.ts`: el `events` de la app prima el motor de la app (quitando el reenvío: `expected null not to be null`). - `sound-e2e.test.ts`: un motor inyectado sobrevive a 3 reconstrucciones con UN contexto y sin `close()`; sólo lo cierra quien lo creó. Verificación: `client` 130/130 · 842/842 · `server` 262/264 · 3718/3722 — los 3 rojos son ajenos (`aura`, `menubar`, `radio-group`) y el cuarto fue un timeout de `soma-attr-audit` por contención, verde aislado en 3,1 s · `check` en la baseline exacta (73 errores, 0 propios). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
eb13fff409 |
feat(blocks): `site-footer` — el pie de la página, con el selector de idioma vivo
F2.10. Es el block donde la regla de forma del tier se ve mejor: `Column` SE
REPITE (el app mapea sobre N grupos), así que es parte compound; la marca, la
banda de alta, lo social, lo legal y el `extra` no se repiten ni coordinan, así
que son slots. El landmark es un `<footer>` de verdad: `contentinfo` sin pedir
nada.
Las columnas van en `AutoGrid minChildWidth`, **sin un solo breakpoint**: el mismo
código sirve para 2 grupos o para 5, y en móvil caen a dos. La entrada es un
`Motion trigger="viewport"` solo en la región superior y **sin escalonado** — una
línea de copyright que aparece con fundido es teatro, y nadie lee un pie columna
por columna.
## Los dos huecos que nombraba el dossier, cerrados
- **La banda de alta al boletín EN el pie** (3 de 7 en Tailwind Plus) como slot
`signup`, con su propia fila a lo ancho: en la columna de la marca (~230px) la
fila de correo tendría que apilarse. El formulario es del app; este block NO
importa el block `newsletter` (B-10) ni inventa validación.
- **El selector de idioma** como `extra` (el slot libre de D-BLK.6), y está VIVO:
escribe `uix.prefs.setIntent('language', …)`, el eje real del ecosistema.
Verificado — al elegir «English», `document.documentElement.lang` pasa a `en`.
## Tres números que salieron de medir, no de suponer
- El `container` bajó de `xl` a **`lg`**: con `xl` el pie no se alineaba con
ninguna sección de la página compuesta encima.
- La rejilla superior pasó de `1fr 2fr` a **`1fr 3fr`**: con `2fr`, un pie de
cuatro grupos se partía en 3+1.
- El gap entre columnas es **6, no 8**: con 8, cinco grupos no comparten fila al
ancho `lg` (728px justos). Horizontal más apretado que vertical, porque el gap
horizontal es el que decide cuántos grupos caben.
## Dos hallazgos de canon (F21/F22 en `PLAN-blocks-quality.md` §6)
- **Los primitivos de layout no pueden cambiar de elemento.** `Text` y `Heading`
aceptan `as`; `Box` —y por tanto `Stack`, `Flex`, `Grid`, `Group`, `Wrap`,
`Container`, `Section`— renderiza un `<div>` fijo. Consecuencia: una columna de
enlaces no puede ser `<ul>/<li>`, que es como la marcan las referencias. Un
block solo puede elegir entre divs o escribir markup que luego no puede estilar.
- **Un `Select` controlado muestra el VALOR crudo hasta que se abre una vez.**
`getDisplayText()` resuelve contra un registro de etiquetas que llenan los
`Select.Item` al montarse, y con el `Content` en un portal cerrado no hay ninguno
montado: `value=['es']` pintaba «es» en vez de «Español». Rodeado con el `child`
de `Select.Value`.
Verificado en navegador: 2/3/4/5 columnas, banda de alta sí/no, claro/oscuro/RTL
y 420px (2×2 columnas, fila de correo apilada), cero desbordamiento horizontal,
separador `aria-hidden`, los tres `IconButton` con nombre obligatorio, y el
idioma cambiando de verdad. Cero errores de página. Gates: `blocks:check` verde
(11 blocks) · `svelte-check` sin errores propios · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
f69cf76cfd |
feat(blocks): `newsletter` — el alta al boletín, y el reset que el framework daba por hecho
F2.9. Slots de snippet (eyebrow · title · description · field · submit · note ·
children), `center` y `justified`, y `panel` como interruptor real: el panel de
marca es la cara de las referencias, pero el control, la ayuda y el error están
calibrados para la superficie de página, así que apagarlo es un estado de primera
y no un fallback.
La fila es `Grid templateColumns={{ base: '1fr', sm: '1fr auto' }}`: el campo se
queda la pista libre y la acción abraza su contenido —eso es lo que hace que un
alta se lea como UN gesto— y bajo `sm` pasa a una columna, porque un botón al lado
de un campo de correo deja inservibles a los dos.
El block NO valida y NO emite sema. `Form` posee el runtime, el esquema, dirty/
touched, la agregación de errores y el foco al primer error; `Field` posee el
cableado ARIA; el morfo del `Form` ya declara `commit-submit` y `signal-invalid`.
El app posee esquema, valores y handler. No hay ni un `if` sobre un correo aquí.
## Superación del dossier: la etiqueta
El único hueco que el dossier nombraba era la nota de privacidad, y está. Pero la
diferencia de verdad es la etiqueta: las referencias shipean la fila escondiéndola
con `sr-only` o dejando solo un placeholder. El canon tiene `Field floatingLabel`
—arranca dentro del control y sube al borde al enfocar o rellenar—, así que la fila
queda alineada CON etiqueta real y asociada. Verificado con pulsaciones de teclado
de verdad: 10px dentro en reposo → −11px sobre el borde al enfocar y al rellenar.
## Tres hallazgos de canon más, medidos
- **La fundación de eidos no trae reset de modelo de caja y lo asume del app.**
`[data-field-control]` declara `inline-size: 100%` + padding, así que bajo
`content-box` el control mide 30px más que su contenedor: el campo se metía por
debajo del botón de envío. Campo 480 / control 510 en la galería frente a
502 / 502 en los docs de componentes, que sí resetean (igual que
`web/routes/active/styles.css`). Arreglado en app-land con
`web/routes/blocks/_lib/reset.css`, con A/B sobre los 10 previews y 5 páginas de
shell: cambia el newsletter y NADA más. Hay que importarlo dos veces porque la
galería arranca UIX en línea en vez de pasar por `BootUix` — deuda del arnés,
anotada en el handoff.
- **`onValidSubmit` es un no-op silencioso** cuando se pasa un `form` ya
construido: el componente solo lo reenvía al `createForm` que hace él mismo. El
envío validaba, limpiaba el error y no anunciaba nada. Por eso el block no
expone el prop: el handler va en el `createForm` del app.
- **Los mensajes de SIUM son idlangref.** La vía correcta es
`uix.langs.t(issue.message, issue.params)` —verificado, sale «Debe ser una
dirección de correo válida»—, pero la demo de docs del propio `Form` parte la
cadena a mano tras el `|`, así que el único ejemplo del repo enseña el patrón
equivocado y siempre muestra inglés.
Los tres quedan en los gaps del README y en `PLAN-blocks-quality.md` §6
(F18/F19/F20), sin tocar nada fuera del tier.
Verificado en navegador el arco completo: correo inválido → error traducido con
`role="alert"`, `aria-invalid`, `aria-describedby` y foco al primer error; correo
válido → confirmación del app (`Callout` afirmativo con la dirección) y error
limpio. Claro/oscuro/RTL, tres colores, `panel` sí/no, 420px. Cero errores de
página. Gates: `blocks:check` verde (10 blocks) · `svelte-check` sin errores
propios · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
03d6796582 |
feat(blocks): `cta` — el panel que pide el siguiente paso, y tres mentiras que destapó al medirlo
F2.8. Slots de snippet (eyebrow · title · description · actions · children),
misma forma que `hero`: las partes de un CTA no repiten ni coordinan, así que un
compound no se gana nada. Dos disposiciones: `center` para cerrar una página y
`justified` —copia al inicio, acciones al final— que es el hueco que nombraba el
dossier. Un solo `Motion trigger="viewport"`: el panel llega ENTERO, porque un
CTA es una sola afirmación y repartir sus tres partes se leería como duda.
Lo que salió al verificarlo en navegador, medido y no a ojo:
- `variant='soft'` **fuera de la API**. Su track queda a `oklch(0.9932)` contra
un `--color-surface-default` de `oklch(0.9911)`: 0.002 de luminancia, o sea
ningún panel en claro. Y `Surface` no tiene borde al que caer. Cortar el prop
es más barato que shipear un estado que se esfuma.
- La ranura `contrast` de la paleta es blanco en TODO escalón sólido, así que un
lienzo de luminancia media deja el cuerpo por debajo de AA: `primary` 5.18 ·
`indigo` 5.21 · `plum` 4.75 pasan en ambos modos; `neutral` 3.32 · `teal` 3.07
fallan en claro. El block reenvía cualquier `color`; la demo solo ofrece los
que pasan.
- `Text align` es inerte por defecto: renderiza un `span`, y `text-align` no hace
nada sobre una caja inline. `align="center"` dejaba la copia a la izquierda
dentro del layout centrado, sin avisar. Rodeado con `as="p"`.
Y `Group` no apila: a 420px la etiqueta de la acción secundaria se parte contra
el botón primario, así que las acciones van en `Flex direction={{ base:
'column', sm: 'row' }}`. `hero` compone las suyas con `Group` — anotado.
Los tres hallazgos de canon quedan en los gaps del README del block y en
`PLAN-blocks-quality.md` §6 (F15/F16/F17), sin tocar nada fuera del tier.
Verificado: `center` y `justified` en claro/oscuro/RTL y a 420px, tres colores,
entrada disparada, descripción en `<p>` centrada, cero errores de página.
Gates: `blocks:check` verde (9 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
3097cfcb69 |
revert: deshacer la auditoría entera — se hizo sin leer la doctrina
Revert de los 7 commits de la sesión del 2026-07-29/30: |
2 months ago |
|
|
4e785d32bf |
feat(blocks): F2.7 `stats-band` — la cifra que se cuenta sola
La fila de números que respalda lo que la página acaba de afirmar. Compound
(`.Stat` repite, sin contexto): `<StatsBand>` + `.Stat` + `.Value` + `.Label`.
- **Compone, no reinventa**: la semántica de KPI es el `Metrics` del canon, leído
de su API y pasado tal cual (precedente `faq`/`Accordion`). `Metrics` es
surfaceless, que es exactamente lo que una banda necesita: no son tarjetas de
panel. Un delta, un icono o un sparkline son `Metrics.Delta`/`.Icon`/`.Chart`
que la app compone dentro — el block no los re-expone.
- **La superación literal del dossier**: `<StatsBand.Value count={12500} />`
compone `CountUp`, así que la cifra se cuenta sola al entrar en pantalla.
NINGUNA referencia puede shipearlo: todas entregan markup estático. El formato
locale-aware y el salto directo bajo reduced-motion vienen del `CountUp`, no
de aquí.
- **`count` es opt-in, nunca default**: una cifra que se anima sin que el lector
lo pida es ruido, y algunas no son contables («99,98 %»). Sin `count`, la
cifra la pone la app por children.
- **Ni una palabra ni un separador salen del block** (B-7): las palabras por
children, el formato por `uix.format` dentro del `CountUp`.
- Escalonado estructural: `data-stagger` en la banda y cada `.Stat` es un
`Motion trigger="viewport"`, así que las cifras aterrizan una tras otra sin un
solo milisegundo escrito a mano.
Verificado en navegador (con la banda bajo el pliegue, para cazar el conteo desde
el primer fotograma): 4 cifras con índices estructurales 0,1,2,3 contando
—9377→11.678 · 255→318 · 36→45—, el porcentaje quieto por no ser contable, y el
separador de millares por locale (`11.678`). Cero errores de página.
`blocks:check` verde (8 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
2 months ago |
|
|
c39170abbf |
fix(uix): la auditoría del sistema — lo que los guards no veían
Auditoría clean-room de todo ActiveUIX (excluido `web/`), componente a
componente. Lo que sale de aquí no es una lista de bugs: es un patrón.
El framework validaba que lo escrito fuese VÁLIDO, no que lo declarado
se CUMPLIESE — y sus guards fallaban ABIERTOS.
## El colapso de las uniones de props (95 → 0)
Un `Props` de eidos es `{ …props propias… } & <atributos nativos>`.
Cuando el elemento declara un atributo homónimo, la intersección funde
ambos y una unión estrecha contra el `string` nativo COLAPSA a `string`.
Causa: `Without<T, U> = Omit<T, keyof U>` invocado como `Without<T, {}>`
— `Omit<T, never>`, un no-op — 433 veces en soma; sólo 3 con argumento
real. Invisible para `svelte-check`: ensanchar un tipo no es un error,
es una garantía perdida.
Medido: 95 props en 72 componentes. `<Avatar color="nonsense">`
compilaba. `ComboboxInput.size` chocaba con el `<input size>` numérico
y era inusable. Migrado con codemod sobre AST (nunca regex) a
`Own & Omit<Nativos, keyof Own>`: 92 tipos en 73 ficheros + carousel a
mano. `check` no se movió.
Garantía nueva: `eidos/prop-surface.test.ts` (PROP-1) compara los
literales de la anotación del autor contra los de la propiedad pública.
Verificado que falla reintroduciendo el defecto.
## Los cuatro guards que fallaban abiertos
- `translations:check` crasheaba en CADA ejecución de su historia — un
stripper de comentarios borraba `//` dentro de strings. Sustituido por
import dinámico. Al arrancar destapó 8 slots `texts` sin traducción.
- `soma-attr-audit` agotaba el timeout de 5 s: sin veredicto, verde por
omisión.
- `component-audit` D-7.4 hacía `continue` mudo cuando el tipo no
resolvía. Ahora resuelve con el checker de TypeScript
(`scripts/prop-unions.ts`): puntos ciegos de 124 → 3.
- `component-audit` R-1.1: el regex casaba `[data-motion='reduce']` y
daba PASS por el motivo equivocado.
Regla adoptada: un guard que no puede evaluar TIENE que decirlo. El
informe lleva ahora bloque «Not verified» y recuento en el resumen.
## D-1 · tooltip y D-2 · card, cableados
`tooltip` declaraba 3 eventos `emerge` que nadie emitía. Ahora emiten;
`present` pasa a `sequence: 'post'` — con `'pre'` el hold de ~240 ms
gateaba el montaje del propio overlay.
`card` declaraba `commit-select` sin emisor posible (scope sin soma).
Puente headless en `soma/components/card/` con la forma ya establecida
por `menu-dial` / `onion-menu`: eidos posee estado y render, soma posee
sólo el `SomaRuntime` que emite.
## Documentación: 22 mentiras corregidas
`docs/` afirmaba guards inexistentes (`NO_MISSING_PROVIDER_TESTS`),
APIs con firma equivocada y un modelo de Motion que el código no
implementa. Corregido en CANON, arquitectura, glosario, theming/motion,
checklist y los README de `motion` / `callout` / `arts/motion`.
## Además
- CardGroup: la descripción se metía en la primera celda del grid.
- Motion: `data-state` siempre estampado, salida real en `leave()`,
token fantasma `--motion-stagger-each-default` eliminado.
- `engine-motion`: `handoffState` Map → WeakMap (fuga por nodo).
- `mockup`: primitivo crudo → token de rol (R-4.6).
- 5 catálogos de traducción que faltaban.
Handoff: `docs/process/CONTINUE-audit-2026-07-29.md`.
Batería: check 0 errores en src · vitest server 3701/3701 ·
docs:check 0/0 · component:audit 161 PASS / 2 NEEDS-WORK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
d4c49aedbf |
feat(eidos): `Barcode` — el código de barras 1D, con encoder propio y cero dependencias
Ocho simbologías desde su estándar ISO publicado (Code 128 con auto-switching A/B/C, EAN-13/8, UPC-A/E, Code 39, ITF/ITF-14) en `$libs/barcode`: tablas transcritas de la especificación o derivadas de su regla de construcción, sin una sola dependencia npm añadida. JsBarcode y bwip-js son referencia de corrección, jamás import — la misma doctrina con la que `$libs/qr` usó a Nayuki. Ningún headless del mercado trae barcode: el terreno lo ocupan encoders imperativos sin anatomía ni a11y. - **La física manda sobre la ergonomía visual.** La dimensión X (`moduleWidth`) es el ancla de escaneabilidad y la altura de barra un eje libre — un escáner lee una franja horizontal. Por eso la geometría es px absolutos en el viewBox (precedente: la familia `chart`) y no el token `size` cuadrado del QR: colapsar los dos ejes en uno haría que la X dependiera de la altura, que es justo lo que no puede flotar. La zona muda va en módulos, con el mínimo de cada estándar. - **Lo que hace reconocible a la familia.** Las guardas EAN/UPC bajan hasta la línea base y los dígitos se imprimen dígito-bajo-dígito sobre su celda de 7 módulos, en mono (la HRI del estándar es OCR-B, de ancho fijo). ITF-14 lleva su barra portadora por fuera de la zona muda, que no puede comerse. - **Un valor no codificable es un ESTADO, no una excepción.** `encode` lanza un `BarcodeError` tipado; el wrapper lo captura, marca `data-invalid`, avisa por `onInvalid` (deduplicado por razón: `encoded` es un objeto nuevo por pulsación) y deja un marco placeholder decorativo. Un EAN a medio teclear es un paso normal de edición; dejarlo reventar dentro de un `$derived` tumbaría la página. - **`'text'` entra en `MorfoElement`** — y también en el schema de sium, que tiene su propia lista literal. Añadir solo la unión de TypeScript compila y falla en runtime; lo cazó `morfo:check`, no `npm run check`. Verificación: 48 tests de encoder en tres capas (invariantes estructurales de cada tabla · vectores publicados transcritos aparte · decoders independientes que releen la retícula), la lectura real confirmada con escáner por el usuario, y la retícula decodificada desde el SVG ya pintado en navegador. `component:audit` PASS con 0 errores; los 2 warnings (D-1.5 / D-4.3) son los mismos que da `button`, la demo canaria: sus regex buscan la forma v1 inline del MutationObserver y del `emit`, que la v2 movió al harness compartido. Demo v2 canónica de 9 pestañas. El alcance diferido (Codabar/MSI/Pharmacode, GS1-128, add-ons EAN-2/5, y las 2D como componentes aparte) queda registrado en `next-features.md` §10 — el hueco de numeración se cierra cuando el track de blocks commitee sus §8/§9, hoy sin commitear. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
cdc00bc117 |
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 |
|
|
ceab195ae1 |
feat(blocks): el arco semántico completo — contacto al pulsar, commit al elegir
Elegir un plan ya no era solo un click: ahora demuestra el momento `--event` entero, que es lo que ninguna referencia puede shippear. - Pulsar emite `contact-activate` — gratis, por composición: es el evento del morfo del propio `Button`. Eso ya funcionaba en todos los bloques. - Lo que faltaba es la CONSECUENCIA: la app emite `commit-select` con intent `affirm` (CANON §2: una elección activa es afirmativa) vía `uix.events`. Un block no puede emitir eventos propios —no tiene morfo, contrato B—, así que la consecuencia es necesariamente app-land; la demo la ejerce para que el arco se vea. El commit va DESPUÉS del resultado (`await tick()`), no en la pulsación. Es la doctrina (un commit es lo que quedó fijado, no el contacto) y además evita una colisión real que medí: emitidos a la vez sobre el mismo elemento, la proyección del contacto pisa la del commit. Verificado con MutationObserver usando `attributeOldValue` —sin él la medición MIENTE, porque el callback se agrupa en microtarea y `getAttribute` ya devuelve el valor sobrescrito—: la secuencia es `contact-activate` → `commit-select` (family `commit`, intent `affirm`), cero errores de página. `blocks:check` verde (7) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
2 months ago |
|
|
19e8dbfb63 |
feat(scene): `portal` — la presencia leída como esfera a contraluz (variante)
Encargo del usuario a partir de un componente de referencia de pago cuya fuente NO está disponible (la pestaña «Code» es de clientes). Así que implementación propia — que es la regla de la casa de todos modos: solo el vocabulario de parámetros se dejó cercano para poder compararlos. QUÉ ES Una esfera a contraluz: filo encendido, interior abierto, y capas de estelas que la envuelven y giran — cada una sobre su eje inclinado, con su ritmo, con motion blur a lo largo del barrido. El fragmento se mapea sobre la esfera unidad y las estelas viven en ese espacio 3D, que es lo que las hace curvarse con la superficie en vez de leerse como rayas planas. ES UNA VARIANTE, LA ELIGE EL DESARROLLADOR No sustituye al `orb`: conviven. `orb` está afinado para 32-48px (silueta simple, movimiento perceptible a ese tamaño); `portal` tiene mucho más detalle y luce en superficies grandes — a tamaño de componente pierde justo la irregularidad que lo define. La demo gana un switch **studio** (220px) para poder juzgar eso, porque un shader no se juzga a 40px y forzarlo desde la consola no deja nada para la sesión siguiente. EL INTERIOR ES TRANSPARENTE No se pinta nada dentro: lo que se ve a través es la página. Eso es lo que hace la variante agnóstica al tema SIN una sola rama claro/oscuro — un interior relleno tendría que elegir bando, y elegirlo fue lo que en el primer intento la convirtió en una bola de cristal. Los tres colores salen de los mismos slots de rol que el orb (`solid` en el filo, `solid-hover` en la cara cercana, `element` en la que se aleja): retintas el tema y cambia entera. TRES LECCIONES DEL FORMATO, no de este efecto — anotadas en el README porque las va a repetir quien toque el siguiente shader: - Las estelas no pueden salir de senos. Un `sin()` da anillos de latitud PERFECTAMENTE regulares que se leen como jaula de alambre por mucho que se afinen; la irregularidad (longitudes distintas, trazos sueltos) exige ruido anisótropo — aquí 1 : 9 en longitud/latitud, lento a favor del flujo y rápido a través. - `sin(q.y * freq)` sobre la esfera unidad da `freq/π` ciclos: ~9 son tres trazos finos por capa, 22 es malla, 2 es un borrón. El número sale de la cuenta, no del tanteo — tanteando me pasé de largo en ambos sentidos. - El motion blur PROMEDIA: 5 muestras × 0.16 rad son 46° de barrido, que borra las bandas en vez de estirarlas. Verificado en Chrome real, claro y oscuro, en `acting` y `escalated`, a 48px y en studio. Tests 370 (scene 9 + eidos 361) · eidos-lint aura 0 inválidos · docs:check sin errores nuevos · check en los 73 preexistentes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
2 months ago |
|
|
164ca0183e |
feat(blocks): movimiento en TODOS los bloques, vídeo e intent semántico
El acabado deja de vivir solo en el hero. Los seis bloques restantes entran ya con movimiento, la media admite vídeo de verdad y los CTA declaran su intención semántica en vez de ser neutros. - **Movimiento en los 7**: cada rejilla marca `data-stagger` en SU contenedor y sus celdas SON un `Reveal` (feature-grid.Item, testimonials.Item, pricing.Plan) — el foundation les da el índice por `:nth-child`, así que las tarjetas se reparten una tras otra sin un solo milisegundo escrito a mano. Cabeceras, filas de feature-split (copy y media con un compás de diferencia) y el acordeón del FAQ entran también. - **Vídeo**: el hero acepta un `MediaPlayer` del canon dentro del mismo `Mockup` —nada a medida, el slot `media` toma lo que la app componga— y la demo lo expone como control («panel» / «vídeo»). La pregunta «¿y si quiero poner un vídeo?» ya tiene respuesta demostrada, no teórica. - **Semántica en la interacción**: los CTA llevan `intent`. El primario del hero es `fulfill` (esa pulsación consuma la página); en pricing, `fulfill` en el plan recomendado y `affirm` en el resto. Un CTA neutro no significa nada, y los 7 bloques lo eran. Nota arquitectónica: un block NO puede declarar eventos sema propios — no tiene morfo, es el contrato B. Su semántica entra por dos vías, y las dos estaban sin usar: el `intent` de los componentes que compone, y los eventos que esos componentes ya emiten (Accordion emerge, ToggleGroup, Drawer). Verificado con Playwright en los 6: reveals con índices estructurales (feature-grid 0..5, testimonials 0..4, pricing 0..2, hero 0..3), vídeo pintando 470×264, intents `fulfill`/`affirm` llegando al DOM, cero errores de página. `blocks:check` verde (7) · `vitest src/uix/eidos` 361/361 · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
2 months ago |
|
|
f190a2266e |
feat(eidos): `Reveal` + `Backdrop` + cascada por viewport — la capa de acabado
Auditoría del tier (docs/process/PLAN-blocks-quality.md): los 7 blocks de F2 componían EXCLUSIVAMENTE la capa de layout (Box/Stack/Grid/Section/Text/Card). Uso de la capa expresiva del framework: cero. Ni un Motion, ni un Cascade, ni un Display, ni un TextGradient, ni un CountUp. La causa es estructural, no de gusto: el contrato B prohíbe que un block traiga `.css`, así que **un block solo puede ser tan expresivo como los componentes del canon que compone** — y faltaban dos primitivas. El arreglo no es maquillar 14 blocks uno a uno, es construir lo que falta en el canon y componerlo. - **`Reveal`** — reproduce la ENTRADA de un preset al entrar en viewport. No posee keyframes ni timing: estampa `data-animation-style` + `data-state='open'` cuando dispara el observador, así que reduced-motion degrada DENTRO del preset. Mientras espera, el recipe lo mantiene invisible bajo `@media (scripting: enabled)`: sin JS la regla no aplica y el contenido se ve siempre — el fallo de una animación de scroll debe ser «sin animación», nunca «página en blanco». - **Escalonado gratis**: el foundation ya escribe `--motion-stagger-index` en cada hijo de `[data-stagger]` (por `:nth-child`, sin JS) y los presets retrasan por `índice × --motion-stagger-each`. El recipe de Reveal le da ritmo por defecto, así que una lista se escalona poniendo `data-stagger` en SU contenedor: cero delays a mano, cero números en el consumidor. - **`Cascade trigger="viewport"`** — la cascada existente esperaba a `open` (disclosure); ahora puede esperar también a ser vista. - **`Backdrop`** — la capa de profundidad que no existía: `glow` · `mesh` · `grid` · `dots`, pintada en un `::before` desde tokens (acento por el forward de paleta, mesh desde `--gradient-aurora`), con `fade` para no cortar en seco. **El hero como prueba del nuevo listón**: `Display` (el primitivo de tipografía de hero que existía y no usaba — el titular pasa de `Heading` a 80px reales), entrada escalonada de eyebrow/título/subtítulo/acciones, media con `scale-fade` y `decor` (glow por defecto) expuesto como control vivo en la demo. Verificado con Playwright: glow pintado desde el token de paleta, 4 Reveal con índices estructurales 0·1·2·3 a 70ms, titular H1 a 80px, y el patrón `grid` con su máscara. `blocks:check` verde (7 blocks) · `vitest src/uix/eidos` 361/361 · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
2 months ago |
|
|
54b8b838e7 |
feat(blocks): F2.6 `faq` — proxy fino del Accordion del canon
La sección de preguntas: una cabecera sobre un acordeón de P/R en columna estrecha. Compound (`.Item` repite) y —la regla al envolver un componente interactivo— un PROXY FINO del `Accordion` del canon: el block lee su API y la pasa tal cual, no reinventa el disclosure. `<Faq>` + `.Header` + `.List` + `.Item`. - `.List` **ES** el `Accordion`: toda su API pasa sin gate — `type` (single/multiple), `bind:value`, `collapsible`, `variant`, `size`. Defaults de FAQ: single, collapsible (el abierto se puede cerrar), outline. - `.Item` proxya `Accordion.Item > Header > Trigger`(snippet `question`) / `Content`(children) y autogenera el `value` (clave de estado) con `$props.id()` si no se pasa — la única conveniencia sobre el andamiaje. - El teclado (flechas, Home/End, Enter/Espacio), `aria-expanded` y `aria-controls` salen del `Accordion`; el block no toca la a11y del disclosure. `Container` estrecho (`md`) para una columna legible. Demo (`web/routes/blocks/faq/`): full-bleed + ruta `preview`, cinco preguntas en una columna, con la cola «¿aún tienes dudas?» que la app pone tras `.List`. Huecos a decisión del usuario (Gaps): la **lista estática 2/3 columnas** (6 de 7 en TW NO son acordeón, sino P/R siempre abiertas) — otra disposición, prop `layout` o hermano. Verificado en navegador: el acordeón abre/cierra, claro/oscuro × LTR/RTL. `blocks:check` verde (7 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
b59332132f |
feat(blocks): F2.5 `testimonials` — rejilla de citas con autor
Prueba social: una cabecera sobre una rejilla responsive de tarjetas de cita, cada una con su autor (avatar · nombre · cargo). Compound —la `.Item` se repite, como feature-grid— y SIN contexto (las partes no coordinan). `<Testimonials>` + `.Header` + `.Items` + `.Item` + `.Quote` + `.Author`(slot `avatar` + `.AuthorName`/`.AuthorRole`). - `AutoGrid` de tarjetas de igual alto; `.Author` fijada al borde inferior de la tarjeta (`margin-block-start: auto`) para que una fila de citas de distinto largo alinee las caras. - La cara es del app: un `<Avatar>` con imagen o un `Avatar.Fallback` de iniciales va en el slot `avatar` (B-7). - La tarjeta del quote usa `variant="outline"` — el `soft neutral` es casi invisible en claro. Encontrado al componer (en el README del block): el tema activa solo un SUBCONJUNTO de escalas donor (`green/indigo/orange/plum/teal`); una escala no activada (`cyan/ruby/amber/jade`) en `color` cae en SILENCIO a `primary` (la regla `[data-color]` hace `var(--scale-…, primary)`). Config del tema, no bug de componente; la demo usa escalas activadas. Hueco a decisión del usuario (Gaps): la **cita única en spotlight** (grande, centrada, logo+avatar+autor) — la variante modal del dossier, el grid es minoría. Otra disposición → candidato a hermano `testimonial-spotlight`. Demo (`web/routes/blocks/testimonials/`): full-bleed + ruta `preview`, cinco citas con avatares de iniciales en cinco colores distintos. Verificado en claro/oscuro × LTR/RTL, caras alineadas al fondo. `blocks:check` verde (6 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
594e5a5db4 |
feat(blocks): F2.4 `pricing` — el primer compound CON CONTEXTO del tier
La sección de planes: un toggle de periodo sobre una fila de planes, uno destacado. Es el primer block cuyas partes se COORDINAN de verdad (no solo se repiten como en feature-grid/split): el `Switch` escribe el periodo de facturación y cada `PlanPrice` lo lee. Esa coordinación es lo que gana un contexto compartido — la forma más fuerte de compound. `<Pricing bind:period>` + `.Header` + `.Switch` + `.Plans` + `.Plan`(featured, badge) + `.PlanName`/`.PlanDescription`/`.PlanPrice`/`.PlanFeatures`/ `.PlanFeature`/`.PlanAction`. - **Contexto reactivo** (`context.ts`): la raíz provee el periodo como getter sobre un `$bindable`; el `Switch` (un `ToggleGroup`) lo escribe, el `PlanPrice` lo lee y muestra el snippet `monthly` o `annual`. Mismo patrón que `CardGroup`. `period` es bindable por si la app quiere observarlo. - **El block NUNCA formatea moneda**: la app compone `FormatNumber` dentro de los snippets de precio (B-7). El block posee el switch, no el dinero. - `.PlanAction` fija el CTA al borde inferior de la tarjeta (`margin-block-start: auto`) para que una fila de planes alinee sus botones aunque tengan distinto nº de features; `.Plan` con `align="start"` deja los checks en columna limpia; `featured` da acento (borde primary) + elevación. Demo (`web/routes/blocks/pricing/`): full-bleed + ruta `preview`, tres planes (Pro destacado en el centro) con el toggle mensual/anual vivo. Hueco a decisión del usuario (Gaps del block): la **tabla de comparación** (features × planes) — la brecha recurrente del dossier en pricing. Es una tabla, no una fila de tarjetas: candidato a hermano `pricing-table`. Presentado, no resuelto. Verificado en navegador: el toggle cambia los TRES precios a la vez (0/29/99 → 0/23/79), tarjetas de igual alto con CTAs alineados, featured con acento, en claro/oscuro × LTR/RTL. `blocks:check` verde (5 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
08ea82d128 |
feat(blocks): F2.3b `feature-split` — la brecha nº1 del dossier, con Mockup
La sección que las 5 refs shippean y que va justo tras el hero: una afirmación de producto junto a un screenshot, alternando lados fila a fila. Otra disposición que la rejilla de iconos de `feature-grid`, así que block hermano (decisión del usuario), no una variante turbia dentro de aquél. Compound —la `.Row` se repite—: `<FeatureSplit>` + `.Row` (`reversed`, slot `media`) + `.Eyebrow` + `.Title` + `.Text` + `.Features`/`.Feature` (checklist) + `.Actions`. El block coloca; la app pone la copy y la media. - `reversed` mueve la media al lado de inicio vía `grid-column` (Box expone `gridColumn`/`order`), dejando la copy SIEMPRE primera en el DOM — el orden de lectura y el foco no cambian aunque el screenshot salte de lado. - El check de cada `.Feature` es decorativo (`aria-hidden`): la palabra lleva el significado. - La media se COMPONE con el primitivo `Mockup`: la demo enseña cromo de navegador (dashboard) y de teléfono (app), cerrando el hueco de «tratamiento de media» del dossier de raíz en vez de falsearlo. `.Row` usa un tipo limpio (no `HTMLAttributes`) porque su slot `media` colisiona con el atributo HTML homónimo; el resto de sub-partes extienden los props del componente canon que envuelven (lección de feature-grid). Demo (`web/routes/blocks/feature-split/`): full-bleed + ruta `preview`, con control del nº de filas y del lado inicial; 3 filas con Mockup navegador/teléfono. Verificado en navegador (Playwright, módulos frescos): filas alternas en claro/oscuro × LTR/RTL, `reversed`, el orden de lectura copy-primero, y los dos cromos de Mockup. `blocks:check` verde (4 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
e95ebd45f9 |
feat(eidos): `Mockup` — el primitivo de media (navegador · teléfono · liso)
La clave del dossier para el hero y las secciones de features: la brecha no es el conteo de layouts, es el TRATAMIENTO DE MEDIA. En vez de falsear un screenshot enmarcado por demo, se cierra de raíz con un componente del canon que los blocks componen. `<Mockup chrome="browser" url="acme.app"><img …/></Mockup>` — enmarca cualquier media (imagen, vídeo, AspectRatio) en cromo de dispositivo: - `browser`: ventana con barra (tres dots + pill de URL centrada) y pantalla. - `phone`: bisel oscuro redondeado con notch. - `plain` (def.): pantalla limpia enmarcada, con sombra. Es un primitivo visual eidos-native con morfo de 0 eventos, justificado igual que `aspect-ratio`/`box` (no commitea, no emerge, no reacciona — da forma a su hijo). El recipe dibuja el cromo sobre tokens del ecosistema (`--radius-*`, `--color-border-*`, `--shadow-*`, `--primitive-*`), nada de color a mano. eidos-lint: 0 invalid / 0 class-hooks (mismo patrón que aspect-ratio). Dogfood + bug arreglado de paso en `hero`: - La demo del hero deja de componer a mano el marco del screenshot y usa `<Mockup chrome="browser" url="acme.app/analytics">` con el dashboard dentro. - Arreglados dos type-errors que ya viajaban en el hero (los tapaba un filtro de svelte-check con backslashes mal escapados): los snippets `title` y `background` colisionaban con los atributos HTML homónimos y se volvían `string & Snippet`. Fix: `Omit<HTMLAttributes, 'children' | 'title'>` y renombrar el slot `background`→`backdrop`. Lección: un slot de snippet cuyo nombre sea un atributo HTML necesita Omit o un nombre distinto. Verificado en navegador: browser (dots + URL + pantalla) y phone (bisel + notch) en claro/oscuro/RTL; el hero split enseña ahora una ventana de navegador de verdad. `svelte-check` sin errores propios · eidos lint.test + morfo 131/131 · `blocks:check` verde. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
d01b3e6af5 |
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 |
|
|
5125502486 |
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 |
|
|
356ebf92af |
feat(button): el CTA que navega y parece botón, por composición (opción D)
El hueco que registró `site-header`: un «Empezar gratis» de cabecera tiene que
NAVEGAR y parecer botón. `Button` no crece un `href` —`Link` posee la
navegación, y un ancla se activa con Enter, no con espacio, lo cual es
correcto—: presta la pintura por composición. Lo que hacía de esa forma un
downgrade era que el `child` (asChild) descartaba la decoración; se completa el
slot.
- **eidos `<Button>`**: el `child` recibe ahora `content`, el cuerpo YA
decorado (icono · etiqueta · endIcon · spinner) en su propio snippet que
comparten las dos ramas de render. Así
`<a href {...props}>{@render content()}</a>` conserva TODOS los slots en vez
de sustituirlos (antes la flecha del sitio alpha estaba escrita a mano).
Nuevo tipo exportado `ButtonChildProps`.
- **soma / morfo**: en la forma `child` el elemento es del consumidor, así que
soma deja de estampar `type` (un `<a type="button">` es una pista de MIME
falsa). El componente pasa `type: undefined` cuando hay `child`; el provider
lo REENVÍA verbatim (antes lo re-defaulteaba a `'button'` y pisaba el drop —
el default vive en el destructure del componente); el morfo declara el attr
`type` condicional (`prop-truthy`).
- **docs**: ejemplo rancio de `index.ts` corregido (anunciaba un `asChild`/
`variant="link"` que no existen); sección «CTA que navega» en el README de
eidos con el patrón y el footgun documentado (un `<button>` en un `<form>` vía
`child` se pone su propio `type`); nota en el README de soma.
- **site-header**: el CTA de la demo usa ya la forma real (`<a>` sólido con
flecha), y el hueco pasa de «candidato a canon» a CERRADO por composición —
`Button` sigue sin `href` y `Link` sigue poseyendo la navegación, las dos
decisiones firmadas se mantienen. Actualizados PLAN/CONTINUE-blocks.
- **demo de Button**: control `child (asChild → <a>)` vivo, snippet del código
y fila de a11y explicando por qué el ancla activa solo con Enter.
Verificado en navegador (dev, restart para módulos frescos): asChild ON →
`<a href="#pricing">` sin `type`, pintura sólida completa (bg primary, tinta
blanca, 36px, padding 16px), y el slot de icono SOBREVIVE dentro del ancla
(`[data-button-icon]` + svg + body); asChild OFF → `<button type="button">`
intacto (sin regresión de submit implícito); el CTA real de `site-header` sale
`<a>` con la flecha final y 0 errores de consola. `blocks:check` verde ·
`vitest src/uix/morfo` 114/114 · `svelte-check` sin errores propios nuevos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
ae7b4f8c3b |
demo(blocks): el block se ve en una página de verdad, no dentro de una caja con scroll
«¿Dónde has visto que el sticky esté por debajo del scroll?» — en ningún sitio, y la respuesta correcta era esa. Un `position: sticky` metido en un div de 460px con scroll propio no es el que va a vivir nadie: se pega al borde de una caja, no al del viewport. Estaba enseñando un comportamiento que no existe fuera de mi página. Ahora la demo presenta el block A SANGRE en la propia página: el header se pega al viewport de verdad, con el scroll de verdad. Verificado: al bajar queda en top 0, con `data-stuck`, visible y por encima de todo (hit-test), y con `offset: 48` se queda exactamente a 48. Los anchos de dispositivo (375 / 768) sí necesitan un documento propio, así que ahí —y solo ahí— se monta un iframe de la ruta `preview`. Es opt-in a propósito: en dev, dos documentos sin empaquetar a la vez agotan las conexiones del navegador (`ERR_INSUFFICIENT_RESOURCES` se llevaba por delante las DOS páginas; lo medí). Con `loading="lazy"` y bajo demanda, no molesta. Para no duplicar contenido, el mini-sitio vive en `SiteHeaderSite.svelte` y lo consumen las dos superficies: la demo lo renderiza inline con sus props y la ruta `preview` lo sirve como documento leyendo la URL. «Abrir ↗» lleva ahí. De paso: la topbar de la sección deja de ser pegajosa. Una página cuya estrella es un block que se pega no puede tener su propio cromo peleándole el borde superior — el header del block se metía debajo y el `offset` que elegías se leía como otro número. El raíl sí sigue pegado, que no compite. Verificado: `/blocks`, `/blocks/site-header` y la preview con `?dir=rtl&mode=dark` responden 200 sin desbordes ni errores de consola; `blocks:check` verde; `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
d574982ab9 |
demo(blocks): el escenario va a ras — `offset: 0` volvía a ser 0
Tenías razón: con `offset` a 0 la barra quedaba a 21px del borde del marco, y
16 y 48 sumaban sobre esa deuda (37 y 69). El offset del block estaba bien;
el marco era el que mentía.
Causa: usaba un `Card` como escenario y le pasaba `padding={0}`, pero el
recipe del Card pinta con su propia variable (`--card-padding-*`), que la capa
Box no alcanza — así que se quedaban sus 20px de relleno más 1px de borde.
Un header pegado se pegaba al borde INTERIOR de ese relleno.
El escenario pasa a ser un marco desnudo (`[data-blocks-stage]` en
`blocks.css`): borde, radio, altura y su propio scroll, sin relleno. Un
escenario tiene que ir a ras o miente sobre lo que enseña.
Verificado en navegador: offset 0 → 0px del borde, 16 → 16, 48 → 48, y al
volver a 0 → 0. Cero errores de consola.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
33328df7ae |
demo(blocks): la sección tiene cromo de verdad — tema, idioma, dirección, densidad y catálogo
La crítica era exacta: no se podía cambiar a oscuro, ni de idioma, ni había forma de moverse entre blocks. La sección arrancaba UIX y renderizaba la página desnuda, con un comentario que decía que el modo seguía al sistema operativo «para verificar oscuro emulando prefers-color-scheme» — cómodo para mí, inútil para quien mira la página. Ahora hay shell: topbar pegajosa con los ejes que de verdad mueven el framework (idioma ES/EN/FR/DE y dirección como INTENTS de prefs, así que llegan a langs, formatos y al AGENTS.md debug.log routes tsconfig.json CLAUDE.md docs scripts tsconfig.tsbuildinfo README.md node_modules src vite.config.ts STUMBLES.md package-lock.json static web audit package.json svelte.config.js worker.js build plans tmp workers del documento por el camino del sistema; densidad proyectada en :root para que la hereden también los overlays porteados; y el conmutador de tema, que alimenta el del ActiveEidos) y un raíl con el catálogo entero de los 27 blocks planificados, marcando el actual y atenuando los que aún no existen. Todo persiste en localStorage, así que una recarga te deja donde estabas. El catálogo pasa a : raíl y galería leen la MISMA fuente, y un block que aterriza no hay que recordarlo en dos sitios. Las tres regiones las coloca — cromo de app-land, el papel que hace en las docs de componentes; la regla de «sin CSS» del contrato B es para el tier (), no para el sitio que lo enseña. Dentro de esas regiones todo son componentes del canon. Verificado en navegador: el botón conmuta a oscuro de verdad (fondo oklch(0.1776 0 0)), el idioma llega a , el raíl lista los 27 y desaparece por debajo de 64rem sin desbordes, cero errores de consola. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
50574806d6 |
demo(blocks): harness compartido de demos + site-header con contenido real
La página de demo del primer block era mala y con razón: sin cabecera, sin contexto del block, controles sueltos flotando, backticks de markdown en crudo dentro de <Text> y relleno del tipo «filler section so the page scrolls». Harness (`web/routes/blocks/_lib/BlockDemo.svelte`), escrito una vez para las 14: identidad arriba (chip, ruta de import, vuelta a la galería, título, una frase de función y chips de datos: qué compone, landmark, props, cuántas cadenas propias) → el block VIVO dentro de un marco con su propio scroll, que es una página en miniatura → panel de controles agrupado → pestañas Composición · API · A11y · Gaps · Notas, que solo aparecen si la demo las pasa. Todo con componentes del canon (Card, Tabs, Badge, Stack…): esta sección no tiene cromo de docs propio y sigue haciendo dogfooding. `DocRow` acompaña al harness porque el `Table` del canon es una tabla de DATOS (quiere un `createTable`): la documentación en prosa usa una fila de dos columnas, igual en las 14. site-header: reescrita sobre el harness con contenido creíble de producto —nav con destinos reales y descripciones, hero y secciones que dicen algo— en vez de relleno evidente, y con control vivo de los cinco props (sticky, container, breakpoint, offset, dir) más el eje de dirección. Sin backticks literales: lo que es código va en <Code>. Verificado en navegador tras el arreglo de Box: cabecera compacta, escenario con la barra a 950px que se pega al hacer scroll dentro del marco, controles en una fila y las cuatro pestañas con contenido. Cero errores de consola. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
016658a0c9 |
demo(site-header): controles vivos de cada prop + ejes claro/oscuro y RTL
La demo cumplía B-9 (existe y está enlazada) pero se quedaba corta frente a la ficha del plan («claro/oscuro, 375/1280, RTL») y a la doctrina de demos como banco de pruebas: `offset`, `container` y `breakpoint` no tenían control, y la dirección no era un eje de la página. Ahora cada prop público se maneja en vivo con `ToggleGroup`/`Field` del ecosistema (nada de nativos crudos) y la página entera cuelga de un `dir` conmutable. El modo de color NO lleva control a propósito: esta sección sigue el esquema del sistema operativo por diseño del layout, así que oscuro se verifica emulando `prefers-color-scheme`. Verificado en navegador: `container` lg→sm mueve la medida (1024→480px); `breakpoint` discrimina de verdad (a 800px, con `md` se ve la nav y con `lg` aparece el drawer); RTL espeja la barra entera (marca a la derecha, acciones a la izquierda) y oscuro pinta el header sobre superficie oscura. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
3cfc030b3a |
blocks(site-header): F2.1 · primer block del tier + landmark de NavigationMenu
Arranca F2 con el patrón del tier: composición pura de canon, sin CSS propio, sin morfo y sin una sola cadena visible suya (B-7 — todo texto llega del app por snippets). El block - `Sticky` (F1.1) envuelve solo cuando `sticky` (si no, no hay wrapper que no haga nada) → el estado pegado llega al app como `[data-sticky][data-stuck]` y el tratamiento es suyo; el block no pinta. - `Container` para la medida, `Group` para la barra, `Drawer` para la navegación estrecha; `brand` / `nav` / `actions` / `mobileNav` / `mobileTrigger` son snippets del app. - El interruptor ancho/estrecho va por el `display` responsive de los componentes de layout (B-6): el block no observa nada y las dos navegaciones nunca coexisten. - Suelo de referencia (dossier §P1: TW Headers 8 + Navbars 11 + Flyout 7 · Untitled · Flowbite): adoptamos las tres agrupaciones, el estado pegado como eje visual, el drawer móvil y el FLYOUT —la brecha de paridad que el dossier señala— sin reimplementarlo: el app pasa un `NavigationMenu`. Descartado: los volcados estáticos de cada arreglo; la barra es UN layout. El banner de anuncio no se hornea aquí (es el block F2.11 o un `Banner` en `children`). Canon arreglado de paso - **`NavigationMenu` no emitía landmark**: su morfo declara `defaultElement: 'nav'` para el Provider, pero el componente de soma renderizaba un `<div>` — el componente que ES la navegación del sitio no tenía `<nav>`, justo el hueco de a11y que el dossier atribuye a todas las referencias. Corregido en el canon, no parcheado en el block. Verificado: su demo sigue abriendo flyouts, ahora bajo `<nav>`. Hueco registrado (no falseado) - Un CTA que NAVEGA y parece botón no existe hoy: `Button` no tiene `href` por decisión propia («Link posee la navegación») y `Link` no tiene variante prominente. Queda como candidato a canon en los Gaps del block; la demo usa `Link` de verdad en vez de fingirlo. Verificado en navegador: `<header aria-label>` dentro de `[data-sticky]`, `data-stuck` al hacer scroll con el header a top:0, flyout abriendo, cero nativos crudos interactivos (B-2), 375px sin desbordes con el drawer abriendo 6 enlaces, y el `<nav>` del NavigationMenu presente. `blocks:check` verde · `svelte-check` 0 errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
75e1fe32a1 |
uix(sidebar): review adversarial · 31 hallazgos confirmados, arreglados
Review de 6 dimensiones × 3 verificadores escépticos (135 agentes): 43 hallazgos brutos → 31 confirmados, 12 rechazados. Todos los confirmados arreglados y verificados en navegador real. Estado (los dos ejes) - `collapsible='none'` ya no deja el sidebar INALCANZABLE en móvil: la inercia del modo se limita al escritorio, que es donde «nunca colapsa» significa algo. En móvil el panel es un Drawer que arranca cerrado, el raíl está oculto y el drawer no tiene trigger propio: con el toggle inerte no había forma humana de abrir la navegación en un teléfono. - `'none'` PINTA el estado a expandido: antes `data-state`, `aria-expanded` y `useSidebar().open` podían decir «colapsado» sobre un panel plenamente visible, y ninguna regla del recipe ni el toggle podían reconciliarlo. - El estado del drawer móvil se DESCARTA al salir de la presentación móvil: abrirlo en un teléfono y rotar dos veces remontaba el Drawer ya abierto, con overlay, foco atrapado y scroll bloqueado sin tocar nada. Submenú flotante (era el nudo con más hallazgos) - Cierra también por PUNTERO: se abría en `pointerenter` de la fila, pero el único cierre por puntero vivía en el propio flyout — salir hacia la página sin cruzarlo lo dejaba pintado para siempre. - Escape funciona desde donde el foco ESTÁ (la fila), no solo dentro del sub. - Entrar/salir se resuelve en el `menu-item`, que contiene fila Y sub, así que viajar entre ambos no lo cierra. - El flag se limpia al cambiar de presentación: expandir y volver a colapsar reabría un flyout que nadie había tocado. - El motor flotante se engancha solo cuando el sub está ABIERTO, no por modo: antes `autoUpdate` (rAF + observers) corría por cada fila del raíl. - `hasSub` deja de latir a true: se limpia en el teardown del sub. A11y - Las filas del raíl recuperan NOMBRE: un tooltip solo describe (`aria-describedby`), así que con la etiqueta oculta el texto de `tooltip` pasa también a `aria-label` — incluidas las filas con submenú, que no llevan tooltip visible. - El panel off-canvas colapsado sale del tab order y del árbol de accesibilidad (`visibility: hidden` con la transición retrasada para que el deslizamiento siga animando). - El diálogo móvil recibe el mismo nombre que el landmark. - El raíl se llama «Contraer barra lateral» (lo que hace) en vez de «Redimensionar», que prometía un arrastre fuera de alcance; cursor de puntero en vez de `ew-resize`. Recipe - El signo del off-canvas se deriva de side × dirección: en RTL la fila flex se invierte, así que el negativo fijo barría el panel POR ENCIMA de la página en vez de sacarlo por su borde. - El flyout declara banda `z-index` (nuevo token `--sidebar-sub-z`); era la única superficie flotante del ecosistema sin ella. - La transición de `inline-size` (layout por frame) queda SOLO bajo el modo icono; la base anima únicamente `transform`. - El `Drawer` es el único dueño del ancho móvil (dos dueños dejaban una banda de cromo en dos tonos) y se compone con `dragToDismiss`. - Las reglas de modo icono ya no alcanzan el subárbol flotante: sus filas conservan badges y acciones. - Bloque `prefers-reduced-motion` — la traslación del panel es la mayor del sistema. Demo - Copias corregidas donde afirmaban de más: el tooltip ahora sí NOMBRA (y se explica por qué), y los anchos son rem fijos a propósito (no los alcanza el escalado; sí el interior). - Tabla de eventos y de teclado en la pestaña morfo; `onOpenChange` y una tabla de props por parte en API. - Los chips de estado se inhabilitan en `collapsible='none'`, que es lo que el componente hace; botón «show me» que pone icon + colapsado de una vez. - El trigger pasa a ser solo icono: el morfo le da `aria-label`, que pisaba cualquier texto visible. Verificado en navegador: nombres accesibles en el raíl · flyout que cierra por puntero, por foco y por Escape (con retorno de foco) y que no resucita al cambiar de modo · off-canvas fuera del tab order · RTL sacando el panel por su propio borde · z-index 80 en el flyout · drawer móvil nombrado, a ancho completo y con el panel a ras · `collapsible='none'` abrible en un teléfono. Gates: audit PASS 0E/0W · eidos-lint 47 morfo-backed / 0 invalid · svelte-check 0 errores propios · vitest src/uix/eidos 353/353 · contracts.test solo con los 3 fallos ajenos conocidos. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
e10eed98cc |
uix(aura): la superficie visible del agente — anillo de custodia, orbe-contorno y desenlaces expresados
Rediseño dirigido por el usuario sobre un banco de mockups con el orbe REAL
corriendo dentro. Seis frentes, todos con maquinaria existente:
ANILLO DE CUSTODIA (parte nueva `Aura.Ring`, morfo+soma+eidos)
El estado se lee como FORMA y no solo como tinte: raíl punteado en reposo,
arco que respira al ofrecerse, segmentos = pasos del plan (laten), dos arcos
enfrentados y QUIETOS esperando tu revisión (la quietud lo separa de acting),
arco que gira actuando, anillo cerrado + pulso saliente cuando te necesita.
Envuelve al Indicator (es su marco de referencia, no un hermano posicionado a
mano). El movimiento continuo son loops REGISTRADOS (data-animation-style),
nunca @keyframes de receta (R-4.5) — vienen con su puerta de reduced-motion,
donde el dash sigue distinguiendo el estado. El attr va en los hijos del SVG,
jamás en la parte: motionAttrs() estampa además data-state, del morfo ahí.
EL ORBE ES UN CONTORNO
`thickness` en el efecto orb; el hueco es real (alfa propia) sobre cualquier
fondo. Se va la rama claro/oscuro y el parámetro backgroundColor: el shader
mezclaba con el fondo y derivaba el alfa del canal máximo, o sea pintaba un
disco OPACO del color de la superficie justo en el centro (en claro un círculo
relleno; en oscuro el azul del seed). Y la receta deja de pintar su disco en
cuanto monta el canvas (:has(canvas)) — solo sobrevive donde no hay render
vivo: SSR, tests, effect={false}, sin WebGL.
COLOR = TOKENS DE ROL, PINTADO Y NO ROTADO
colorA/colorB/colorDeep (solid / solid-hover / element) resueltos con
eidos.resolveToken y pasados como hex, que es el seam que el propio arte
documenta. El hue-delta anterior se medía en OKLCH y se aplicaba en YIQ: risk
salía VERDE junto a su propio anillo naranja.
ONION EN EL IDIOMA DEL ORBE
Prop `look="solid" | "luminous"` (defecto solid: nadie más se entera). En
luminous el sector no tiene relleno — filo de luz (pasada borrosa + nítida) y
etiqueta en el color del sector; la cuña sigue siendo área táctil. El trigger
con snippet propio va BARE: sin disco, sin sombra FAB y sin el salto del hover
(el orbe ES el trigger, no su contenido). Medio anillo bottom-center y radios
44/40. El halo respira (loop pulse) y en commit-select da UNA pasada de ping
pivotando en el centro real del menú — la firma genérica de commit anima
box-shadow, que sobre un path de SVG no hace nada.
DESENLACES EXPRESADOS (el hueco real: un fallo era indistinguible de un éxito)
Tres eventos terminales con intent INTRÍNSECO — delegate-return (fulfill) /
-aborted (sin intent: un decline es ausencia, no pérdida) / -failed (loss) —
en vez de un intent dinámico, que no tiene dónde vivir: TriggerOptions no
lleva intent y el fromProp del morfo ata a un prop PÚBLICO, no a estado de
máquina. Puerto `runs?` opcional en AuraAgentSource porque activeRun ya es
null al cerrar (§0: sin él, anuncio genérico). Primeras firmas de la familia
delegate en el registro; la de loss es MOVIMIENTO (return-recoil: retirada +
descenso), no tinte — no estrena ningún hue.
MAPA SEMÁNTICO DE COLOR (demo)
Un hue = un significado en toda la superficie: gris=sin agencia ·
naranja=tu revisión · morado=él actúa · rojo=escalated (exclusivo) ·
teal=tu sí. El anillo de autoridad reusa los colores de la custodia con el
MISMO sentido (Review=risk porque vivirás en reviewing; Auto=primary porque
actúa) y los declines pasan a neutral — canon: el intent de un cancel es
`absent`. Además el anillo en reposo DELEGA (las tres autoridades abren el
run) y se cierra por transición, no siempre que no hay run.
Dos guardas del framework corrigieron el diseño por el camino:
- BK-FRAME-NO-INTENT rechazó dar intent a una familia de marco sin
justificación escrita (excepción A-1): queda el intentRationale — el motor
no emite nada y no hay evento evaluable que enmarcar, así que el retorno
consolidado es el ÚNICO sitio expresivo del desenlace.
- fallbackTarget gana sobre el target del morfo (contrato de TriggerOptions):
el sello caía en el provider. Los terminales pasan el Ring cuando existe.
Verificado en Chrome real (ventana delante: en segundo plano el navegador
congela rAF y ResizeObserver y las medidas mienten): seis estados en claro y
oscuro, y un error de transporte REAL apuntando a un puerto muerto →
delegate-return-failed · loss · return-recoil sobre el Ring · región
«Agente: falló — control devuelto».
Tests 478 (morfo 114 · soma/aura 11, +5 casos de desenlace · eidos 353),
eidos-lint 0 invalid / 0 class-hooks nuevos, morfo:vocabulary limpio,
check en los 73 errores ajenos preexistentes.
Pendiente (handoff actualizado): la jerarquía tipográfica del Status (D1
llevaba dos líneas y solo se hizo el anillo) · el pulso de commit-select se
corta si el consumidor cierra el anillo al elegir (necesita retención tipo
Presence) · D5 la superficie de composición del encargo, decisión abierta.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
245c22471b |
demo(sidebar): controles vivos de mobileBreakpoint y onOpenChange
Faltaban dos props públicos como control (regla de demo-authoring: cada prop público es un control vivo). `mobileBreakpoint` va con chips y el valor `xxl` (1536px) deja este viewport POR DEBAJO del corte, así que la presentación móvil —el `Drawer` compuesto— se puede ver sin tocar la ventana. `onOpenChange` se cablea a un contador que además enseña la doctrina: no se dispara con el estado del drawer, porque ese no es la preferencia de layout que el app persiste. Verificado en navegador: con `xxl` el shell estampa `data-mobile`, el drawer arranca CERRADO y el trigger de la página lo abre con el panel dentro de `[data-drawer-content]`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
21bbf9f506 |
uix(sidebar): F1.7 eidos + demo · raíl, modo icono y submenú flotante
Segunda tanda del sidebar: la capa visual, la demo canónica de 9 pestañas y la verificación en navegador real. Con esto el componente está vivo; queda el review adversarial. Eidos - Recipe con los dos ejes: off-canvas por `transform` (trabajo de compositor, el inset no reflowea) y `width` animado SOLO en modo icono, donde el inset sí tiene que seguir al raíl. El raíl NO lleva `will-change` (una franja fina tiembla a DPR fraccionario — memoria NavMenu). - Anchos como TOKENS: `--sidebar-width` 16rem · `--sidebar-width-icon` 3rem · `--sidebar-width-mobile` 18rem, más raíl, gaps, altura de fila y colores. - Tres composiciones que la capa headless no puede hacer: `Drawer` para la presentación móvil, `Tooltip` para nombrar filas en modo icono y `Badge` para los contadores. El trigger compone `Button` por el patrón `child`. - Convención de icono documentada: sobrevive el PRIMER elemento de la fila. Arreglos que destapó el navegador - **`mobile` en SSR**: sin viewport, `isAtLeast` responde «por debajo de todo» → el HTML salía con `data-mobile` y la hidratación NO lo corrige (no diffea atributos y el valor ya no cambia). Viewport desconocido = ESCRITORIO. - **Estado móvil propio** (arranca cerrado): en escritorio `open` es preferencia de layout persistida; en móvil sería un overlay tapando la página, que jamás puede ser el estado inicial. `onOpenChange` solo se dispara con el estado de escritorio. - **`open` controlado/no controlado**: se adopta el patrón de la casa (`$bindable` con default, el provider escribe siempre) — mi versión previa con estado interno no propagaba `bind:open`. - **El submenú flotante no se posicionaba**: `FloatingAnchor` resuelve su provider por CONTEXTO y el eidos envuelve la fila en un `Tooltip`, que publica el suyo → el ancla caía en el tooltip y el submenú se quedaba en `translate(0,-200%)`. Ahora el ancla se cablea directamente al provider flotante del item. Además el `<ul>` ya no se posiciona a sí mismo (colapsaba el wrapper a tamaño cero) y toma elevación del plano `overlay` por `data-depth`, como cualquier panel flotante del ecosistema. - **Tooltip vs submenú**: una fila con submenú no lleva tooltip en modo icono (dos superficies sobre el mismo ancla se pisan) — nuevo `useSidebarItemOr()`. - El item envuelve (`flex-wrap`) para que el submenú caiga en su propia línea en vez de ponerse al lado de la fila. Demo: shell de app real (grupos, badge, acción, submenú, fila deshabilitada), controles vivos de los tres props y las 9 pestañas del canon. Verificado en navegador real (Playwright): dos landmarks nombrados en el idioma activo · `aria-current="page"` en la fila activa · cada menú etiquetado por SU grupo · raíl enfocable que alterna con Enter · colapso 256→48px · off-canvas por transform · submenú flotante anclado a su fila y Escape que devuelve el foco · sema `emerge-expand/collapse` estampando en `panel` · móvil 600px = Drawer cerrado con el landmark dentro · RTL espeja el shell entero · oscuro · 0 errores de consola. Gates: `component:audit --only sidebar` PASS 0E/0W · eidos-lint 45 morfo-backed / 0 invalid / 0 class-hooks · `svelte-check` 0 errores propios · `vitest src/uix/eidos` 353/353 · `contracts.test` solo con fallos ajenos (menubar, radio-group y las claves camelCase de aura). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
ef6bd13be3 |
uix(nav-tree): `disabled` por nodo — destino no disponible
Decisión del usuario. Un nodo deshabilitado conserva su fila (sigue siendo parte del mapa) pero pierde el `href`: no hay nada que activar con clic, Enter ni «abrir en pestaña nueva». Un `pointer-events: none` de CSS solo habría tapado el ratón. - morfo: la parte `link` gana `data-disabled` (propRef) y `aria-disabled` (literal `true` bajo `prop-truthy: disabled`). - soma: `disabled` solo aplica con `href` (un grupo sin destino lo ignora); el toggle del grupo NUNCA se deshabilita — mostrar/ocultar es control de vista, no destino. - eidos: fila en `--color-content-disabled`, sin puntero y sin hover. - demo: «Agent» va deshabilitado con badge «Soon» + filas de API y a11y. Verificado en navegador real: `<a>` sin href, `aria-disabled="true"`, `data-disabled`, color 0.79 frente a 0.61 de una fila normal, clic sin efecto, fuera del orden de tabulación (44 focusables en vez de 45), badge dentro del control. `component:audit` PASS · eidos-lint 28 morfo-backed / 0 invalid · `vitest src/uix/eidos` 353/353 · svelte-check 0 errores míos. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
a7e629f9ed |
uix(nav-tree): F1.8 CERRADA · demo + navegador + review adversarial
Cierra el árbol de navegación data-driven (E-1) de F1: demo canónica de 9
pestañas con el mapa real de docs (43 nodos, 3 niveles), verificación en
navegador real y review adversarial (5 dimensiones × 3 verificadores
escépticos; 22 hallazgos brutos, 10 confirmados) con todos los confirmados
arreglados.
Arreglos del review
- sema: la parte `group` —target de los eventos emerge— se registraba SIN
`ref`, así que `runtime.trigger` lanzaba `SomaRuntimeTargetError` en
silencio y el pack no sonaba nunca (cero `data-event-*` en el grupo frente
a los de collapsible). El provider posee ahora el ref del `<ul>`.
- eidos: en una fila navegable el chevron resolvía `inline-size: 100%` como
flex-basis y ocupaba media fila (101 de 231 px en «Soma»), robándole clics
al enlace. Toggle compacto con suelo de diana de 24 px (WCAG 2.5.8).
- soma: una clave duplicada podía volver cíclico `parentByKey` y colgar la
pestaña dentro de `trailKeys` (deriva en render) → clave sufijada + aviso
del logger + guarda de ciclo en el paseo.
- soma: el colapso es CONTEXTUAL (recuerda el `activeKey` bajo el que se
hizo): cerrar la sección que lees se respeta, pero caduca al navegar
DENTRO del grupo, para que la página actual nunca quede sin fila visible.
Sigue siendo query pura, sin `$effect` que escriba estado.
- soma: `child` recibe también `children` (el árbol renderizado); antes
dejaba el landmark vacío, porque un árbol data-driven no lo puede
reautorar el consumidor.
- morfo + langs: el nombre accesible del chevron se declara en el contrato y
se localiza («Alternar sección {label}»); ya no duplica el del enlace.
- eidos: RTL completo — el glyph espeja solo (bordes lógicos), lo que no
espeja es el giro, así que bajo `[dir='rtl']` las dos rotaciones se
intercambian. El Gap «dirección del chevron en RTL» queda RESUELTO.
- demo: paridad de snippet con los controles vivos; fuera el token fantasma
`--nav-tree-rail-width` del docblock del recipe.
badge en v1 (decisión del usuario, delegada)
Está en el suelo de paridad del dossier §P5 (los 6 refs lo llevan). Se
resuelve con SNIPPET, no con recursión a nivel de eidos: el morfo declara la
parte `badge`, soma renderiza el snippet recibido (sin él, el valor crudo —
sigue siendo headless) y el wrapper de eidos pasa el `Badge` canónico. Va
DENTRO del control de la fila, así su texto entra en el nombre accesible
(«TSC, New, enlace»). `disabled` se descarta en v1 (fuera del suelo, y un
enlace de navegación deshabilitado es semánticamente dudoso); ambos quedan
registrados en la tabla de Gaps.
Verificado: `component:audit` PASS · eidos-lint 26 morfo-backed / 0 invalid ·
`svelte-check` 0 errores en estos archivos · `vitest src/uix/eidos` 353/353 ·
navegador real (Playwright): trail auto-expandido, sema estampando en el
grupo, teclado nativo, foco visible, claro y oscuro, RTL, 375 px sin
desbordes, 0 errores de consola.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
e835163103 |
uix(nav-tree): F1.8 WIP · data-driven docs nav tree (soma+sema+eidos)
Complete component, green on all static gates (audit PASS, eidos-lint 20/0, svelte-check 0, contracts clean for its parts). NOT tree-view: APG Disclosure Navigation (<nav> landmark + native links + disclosure groups), data-driven (E-1) — the app passes `nodes` + `activeHref`, the provider resolves the active node and auto-expands its ancestor trail. - morfo: nav/list/item/trigger/link/group; emerge-expand/collapse events - soma: NavTreeProvider (shared state, pure isExpanded query) + per-node NavTreeItemProvider (menubar pattern, recursive render) - sema pack: soft emerge on group disclosure (mirrors collapsible) - eidos: rail + per-depth indent recipe; own bespoke rows (not composed Link), Badge deferred to a Gap; aria-current="page" from data-active Remaining before F1.8 closes: demo + browser verify + adversarial review. Handoff: docs/process/CONTINUE-nav-tree.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
0ab40cdcee |
uix(agnt): Aura al centro de un OnionMenu — afordancias radiales por estado
La composición que pidió el usuario: el orb es el trigger central de un OnionMenu placement="center" y las afordancias de custodia aparecen como sectores contextuales — reviewing→Authorize/Reject, escalated→Approve/Deny, activo→Stop — sin fila de botones. El anillo se abre SOLO mientras la custodia espera a un humano, con re-aserción (un cierre externo sin resolver re-abre: el efecto lee onionOpen como dependencia); por click en el centro mientras actúa; se pliega al devolver el control. App-land por doctrina (agent.md §7): el framework da estados + afordancias (Aura) y componentes (OnionMenu); el app da forma a la superficie de review. Patrón documentado en el README de aura (§Patrón). E2E verificado en Chrome real contra qwen3-coder: la escalada real por doc.clear abre el anillo con el orb rojo al centro → Deny en el sector → returned(aborted, user-cancel) + «Agente: terminó — control devuelto» + documento intacto. check 0 propios · smoke agnt PASS. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
d008605fac |
uix(aura): variante orb por defecto en el indicator — presencia perceptible
El aurora (banda horizontal recortada al círculo) leía estático a 32-48px (señalado por el usuario). El render vivo pasa a ser el efecto `orb` de $scene — la presencia esférica nativa: anillo respirando con ruido + luz orbitando, movimiento de silueta perceptible en tamaños pequeños y deformable por el puntero. `aurora` queda como variante para superficies grandes (`<Aura.Indicator variant="aurora">`). Modulación §F6 del orb: el estado fija la energía (forceHoverState + hoverIntensity como gain + rotateOnHover en acting/escalated) y el rol el matiz como delta de hue OKLCH sobre la identidad del shader (primary; reviewing=risk, escalated=threat). Verificado en Chrome real, estado por estado: idle recogido → planned anillo violeta → acting denso rotando → escalated rojo agitado. Demo de aura: chips de variante; scene/README: acta D4 pendiente de F3. eidos 353/353 · check 0 propios. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
97ae19da50 |
docs(agent): sección de configuración del transporte en la página agnt
La demo usaba el adaptador sin documentar cómo se configura. Nueva sección «Configuring the transport»: qué es el puerto (turn = un intercambio de cable), adapters fuera del barrel, ejemplo dev-directo vs prod-proxy (topología de credenciales), tabla completa de opciones (baseUrl · model · headers · request · fetch) y qué POSEE el adaptador como traducción de dialecto (SSE→v1, sanitización de ids, re-pairing de tool_calls, split por id en servidores que reutilizan index 0, error de cable → transport-error). README del arte: opciones inline en la entrada del adaptador. check 0 propios · smoke agnt PASS. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
595040b73b |
feat(agnt): Aura como superficie de presencia de la demo con modelo real
La respuesta a «¿está pensando?» no se improvisa — es Aura, el componente
del eje construido para ello. La sección real de la página agnt bootea un
uix standalone mínimo (createActiveUix + Soma + ActiveEidos + eidos css):
legítimo porque un app compone ambos tiers, y la demo ES el app.
- Motor llama → createActiveAgent (mismas opciones bus+journal); activeRun
reactivo satisface AuraAgentSource; stream vía .engine.onEvent
- <Aura {agent}> con Indicator (orb $scene) + Status + Region + Cancel;
authorize/escalation quedan como afordancias app-level (doctrina §7)
- Verificado sobre el run real de qwen3-coder: «Inactivo» → «Propone un
plan» → announcement «Agente: terminó — control devuelto» en la Region,
doc mutado, answer en es; Cancel = Button disabled-en-idle; consola 0
check 0 propios · agent 30/30 · smoke agnt PASS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
447a87239c |
feat(agent): adaptador openai-chat real + carril de iniciativa del bus
El bus como eje del agente a nivel de app, cerrado en tres direcciones:
observar (agent.*), reaccionar (orca) e INICIAR — hecho de dominio → acción
orca → App.agent.start({initiative:'system'}) con clamp auto→review por
construcción. Nunca command-events. Sección doctrinal + ejemplo en la página.
Adaptador real `$agent/adapters/openai-chat` (fuera del barrel, D-AG.1c):
dialecto OpenAI chat-completions (Ollama/llama.cpp/LM Studio/proxy), SSE →
protocolo v1, sanitización bidireccional de capability ids con puntos, y
re-pairing de tool_calls al reproducir el transcript (memoria por callId).
Fix cazado en vivo: Ollama reutiliza index:0 para todas las calls del
stream — una call nueva se detecta por id distinto, jamás por index (los
args concatenados producían invalid-args en motor + 400 en el replay).
Demo viva en /active/docs/agnt contra qwen3-coder:30b real: capacidades
doc.* tipadas (sium), read snapshot-por-turno, streaming, escalada
irreversible. Ciclo completo verificado en navegador: 3 acts, 2 turnos,
doc mutado, respuesta en es, rastro entero en bus (correlationId=runId).
agent 30/30 · smoke agnt PASS · check 0 propios.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
fa71890564 |
fix(agent): conformidad bus/orca — payload del bus sin contenido de usuario
Auditoría punto-a-punto contra los README de bus y orca (12/13 conforme).
La no-conformidad real: `goal` viajaba en AgentRunBusPayload, contra la
higiene documentada de ambos (bus: «pasa correlationId y que el suscriptor
resuelva el contexto sensible del módulo»; orca: «no unnecessary private
data — la acción lo resuelve del módulo al ejecutar la acción»).
- AgentRunBusPayload = SOLO metadata (runId/initiative/autonomy/state/
outcome/reason/acts/turns); el suscriptor resuelve vía App.agent.run(runId)
- spine test prueba el patrón: 'goal' in payload === false + resolución
desde el módulo con el orca REAL
- página agnt: nota de higiene + recomendación configureEvent(
'agent.run.state', { queuePolicy: ORCA_QUEUE_REPLACE_QUEUED }) para el
evento frecuente (doctrina de colas de orca)
- PLAN-agent F4a: acta de la auditoría de conformidad
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
803b588ba5 |
feat(agent): F4a · marco de integración app — bus/orca/página del arte + fixes de auditoría
Auditoría del track (encargo): divergencias firmadas-sin-implementar corregidas y el marco de integración INDEPENDIENTE del componente — el agente como servicio del app, no como demo de un componente. Fixes de decisiones firmadas (con tests): - C3/D-AG.7: la iniciativa autónoma (system/scheduled) clampa auto→review — un run que el usuario no pidió jamás se auto-autoriza. - D-AG.5: presupuesto de contexto (maxContextBytes) con FIFO-trim byte-based (el goal sobrevive; resumen semántico diferido) + log del recorte. - F4c: tope por-arg (maxArgBytes) → invalid-args tipado. - D-AG.6d: schema Standard-Schema del resultado (`result?`); un mismatch vuelve como recoverable para auto-corrección y no cuenta como act. Integración app (patrón session EXACTO): - El arte posee el vocabulario `agent.*` (consts) + bus-helpers tipados que publican con bus INYECTADO (type-only $libs/bus): run.opened/state/ escalated/closed + act.applied, con source='agent' y correlationId=runId — la costura causal ligera (una delegación correlaciona de punta a punta). - defineActiveAgent cablea core.bus (junto a timers/logger). - Reacciones vía orca APP-LAND (el invariante intacto): probado con EngineOrca REAL reaccionando a agent.run.closed en integration.test. - Página del arte /active/docs/agnt (la convención que faltaba): sección Delegation en nav; artículo + playground VIVO con los 4 motores puros reales (bus log en vivo, journal WAL, contador orca, kill switch, compuertas deterministas) — cero componentes, integración pura de app. Gates: agent 26/26 · smoke agnt PASS · check 0 propios · verificado en navegador (pane + Chrome real): ciclo completo, orca archiva runs, kill switch reactivo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
fe2d43b7ac |
uix(anchor-nav): F1.6 · scrollspy TOC (soma+eidos) + hardening
Nav landmark + native anchors; IntersectionObserver band detection (never a scroll listener + getBoundingClientRect — anti-reflow doctrine). aria-current="location", per-depth rail indent, data-active drives the eidos accent. Real-IO chromium test: band tracking + bottom dead-zone. Adversarial-review hardening: - ref-count register/unregister so duplicate hrefs / mount churn can't drop a still-referenced target (+ test) - tokenize the rail transition (--duration-fast/--ease-default) and the focus outline-offset (calc(var(--focus-ring-width) * -1)) - document two v1 limits (instant-jump dead-zone, link-order == section order) — both need a v2 observation-contract change Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
6be7195f0f |
feat(agent): eje agéntico — motor $agent (F1) + Aura + promoción uix.scene
Materializa el eje ortogonal agéntico del ecosistema: la 8ª familia semántica
`delegate` («¿quién actúa ahora?») deja de estar sin materializar. El agente es
OTRO ACTOR (LLM, macro, regla, workflow) que actúa por la MISMA API pública del
provider; la ruta de llamada no se bifurca, la concreción semántica depende del
actor.
F0 · Doctrina
- docs/architecture/agent.md — la doctrina permanente (capítulo comparativo de
referencias + bibliografía de seguridad, máquina estados=verbos, contrato de
participación, fila §0, a11y, amenazas).
- docs/process/{PLAN,TRIAGE,INFORME}-agent — plan de ejecución con todas las
decisiones firmadas (D-AG.1–11 + ⚖️1/2/3), triage de 4 revisores externos, e
informe autocontenido para revisión externa.
F1 · Motor ($libs/actor + arts/agent, 24º arte)
- EngineAgent puro (sin DOM/runes, portable a servidor): máquina D-AG.4
(suggest/review/auto · escalated con reason+timeout · returned outcome-tipado
· kill switch · autorización journaled aunque sea auto), tool-loop D-AG.5
(acts secuenciales, fallos→verbos, unknown-capability acotado, idempotencia
por callId, presupuestos acts/turns/wall-clock vía puerto de timers), techo de
autonomía por origen no confiable (F8b).
- ActiveAgent (sesión reactiva, contrato ActiveEngine) · protocolo v1 espejo
AG-UI (5 categorías + dirección tipada + reservas) · ScriptedAgentTransport
determinista (adapters/ fuera del barrel) · journal WAL + puerto de trazas
OTel · emisor sium→JSON Schema.
- Acuñación del actor (⚖️2/F6b): ActorToken opaco en $libs/actor (hoja bajo
orca/agent), registro privado WeakMap — los forjados resuelven a null;
costura `actor?` en TriggerOptions→SemanticSignal (runtime copia verbatim;
sema no resuelve). defineActiveAgent (service-factory app-level, timers del
core — cero setTimeout a pelo).
F3 · Aura — primer componente del eje (ruta 9 fases 0–6)
- El materializador canónico de `delegate` (reservado en scene §F6): morfo con
los eventos del ciclo (offer/escalate-untilAction/return + sustain-processing
stateBound) — PRIMER morfo del ecosistema que emite familia delegate; provider
soma que observa un puerto estructural (sin importar $agent — degradación
total); orb eidos = aurora $scene modulada por estado (§F6) con fallback CSS
= render de reduced-motion; live region única atribuida (WCAG 4.1.3); cancel
compone Button (asChild). Demo v2 sobre el MOTOR REAL con compuertas
deterministas. Orb-size derivado de la primitiva Avatar (32/40/48).
D4 · Promoción uix.scene (Aura llegó)
- defineEngineScene + superficie ActiveUix/ActiveEidos/contracts; el orb prefiere
el motor compartido (presupuesto de escenas global) con fallback por-superficie.
Gates: arts/agent 18/18 · aura 6/6 · arts:check 24 · eidos battery + recipe
30/30 + eidos-lint aura 0 inválidos · morfo:vocabulary + esquemas · smoke aura
PASS · verificado en navegador (ciclo completo + estampa delegate en DOM +
aurora WebGL pintando). Los tokens de recipe base.ts + CSS generado ya entraron
en HEAD vía una sesión concurrente.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |