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 }
213 Commits (01b891b64529ecf4bb82aded70d721b0a878b797)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
714a1ddb59 |
fix(carousel): en RTL el rail se movia AL CONTRARIO del dedo durante el arrastre
El usuario vio a mano lo que mi simulacion de puntero no consiguio disparar en cinco intentos. El swipe estaba mal, y no donde yo habia mirado. La decision de finishDrag SI voltea (isRtlHorizontal ? -offset : offset) y por eso la daba por buena. El defecto estaba en el PINTADO durante el arrastre: translate = base + dragOffset // base = indice, dragOffset = dedo itemGroupTransform = translate * flip // el flip caia sobre LOS DOS El recorrido por indice es LOGICO —N->N+1 mueve el rail a la izquierda en LTR y a la derecha en RTL— y debe voltear. `dragOffset` es el desplazamiento FISICO del puntero y no debe. Al voltear ambos, en RTL el rail se movia en sentido contrario al dedo: arrastras a la derecha y el contenido huye a la izquierda. Separados: solo baseTranslate lleva el signo. A/B con git stash, midiendo el transform A MITAD del arrastre —que no exige superar el umbral de gesto, y por eso ahora si es medible—: sin el arreglo, con el dedo a +100px el rail da Δ-100 (huye); con el arreglo, Δ+100 (lo sigue). LTR intacto en los dos casos. NOTA DE METODO, anotada en el handoff: para un gesto con umbral de distancia y velocidad, no intentes completar el swipe con page.mouse; mide el estado INTERMEDIO con el puntero aun abajo. Es lo que me faltaba en §6.8. DESCARTADOS CON MEDIDA, no se tocan: scroll-area FUNCIONA. Confirmado por el usuario y medido: con el toggle en auto, ES->ltr y AR->rtl, y el elemento pasa a direction:rtl. Su resolvedDir sigue siendo codigo muerto y el uso de scrollLeft sin verificar en un eje horizontal real, pero el componente responde a la direccion. chart NO NECESITA ARREGLO y las referencias lo respaldan. Medido: chartDir, legendDir y el texto de los ejes pasan de ltr a rtl; el eje numerico no se voltea. Es el consenso: Chart.js implemento RTL SOLO para leyendas y tooltips y mantiene abierto el issue de ejes/etiquetas (#11937, PR #6460), y el patron documentado es eje numerico LTR con textos RTL. amCharts es de los pocos con RTL integral. Y el sintoma «la demo no reacciona al idioma», reportado tres veces (virtual-list, carousel, scroll-area): medido en las tres, con el toggle en AUTO las tres reaccionan. Si el toggle esta en ltr/rtl explicito el idioma no manda, porque el intent gana sobre la derivacion. No es defecto, pero despista de forma sistematica: parece un interruptor de dos posiciones y es un ciclo de tres donde solo uno cede el mando al idioma, y el estado solo se lee en el title. Cerrarlo seria trabajo de la shell, no del eje. Verificado: check 74 = linea base exacta. 4/4 en carousel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
f420c74920 |
fix(rating-group,splitter): dos wrappers se quedaron fuera de 013ceac57, y el arrastre del splitter no volteaba
Verificados los tres componentes que "tenian codigo de direccion" pero nadie habia comprobado que acertara. Dos estaban rotos. RATING-GROUP, roto por DOS motivos encadenados. 1) calcFromPointer medía (clientX - rect.left) / rect.width, una fraccion desde el borde FISICO izquierdo. En RTL los items van de derecha a izquierda, asi que la mitad que EMPIEZA una estrella es la derecha: pinchar la primera mitad visual daba la estrella entera. Volteado con el mismo `1 - pos` que ya usa el slider. 2) Y aun asi seguia sin funcionar, porque su WRAPPER se quedo fuera de la normalizacion de 013ceac57: tenia `dir = 'ltr'` como default duro —que ademas incumple "el atributo nunca tiene defecto"— y readableActive(() => dir) en vez de activeDir(() => dir, soma). Nunca consultaba prefs, asi que resolvedDir valia 'ltr' pasara lo que pasara. NATURAL-TIME-PICKER tenia el mismo agujero (dir ?? 'ltr'). Son los DOS unicos que quedaron fuera; los otros 36 si usan activeDir. Medido tras arreglar ambos: en RTL la mitad derecha da 2.5 y la izquierda la entera — espejo correcto. SPLITTER: el arrastre estaba roto, el teclado no. resizePanels toma un delta LOGICO —positivo agranda el panel de ANTES del handle— y recibia el delta fisico del puntero sin voltear. En RTL arrastrar a la derecha agrandaba el panel que debia encoger, y el arrastre CONTRADECIA a las flechas, que si pasan por getDirectionalKeys. Tras el arreglo: LTR arrastre +118 / ArrowRight +6; RTL arrastre -118 / ArrowRight -6 — espejo exacto y los dos de acuerdo. NOTA DE METODO, anotada en el handoff: al ver `resolvedDir = opts.dir.current ?? 'ltr'` en unos 30 providers di por hecho que a todos les faltaba el eslabon de prefs. Es FALSO — la cadena vive en el WRAPPER (activeDir), y el provider recibe la prop ya resuelta. El slider, que tiene ese mismo resolvedDir, voltea perfectamente. Antes de "arreglar" 30 componentes, mirar el wrapper. SIN VERIFICAR: scroll-area. Su resolvedDir aparece UNA sola vez —la declaracion—, o sea es codigo muerto, y usa scrollLeft en cuatro sitios sin tratar que en RTL los navegadores lo devuelven NEGATIVO (isAtLeft seria siempre cierto, el ratio del thumb saldria negativo). No esta medido porque el scroll-area de la demo no tiene eje horizontal. Verificado: check 74 = linea base exacta. 20/20 en splitter + rating-group + natural-time-picker. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
b550c8cd4b |
fix(virtual-grid,virtual-list): en RTL el contenido desaparecia — el ancla de celda era fisica sobre un sizer logico
Reportado por el usuario: en la demo del virtual-grid, voltear la direccion deja
la rejilla EN BLANCO. Reproducido y medido en Chrome.
LTR : 108 celdas, 70 visibles, primera en x=381 (borde del viewport)
RTL : 108 celdas, 0 visibles, primera en x=-5101 (fuera de la pantalla)
No era scrollLeft —esta en 0 en ambos casos—, era el anclaje de la celda:
position:absolute con top:0, left:0 y translate3d(columnStart). Los dos terminos
son fisicos y coherentes ENTRE SI, pero el sizer interno es
`inline-size: 6000px`, o sea LOGICO: en RTL desborda hacia la izquierda y ocupa
[-5101, 899], asi que su borde fisico izquierdo es donde el contenido TERMINA.
`left: 0` mandaba las 108 celdas justo ahi.
Arreglado con ancla logica (inset-inline-start) y el signo en el offset, de modo
que el recorrido se queda en translate3d: un virtualizador recoloca miles de
celdas y conviene que siga en el compositor en vez de animar layout. Medido tras
el arreglo: 70 visibles en RTL, primera en x=779 = borde derecho del viewport
menos el ancho de celda, que es el inicio logico.
virtual-list tenia el MISMO patron (left:0 + translateX) y por tanto el mismo
defecto en modo horizontal. Mismo arreglo. El modo vertical no se ve afectado:
inset-inline-start:0 con width:100% es exactamente lo que left:0 era. Verificado:
12 items visibles en RTL, primero en x=755.
EL OTRO SINTOMA REPORTADO NO ES UN DEFECTO. «El cambio de idioma no le afecta»:
con el toggle en `auto`, elegir arabe voltea la lista correctamente —medido,
htmlDir y el direction computado del componente pasan a rtl—. Si el toggle esta
en ltr/rtl EXPLICITO el idioma no manda, porque el intent gana sobre la
derivacion; es lo ratificado en
|
2 months ago |
|
|
caf3cfd884 |
fix(float-panel): la semilla anclada usa la matematica de $ethereal en vez de una copia sin direccion
Verificados uno a uno los cinco componentes de la §2.2 del handoff, y despues barrido el punto ciego del guard. Ningun cambio de comportamiento salvo el de float-panel. LOS CINCO DE §2.2 ESTAN SANOS. slider: click al 25% del ancho FISICO da 75 en RTL, arrastrar a la derecha sube en LTR y baja en RTL, y ArrowRight igual. number-field y css-field: el mismo arrastre fisico sube en LTR y baja en RTL. dropdown-menu: el panel raiz se alinea al inline-start y el submenu abre a la derecha en LTR y a la izquierda en RTL. carousel: el track voltea (Next mueve -622px en LTR y +622px en RTL). Ninguno necesito arreglo. EL SWIPE DEL CAROUSEL NO ESTA MEDIDO. Cinco intentos de simulacion de puntero y no consegui disparar el gesto de forma reproducible ni en LTR: la capa tiene umbral de distancia y de velocidad. El signo esta bien por LECTURA (isRtlHorizontal ? -offset : offset). Son diez segundos con un raton de verdad. UN MOTOR CUBRE NUEVE COMPONENTES. soma/layers/floating lo consumen combobox, context-menu, dropdown-menu, link-preview, menubar, popover, select, sidebar y tooltip. Verificado a traves de popover (align=end: derecha en LTR, izquierda en RTL) y menubar (align=start, al reves). Los nueve quedan cubiertos. FLOAT-PANEL NO USA ESE MOTOR, Y HACE BIEN: useFloating mantiene el panel PEGADO a su ancla, y un float-panel se arrastra y redimensiona libremente. Pero su SEMILLA anclada si era redundante: computeInitialPosition() re-derivaba side + align contra el rect a mano, y esa copia resolvia align:'start' a r.left SIEMPRE — una alineacion LOGICA clavada a un borde FISICO, que nunca voltea. $ethereal ya exporta computeCoordsFromPlacement(rects, placement, rtl): pura, sincrona, sin ciclo de vida, y es la misma que usa el motor compartido. Migrada la semilla a ella. Se comparte la semilla, NO useFloating. anchored-seed.test.ts fija la migracion, porque la demo NO ancla ningun panel y el defecto no era observable ahi: en LTR el resultado es IDENTICO al algoritmo anterior en las 12 combinaciones side x align (cero regresion); en RTL con side vertical start y end se espejan; con side horizontal el align no cambia, que es el eje de bloque; y center sigue centrado en ambas. La demo, medida antes y despues, no se mueve. NO TOCADO, hace falta criterio: Home/End en el eje X llevan el panel a bounds.left / bounds.right. Para un panel que se arrastra libremente es defendible que sean extremos fisicos y no principio/final de lectura. Descartados como falsos positivos tras mirar el uso real: container (prop de margenes), text-focus, text-scramble y path-trace (miden donde esta algo para un efecto), drag-drop (sueltas donde ves), cropper (paneas una imagen que no se voltea) y gradient-builder (arrastras la parada donde la ves). Y drawer, que no aparecia en el primer cruce, esta BIEN y es el ejemplo a imitar: traduce start/end a left/right una sola vez en resolveDirection() y desde ahi todo es fisico y coherente — aunque su demo solo expone valores fisicos, asi que su mitad logica no es observable. Verificado: check 74 = linea base exacta. 12/12 en float-panel + 4/4 del test nuevo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
76f18a5e87 |
refactor(tabs): el indicador pasa a MeasuredIndicator, y aparece un punto ciego del guard
Buscados los indicadores que comparten mecanismo con el de |
2 months ago |
|
|
dc5e956658 |
fix(direction): el indicador deslizante no se remedia al voltear la direccion
Verificar los tres arreglos que quedaron sin ver en
|
2 months ago |
|
|
ec533845d1 |
feat(direction): un guard para el eje logico-fisico, y el <main> que congelaba las 51 demos
Cierra la §2.1 del handoff. El guard nacio en rojo con 24 hallazgos y al arreglarlos aparecieron dos defectos mas grandes que el que buscaba. 1) EL GUARD RTL-1. Nada cruzaba un ancla LOGICA con un desplazamiento FISICO en la misma regla CSS: eidos-lint clasifica SELECTORES, no declaraciones, y la fila RTL del contrato de construccion decia "rule (LIVE)" — revision humana. Por eso el defecto del slider llego a produccion y lo cazo el ojo del usuario. Ahora `npm run rtl:check`, con la logica en src/uix/eidos/rtl-lint.ts (14 tests) para que sea testeable, calcando el par lint.ts / scripts/eidos-lint.ts. El test ancla el caso historico: slider.css ANTES de |
2 months ago |
|
|
86b6381388 |
docs(process): handoff del eje de direccion
Que esta cerrado (3 commits), que queda por orden de valor, y las trampas de metodo que costaron horas. Lo primero de la cola NO es codigo de componente: es el guard que no existe. Nada cruza `transform: translate*` con `inset-inline*` — hay 39 transforms en el CSS de eidos y ningun script los mira, porque eidos-lint solo clasifica selectores y el guard de la fila RTL del contrato es literalmente revision humana. Por eso el bug llego a produccion y lo cazo el ojo del usuario. Incluye la investigacion de las cinco librerias de referencia ya hecha (25 agentes, 5 hallazgos tumbados por refutacion) para que nadie la repita, y los dos datos que mas costaron: `unicode-bidi: isolate` NO arregla el interrogante desplazado, y `dir="ltr"` si — porque la hoja de agente del WHATWG le da isolate de propina. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
538f4932bc |
docs(process): el handoff decia que el Slider RTL estaba roto, y ya no lo esta
Cerrado en |
2 months ago |
|
|
42d58c3940 |
docs(process): handoff del RTL — el Slider esta roto y es la prioridad de manana
Nace CONTINUE-player-rtl.md con lo que la MIRADA del usuario abrio el 2026-08-01 y quedo sin cerrar, mas el enlace cruzado desde el handoff del hilo de audio. Lo que lleva: 1. PRIORIDAD — el Slider esta roto en RTL, confirmado por el usuario con el componente SUELTO. No es del media-player: es el canonico del que cuelgan waveform, media-player, color-picker y los dos time-pickers. Se deja descartado por lectura que getValueFromPointer y rangeStyle estan bien en aislamiento, y se deja una HIPOTESIS comprobable en dos minutos: el Slider estampa `dir` como atributo HTML, lo que activa `direction: rtl` en CSS, y slider.css mezcla propiedades logicas con los estilos fisicos en linea que calcula el provider — doble volteo. El arreglo sera elegir UN dueno de la inversion. 2. La disposicion de los botones del player, que el usuario insiste en que es un problema APARTE. Las sondas de la sesion decian que mirroriza, pero esas sondas fallaron varias veces ese dia (dos midieron video creyendo audio): quedan marcadas como NO fiables. 3. Iconos de seek: decision tomada (voltearlos, con su razon), sin aplicar. 4. La medicion del escenario mixto quedo a medias y se explica por que: se instrumento DESPUES del arranque, asi que el "0 contextos" no prueba lo que parecia. Lo que si queda establecido es que la media no abre ningun AudioContext. 5. Captions y el cableado del waveform, con su diagnostico completo. Y las lecciones de metodo del dia, que son el motivo de que este documento exista: medir el contrato NO es verificar la experiencia (se afirmo tres veces lo contrario y las tres las caza el usuario); el panel embebido no sirve para nada visual (innerWidth 0 — llego a inventar tres "overflow" inexistentes); comprobar que el control se pulso de verdad antes de creerse la medida; la distancia RGB es ciega al croma; y la FORMA manda sobre el color. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
58acb778ee |
docs(waveform): F6 firmada — la iniciativa queda completa
El usuario dio por bueno el waveform contrastandolo con el reproductor de Suno AI, que valida dos decisiones del gate: barras (D-WF.5, la lectura SoundCloud) y los dos colores reproducido/pendiente como lo esencial — Suno ni siquiera pinta buffered, que en el nuestro es opcional y arranca apagado. Queda anotado lo que costo la mirada, porque ninguna sonda lo habia visto: el anillo del commit enmarcando el control entero ( |
2 months ago |
|
|
e096f65e39 |
docs(sound): MediaSession verificada por el usuario — el pendiente de tres sesiones, cerrado
El usuario abrio el media hub de Chrome con la demo de audio sonando: caratula + titulo + artista + transporte, origen localhost:5173. Cierra "prueba manual de teclas de medios / pantalla de bloqueo", pendiente desde que se escribio el rediseno porque no habia pagina que consumiera sound.media(). Se anotan tres cosas para no repetir el camino: - Las teclas de VOLUMEN no sirven (muestran el OSD de Windows; en Win11 Microsoft retiro el panel de medios que se pegaba a ese OSD). La superficie fiable es el boton de nota musical de la barra de Chrome. - La sospecha sobre la caratula era INFUNDADA: se temio que artwork sin sizes/type y sin CORS fuera descartado por el MediaImageManager. Carga perfectamente. Queda registrado para que nadie "arregle" un no-problema. - Anterior/siguiente apagados es CORRECTO: setActive() registra play, pause y seekto; un reproductor de una sola fuente no tiene lista. Y la leccion de metodo: se afirmo "el audio ya funcionaba" apoyandose en una medicion JS (metadata poblada) como si zanjara lo que el usuario VE. Una medicion del contrato no es una verificacion de la experiencia. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
df86a4e509 |
fix(slider,media-player): el anillo del commit no enmarca contenedores, y saltar al final no es completar
Cuatro defectos que solo aparecieron cuando el usuario MIRO la pagina.
1) EL BORDE DE LOS SLIDERS ("se viene arrastrando desde el principio").
No era del arquetipo ni del slider: es la firma GLOBAL de la familia
commit. BUILTIN_SIGNATURES.commit monta commit-settle, cuyo 30% pinta
box-shadow 0 0 0 3px. El commit-set del Slider apunta al PROVIDER, asi que
cada paso de flecha y cada release dibujaban un aro alrededor de la caja
entera (medido en vivo: 544x40). Un aro lee como feedback en un boton y
como borde parasito en un rail ancho y transparente. Exencion acotada en
slider.css — el patron que gradient-builder.css ya practicaba. Alcanza a
todo slider embebido (waveform, media-player, color-picker, time-picker)
porque todos llevan [data-slider]. `animation` es la palanca, no
`box-shadow`: las declaraciones de keyframe ganan a las normales, asi que
solo reiniciar el NOMBRE de la animacion detiene la pintura.
2) EL MISMO ANILLO EN EL PLAYER: commit-complete / commit-fail apuntan al
provider -> borde alrededor de los ~700px del reproductor. Misma exencion
en media-player.css. Los botones conservan su aro pequeno (20x36), que es
donde el feedback pertenece.
3) SALTAR AL FINAL ANUNCIABA UNA COMPLECION FALSA. Los botones de salto no
tienen evento propio: seek() delega a proposito (D-AP2.13, "el release del
scrubber ya ES el commit-set del Slider") — cierto para el scrubber y
VACIO para los botones, que no tienen Slider detras. Al saltar mas alla del
final el elemento dispara `ended` -> commit-complete en el provider ->
sonido (es audible por D-AP2.7) + anillo, mientras el boton de retroceso
quedaba mudo. Saltar al final no es completar: es abandonar la obra en el
final. seek()/scrubTo() marcan el salto que aterriza en el final y `ended`
no anuncia; un `play` posterior limpia la marca, asi que el final natural
se sigue anunciando. Los dos botones quedan simetricos y mudos, que es la
doctrina del transporte.
4) MEDIASESSION SIN OVERLAY DEL SO: el modo audio YA funcionaba (metadata
completa, medida en vivo); el roto era VIDEO, el modo por defecto de la
demo. <AudioPlayer> deriva metadata de title/artist/artwork; el
<MediaPlayer> compositivo no puede leer su hijo Title sin volverse
data-driven, asi que la declara el consumidor — y la demo no lo hacia.
El arreglo es de la demo; el art se comporta como D-SR.6/D-AP2.5 lo firmo.
Guard nuevo (no anunciar compleccion al saltar al final; si al final
natural) VISTO EN ROJO antes de darlo por bueno.
Nota de doctrina, tras leer el corpus a peticion del usuario: la exencion
acotada en la receta ES la puerta, no un olor. motion.md §4 fija que la
firma es generica por decision estructural ("un commit asienta igual en
todo el sistema"), eidos es el UNICO dueno del canal visual, los packs de
sema solo transportan sound/haptic, y el VisualChannel no consulta
activeChannels — no hay palanca declarativa para callar solo lo visual.
Queda anotado, menor: commit-settle escribe 3px a pelo donde sus hermanos
announce-pulse-* usan var(--focus-ring-width).
Verificado: check 75 = baseline, 0 propios; 11/11 media-player;
motion.test.ts verde (la firma global intacta); sonda en Chrome vivo —
slider real -> animation none, elemento no-slider con el mismo sello ->
sigue commit-settle, aro de foco intacto.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
4e4f1b612d |
feat(waveform): F4 eidos + F5 demo — y el buffered pasa a ser la onda
F4 (eidos): wrapper raiz que carga slider.css + waveform.css y resuelve
data-size por ActiveEidos; Wave passthrough; recipe con bars = stroke en
UNIDADES DE VIEWBOX (el svg estira con preserveAspectRatio="none", asi el
ciclo barra/hueco sobrevive) y wave = fill sin stroke; espejo RTL en CSS;
el Slider embebido en scope con track/range transparentes y el thumb
convertido en playhead de 2px a alto completo. Tokens waveform:{height-*,
bar-width, color, color-buffered, color-played, playhead-width} —
playhead-width y color-buffered van ANADIDOS sobre la lista firmada en el
gate, ambos declarados en el README.
F5 (demo + dossier): /uix/components/waveform con picos REALES (fetch de
/sounds/*.wav -> uix.sound.decode -> uix.sound.peaks), cada prop como
control vivo, READMEs de soma y eidos. Fix arrastrado de F3: `dir` no se
reenviaba al Slider embebido.
BUFFERED — cambio de forma (decision del usuario tras ver el render).
El primer intento re-tintaba el SecondaryRange del Slider; el usuario lo
rechazo ("el secundario no puede ser del mismo color") y la auditoria
encontro por que ningun color lo arreglaba del todo: ese part es una BARRA
de 6px con la geometria del track, y sobre una pintura de barras al ~58%
de ciclo el 42% de su area cae sobre fondo desnudo, ademas de tenir el
tramo ya reproducido. Ahora el buffered es una TERCERA COPIA del mismo
path, recortada a su fraccion — es la onda un escalon mas alla, la lectura
de SoundCloud/wavesurfer. El waveform ya no monta Slider.SecondaryRange.
Escalera: color 28% -> color-buffered 55% -> color-played acento.
Guard nuevo (bufferedFraction: ausente sin valor, clampado, dominio
degenerado) VISTO EN ROJO desconectando el cableado antes de darlo por
bueno.
Verificado: check 75 = baseline, 0 propios; 5/5 soma + 4/4 morfo;
component:audit --only waveform PASS; eidos-lint 0 invalidos;
recipe-css-contract y motion.test verdes; docs:check 0; prettier limpio.
Queda F6 (mirada del usuario): el panel del navegador no composita, asi
que la verificacion fue por sonda del DOM, sin captura.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
cd9b06fa2f |
docs(blocks): auditoria del tier congelada — 29 confirmados, 50 sin verificar
Auditoria de los 15 blocks con 50 agentes: dos dimensiones en paralelo —doctrina
(contrato B + regla de coordinacion, por lectura) y percepcion (sema en vivo en
navegador, interaccion por interaccion)— y despues un verificador ESCEPTICO por
hallazgo, con el encargo de refutarlo y la lista de falsos positivos conocidos
del repo.
90 en bruto → 40 verificados → 29 CONFIRMADOS + 11 refutados (28%)
Ese 28% es la razon de existir de la fase adversarial: sin ella habrian entrado
once acusaciones falsas.
⚠️ 50 hallazgos quedaron SIN VERIFICAR, 18 de ellos marcados ALTA. El tope de 40
lo puso mi script, no el trabajo. No son menos graves: estan sin comprobar.
`AUDIT-blocks-2026-08-01.md` es la UNICA copia — el journal del workflow era de
sesion y ya no existe. Incluye los refutados con su motivo para que nadie los
vuelva a reportar.
Lo gordo confirmado:
- `contact` importa `$libs/forms`, fuera de la lista de la frontera dura 1.
- El nombre accesible del submit de `contact` esta congelado en «Enviar» en los
cinco estados: `Form.Submit` impone su `aria-label`, asi que las palabras de
estado que el block existe para poseer NO llegan al lector de pantalla. El
block cumple su promesa solo a la vista.
- `site-header` congela la pagina: con el cajon abierto, cruzar el `breakpoint`
esconde cajon/overlay/trigger con `display:none` pero deja el Drawer abierto y
el scroll del body bloqueado. Reproducido con swipe tactil real rotando un
telefono; la unica salida es Escape, que en tactil no existe.
- `hero` en `layout="background"`: CTA secundaria a 1.78:1, bajo AA, y el token
que la demo escribe a mano para arreglarlo es inerte.
- `height="100%"` sobre `Card` es INERTE (atributo HTML, no prop) en `pricing` y
`testimonials`: las tarjetas no igualan alto mientras README y demo afirman lo
contrario.
- B-8 sin declarar (landmark + jerarquia de encabezados) en seis READMEs.
La dimension CRUZADA —los 15 en una pagina compuesta— NO se ejecuto, y es ademas
la condicion de cierre de F2 del plan.
La auditoria confirmo su propia premisa: la capa perceptiva no se habia ejercido
NUNCA porque la galeria arrancaba sin packs de sema desde F0, y ahi esta lo mas
feo (todavia sin verificar): el nav del header emitiendo `commit-select`+`affirm`
con earcon audible al pasar el RATON por encima, un `Enter` en `newsletter`
emitiendo dos `commit-submit` con intents contradictorios, y 4 de 7 elementos del
header mudos.
El handoff `CONTINUE-blocks.md` arranca ahora por la auditoria, con el orden
recomendado (verificar los 50 → arreglar por severidad → pagina compuesta →
catalogo → v2) y la deuda declarada del arnes, que triplica la cadena de arranque.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
0ab6cc822c |
feat(waveform): F1-F3 — sound.peaks, morfo delegado y soma componiendo Slider
Waveform (gate D-WF.1-8 firmado, PLAN-waveform.md): la visualizacion de audio cuyo contrato es el DATO y su pintura — la interaccion ES el Slider canonico. - F1: uix.sound.peaks(buffer, buckets) en el art — max absoluto por bucket sobre todos los canales, normalizado por el bucket mas alto (documentado); puro, sin contexto. El flujo natural decode -> peaks en la misma puerta. - F2: morfo con DOS partes (provider con data-shape/data-readonly; wave svg presentacional aria-hidden), SIN campo events y expression 'delegated' — re-declarar drag/commit seria la duplicacion que D-AP2.13 retiro del player. El part Cursor del borrador, PODADO como especulativo (el thumb del Slider embebido ES el playhead). - F3: WaveformProvider con la geometria testeable (buildWavePath: bars = trazos verticales con piso perceptivo 0.5 — el silencio pinta linea fina, no hueco — y ancho de barra via stroke-width de la recipe; wave = area() de $libs/plots espejada) + progressFraction clampada; wrapper que compone el Slider COMPLETO (SecondaryRange G-2 + Range + Thumb, con valueText G-1 y readonly/disabled/step reenviados); Wave con el clip de progreso (dos copias del mismo path, hooks eidos-only -played/-remaining). Verificacion: art+port 42/42 - morfo 4/4 - soma 4/4 - check 0 propios en los arboles del waveform - vocabulario limpio. Quedan F4 (recipe eidos + tokens, disenada en el CONTINUE), F5 (demo+dossier+audit) y F6 (mirada del usuario). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
96393e4305 |
docs(blocks): cada block declara su posicion bajo la doctrina de coordinacion
Y `content-section.Media` pasa a seguir al texto por defecto. DEFAULT: `.Media` era `width='wide'`, asi que una figura se salia de la columna SALVO que dijeras lo contrario. Al reves de lo que hacen todas las referencias (en `@tailwindcss/typography` una imagen dentro de `prose` queda capada a la medida y salirse exige el truco del `translate`; en `markdown-body` de GitHub, `max-width: 100%` de la columna). Ahora el default es `measure` — la figura sigue al texto y el escape se PIDE. Cambiado en los cuatro sitios que decian lo contrario: la parte, la ruta `preview`, el estado inicial del control de la demo (una demo que pisa el default en silencio ensena algo que el block no hace solo) y la doc. Verificado: 528 = ancho del texto exacto a 1011/1280/1920, y `wide` / `full` siguen funcionando cuando se piden. DOCUMENTACION: los 15 READMEs declaran ahora su posicion bajo §«Coordination», y el mapa real no es uniforme — - Coordinan: `contact` (maquina + esquema + palabras) y `pricing` (el periodo por contexto). Pero en pricing nada se puede BLOQUEAR, asi que no necesita maquina ni palabras: coordinar no es siempre una maquina. - Comparten CONFIGURACION, no estado: `team` (`align`) y `content-section` (la medida). - Costura bindable hacia el canon: `faq` (`value` → `Accordion`) y `site-header` (`mobileOpen` → `Drawer`). Reenviar no es poseer. - No poseen nada: banner · cta · feature-grid · feature-split · hero · site-footer · stats-band · testimonials. Decirlo explicitamente es el punto: inventarle estado a un block que no coordina nada es el error contrario. - `newsletter` queda como CANDIDATO ABIERTO: coloca un envio que puede quedar bloqueado sin que nada diga por que — el `disabled` lo monta el app en el punto de uso — que es exactamente el hueco que cerro `contact`. Es anterior a la doctrina (block del 30-07, doctrina del 31-07). Registrado, no asumido: se toca con decision del usuario. El README del tier apunta a la doctrina y el handoff recoge el mapa completo. Gates: blocks:check verde (15) · docs:check 0 · svelte-check sin errores en lo tocado · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
378457f46c |
feat(blocks): content-section cierra F2 — la rejilla de escape, y las escalas tipograficas salen de Text
El ultimo block de sitio, y el que mas cerca estuvo de ser un envoltorio:
`Section` + `Container` + `Prose` no habria anadido nada, porque `Prose` ya trae
su medida de lectura. Lo que falta en el ecosistema es el ESCAPE — dentro de un
`Container` nada puede ser mas ancho que el, asi que una figura a sangre hay que
sacarla del articulo y ponerla de hermana, y el orden de lectura se rompe.
La seccion es una rejilla de 5 pistas (canalon · flanco · MEDIDA · flanco ·
canalon) y cada parte dice hasta donde llega: `measure` col 3, `wide` col 2/5,
`full` col 1/-1. Todas las longitudes son tokens del sistema (`--measure-*`,
`--container-width-*`, `--container-padding-inline`). `.Body` y `.Media` son
compound porque SE REPITEN; la cabecera son slots de snippet. `.Body` apaga la
medida de `Prose`: dos duenos del mismo ancho se pelean y ganaria el mas
estrecho en silencio.
Los tres ejes son RESPONSIVE (`measure`, `wide`, y el `width` de cada parte),
resueltos con `eidos.resolve()` — el mismo camino que usan los primitivos de
eidos, asi que los unicos breakpoints en juego son los canonicos. Es lo que un
valor unico no puede decir: `{ base: 'full', md: 'wide' }` = foto a sangre en
movil y contenida de `md` arriba.
CANON: un componente no es libreria de otro. `Heading` importaba CINCO escalas
tipograficas de `Text` (`TextTracking`, `TextLeading`, `TextWrap`,
`TextNumeric`, `TextMeasure`) y al tipar este block repeti la violacion. Las
cinco se mueven a `eidos/lib/types.ts`, que es donde su propio encabezado dice
que van y donde ya esta documentado el precedente del mismo fallo (`ColorRole`
re-escrito a mano que derivo a 8 miembros contra 9). Sin shim de compatibilidad:
`text` y `heading` las importan de la libreria.
Cuatro defectos que solo dijo el navegador:
1. `gap` separa tambien las COLUMNAS y una parte que las cruza se lleva esos
huecos encima — `wide` media 1088 en vez de los 1024 del contenedor con el
que debe alinearse, y a 420px `full` llegaba a 532 dentro de una rejilla de
404 y hacia scrollear la pagina. Van `rowGap` + `columnGap={0}`.
2. Una pista `min(medida, 100%)` ignora sus propios canalones: a 420px la
central se quedaba los 404 enteros y la rejilla se iba a 436.
3. Un token de container es un ancho EXTERIOR: con `--container-width-lg` a
pelo la figura `wide` sobresalia 16px por cada lado respecto al texto de la
seccion hermana de debajo, a 1280/1440/1920. La pista ancha resta ahora
`2 * --container-padding-inline` (992 en `lg`) y el desfase es 0. Lo cazo el
usuario mirando la pantalla: yo habia medido el block contra si mismo, no
contra la pagina.
4. `100%` significa algo distinto dentro de cada parte — en `measure`/`wide` ya
es una pista, en `full` es la rejilla entera con canalones —, asi que capar
el pie igual en los dos casos lo dejaba a 340 contra una columna de 372 en
uno y a 404 en el otro.
Verificado en navegador: barrido 375→1920 sin desbordamiento; alineacion
pie↔prosa identica en 3 viewports × 3 medidas × 3 anchuras (18/18); figura
`wide` a ras del texto de la seccion hermana en 1280/1440/1920; y el valor
responsive cambia exactamente en el `md` canonico (a sangre a 767, contenida a
768). Claro, oscuro, 420px y 1920 mirados, y la geometria confirmada tambien en
el Chrome del usuario.
Semantica propia `figure`/`figcaption` (no interactivos → estructura de
documento, B-8) con el hueco senalado: el canon no tiene primitiva `Figure`.
Contexto de CONFIGURACION, no de estado — es un block de layout y no coordina
nada.
Gates: blocks:check verde (15 blocks) · svelte-check sin errores en lo tocado ·
eidos 28/29 suites (la que falla es `audio-player` sin morfo, del hilo de audio,
ajena) · docs:check 0 · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |