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 }
1673 Commits (69c108d2a090461cfdec4d4dba5173269bfa6893)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
69c108d2a0 |
fix(demo): cargar los 25 packs sema que faltaban en la composicion de demos
Cierra la CLASE del hueco que destapo el reporte del media-player
(
|
2 months ago |
|
|
5d2816a0c4 |
fix(sema): silencio del player ampliado a los sliders — pick/drag por canal
Escucha real del usuario tras D-AP2.7 v2: los botones ya callan, los sliders no — sonaban el pickup del agarre y el feedback continuo del drag (el commit-set del soltar ya estaba restado). Dos reglas de descendientes con channels: ['haptic'] para handle-pick y handle-drag de los Sliders compuestos (scrubber y volumen): ambos son eventos ESTRUCTURALES sin intent — el libro concentra la evaluacion en el drop, cubierto por la resta de commit-set — asi que desactivar su canal sonoro dentro del player no borra carga evaluativa (la regla que prohibe tirar canales protege intents, y handle no lleva ninguno). El resolver por-emit del drag sigue poseyendo las primitivas del sonido, pero una senal con el canal inactivo no suena; el tick tactil se conserva. El pickup lleva primitivas absolutas de libreria (gain 0.12), por lo que una resta se acoplaria a su valor — el canal es la via limpia. Corrige la nota del pack («no callable desde el pack era conservadora de mas») y el plan v2 (D-AP2.7 v2 ampliada). Fuera del player los sliders conservan su firma completa (selectores de descendientes, especificidad 30 sobre 20 del pack del slider). Verificado: morfo:vocabulary limpio - sema 192/192 - check 74 = baseline, 0 propios. El verify final es la escucha: agarrar y arrastrar el scrubber debe ser mudo (solo tacto), y fuera del player un Slider suelto debe seguir sonando como siempre. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
63f20f3451 |
fix(demo): cargar mediaPlayerSema en la composicion de la app de demos
Reporte del usuario en vivo: el transporte del player seguia sonando en la
demo pese al pack silencioso (D-AP2.7 v2). Causa raiz: los packs de sema son
OPT-IN por app (defineEngineSemantic({ components: [...] }), tree-shaking) y
`mediaPlayerSema` NUNCA estuvo en la lista del layout de demos — ni el
silencio nuevo, ni el tap sutil v1, ni el fix del selector (H-1) llegaron a
aplicarse alli jamas: lo que sonaba era el default crudo de familia (los dos
earcons por clic que H-10 midio).
Dos lineas: import + entrada en `components` de
web/routes/uix/+layout@.svelte. Verificado: check 74 = baseline, 0 propios.
Observacion reportada (fuera de alcance, no tocada): otros packs del indice
tampoco estan en esa lista (dropdown-menu, context-menu, menubar, switch,
toggle, tooltip, tree-view...) — la misma clase de hueco para sus demos.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
85b269f745 |
feat(sema): transporte del player silencioso por defecto — D-AP2.7 v2
Firmado 2026-07-31: el player es el unico componente cuyo output ES audio — un tick de UI en su transporte compite con la obra por definicion (D.7). El pack aplica el patron D.5 (resta exacta del gain de familia; las primitivas del intent intactas, threat/fulfill afloran) a todo lo que dispara un gesto: - commit-toggle-play / commit-toggle-mute / commit-set-rate en sus botones; - el contact-activate de los Buttons compuestos (regla de descendientes, H-10; tuning nuevo `contact.silent` -0.25 en sounds.ts) — IconButton compone Button sin morfo propio, asi que [data-button] casa (verificado); - el commit-set de los Sliders compuestos (scrubber y volumen). Audibles se quedan: commit-fail (el risk aflora sobre la resta) y commit-complete (informa de la obra; revisitar con escucha real). El handle-drag continuo conserva su resolver por MECANISMO (el payload por-emit posee las primitivas — el propio pack del slider lo declara); callarlo seria iniciativa de resolver/canal. La voz se reactiva en la capa de app (cascada 5b), sin prop nueva. Complementario con duckUiWhileContent: el pack calla AL PLAYER, el duck atenua al RESTO de la app. Haptica se queda. Selectores 100% del builder tipado (solo el combinador descendiente es literal). Verificado: morfo:vocabulary limpio - sema 192/192 - check 74 = baseline, 0 propios. Auditoria posterior: regla de descendientes confirmada viva (IconButton -> Button), sin regla rival de commit-set en el pack del slider, deltas componen por el mecanismo D.5. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
c4d64ba98a |
feat(media-player): modo audio de referencia — AudioLayout, <AudioPlayer> con 4 variantes y buffer real (F4-F7 parcial)
El modo audio deja de ser "video colapsado" (las 3 reglas CSS de v1):
- F4 sema: el docblock del pack escribe la doctrina D-AP2.7 — el silencio
mientras suena la obra lo posee el SERVICIO (duckUiWhileContent, receta
documentada); la resta-de-gain queda disenada sin cablear a proposito.
- F5 buffer real (G-2 consumido): el TimeSlider soma pasa bufferedTime como
secondaryValue del Slider compuesto y monta Slider.SecondaryRange; se
retiran el div .mp-buffered, la var --media-buffered (y su writeProperty),
--_mp-buffered y --_mp-rail. Look intacto: el 42% pasa a
--slider-secondary-bg en scope.
- F5 wrappers eidos de las 6 partes: Artwork compone AspectRatio+Image del
catalogo via child; RateButton compone Button ghost mostrando {data-rate}x;
Artist/Identity/Transport/LiveIndicator passthrough.
- F5 AudioLayout: espejo de DefaultControls para audio — identidad
(artwork+title+artist+live) + transporte + fila de scrubber + secundarios.
- F5 raiz competidora <AudioPlayer> (eidos/components/audio-player/, el
precedente Toast/Toaster): una etiqueta monta todo, y la metadata del SO se
DERIVA de la identidad visible (title/artist/artworkSrc) salvo override —
lo que ves es lo que muestra el lock screen (D-AP2.5).
- F5 recipe de 4 variantes (card/row/bar/inline) scoped a [data-variant]
(componer a mano sigue sin opinar) + tokens publicos --audio-player-*
(gap, artwork-radius, artwork-size-row/bar), regenerados.
- F7 parcial: el badge live usa el text localizado del morfo via soma (fuera
el "LIVE" hardcodeado) · demo con el modo audio real (<AudioPlayer> +
selector de variante + snippet con paridad) · README eidos con la seccion
del modo audio · component:audit --only media-player PASS.
Verificacion: player 18/18 · recipe-css-contract + component-api-contract
37/37 · eidos-lint media-player 0 invalidos · check 74 = baseline, 0 propios
(demo incluida). Declarado: audio-player.css es recipe de composicion sobre
el morfo del player (el lint per-component no la cubre — gap del tooling);
los internos --_mp-* los comparte la recipe hermana del MISMO morfo.
Quedan F6 (verificacion VISUAL de las 4 variantes — exige ojos) y el resto
de F7 (medicion Chrome del escenario mixto + teclas de medios + stamp sema).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
2f872ef864 |
feat(sound): $sound orquestador (buses+voces+media) y player v2 F1-F3 sobre el servicio
Rediseno clean-room del sonido (gate D-SR.1-12 firmado; la doctrina "la voz
es del art" de PLAN-sound-engine s16.1 queda revocada):
- Mezcla: master -> buses ui/content (gain/mute/duck refcount, gana la mas
fuerte; politica pre-contexto). prefs de UI jamas tocan la obra.
- Voces registradas: la calibracion es DATO del consumidor (sema registra
'sema'; el default conserva la calibracion historica - cero cambio audible,
A/B del usuario: "suena igual").
- Ciudadania de media: sound.media(el) PROVEE el transporte (la forma exacta
del puerto MediaProvider) + foco mixed/exclusive/duck PERSISTENTE (estado
derivado; el ducker mas reciente retiene la palabra) + duckUiWhileContent +
MediaSession de la fuente activa (play/pause/seekto + posicion, liberacion
y promocion) + visibilidad content-aware + attach() opt-in (irreversible,
CORS declarado). Re-registro idempotente por elemento.
- diagnostics.ts con catalogo tipado (contrato de arts) y guards post-dispose
(ningun camino abre un contexto huerfano). Auditoria AU-1..9 resuelta;
bundle medido: 16,2 KB min / 5,6 KB gz.
Player v2 (gate D-AP2.1-13 firmado) F1-F3:
- F1 gaps de framework: aria-valuetext en Slider (valueText por thumb) +
parte SecondaryRange con token --slider-secondary-bg (buffer/clip/capitulos)
+ formatDuration en $libs/days + fix del selector del pack (H-1: la regla
de play/pause construia sobre provider y el stamp aterriza en play-button).
- F2 morfo: 6 partes audio-only (artwork/artist/identity/transport/
rate-button/live-indicator), commit-set-rate, commit-set-time RETIRADO
(D-AP2.13: el scrubber delega en el commit-set del Slider compuesto),
sustain-loading cableado (H-9), teclado j/l, </> y 0-9 (con guard de
modificadores para no pisar atajos del navegador).
- F3 la migracion: el transporte default es uix.sound.media(el, {metadata})
(pineado con el motor real); nativeMediaProvider RETIRADO (media-provider.ts
queda como contrato puro del puerto, sin shim); G-5: los commits de
play/mute/rate cabalgan el evento de RESULTADO (play/pause saltando el
pause de ended, volumechange con transicion de muted, ratechange);
formatTime -> formatDuration; sub-providers + wrappers + exports de las 6
partes; prop metadata publica (captura al registrar, documentado).
Verificacion: 45 suites / 458 tests verdes en el alcance (sound, sema,
active-uix, active-app, morfo, slider, media-player, days) - check 74 =
baseline de la rama, 0 propios - docs:check 0 - invariante de UN AudioContext
intacta (e2e sin tocar). Planes de proceso y handoff al dia
(PLAN-sound-redesign, PLAN-audio-player-v2, CONTINUE-sound-engine).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
b1b568e32f |
feat(blocks): contact CERRADO — la coordinacion entra en el contrato B
El estado se le pregunta al ESQUEMA, no al Form. `form.isValid` con `validationBehaviour: 'progressive'` significa «aun no se ha encontrado nada mal»: un formulario vacio e intacto no tiene errores, asi que se declaraba valido y `incomplete` era INALCANZABLE al cargar — la seccion pedia resolver la verificacion con los tres campos vacios, justo el hueco que la doctrina existe para cerrar. Ahora la raiz valida los valores con `validateSync` de SIUM: sincrono y sin escribir errores (`form.validate()` habria encendido los tres campos en rojo en el primer frame). `state.test.ts` fija la maquina con 9 casos — primer test unitario del tier, porque `state.ts` es su primera logica pura: el orden de prioridad, que solo `ready` deja enviar, y que todo estado bloqueado tiene frase. Contrato B (architecture/blocks.md): seccion «Coordination» con las tres cosas que posee un block que coordina (estado, forma de sus datos, palabras), B-5 admite el esquema por defecto, B-7 enmendado (posee las palabras de SUS estados como idlangref con fallback ingles) y la convencion de servicios acotada: el traductor es el UNICO servicio sancionado. Dice tambien a quien NO aplica — los blocks de layout no coordinan nada. i18n por la via real: el arnes registra el namespace `blocks` en las DOS cadenas de arranque (galeria y previews, que duplican el boot) y la demo se lee en castellano en vez de caer al fallback ingles. Ademas: README y pestanas de doc rehechos al reparto nuevo, ficha F2.13 y bitacora en PLAN-blocks.md, y el control vivo que faltaba para un prop publico — `verification` con reto / sin reto, porque omitir el prop no es lo mismo que pasar un estado que nunca verifica. Verificado en navegador, arco completo y las dos ramas: sin reto vacio → un campo → tres campos (se desbloquea) → «Enviando…» → «Enviado»; con reto, con los tres campos llenos el boton sigue bloqueado y la razon pasa a «Resuelve la verificacion de arriba». `verifying` y `rejected` quedan cubiertos por test unitario, no por el reto en vivo: el provider de ProofOfHuman escribe `status` el mismo y el veredicto del app no se sostiene (hilo de canon abierto). Gates: blocks:check verde (14 blocks) · vitest 9/9 · docs:check 0 · svelte-check sin errores propios · prettier limpio en los ficheros propios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
75c2602591 |
feat(blocks): `contact` — el primer block que COORDINA (estado, datos y palabras)
WIP declarado: el block funciona y renderiza, pero el arco interactivo completo no está verificado todavía. Lo que falta está en `CONTINUE-blocks.md`, en orden. ## El cambio de doctrina Decisión del usuario, textual: «si al final es una lista de componentes… el bloque es coordinación, estado, y data también». El motivo se veía en este mismo block: con el estado fuera, cada `disabled` era una expresión distinta montada en el punto de uso, y aparecían huecos — un envío que no se podía hacer y nadie decía por qué. Eso cambia lo firmado en `docs/architecture/blocks.md` (B-5/B-7, D-BLK). Queda ANOTADO como pendiente de escribir en el contrato: propagarlo sin actualizarlo sería deriva. ## Lo que ahora posee el block - **La máquina de estado** (`state.ts`): `incomplete · unverified · verifying · rejected · ready · sending · sent`, derivada de la validez del `Form`, del veredicto de verificación y del envío. Con dos registros EXHAUSTIVOS — `CONTACT_REASON` y `CONTACT_ACTION`— que hacen imposible un estado mudo: no se puede añadir un estado que bloquee el envío sin darle su frase, porque el `Record` obliga. - **La forma de los datos** (`schema.ts`): nombre · correo · mensaje, con las etiquetas como idlangref. El app lo sustituye pasando su `form` + `schema`. - **Las palabras**, por el traductor (`#?blocks.contact.…|fallback`), la misma puerta que usa el canon para sus `texts:`. Partes nuevas: `Contact.Fields` (los tres campos del esquema propio), `Contact.Submit` (bloqueo Y etiqueta desde el estado, nunca desde una expresión en el punto de uso) y `Contact.Reason` (la frase del estado). `Contact.Form` toma el handle del contexto en vez de pedirlo otra vez. La demo adelgaza en consecuencia: se le cayeron el esquema, las etiquetas, el `disabled`, el flag de envío y la frase. Le queda lo que es de un app — las palabras de la página, los datos de contacto, el reto que elige y qué significa «enviar». ## De paso, dos arreglos de composición - El `TextArea` iba dentro de `Field.Control` y salían DOS marcos: pinta su propia carcasa (borde, radio y relleno son de su recipe). `Field.Control` es la carcasa del `Field.Input` desnudo. - El `Form.ErrorSummary` vacío dejaba una caja naranja: su raíz pinta borde de riesgo en cuanto el form es inválido, aunque la lista esté vacía. ## Dos hilos de canon abiertos (medidos, sin causa raíz) - Con `progressive`/`onSubmit`, un envío inválido bloquea y mueve el foco pero no expone mensajes; con `onChange` sí, pero saltan los tres campos al escribir en uno. La máquina tapa el agujero de cara al usuario; el hueco sigue. - El provider de `ProofOfHuman` escribe `status` él mismo, así que el veredicto que devuelve el app no se sostiene y los slots `stage*` no renderizaron nunca. Gates: `blocks:check` verde (14 blocks) · `svelte-check` sin errores propios · prettier limpio. Verificado que la sección renderiza (section · form · 3 campos · submit · cero errores de página) en un dev server limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
59bd5ffb71 |
fix(blocks): contact — la demo al dia con el block que ya coordina
El add del commit anterior capturo la iteracion previa de ContactSite (el app poseia schema/labels/disabled); el guardado del editor con la version final — el block coordina y al app le quedan sus palabras, sus canales, el reto y el significado de enviar — llego justo despues. Esta es esa version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
d241409138 |
chore: allowlist acumulada de settings.local.json
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
d9c79bf584 |
docs: corpus al día — blocks tier indexado, factorías nuevas, cifras atemporales
blocks.md entra en el índice y next-features gana el registro §8 del tier; active-app documenta las factorías que faltaban en la tabla (agent, motion, scene…); docs-check aprende a censar catálogos sin tests ni barrels; book-deviations y component-guide dejan de citar recuentos congelados. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
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 |
|
|
7b52ae8b6a |
fix(soma): dropdown-menu — la apertura por teclado enfoca el primer item (APG)
openReason distingue pointer/keyboard en handleOpen: el teclado aterriza en el primer item, el puntero deja el foco en el panel y manda el hover. Un submenú abierto por hover cierra al mover el foco del panel padre, y el cierre por teclado devuelve el foco al sub-trigger. Tablas de teclado del README al día. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
b48264902f |
docs(process): el gate del reproductor, consolidado — se presenta una vez y no se mueve
La sesión perdió convergencia: tres presentaciones del gate lo movieron tres veces (D-AP.7 corregida dos veces, D-AP.12 → v2, D-AP.13 apareció al final), y de sus commits los últimos corrigieron a los anteriores. Este pase consolida TODO en el cuerpo del plan para que la sesión siguiente firme en frío, sin re-excavar. Consolidación pura: sin código y sin hallazgos nuevos. - **D-AP.7, criterio final: incongruencia, no frecuencia** (D.7: «si el contenido ES audio, un tick de UI compite con la obra»). Con frecuencia como criterio, play/pausa —2 a 10 veces por escucha— quedaría fuera y la línea sería arbitraria. Se cancela el gain de lo que suena MIENTRAS suena la obra; se conserva lo que informa del estado de la obra (`commit-complete`, `commit-fail`), con `commit-complete` anotado como el discutible de los dos. - **D-AP.13, nueva**: `commit-set-time` duplica el `commit-set` del `Slider` embebido (`onValueCommit` → `seek()` → `trigger`, dos líneas leídas; el del Slider, medido en navegador). El rationale de radio-cards aplica literal, y el propio provider ya practica la delegación en `setVolume`. Recomendación: retirar y delegar; el coste va a F2 como cambio de contrato declarado. - **§8.7 — la postura de participación, resuelta**: `expression: 'pack'` es correcta (los eventos de ciclo de vida no los posee ningún hijo, y el guard S11d la exige mientras exista el pack); el player es un híbrido legítimo con UNA duplicación (D-AP.13) y UN defecto de timing (G-5). La premisa de D-AP.1 queda intacta — la pregunta que la última pasada dejó como abismo tenía suelo en la doctrina. - El clip queda condicionado a D-AP.12(b) en §5, F3 y F5, coherente con la recomendación v2; F2 recoge D-AP.13 y la ejecución de H-9. - La cabecera fija la regla de cierre: la cuarta presentación **se firma o se rechaza, no se re-excava** — y el kickoff de sesión nueva lo dice explícito. - El handoff (`CONTINUE-sound-engine.md`) queda en «firmar, sólo firmar», con la trampa que costó la sesión escrita donde las demás: **una medición no es un diagnóstico** — leer el contrato de la pieza (README → CANON §8 → book-deviations → código) antes de acusarla. `docs:check` 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
061d66299b |
fix(docs): D-AP.7 proponía un mecanismo que viola la regla dura de la cascada
Leyendo `book-deviations.md` D.5/D.7 —que llevo dos días citando sin abrir—
resulta que el mecanismo que propuse para el silencio en modo audio está mal, y
que el correcto ya estaba canonizado en el catálogo.
Proponía `channels: ['haptic']`: tirar el canal sonoro entero bajo
`[data-media-player][data-media='audio']`. Eso **contradice la regla dura que
está en `CLAUDE.md`** — *«Cascade rules add character, never the intent's
evaluative profile… to shift one, compose with `{ op: 'add', value: … }`»* — y
su consecuencia práctica es peor que el problema: borra también la carga
evaluativa, así que un `commit-fail` dejaría de oírse justo cuando hace falta.
El patrón correcto ya existe y está canonizado como D.5: `form.toggle.silent`
(`gain: { op: 'add', value: -0.3 }`) y `tooltip.silent` (-0.2) **restan
exactamente el gain de la familia**, de modo que lo cotidiano cae a 0 y los
deltas de intent siguen aflorando. Su propio comentario lo dice: «avoids fatigue
without erasing evaluative loading».
Aplicado al player, con una precisión que obliga un dato medido: **`risk` no
añade gain** (sólo `threat` +0.1 y `fulfill` +0.05), así que una cancelación
global también silenciaría el fallo de carga, que es `commit-fail + risk`. Por
eso la regla cancela el gain **sólo en los eventos frecuentes del transporte**
—`commit-toggle-play`, `commit-set-time`, `commit-toggle-mute` y el
`contact-activate` de los botones compuestos— y **deja intactos**
`commit-complete` y `commit-fail`, que son ocurrencias, no frecuencia.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
81378a6875 |
fix(docs): H-10 mal diagnosticado — el canon separa las dos voces, el player las junta
Cerré H-10 hace un commit diciendo que el doble earcon era «el patrón de composición del catálogo» y que la decisión —regla nueva en el árbitro, o canon que declarase el solape intencionado— era materia de `book-deviations.md`. Es al revés: **el canon ya lo prescribe, y quien lo incumple es el media-player.** El README del `Button` lo dice citando el libro (cap. 22 §8 y §10): `contact-activate` es `sequence: 'pre'` y sólo acusa la RECEPCIÓN del gesto; la consecuencia evaluativa la dispara el consumidor **cuando el resultado aterriza** — *«the celebration sound plays at the moment the save actually resolves, not when the click is received»*. Dos sonidos, sí, pero **separados en el tiempo** y con dos significados. En el diseño no hay solape. El solape lo mete el player: `togglePlay()` dispara `commit-toggle-play` síncronamente dentro del handler del clic, no cuando el medio arranca — y el resultado real llega después, en el evento `play`/`pause` que el provider YA recibe (ahí arranca el bucle de progreso, y `commit-complete` ya cabalga `ended` con ese patrón exacto). Mismo defecto en `toggleMute()` y `seek()`; `toggleFullscreen` y `togglePip` lo hacen bien, esperando a la plataforma. Consecuencias: - H-10 deja de ser materia de canon y pasa a **G-5** en §8: mover el commit del clic al evento de resultado, aplicando el patrón que el propio fichero usa. - **El árbitro de dominancia se queda como está.** Nunca tuvo que arbitrar dos ocurrencias que el diseño no quiere simultáneas; proponer una regla nueva era arreglar el síntoma en la capa equivocada. - El test conserva la medición (dos ocurrencias en el mismo tick = dos earcons, 4 osciladores) porque el dato es correcto y vale como guardia; lo que se reescribe es su lectura. Para D-AP.7 no cambia lo demás: ninguna de las dos señales vive en el provider, así que el silencio en modo audio tiene que cubrir descendientes. sema e2e 5/5 · `docs:check` 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
5ac194cfe1 |
test(sema): un botón compuesto suena DOS veces, medido (H-10)
En F0 observé que el botón de play estampa dos señales por clic. Afirmé de ahí que sonaría dos veces, y eso era un salto: el motor tiene un árbitro de dominancia precisamente para esos casos. Medido: **4 osciladores, dos earcons**. Por qué el árbitro no los colapsa. `occurrenceRank = evaluable×10 + activación`: `contact-activate` del `Button` es estructural y sin intent → **0**; `commit-toggle-play` del player es evaluable con intent `neutral` → **10**. La regla silencia a un recién llegado sólo si una ocurrencia YA ACTIVA lo supera, y aquí llegan en orden 0 → 10: cuando entra el segundo, el activo no lo supera; y el primero ya había sonado. El modelo asume que la señal de más rango llega antes o sola — **el caso inverso, a microsegundos, no está arbitrado**. Tampoco hay pack de `button`, así que `contact-activate` usa la base de familia (800 Hz, 60 ms) y se solapa con el commit de 100 ms que entra encima. **No es del media-player**: es el patrón de composición del catálogo — la primitiva acusa el contacto, el componente confirma el valor. Cualquiera que componga `Button` y dispare su propio evento suena dos veces con el sonido activo. El test lo pinea donde se puede oír el camino entero (`sound-e2e.test.ts`), y la decisión —regla nueva en el árbitro, canon que declare el solape intencionado, o packs que silencien `contact` bajo componentes que hablan por sí mismos— queda fichada en el plan como materia de `book-deviations.md`, no de una sesión. Para el reproductor cambia una cosa y es la que importa para D-AP.7: el silencio en modo audio tiene que cubrir las DOS señales, y ninguna vive en el provider. sema + art 17 suites / 205 tests · `docs:check` 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
7ae5a8117d |
docs(process): F0 del reproductor — las verificaciones, hechas antes de pedir la firma
La mitad de F0 que no necesita firma, ejecutada primero, porque tres de los cinco resultados cambian lo que hay que firmar. Sin código del player. - **H-1 confirmado con stamp real.** `data-event="commit-toggle-play"` aterriza en `<button data-media-player-play-button>`; el pack selecciona el provider y no puede casar. Es **1 de las 3 reglas** —`commit-complete` y `commit-fail` apuntan bien—, y el fix de una línea **cambia comportamiento** (empieza a sonar donde no sonaba), así que espera a la firma como G-4. - **El `Slider` compuesto sí sonoriza**, por composición y sin excepción: `TimeSlider` → `Slider.Provider` → `resolveSliderDragSound` en cada `handle-drag`. Stamp medido sobre `[data-slider]`. La regla propuesta funciona porque `Element.matches()` evalúa descendencia. - **Hallazgo nuevo**: el botón de play emite **dos** señales por clic — `contact-activate` del `Button` compuesto y `commit-toggle-play` del player, ambas en el propio `<button>`. Una regla de silencio anclada al provider no alcanza a ninguna: el silencio en audio tiene que cubrir los DESCENDIENTES. - **H-9 — `sustain-loading` es superficie de contrato muerta.** La pregunta de F0 («¿se auto-limpia?») era inválida: **no se emite nunca**. Está declarado en el morfo, verificado por su test y documentado en el README de soma como si viviera; no hay un solo `trigger`. El spinner lo mueve `data-buffering`, que es estado. `sustain-end` queda descartado, y qué hacer con el evento —cablearlo o retirarlo— entra en el gate. - **Segmentos: 1 de 6, no paridad.** Media Chrome no tiene componente de clip en su `main` (revisados `src/js` y `experimental`; el `media-clip-selector` que circula en buscadores es de una 0.x retirada), Plyr sólo `markers`, Red Hat no lo documenta, react-h5 no lo lleva. Sólo wavesurfer, por plugin. **D-AP.12 cambia de recomendación: v2**, con el diseño de §7.5 congelado. Diferirlo no acorrala —sin `clip` el dominio es `0…duration`, añadirlo luego es un modo nuevo, no un cambio de significado. - **vidstack#1195 caracterizado, NO reproducido**: abierto, sin respuesta, iOS 17.3.1 en Chrome y Safari móviles, escritorio sano, `webkit blob resource error 1`. Apunta a fuentes `blob:` + su forma de acotar el rango seekable, no a la idea de clip; nuestro diseño no toca la fuente. Pero *probable* no es *verificado*: exige un iOS real, y este entorno no lo tiene. Correcciones de método aplicadas al propio trabajo: `gh` no existe en este entorno y un `|| echo` lo convirtió en un falso negativo sobre media-chrome — rehecho por la API. Y la recomendación de D-AP.12 se cambia con los datos delante en vez de defender la que ya estaba escrita. `docs:check` 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
59aa41a132 |
docs(barcode): la trampa de `MorfoElement`, y el registro de diferidos al día
Dos huecos que dejó el trabajo del componente. - **`morfo.md` no decía que el vocabulario `MorfoElement` vive DUPLICADO**: la unión de TypeScript en `types.ts` y la lista de literales del validador sium en `schema.ts`. Añadir solo la unión compila limpio y revienta en runtime, y `npm run check` no lo ve — lo caza `morfo:check`, o simplemente montar el componente. Entra como pitfall con el error literal que escupe y el incidente que lo destapó (`'text'`, para la interpretación legible del Barcode). - **El §10 del registro de diferidos se escribió ANTES del perfil ISBN** y ya no decía la verdad: listaba como pendiente un trabajo que se envió el mismo día. Ahora separa lo enviado (el perfil de entrada sobre EAN-13, con su razón de no ser una simbología) de lo que sigue diferido, y deja escrita junto al add-on EAN-5 la restricción dura de la hyphenación: no se calcula desde el número, necesita las tablas de rangos de la International ISBN Agency — dato versionado con fecha de caducidad que una librería de cero dependencias no puede tragarse. El string con guiones lo trae el consumidor. Co-Authored-By: Claude Opus 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 |
|
|
d2e841c65d |
fix(sound): la auditoría del art, cerrada — niveles por reproducción y opciones que no mienten
Los tres hallazgos que quedaban de `PLAN-sound-engine` §16, firmados y aplicados.
Dos con código, uno con una medición que cambia la doctrina.
**A-5 — `gainScale` es de la reproducción, no del documento.**
`play()` escribía `master × gainScale` en el nodo master, del que cuelgan TODOS
los earcons. Un parámetro transitorio corrompía un mando de política: el nivel
se filtraba entre reproducciones, una nota en vuelo saltaba de volumen, y los
dos consumidores que motivan el motor compartido —la reducción de sema y el
ducking de un reproductor— se pisaban sobre un único valor. Ahora el factor
multiplica el pico de la envolvente de ESA reproducción, y **también la
profundidad del AM**: dejarla absoluta habría hecho que un earcon atenuado
sonara relativamente más áspero, cambiando el timbre en vez del volumen, que es
lo contrario de lo que significa una reducción.
**RC-5 se reescribe, y es legítimo**: un contrato de regresión protege
comportamiento, no defectos. Lo que pineaba —«la ganancia se fija ANTES de que
la síntesis reviente»— sólo importaba porque el valor iba a un nodo compartido.
La cobertura no se pierde, se coloca donde vive cada responsabilidad:
`sound-port.test.ts` ya pineaba que el canal resuelve el NIVEL y lo entrega;
`sound.test.ts` pinea ahora que el master **no se mueve**; y el art estrena un
contexto falso que sí construye el grafo, para ver el pico de la envolvente.
**A-4 — las opciones del canal, imposibles de equivocar.**
`SoundChannelOptions` pasa a unión discriminada: `{ engine, preferences?,
logger? }` **o** la forma de construcción (`audioContextFactory` / `fetcher` /
`dom` / `masterGain` / `timers`). Nunca ambas — un motor llega ya construido, así
que sus opciones de construcción no significan nada a su lado, y hasta hoy se
aceptaban y se tiraban en silencio. La defensa primaria es el tipo (doctrina de
la casa), con un guard `@ts-expect-error` que hace fallar `check` el día que la
unión deje de rechazar la mezcla; el aviso por logger es la red para JS.
`engine.ts` pasa de un spread a tres ramas en orden de precedencia.
Y **`fetchFn` → `fetcher`**, para alinearse con `$perm`: un nombre por concepto.
`audioContextFactory` **se queda** — es el patrón `idFactory` que ya usan
`$logger` y `$bus`, y nombra el tipo exacto que fabrica (`context` a secas
colisiona con el contexto GL de `$scene`).
**A-3 — S-1 aceptado, medido: 2,2 KB.**
El art entero son 6.410 bytes minificados / 2.177 gzip. Y mi propia propuesta
para cerrarlo era falsa: quitar el fallback del canal no saca el art de ningún
bundle, porque `createActiveUix` y `defineEngineSound` lo importan
incondicionalmente. Cerrarlo de verdad exige carga diferida, que choca con que
`prime()` deba ser síncrono dentro del gesto, y volver `uix.sound` perezoso —
cambio de superficie pública por 2,2 KB. Se acepta: el art es servicio de
núcleo, como `motion` y `scene`. Queda corregida la doctrina: S-1 era el más
débil de los cuatro síntomas; los que justificaban la extracción eran S-2 y S-4.
**La voz, declarada como diseño.** La quinta a 1.5×, el ADSR recortado a
15 %/40 %, el contour de ±400 cents y el mapeo del AM son decisiones perceptivas
calibradas contra el vocabulario de sema, no maquinaria. Escrito en el README
del art y en la cabecera de `engine-sound.ts`, con la regla: el día que un
segundo consumidor quiera otra voz, ése es el momento de partir el art en dos
—gobierno de contexto / voz—, no antes.
Verificación: art + sema **17 suites / 204 tests** · `contracts.test.ts` 35/38
(los 3 rojos, ajenos) · `check` en la baseline exacta (73, 0 propios) ·
`docs:check` **0 errores** · los guards de A-5 **vistos fallar** al reintroducir
la escritura al master · navegador `/temas/sema`, 4 ciclos de editar→disparar:
**1 contexto, 16 osciladores, master en 1, cero errores de consola**.
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 |
|
|
cf37aee002 |
docs(sound): el aviso de segundo contexto dice lo que hace, no lo que promete
A-2 de la auditoría (`PLAN-sound-engine` §16). El contador de contextos vivos sólo ve **los que crea el art**, así que un `new AudioContext()` en cualquier otro sitio le es invisible: detecta cableado doble de ENGINES, no el antipatrón del contexto crudo que su propio mensaje describe. Ese hueco es exactamente lo que dejó al estudio de sema abrir un segundo contexto en silencio hasta ayer (A-1) — y lo que hizo inútil el plan del handoff anterior, que era «abre la página y mira el warn». Cerrarlo de verdad exigiría enganchar el constructor global, es decir parchear la plataforma. El art no lo va a hacer. Así que se escribe donde prometía de más, en `consts.ts` y en el README, y se apoya en la salida real: los consumidores que necesitaban su propio contexto ahora tienen puertas —`decode` para las muestras, `context` para el grafo— que no les obligan a abrirlo. Sin cambios de comportamiento. Art 13/13 · `check` en la baseline exacta (73). 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 |
|
|
15621632be |
fix(color): un solo vocabulario de rol, y la documentación del color reconciliada
Había DOS tipos exportados con el mismo nombre. config-types.ts:39 declara
`ColorRole = HierarchyColorRole | Intent` derivado del const (9 roles, con
tertiary) y existía desde el 2026-05-13; eidos/lib/types.ts:125 lo re-escribió
a mano siete días después con 8 miembros —sin tertiary— y nada lo justifica:
ni el commit (
|
2 months ago |
|
|
c0a7050f54 |
docs(process): registro de lectura — guías + theming leídos, e inventario corregido
Cuarta sesión de lectura del corpus. Leídos completos sobre el original:
component-guide (1568), component-audit (369), demo-authoring (266),
book-deviations (851) y todo theming salvo el changelog (gradient-finish,
guide, notes, channels). 4.247 líneas.
El hallazgo que más cambia el trabajo es sobre el propio registro: el
inventario estaba TRUNCADO. Contaba «30 documentos / 19.000 líneas»; el corpus
que el mapa declara son 59 ficheros / 27.348 líneas. Faltaban dos que el propio
corpus llama vinculantes — guides/demo-authoring.md (LOCKED, parte del «paquete
mínimo» de todo brief) y spec/delegation-contract.md (NORMATIVE, RFC-2119) — y
theming/changelog.md (1879), el mayor sin leer. Inventario ya corregido.
Segundo hallazgo de método: una lectura CADUCA. sema.md creció 1024 -> 1051 el
mismo día que se dio por leído (extracción del motor Web Audio al art $sound).
Comprobar wc -l contra el inventario al arrancar cada sesión.
15 hallazgos nuevos (nº 24-38), todos con doble anclaje: documento + línea sobre
el original, y comando contra el código. Los que más pesan:
- Sema tiene CUATRO canales en el código (AnnounceChannel incluido, cableado en
engine.ts desde
|
2 months ago |
|
|
b27f2345fe |
docs(process): el motor cierra sus fases y abre 5 hallazgos; el player, al gate
`PLAN-sound-engine` pasa a CERRADO (F0…F5) y `PLAN-audio-player` a DESBLOQUEADO. Pero cerrar las fases no es dejar el diseño incuestionado, así que el plan gana una auditoría propia que lo contradice donde toca. - **§15 — la invariante, cerrada y medida.** Incluye lo que la medición DESMINTIÓ: el handoff anterior predecía un aviso por consola en `/temas/sema` que no podía aparecer, porque el aviso cuenta contextos VIVOS y allí sólo había uno. Y **E-8**, el agujero de attach que E-1 dio por cableado sin estarlo. - **§16 — 5 hallazgos ABIERTOS sobre el art**, tres con decisión pendiente: `analyze.ts` sigue abriendo su contexto (medido 1 → 2 al importar un `.wav`); el aviso de segundo contexto es ciego a los contextos crudos; **S-1 NO está cerrado** y §7 afirmaba lo contrario —queda tachado en su sitio—; el canal tira en silencio `masterGain` y compañía cuando le inyectan el motor; y `gainScale`, que es de UNA reproducción, se escribe en el master compartido. - **§16.1 — la pregunta de fondo**: el corte separa módulos, **no conocimiento**. La quinta a 1.5×, el ADSR recortado a 15 %/40 %, el contour de ±400 cents y el mapeo de `roughness` son diseño sonoro calibrado contra el vocabulario de sema, viviendo dentro de lo que el plan llama «maquinaria». El precedente de `$motion` —datos en la capa, mecanismos en el art— no se replica tan de cerca como §3 afirma. Y la superficie no sirve a dos de los tres consumidores que la justificaban: `Waveform` y el analizador necesitan los `AudioBuffer`, y `preload(urls)` los esconde en una caché privada. - **`sema.md` corregido**: «sema no importa el art» era cierto para la INSTANCIA y falso para el grafo de módulos. - **`PLAN-audio-player` con las 5 correcciones de su cabecera incorporadas al cuerpo**, que era la condición para presentar nada: segmentos en la matriz §2 y en el contrato §7.5; **G-2 sube a bloqueante** (buffer, ventana de clip, capítulos); las dos invariantes de §7.6 (`prefs.sound` NUNCA toca el volumen del contenido; el silencio de UI debe alcanzar al `Slider` compuesto). **D-AP.11 escrita contra el art** —su premisa, «no hay motor», era falsa— y **D-AP.12** (segmentos `clip`) añadida; las dos existían como referencia sin fila en §4. **D-AP.7 reescrita** como ducking. - El handoff añade dos reglas que costaron caro: **un guard que no has visto fallar no vale**, y acoplarse al dev server vivo del usuario en vez de levantar otro. D-AP.1…D-AP.12 siguen SIN FIRMAR: el gate es lo primero de F0 y no se escribe una línea de código del player antes. `docs:check` deja 1 error ajeno (`blocks/banner/README.md:15`, «8 roles» por 9). 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 |
|
|
dd7efae9cd |
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
b7139cd825 |
docs(process): handoff en frío — el registro deja de exigir leerse entero
El registro había crecido a 450 líneas y una sesión nueva tenía que atravesarlas para saber dónde está y qué hacer. Ahora arranca con «Empieza aquí»: dónde está (21/30 documentos, ≈10.400 L), qué leer y en qué orden, las tres reglas que no se saltan, lo único que espera decisión, y el estado del árbol. Y se retira lo que ya se leía como TODO vivo estando cerrado: - La sección de contaminación queda marcada como histórica — se conserva porque la lección (una edición revertida citada como doctrina) vale más que el incidente, pero deja de pedir una relectura hecha. - Los tres «NO resolver antes de leer §25» de los hallazgos 7, 9 y 12 apuntan al nº 13, que los cierra. Un lector que llegue a ellos ya no cree abierta la pregunta del color. - El hallazgo nº 1 pierde su aviso de RELEER: el párrafo se confirmó sobre el original (`active-uix.md` 61–63). - El inventario dice explícitamente que no queda ningún documento en RELEER. Sin cambios en los hallazgos ni en el material leído. `docs:check` sigue con su único error preexistente (nº 21). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
6920684d09 |
refactor(sound): extraer el motor Web Audio de sema al art `$sound`
`sema/chans/sound.ts` tenía 407 líneas de las que ~255 (≈63%) eran maquinaria Web Audio —ciclo de vida del AudioContext, síntesis, samples, desbloqueo por gesto— mezclada con doctrina perceptiva. Deuda arrastrada, con tres síntomas medidos: el import era estático, así que toda app con sema metía el sintetizador en el bundle aunque el sonido estuviera apagado (que es el default); faltaba ciudadanía que `$scene` ya resuelve; y la costura de inyección (`audioContextFactory`) llevaba ahí sin usar desde el principio. Mismo movimiento que `$motion` hizo desde eidos: el art se lleva el RUNTIME, la capa conserva sus DATOS y su doctrina. - `$sound` / `EngineSound`: UN AudioContext por documento (los navegadores los limitan y el gesto de desbloqueo es por contexto), síntesis de earcon, samples con caché y fallback a síntesis, `autoSuspend` OPT-IN —suspender con la pestaña oculta es correcto para earcons y erróneo para contenido, así que es decisión de quien compone— y aviso cuando un segundo contexto va vivo. Puertos `SoundDom` / `SoundTimers` inyectados; no importa ningún otro art ni nada de `$uix/sema`, que es la prueba objetiva del corte. - `SoundChannel`: 407 → 136 líneas. Solo doctrina: el gate de `prepare`, la política de reducción y el reparto «el canal resuelve el NIVEL, el motor aplica la ganancia». Sema no gana ni un import: recibe el motor por puerto. - `uix.sound` en standalone y attach con `ownsSound` (idioma ya shipped: `ownsMotion` / `ownsScene`), fila `sound` en la tabla ejecutable `contracts.ts`, y `defineEngineSound()` para el camino de app. - Tests nuevos: `engine-sound.test.ts` (11), `sound-port.test.ts` (guard de deriva de tipos + la regla de propiedad) y `sound-e2e.test.ts`, que recorre `emit -> cascada -> canal -> art -> grafo real`: el camino que las 16 suites previas no cubrían porque paraban en canales falsos. Sin `diagnostics.ts` ni `errors.ts`, y es decisión: espejo de `$scene`, aquí todo fallo es degradación documentada, no error de programador. Verificación: 17 suites / 198 tests · `check` en la baseline exacta (73 errores, 0 propios) · `sound.test.ts` verde SIN tocar un solo assert, que era el criterio de que el movimiento fue value-preserving. Planes: `PLAN-sound-engine.md` (completo, con el registro de la revisión adversarial E-1..E-7), `PLAN-audio-player.md` (aparcado tras el análisis del reproductor, con sus correcciones en cabecera) y `CONTINUE-sound-engine.md` (handoff). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
37061a7371 |
docs(process): canon + theming completos — el corpus ya tenía escrita la respuesta
Tercera sesión de lectura: el canon entero (tsc · vocabularies · recipe-contract), lo que faltaba del núcleo (active-app · agent · blocks) y `theming/reference.md` COMPLETA. 21 de 30 documentos leídos sobre el original (≈10.400 L de 19.000). ## La pregunta del color: el corpus la tenía respondida, dos veces y mejor `reference.md` §1.bis se titula «Theming lives in Eidos, not in Morfo (by design)» y se abre con «This is the most frequent architectural question». Existe literalmente para impedir el error que cometió la sesión revertida. Su apartado «What SHOULD enter morfo regarding theming» es inequívoco: un componente que expone `color` como prop DEBE declarar `data-color.values` en su morfo; los attrs puramente visuales (`data-variant`, `data-size`) NO. Y §39 (2026-07-15, la doctrina más reciente) da el criterio mecánico y el porqué: «morfo declares an attr only when its driving prop crosses the soma boundary», con el modo de fallo medido — si declaras en morfo un attr cuyo prop no llega a soma, el runtime emite `undefined` y `mergeProps` pisa el sello del wrapper. La frontera deja de ser convención: es de dónde se resuelve el valor. ## Y el agujero real, ahora seguido extremo a extremo `resolveComponentColor` (`eidos/lib/component-color.ts`) considera canónicos los 9 roles MÁS las 33 escalas donantes = 42 nombres; sólo un color CSS crudo se desvía a `data-color-custom`. El wrapper de switch pasa ese `dataColor` a soma, y el provider lo devuelve verbatim. El morfo declara 6 valores. `<Switch color="teal">` emite `data-color="teal"`, fuera del enum — y no es un caso aislado: avatar 8 · button 8 · card 8 · switch 6 · toggle 6 · dialog 3, contra 42 posibles. NO verificado si salta hoy en CI: `morfo:check` sólo ve lo que las demos renderizan de hecho, y no lo he ejecutado. Ese es justamente el agujero — el guard de «lo declarado se cumple» depende de que una demo elija el valor infractor, en el eje que la decisión de 2026-07-18 abrió a 42. Es el único hallazgo que pide decisión de usuario. ## Dos derivas más - `active-app.md` enumera a mano 12 service factories; hay 15 (faltan `agent`, `motion`, `scene`, en la tabla y en el árbol). Misma enfermedad que el «8 roles» y contra la ley que el propio corpus escribió: catálogo hardcoded → puntero. - `canon/tsc.md` anuncia el scope `event:` sin nota, mientras `reference.md` §13 lo marca «Superseded» y `motion.md` «descartado». El tipo existe, cero recetas lo consumen: las tres frases son ciertas, pero quien construya desde el canon lo leerá como disponible. Nada arreglado (regla 5). Quedan ≈6.200 líneas: las guías, `decisions/` y los RFCs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
d8ff647d7c |
docs(process): los 12 contaminados, releídos — y la pregunta del color, cerrada
Segunda sesión de lectura. Los 12 documentos que el revert dejó marcados
`RELEER` (≈5.000 L) leídos enteros sobre el original, más `theming/reference`
§20–§28, que era donde el propio registro decía que estaba la mitad que
faltaba.
Paso 0, antes de leer nada: comprobar que el árbol no sigue contaminado.
`git diff c39170abb^ HEAD` sobre los docs doctrinales devuelve vacío — HEAD es
byte a byte el estado pre-auditoría. Cuesta un comando y es lo que separa la
doctrina de la autocita; queda escrito como regla 7.
## La pregunta aparcada: `data-color` SÍ va en el morfo
Tres anclajes independientes, ninguno citándose a sí mismo: la tabla §6 de
`active-architecture` lo pone en la casilla de `dom.apply (effect)` — la misma
que `data-state`, no la del wrapper — y `git log -L` traza esa fila a
`d4dfdaffa`, muy anterior a cualquier agente; las reglas E-3.5 / R-3.1 / R-3.3
/ G-1.2 del checklist; y 12 morfos que lo declaran con `v.propRef('color')`.
La conclusión de la sesión revertida era falsa.
Debajo había una pregunta mejor, y esa sigue abierta: el morfo de `switch`
declara `data-color` con 6 valores cerrados, mientras la decisión de 2026-07-18
(§25) manda que `color` acepte el sistema completo — 33 escalas y valor CSS
crudo — con el tipo abierto `ComponentColorProp`. Y `morfo-check.ts:167` falla
cuando un attr con `values` emite algo fuera del conjunto. NO verificado si hoy
salta: pide una demo con valor fuera del enum y `morfo:check` con dev server.
Es lo único de los nueve hallazgos que pide decisión de usuario.
## Lo demás, verificado contra el código, que es lo que no puede haber escrito un agente
- `provider.commitState()` / `emitEvent()`: cero coincidencias en todo el repo.
Los documentan dos docs. La sesión revertida acertaba en el hecho y falló en
el método; entre sí los dos docs no se contradicen — la deriva es doc↔código.
- El guard de tests de provider no existe: `NO_MISSING_PROVIDER_TESTS` sólo
aparece en docs, sin comprobación en `scripts/` ni invariante en
`contracts.test.ts`. Cierra por la puerta buena la pregunta que quedó abierta.
- `clsx` (soma-architecture stale contra soma.md y contra el código), Aura y
`uix.scene` (packs y glossary describen un futuro que ya ocurrió), el
frontmatter de CANON apuntando a un PDF que no está, el entry huérfano de
`package.json`.
- `docs:check` lleva en rojo desde el revert y por el revert: `c39170abb`
arreglaba `callout/README.md:23` y `3097cfcb6` lo deshizo con todo lo demás.
El revert deshizo también arreglos buenos; su diff merece una pasada.
Nada arreglado — la regla 5 lo prohíbe hasta acabar, y quedan ≈11.900 líneas.
Lo no hecho queda enumerado en el propio registro.
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 |
|
|
00118dd9e4 |
docs(process): eidos.md completa — 'eidos-only' es clasificacion valida
`architecture/eidos.md` (1055 lineas) leida completa, limpia. Dos piezas que casi cierran la pregunta aparcada sobre `data-color`: - §Selector-drift defense define **eidos-only** como «the marker is there, but at least one `data-*` is not declared in the morfo. **Valid by convention** (visual tokens like `data-variant`, `data-size` come from the wrapper)». Solo *invalid* es bug. - THM-4 da la disposicion inversa: un attr «declared FOR a visual axis and that nothing anywhere consumes» es deuda, y se «consume it or prune it from the morfo». Juntas apuntan a que un `data-color` del wrapper ausente del morfo es legitimo, y que uno declarado solo por eje visual es podable. Pero falta `theming/reference.md` §25, que `eidos.md` designa canon del color: la pregunta NO se cierra hasta leerla. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
5f01746426 |
docs(process): eidos.md hasta la 750 — media respuesta a la pregunta aparcada
`eidos.md` leida hasta la linea 750 de 1055 (limpia, no la edito la sesion revertida). Lo relevante: existe una categoria SANCIONADA de data-attrs visuales que estampa el WRAPPER y que no van al morfo — linea 489: «The wrapper adds the visual token data-attrs (`data-variant`, `data-size`, `data-block`, `data-icon-only`)». Y la tabla «From morfo» enumera lo que eidos consume del contrato: `data-color` NO aparece. Eso reconcilia a medias la regla de `morfo.md` 878 («lo que el provider emite, se declara») con la pregunta sobre `data-color`: la regla habla del provider de soma; hay otra via legitima por el wrapper. `data-size` es el precedente verificable (`switch.css` lo usa y no esta en el morfo). Falta la otra mitad — si `data-color` pertenece a esa categoria. La lista de la 489 es ilustrativa y no lo incluye; `eidos.md` remite el canon del color a `theming/reference.md` §25. Anotado como NO resolver antes de leer §25. 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 |
|
|
25f8126e0a |
docs(process): morfo.md leída — y una regla que cuestiona la conclusión revertida
`architecture/morfo.md` (888 lineas) leída completa en su estado original. Era la prioridad del registro: la capa del contrato, la mas tocada en la auditoria fallida, y en la que se escribio criterio sin haberla leido. Lo que sale: - El hueco documental de §Step 2 (omite `'private'`, la lista de referencia si lo documenta) es REAL, confirmado contra el original. Lo que estaba mal era el criterio que se escribio ahi como arreglo, que clasificaba mal a los primitivos de API plana. El hueco sigue abierto. - `emit: 'value'` es forma legitima para attrs que llevan valor real. - Y una regla que CUESTIONA la conclusion revertida sobre `data-color`: «Provider emits a data-attr not in the morfo → morfo-check fails in CI. Either add the attr to the morfo or rename it to `data-_*`», mas el incidente 2026-05-20. La sesion revertida concluyo que `data-color` no debe ir en el morfo por ser vocabulario de eidos; esta regla dice que lo emitido se declara. Queda anotado como NO resolver hasta leer `eidos.md` completo y `theming/reference`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
33d4e81daf |
docs(process): registro de lectura del corpus doctrinal
La lectura del corpus no cabe en una ventana de contexto (~19.000 lineas). Sin registro, cada sesion la reempieza o se la salta — que es como se auditó a ciegas el 2026-07-29/30 y hubo que revertir 7 commits (`3097cfcb6`). Lo importante del fichero no es el inventario, es el aviso de contaminación: la lectura del 2026-07-30 se hizo sobre el árbol CON las ediciones de `c39170abb` dentro, así que 7 de los 10 documentos leídos devolvían la escritura del propio agente como si fuera doctrina. Dos casos medidos: - `testing-and-tooling.md` — el original dice que el guard de tests por provider existe; la edición lo había cambiado a «convention, not a guard», y se citó esa frase para justificar una decisión de arnés. Autocita. - `overview.md` — la edición sustituyó «Three Soma scenarios» (`commitState`/`emitEvent`) por «One door: runtime.trigger», y con eso se «descubrió» una contradicción contra `soma-architecture.md` que la propia edición había creado. Quedan por tanto 3 documentos de lectura válida (`README`, `sema`, `eidos` parcial) y 7 marcados RELEER. `morfo.md` (888 líneas) sigue sin empezar, y es la prioridad: es la capa del contrato y se llegó a escribir criterio en su §Step 2 sin haberla leído. Se registran los 4 hallazgos que sí se sostienen (todos de documentos limpios y verificados como originales) y se retiran los 2 que eran autocita. 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 |
|
|
352ca8bbec |
style(soma): formato Prettier en el test de gradient-picker
Dos envolturas de línea que el formateador del proyecto no acepta, en un fichero que escribí yo. Detectado auditando mi propio trabajo contra las reglas duras. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
17a1f167b7 |
test(soma): chat-list — el puente de selección y el runtime por instancia
Sexto de los 13 providers sin red. Dos cosas que este provider decide y que nadie comprobaba: - **El puente de una conversación activa al `string[]` del Listbox.** En las dos direcciones: sin conversación abierta la selección es `[]` y no `[undefined]`; deseleccionar todo cierra la conversación en vez de dejar un id obsoleto; y el consumidor recibe siempre un id plano en `onValueChange`, nunca el array. - **El runtime POR INSTANCIA del Item.** Dos filas, una con `unread` y otra sin, no se pisan — es el patrón AccordionItem, y existe precisamente porque una fuente de props compartida no puede llevar estado por instancia. Sin este test la regresión sería silenciosa: ambas filas leerían la misma. También queda fijada la doble registración sobre el mismo elemento (id + ref compartidos, el precedente de chat-log): el Listbox aporta `role=listbox`, teclado y selección; chat-list añade el contrato de fila. Sobre el arnés: el Listbox compuesto se construye DE VERDAD, y su `TypeaheadBuffer` exige `uix.timers` — va con un `createEngineTimers` real sobre `createFakeTimerClock`, el vehículo de test del propio framework, en vez de un servicio inventado. `prefs.getDir` sí es un lector puro y sigue el patrón de la suite existente. Quedan 7: `chronos`, `gradient-builder`, `month-grid`, `nav-tree`, `password-field`, `sidebar`, `year-grid`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
e70397fcab |
test(soma): field-langs y gradient-picker — el invariante, no la superficie
Dos providers más de los 13 sin red. Como los anteriores, apuntados a lo que cada uno decide y nadie comprobaba: - **`field-langs`** (8): la resolución de `switcher: 'auto'` por número de idiomas (segmentado hasta 3, `<select>` desde 4, y un modo explícito nunca se reinterpreta); el guardado de `activeLang` contra el conjunto vigente — sin él un idioma retirado deja el editor sin dirección; que un valor de sólo espacios cuente como incompleto e invalide el campo si ese idioma era requerido; y que los proxies `activeValue` / `activeLangArray` escriban en el hueco correcto, incluido que deseleccionar en el ToggleGroup NO deje el editor sin idioma. - **`gradient-picker`** (7): que `css` serialice el gradiente COMPROMETIDO y no el de trabajo — es lo que hace que el chip del trigger no se mueva hasta Aceptar en modo diferido; que `showDelete` exija que el preset guardado se haya SELECCIONADO y no sólo que coincida con el valor; el tope y el rechazo de duplicados en `canSave`; y que el provider dispare él mismo `commit-save` / `commit-remove`, porque el Button sólo emite el `contact.activate` neutro. Los dos fallos de `gradient-picker` en la primera pasada eran de la aserción, no del código: `$state` devuelve un proxy profundo, así que comparar gradientes con `toBe` falla por identidad. Comparados por valor. Quedan 8: `chat-list`, `chronos`, `gradient-builder`, `month-grid`, `nav-tree`, `password-field`, `sidebar`, `year-grid`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
c9b83d0257 |
docs(morfo): qué hace pública a una parte, y tres hallazgos retirados
`morfo.md` §Step 2 — la guía de AUTORÍA, donde el autor decide — documentaba sólo `'public'` y `'virtual'` y omitía `'private'`. La sección de referencia sí tenía los tres, pero nadie declara partes leyendo la referencia. El criterio que queda escrito: **lo que hace pública a una parte es que el consumidor la decida, con independencia de la sintaxis**. La composición (`<Dialog.Trigger>`) es la vía habitual, no la única: un primitivo de API plana gobierna sus partes por props y snippets — `<Badge dot>` decide la parte Dot, `icon` la parte Icon. Ambas son públicas. `'private'` queda para lo que nada de lo que escribe el consumidor alcanza (el Track de `Spinner`, el Sentinel de `Sticky`). Importa el matiz porque la primera versión de esta nota decía lo contrario — que el test era mecánico y que sin `<Componente.Parte>` la parte no es pública — y eso habría reclasificado mal a Badge y a todo primitivo de API plana. ## Handoff: tres hallazgos retirados Los tres salieron de heurísticas de una sola vía dadas por hechas sin abrir el código. Es el mismo fallo que la auditoría denuncia en los guards, cometido al redactar el informe: - **D-3 `AgentTimersPort.once`** — el puerto SÍ está satisfecho: `defineActiveAgent()` adapta `core.timers.schedule` a `once` en la raíz de composición, que es el patrón previsto. No había nada roto. - **«5 derivas de scope»** — `avatar-group`, `path-trace` y `rotate-align` están implementados; el framework co-loca sub-componentes dentro del directorio del padre y la heurística asumía «directorio == kebab». - **«29 morfos con `kind:'public'` irreal»** — medía si existe `<Componente.Parte>` e ignoraba props y snippets. Sin recuento fiable hasta cruzar las tres vías. Añadido: `npm run lint` nunca ha pasado. Separando el ruido CRLF del checkout Windows (`--end-of-line auto`) quedan 1544 ficheros con deriva real de contenido. Es decisión del usuario porque el arreglo toca todo el repo de golpe. Y la lección de método: **un analizador que no puede resolver algo tiene que decir «no verificado», no emitir un hallazgo.** Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
feb4a84241 |
test(soma): los primeros providers que no tenían red
13 providers de soma no tenían test, y el guard que dos documentos afirmaban que lo impedía (`NO_MISSING_PROVIDER_TESTS`) no existe. Van los tres primeros, cada uno apuntado a la doctrina que el provider codifica y que hoy no cubría nadie — no a su superficie trivial. - **`button`** (7): la resolución `color`↔`intent`. Un intent evaluativo gana sobre la jerarquía Y suprime el marcador custom (la estética no puede contradecir a la semántica). Cubre también que `onPress` devuelve el intent como contexto VISUAL, no como carga del evento — el contacto no lleva intent (libro cap. 22 §11). - **`anchor-nav`** (6): la escalera de `activeId` (primero en banda → último pasado → primero en orden DOM) y el ref-counting que impide que un remount keyed tire un target aún referenciado por su hermano. - **`textarea`** (9): que el warn de overflow se emita SOLO en la transición —la proyección es `untilFix` y persiste, re-emitir sólo la reemplazaría—, su retracción al bajar del tope, y la matriz completa de `submitOn` (`enter` / `mod+enter` / `false`). Dos fallaron a la primera y en ambos el código tenía razón y la aserción no: `data-color-custom` es flag de presencia (el color viaja por la var CSS `--color-custom`, que estampa eidos), y `clearTarget` no dispara sin la parte `input` montada porque la retracción apunta a ese elemento. Arnés: el patrón ya establecido en los 87 tests existentes. Donde hace falta `langs`, el stub devuelve el fallback embebido del idlangref en vez de copia inventada — es lo que hace el resolver real cuando no encuentra entrada. Quedan 10: `chat-list`, `chronos`, `field-langs`, `gradient-builder`, `gradient-picker`, `month-grid`, `nav-tree`, `password-field`, `sidebar`, `year-grid`. Al cerrar la lista, escribir el guard que la mantenga cerrada. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
f8e35b8fd8 |
fix(morfo): el eje intent/color — el morfo no puede nombrar lo que es de eidos
El `data-color` de los morfos declaraba enums de 3 a 8 valores cuando la prop acepta 42. No era descuido: es que **el morfo no puede nombrar ese vocabulario**. `COLOR_ROLES` y `PALETTE_SCALES` viven en eidos; `INTENTS` vive en `src/uix/intent.ts`, propiedad de UIX y de ninguna capa. Y `src/uix/morfo/**` no importa de eidos jamás — su único import cruzado es `../../intent`. Nadie podía traer la lista real, así que se copió a mano un subconjunto y se fosilizó: `<Switch color="teal">` siempre emitió `data-color="teal"`, un valor que su propia declaración negaba. ## Dos ejes, no uno - `data-color` = **direccionamiento de paleta**: qué fila de la tabla lee la instancia. `renderSharedPaletteLayer` emite un bloque por nombre canónico y el recipe consume los `--palette-*` resultantes. Soma lo resuelve aplicando la doctrina «el intent evaluativo gana a la jerarquía». - `data-intent` = **estado evaluativo persistente**, cuyo gemelo transitorio es `data-event-intent`. Ya lo declaraban `knob`, `timeline` y `toast`. Estaban fusionados en un solo atributo (`switch`/`toggle` llegaban a listar `primary, secondary` junto a cuatro intents en el mismo enum), y por eso no se podía asignar limpiamente a ninguna capa. ## Cambios - `intentsExcept()` en `src/uix/intent.ts`: narra un subconjunto por EXCLUSIÓN, tipado contra `Intent`. Un componente toma los 6 o menos, pero nunca vuelve a escribir el vocabulario — si mañana entra un séptimo intent canónico llega solo a todo el que no lo haya rechazado. `knob` migrado (conjunto idéntico, verificado). - `dialog` → `data-intent`. Aquí SÍ era intent mal etiquetado: 0 usos de `--palette-*`, sin `resolveComponentColor`, y reglas propias cuyos valores eran todos intents. El provider declaraba `intent` **y** `color` con la misma expresión exacta — una copia, no una traducción. Migrados morfo (×2), provider y las 4 reglas CSS. - `switch`, `toggle`, `button`, `card`, `avatar`: enum de `data-color` fuera, `emit: 'value'` en su lugar. El DOM no se mueve: `enumValues` no lo consume nadie en runtime y `evalAttrPlan` hace `case 'enum': case 'string': return raw`. Ambos comprobados antes de tocar. - De paso, los 10 morfos que escribían `expression:` con comilla doble en un corpus de 173 que usa comilla simple. ## Verificación 514 tests de morfo + contracts + eidos · 7/7 del provider de button (cubre `data-color='primary'` por defecto y `'threat'` con intent evaluativo) · `eidos-lint` con `invalid: 0` en los seis componentes tocados · `check` sin errores nuevos. En navegador: `dialog` con `risk` y `threat` tiñe borde y título con los valores exactos de `--color-risk-border` / `--color-threat-border`; `switch` resuelve fila de paleta distinta para `neutral`, `primary`, `threat` y `teal` — esta última una de las 33 escalas donantes que el enum viejo negaba. `rose`, que no es canónica, sale vacía y sirvió de control negativo. Nota: `chronos` ya declaraba la forma correcta (`emit: 'value'`, sin enum). Lo tenía excluido de la auditoría, así que deduje desde cero un patrón que ya estaba en el árbol. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |