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 }
325 Commits (11b54025d49e275cd6bc611c5144bc9fce486a12)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
86fa9a1b77 |
refactor(sema)!: el sonido se ELIGE, no se modula — evento.intent = sonido
Directiva del autor, sin matices: «todo el sistema de sonido es una puta
mierda... la unica solucion es evento sonido, evento.intent = sonido, y
punto y nada de mierdas de que si el intent modifica nada».
Tenia razon, y lo que lo destapo fue oirlo: los dieciseis nombres del
catalogo anterior sonaban practicamente igual. Medido despues: cinco de
ellos eran LA MISMA nota de 700 Hz a cinco volumenes, y las variantes de
direccion se separaban 20 Hz. El modelo de modulacion producia teoria
bonita y mush audible.
LA REGLA ENTERA, y no hay mas:
nombre = per-emit ?? cascada ?? pack ?? morfo ?? familia[verbo] ?? familia.default
sonido = pack[`${nombre}.${intent}`] ?? pack[nombre] ?? nada
Dos busquedas. El intent SELECCIONA un sonido entero; si no existe la
variante se desprecia y suena la base; si no hay base, silencio. Un
`threat` no es un `tick` con mas aspereza: es otro sonido —mas grave, mas
rasposo, descendente— y no se pueden confundir.
EL TIER DEL VERBO es lo que vacia los packs. La tabla de familia se
indexa por verbo (`emerge.close → 'close'`), asi que ni un solo overlay
escribe su cierre. De ~165 reglas de sonido quedan 30, en 16 de los 71
packs — y las 135 borradas no decian mas que el defecto. Para que el
verbo llegue, ahora VIAJA en la señal: se calculaba y se tiraba con un
`void` (hallazgo S-40), el tipo prometia que se emitia y era falso.
CATALOGO NUEVO: 10 bases + 6 variantes, cada una un sonido diseñado
entero. Medido en el grafo de audio real: de 240 Hz (`tick.loss`) a
1900 Hz (`step`), casi tres octavas, y las variantes evaluativas traen su
propio modulador de aspereza — texturas distintas, no un tono movido.
100% sintesis a proposito: el pack por defecto no lleva binarios, funciona
sin red y no puede dar 404. Los .wav quedan como pack de EJEMPLO.
EL GUARD QUE FALTABA: `distinguishability` exige que dos entradas se
separen en al menos DOS ejes perceptuales. Nacio en rojo contra mi propio
catalogo —ocho pares casi gemelos— y dirigio el diseño hasta separarlos.
Una instantanea no lo habria cazado: cada valor era exactamente el que la
tabla decia; lo que faltaba era una RELACION entre entradas.
GESTOS POR REPETICION (idea del autor). Un arrastre ya emite a su ritmo,
asi que toca `step` (18 ms) por emision: la velocidad del gesto ES la del
trinquete, como una rueda fisica. Mueren los tres resolvers que
sintetizaban tono desde posicion y velocidad —la ultima aritmetica del
sistema— y `handle` queda EXENTA de la memoria de frecuencia, que si no
estrangularia el trinquete al primer arrastre. Perdida firmada: el ritmo
lleva la velocidad, pero la posicion ya no mapea a altura.
NADIE ESCRIBE PARAMETROS, ni la app. `SemaSignatureOverride.sound` y
`SemaCascadeRule.sound` aceptan un nombre o SILENT. La rendija de autoria
inline «para la personalizacion» sobraba: un producto REGISTRA su sonido
(nombre + definicion, una vez) y lo nombra.
⚠️ BUG PROPIO durante la cirugia: al reemplazar el bloque del sonido borre
tambien la aplicacion de deltas de intent, y el HAPTICO dejo de recibirlos.
Lo cazaron sus tests. Restaurado — el haptico sigue modulando porque su
vocabulario es categorico y sus deltas cambian el KIND, que tambien es
seleccion.
MUERE: los deltas de sonido del intent, las bases de sonido de familia, la
capa 1.5, `op:'add'` para sonido, `mergeSoundOverride`, y
`d7-intent-survives.test.ts` — vigilaba una inversion que ya no puede
existir.
VERIFICADO: sema+morfo+soma+sound 1108/1108 · docs:check 0/618 · la pagina
`/uix/docs/sound` reescrita y medida en navegador (commit+threat →
tick.threat; commit+affirm → tick, el intent se desprecia; emerge+close →
close sin una linea de pack; sustain → silencio). Los 7 fallos de la suite
completa son los ajenos y preexistentes de contracts.test.
⚠️ NO SE HA OIDO. Todo esta medido en el grafo de audio, no con un oido.
Si `open` suena a lo que debe sonar un panel abriendose es tu decision, y
se afina en un fichero.
⚠️ El estudio `/temas/sema` compila pero edita firmas crudas: su modelo es
el viejo. Queda por reconvertir.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
e0265bed95 |
feat(eidos,sema): un tema es UNA cosa — siete ejes, y ninguno fuera del alcance
Cierra el eje: el haptico entra al mapa, `masterGain` deja de descartarse, y `applyTheme` gana el septimo eje. ⚠️ CORRIGE MI PROPIA PROPUESTA. Dije que la puerta unica iba en la raiz de composicion. Al medirlo, la dependencia corre AL REVES: `ActiveEidos` tiene `#uix` y lo conduce (registra presets con `uix.motion`), mientras `ActiveUix` no tiene eidos. Un tema compuesto en la raiz habria invertido la direccion. Va donde ya vive el metodo que se llama a si mismo «the capstone composing them», y que hasta hoy componia seis de ocho. eidos.applyTheme({ color: '#3b5bdb', sound: { soft: { gain: 0.08 } } }); eidos.clearTheme(); // revierte los siete Un eje que pasas se aplica, uno que omites revierte — la misma lectura atomica que ya tenian los seis visuales, ahora sobre siete. Sin `uix` (render headless) el eje perceptual se salta y los visuales siguen: un tema que se aplica a medias es mejor que uno que revienta. EL HAPTICO, AL MAPA. Su tabla `kind → pattern` vivia como const dentro del canal, asi que era el unico eje perceptual que un tema no alcanzaba — exactamente la asimetria que el trabajo del sonido existia para quitar, dejada en pie un canal mas alla. Ahora es `SEMA_MAP.haptics`, con perfiles derivados (`floorMs` + `scale`/`gapMs`) o constantes (`pattern`): los tres kinds evaluativos son ritmos constantes porque lo que hace reconocible a un `error` es su cadencia, no su longitud. El canal lo lee por GETTER, no por copia, asi que un `applyMap()` en caliente le llega sin re-registrarlo. `masterGain` SE APLICA. Pasarlo con motor compartido se tiraba en silencio (S-05/S-32) pese a ser una perilla viva del motor y la forma natural de poner el volumen desde la raiz. Ahora llega a `master.setGain`, y de las opciones que de verdad no pueden aplicarse a un motor ajeno se AVISA en vez de desaparecer. Y `DeltaValue` pasa a exportarse: es la forma de toda semilla de override, sin ella un consumidor no puede tipar el tema que esta pasando. Su ausencia era un error VIVO en el estudio de sema, que lo importaba y no podia — por eso check BAJA a 73 desde el baseline de 75. VERIFICADO: theming 8/8 (incluye re-vocear el haptico y que la tabla autorada quede intacta) · sema+eidos+morfo 802/802 · check 73 (baseline 75, −2 por el export) · docs:check 0/618 · suite completa 7 fallos, los mismos ajenos y preexistentes de contracts.test. ⚠️ Nada de esto se ha OIDO ni TOCADO en un dispositivo real: todo esta medido con el resolver y las suites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
c5b394574c |
feat(sema): el mapa perceptual se TEMATIZA, y con la misma forma que los ejes visuales
El autor: «que yo no pueda definir a nivel de tema la personalizacion es un
fallo». Lo era, y medirlo destapo algo peor que el hueco que se veia.
NO ERA UN CAMPO QUE FALTABA EN EL SEED: eran dos formas distintas de
personalizar. Eidos retunea en vivo, atomico y revertible (applyX/clearX);
sema leia `overrides.runtime` UNA VEZ en el constructor y nunca mas, sin
forma de revertir. Un `applyTheme` unico construido encima habria sido peor
que dos puertas honestas — una llamada donde el color cambia ahora y el
sonido no cambia nunca, porque ya arranco. Por eso la puerta unica va la
ultima y aqui va el sustrato.
1. EL CATALOGO ENTRA EN EL MAPA (`families · intents · sounds`). Es dato, y
el mapa es la estructura que un tema direcciona. Mientras vivio fuera como
const de modulo —error mio de ayer— el VOCABULARIO era el unico eje
perceptual que un producto no podia re-vocear a ninguna hora. El resolver
pasa a resolver el nombre contra el mapa que recibe, no contra un const,
que es lo que hace que un tema surta efecto.
2. `applyMap(seed)` · `applySounds({ soft: { gain } })` · `clearMap()` — en
vivo y revertibles, reconstruyendo desde el mapa autorado, asi que
aplicar dos veces es aplicar una. Misma forma que los seis applyX de
eidos. El engine ya pasaba `this.map` por emision, asi que el retuneo
surte efecto en la siguiente ocurrencia sin re-arranque.
3. UNA RUTA MALA LANZA (S-09). Antes CREABA la rama: `families.commmit.…`
dejaba el valor real intacto y hacia crecer un fantasma al lado, en
silencio; y descender a traves de una hoja primitiva la sustituia por un
objeto. En una API de theming eso es intolerable — el sintoma es «el
sonido no cambio» y no hay nada a lo que apuntar. Se valida tambien en
construccion, no solo en el retuneo.
LO QUE SIGUE CERRADO A PROPOSITO: el vocabulario. `SoundName` es
`keyof SOUND_CATALOGUE`, asi que un tema cambia a que suena un nombre y no
puede inventar uno que ningun componente pueda referenciar. La voz se abre;
las palabras no.
⚠️ REGRESION PROPIA, cazada y corregida antes de commitear: tipar el mapa
`as const satisfies SemaMap` daba las claves literales que `SoundName`
necesita, pero estrechaba TODO el mapa y rompia a los consumidores que lo
recorren en generico (el estudio de sema: +5 errores en check). El catalogo
pasa a ser su propio const y `SEMA_MAP` conserva su anotacion ancha.
⚠️ Y un hallazgo del harness: los tres primeros tests median 0.85x de lo
esperado. No era el codigo — era BK-FREQ-MEMORY atenuando la tercera
emision del mismo evento. La memoria de frecuencia funciona.
VERIFICADO: `theming.test.ts` 7/7 (re-vocea un NOMBRE en vivo y revierte,
re-vocea una FAMILIA, idempotente, no muta el mapa canonico, rechaza las dos
rutas malas de S-09) · sema+morfo+sound 444/444 · check 75 = baseline ·
docs:check 0/618.
QUEDA, y es decision de producto no defecto: la puerta unica
`uix.applyTheme(seed)` en la raiz de composicion. Mas dos menores:
`masterGain` sigue descartandose en silencio (S-05/S-32) y la tabla haptica
`kind → pattern` sigue en el canal en vez de en el mapa, asi que es el unico
eje perceptual que un tema no alcanza.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
1302f28a5d |
docs(sema,theming): el orden real, como se autora cada canal, y el hueco del theming
Tres cosas que el cambio del sonido dejo sin plasmar.
1. EL ORDEN CANONICO MENTIA. La numeracion 1·2·3·4·5a·5b esta citada como
canonica en sema.md, resolver.ts y engine.ts, y despues del reorden era
falsa para `sound`: un pack ya no autora en 5a, lo hace en 1.5. Corregido
en los tres sitios a la vez, con el porque — todo lo que AUTORA un sonido
corre ahi conservando su precedencia relativa; solo cambio su posicion
respecto al intent. El resto de la regla (channels/haptic/hold) mantiene
el «gana el ultimo» porque no son ejes evaluativos.
2. COMO SE AUTORA CADA CANAL (CANON §7). El reparto por dueño estaba
escrito; lo que un componente ESCRIBE, no. Tabla nueva: el canal visual
no se escribe (se declara el evento y eidos reacciona), `sound` es UN
NOMBRE, `haptic` es un `kind`. La regla es la misma en las tres filas y
ese es el punto: un componente dice QUE ocurre, nunca cuan fuerte, cuan
brillante ni cuanto dura. El sonido era la excepcion hasta ayer.
3. EL ANALISIS DEL THEMING que el autor pidio (theming/channels.md §5b).
Lo que el cambio NO toco: nada de eidos. El canal visual se proyecta
estampando data-event-*, y esa ruta quedo intacta — ni una receta, ni un
token, ni un selector. Eidos no sabe que existe el sonido y no le hizo
falta. Lo que cambio fue DONDE se autora (un catalogo en vez de 71
ficheros) y CUANDO se aplica (antes del intent), ambos dentro de sema.
Lo que si cambio, y es lo util: `applyTheme(seed)` retunea SEIS ejes
visuales (color·type·depth·shape·space·gradient) y el sonido tiene CERO.
Antes esa asimetria se justificaba sola — con 214 reglas autorando 33
firmas a mano no habia objeto que retunear. Hoy hay exactamente uno:
16 nombres en un const. Un eje `sound` en ThemeSeed seria la misma forma
que los otros seis.
Y las tres puertas de personalizacion, medidas, ninguna llega al
catalogo: `overrides.runtime` recorre rutas de SEMA_MAP y el catalogo no
esta en el mapa (ademas se traga las erratas, S-09); `overrides.cascade`
es por selector, no un tema; `masterGain` se descarta en SILENCIO
(S-05/S-32). Un producto puede silenciar y puede pisar una ocurrencia,
pero NO puede re-voceear el sistema. Queda escrito en vez de ser
folclore.
⚠️ chronos: `src/uix/sema/components/chronos.ts` entro en la migracion de
`769e426c5` pese a ser de escritura excluida. Era forzoso — con el tipo
estrechado, dejarlo sin migrar rompe la compilacion del arbol entero — y
el cambio es mecanico (soundTuning(...) -> nombre), sin decision de diseño.
Su SPEC.md NO se ha tocado y sigue citando `soundTuning('commit.soft')`.
VERIFICADO: sema+morfo+sound 437/437 · check 75 · docs:check 0/618.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
fb7759b849 |
docs(sema)!: retirar D.7 y S-07 — el orden hace imposible lo que prohibian
F6 del PLAN-sound-names, y el motivo importa mas que el borrado: no se
derogan por cambio de opinion, se DISUELVEN. La doctrina del autor que
las origino sigue siendo cierta; lo que cambia es quien la sostiene.
D.7 prohibia canonizar un sample sobre un evento cuya familia tuviera
base.sound, porque sound() devolvia una firma COMPLETA y la cascada la
aplicaba en replace DESPUES del intent: un fulfill perdia su +300 Hz
ascendente. S-07 era el mismo defecto una capa mas abajo — el gain
desnudo de los 14 afinados de escalera — y seguia ABIERTO por decision
del autor.
Con el nombre aplicado ANTES de los deltas de intent no queda ninguna
capa posterior que pueda pisarlos. Aplastar el perfil evaluativo ya no
esta prohibido: es INEXPRESABLE. Y S-07 pierde su premisa entera, porque
SOUND_TUNINGS ya no existe.
LO QUE SE CONSERVA, con su sitio nuevo escrito: el silencio como firma
valida (SILENT), el criterio de cuando crear un pack, y los samples como
recurso de producto (el catalogo los admite con nombre; la app autora los
suyos por overrides.cascade, que sigue abierto a proposito).
LO UNICO DE D.7 QUE HAY QUE SEGUIR SABIENDO, y por eso queda escrito en
tres sitios: playSample lee EXACTAMENTE dos campos, sampleUrl y gain. No
hay playbackRate ni detune. Sobre un .wav el intent solo mueve el
volumen — medido, risk es indistinguible de neutral porque solo aporta
roughness y la ruta de sample lo ignora. Un sample con carga evaluativa
necesita UN FICHERO POR INTENT, que es lo que hace signal con ping frente
a error y la verdadera razon de que fuese la excepcion. Es fisica del
audio, no politica.
Tocado: book-deviations §D.7 + §S-07 · sema.md §the sound catalogue
(reescrita entera, con el orden y el limite del WAV) · CLAUDE.md §sema
doctrine · los dos README del art · CONTINUE-sema-audit §3.0/§3.1/§3.6,
que es el handoff vivo y estaba describiendo una cola que ya no existe.
⚠️ Prettier reformatea entero book-deviations.md y sema.md porque NO
estaban formateados en HEAD; reaplicado a mano para que el diff sea solo
lo mio. Mismo motivo por el que CLAUDE.md y arts/README.md van con 1-2
lineas en vez de 30.
VERIFICADO: sema+morfo+sound 437/437 · check 75 = baseline · docs:check
0/618 · prettier limpio en los ficheros que toque.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
769e426c5e |
refactor(sema)!: el sonido se NOMBRA — un catalogo, una palabra por componente
F1-F5 del PLAN-sound-names, por orden directa del autor. El componente
dice UN NOMBRE y nada mas; si ese nombre es una receta sintetizada o un
.wav no es asunto suyo.
sound: { gain: {op:'add',value:-0.25}, -> sound: 'settle'
centroid: {op:'add',value:2800},
decay: {op:'add',value:20} }
EL CATALOGO (src/uix/sema/sound-names.ts) fusiona SOUND_LIBRARY y
SOUND_TUNINGS en 16 nombres + SILENT: cinco niveles, cinco direcciones,
cuatro caracteres, dos samples. Los nombres NO llevan familia delante y
esa ley se retira POR SU MOTIVO: `{family}.{tail}` existia porque un
tuning era un delta sobre una familia; un nombre es una base, y el mismo
`soft` sirve a un commit y a un emerge porque la familia sigue poniendo
su identidad debajo.
EL REORDEN es la pieza que importa. Los packs corrian los ULTIMOS y en
modo replace, asi que la eleccion del componente borraba el perfil
evaluativo del intent — medido: un dialog con `threat` sonaba igual que
uno neutro. Ahora el nombre se aplica sobre la base de familia y ANTES de
los deltas de intent:
base de familia -> EL NOMBRE -> intent -> evento -> app
Con el nombre debajo, D.7 y S-07 dejan de PODER ocurrir. No se arreglan:
se disuelven. `sound-names.test.ts` lo certifica para los 16 nombres, no
para los 3 packs que un guard anterior muestreaba.
LA PUERTA es el tipo: `Sema['cascade'].sound` acepta un nombre o SILENT.
Al estrecharlo, el compilador enumero la migracion entera — 173 errores
en 70 de los 71 packs — y ninguno podia colarse. `SemaCascadeRule` se
queda abierto a proposito: `overrides.cascade` es la puerta de
personalizacion de la app, y una app puede legitimamente autorar un
sonido que el catalogo no lleva. El framework se aplica a si mismo la
regla estricta.
MEDIDO ANTES DE TOCAR NADA: 214 reglas autoraban 33 firmas distintas; 92
(43%) no decian mas que un volumen; 69 pisaban el `contour` del intent y
no podian hacer otra cosa (es un enum, solo admite reemplazo); 5 packs
llevaban un `centroid +2800` copiado que dejaba su commit en 4600 Hz —
mas brillante que `signal`, la familia mas brillante del mapa— porque era
el residuo aritmetico de un sample borrado (4600-1800).
Las colapsaciones de firma van una a una en PLAN §5.1 con su consecuencia
audible; cada una se revierte editando UNA linea. La unica grande es
deliberada: navegar un calendario sonaba a gain 0.18, el valor mas alto
de todo el sistema —mas que una alerta—, residuo de una de las diez
cabezas de tuning que mentian (S-39). Ahora es `soft.rise`.
GUARDS: `sounds-grammar.test.ts` deja de vigilar una gramatica que ya no
existe y pasa a vigilar que ningun pack AUTORE (censo sobre las fuentes,
con los comentarios fuera — la prosa que menciona la forma vieja no es la
forma vieja, y escanear texto crudo marco cinco doc blocks). El invariante
3 del censo de packs pasa de «la cabeza del tuning no miente» a «el nombre
existe». Bug propio cazado por su test: `namedSound` devolvia la entrada
del catalogo POR REFERENCIA — una mutacion de un consumidor redefinia lo
que un nombre significa para toda la sesion.
VERIFICADO: sema+morfo+sound 437/437 · check 75 = baseline exacto ·
docs:check 0/618 · suite completa 7 fallos frente a 8 de la base sin mis
cambios (los 6 de contracts.test son ajenos y preexistentes).
QUEDA F6: retirar D.7/S-07 con su motivo, reescribir sema.md §tunings y
escribir el limite fisico del WAV (playSample solo lee sampleUrl y gain,
luego sobre un .wav el intent solo mueve el volumen).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
7154fb77ef |
docs(process): PLAN — el sonido se NOMBRA, un nombre y punto
Mandato del autor: «yo solo quiero en la semantica en sonido darle el nombre de un sonido y punto, y me da igual que sea generado o sea un wav; todo lo demas es sobreingenieria». No es una simplificacion de conveniencia: restaura la ley que sema.md:390 ya tenia escrita — «components reference names; URLs and parameters live in one place»— y que nada vigilaba. LA MEDIDA que lo justifica, en runtime sobre los 71 packs: 214 reglas autoran 33 firmas distintas, y las 8 primeras cubren el 76%. Dos firmas —gain 0.05 y gain 0.03— explican 70 reglas ellas solas. El 43% de la superficie perceptual del framework es un componente diciendo «mas bajito». Ejes tocados: gain 165, contour 69, pitch 67, roughness 23 — contour/pitch/roughness son del INTENT, y contour es un enum, luego 69 reglas borran el contorno que el intent acaba de poner y no pueden hacer otra cosa. El guard que naci ayer para proteger el intent no mira contour: esta ciego en el unico eje incomponible. EL DISEÑO: un catalogo de nombres (fusion de SOUND_LIBRARY y SOUND_TUNINGS), el tipo de la regla estrechado a nombre|SILENT, y el nombre aplicado ANTES de los deltas de intent. Con el nombre debajo, D.7 y S-07 dejan de PODER existir — no se arreglan, se disuelven. §7.1 registra un error mio corregido por el autor: la primera redaccion abria con una instantanea del estado actual, declarada innegociable. Un A/B necesita una referencia que merezca conservarse y la salida de hoy no lo es; ademas git YA es esa instantanea y el compilador es mejor guard. La instantanea se movio al CATALOGO, que es donde viven las decisiones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
f7a7564b39 |
feat(morfo): allowedTargets — el morfo declara a que partes puede viajar el estampado
El runtime deja que el proveedor redirija el estampado por emision (fallbackTarget GANA sobre el ref del target declarado). Es comportamiento correcto para partes repetidas — el dia pulsado del Calendar, la pagina clicada de Pagination — pero el morfo no decia nada: la declaracion leia target: provider mientras cada emision real caia en day / item / prev-button. Un pack escrito desde el morfo apuntaba a un nodo que nunca lleva el estampado, y nada podia distinguir ese error de esta redireccion legitima. La auditoria 2026-08-05 midio el error (S-12: TextArea 6x/13x sobre su firma escrita); el censo nuevo encontro 12 reglas identicas en forma que estaban BIEN. Solo el morfo puede separarlas, asi que el morfo lo dice. allowedTargets es el simetrico exacto de allowedFamilies en el otro eje: el target canonico sigue siendo el default que el runtime resuelve; la lista declara las demas superficies donde una emision puede caer. Invariantes en schema.ts: parte inexistente o auto-listado = error de compilacion del morfo. Declarado en calendar (5 superficies), pagination (5), rating-group, toolbar y tree-view (branch) — cada entrada verificada contra sus emisores reales, no contra el pack. Pendiente declarado: renombrar fallbackTarget -> targetOverride (nombre honesto para una opcion que gana) va en commit aparte — 193 apariciones en 63 ficheros esconderian este contrato dentro de un diff de renombrado. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
866089a407 |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar
La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
c46a955a0f |
docs(process): el informe de la auditoria de sema — 44 hallazgos con evidencia
`AUDIT-sema-2026-08-05.md`, la fuente de verdad que `CONTINUE-sema-audit.md` referencia y que se habia quedado sin commitear. 8 frentes en paralelo (samples · motor · cascada · tipos · packs · morfo↔sema · canales · doctrina), cada hallazgo sometido despues a un agente adversarial que intentaba refutarlo leyendo el codigo: 53 agentes, 1.790 llamadas a herramientas, 44 confirmados y 1 refutado. Cada entrada lleva fichero, linea, la evidencia literal, el impacto y el arreglo propuesto (sin aplicar). Incluye tambien la seccion de lo que esta BIEN y no hay que tocar, que es la mitad que un informe de auditoria suele omitir: los cinco .wav son PCM valido, la ruta de sample se recorre de punta a punta, los 248 eventos de los 166 morfos cumplen el vocabulario cerrado, el orden de capas es el documentado y la fuga de nivel entre reproducciones esta realmente cerrada. Es cronica: registra lo hallado el 2026-08-05 y no se reescribe segun se van arreglando los hallazgos — el estado vivo esta en el handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
9944d76ede |
docs(process): handoff del hilo de sema — lo cerrado, la cola y como medirlo
`CONTINUE-sema-audit.md`, autosuficiente para arrancar una sesion nueva sin releer las 24 000 lineas del corpus. Registra: - Las seis entregas de esta sesion con su commit, y por que cada una fue necesaria (el silencio como valor, el tremolo en serie, el saneamiento documental, contact-press, D.7 sin versos sueltos, y la regla de cascada que dejaba de alcanzar ancestros). - Las decisiones FIRMADAS que nadie debe re-litigar: `contact-press` (deroga el anexo Tabla 1, con la reversion escrita en el test), la enmienda de D.7 por su motivo —familias sin `base.sound` fuera de la prohibicion— y la reescritura de D.8 —`activeChannels` es default, no prohibicion—. - La COLA en orden de dano, con su evidencia: las 17 reglas de 212 que no disparan o disparan mal (textarea 6x/13x, navigation-menu, gradient-picker, chronos, tree-view mudo por teclado, 5 inertes, 10 con tuning de familia ajena) · los silenciosos (`preload` colgado que ademas abre contexto en el boot, `masterGain` que no hace nada, fallback de sample sin diagnostico, 3 WAV muertos, la rama de tipos que deja pasar un `gain` string) · el interruptor Sound de las demos · el `announce` que no compila · la instantanea perceptual · la recalibracion tras el fix del AM · y los dos ejes (el `contact` ausente en 84 de 91 morfos, `Toolbar.Button` sin componer). - **S-07 marcado como NO TOCAR** sin reabrirlo: decision explicita del usuario. - Como se verifica audio en este repo, que es donde mas tiempo se perdio: render offline replicando el grafo, A/B dentro del MISMO componente, y contar osciladores solo responde «llego algo al motor», nunca «se oye». - Las lecciones de metodo que costaron caro, incluida la propia: medir la envolvente NO es medir la salida, y por eso declare un silencio que a −13.9 dBFS era lo mas fuerte de la interfaz. Baseline al cierre: check 75 · docs:check 0/617 · sema+morfo+sound 369/369. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
4fccf16624 |
fix(sema): D.7 sin versos sueltos — y el mismo defecto aparece una capa mas abajo
Los 7 infractores que quedaban canonizaban un sample de la familia `handle`
sobre un evento `commit`: color-picker `commit-set` · gradient-builder
`commit-set`/`commit-reset` · gradient-picker `commit-save`/`commit-remove`/
`commit-reset` · splitter `commit-set`. Como `sound()` devuelve una firma
COMPLETA y la cascada la aplica en `replace`, aplastaban la modulacion por
intent que D.7 existe para proteger — igual que hacia proof-of-human.
EL CARACTER SE CONSERVA, EN LOS EJES QUE EL INTENT NO POSEE. El intent posee
`pitch` / `contour` / `gain` / `roughness`; libres quedan `centroid`, `attack`,
`decay`, `duration`. El brillo del encaje y la cola del asentamiento viajan
ahora como deltas medidos contra la base de `commit`:
handle.release.soft centroid 4600 vs base 1800 -> +2800 · decay 140 -> +20
handle.snap.chime centroid 5400 vs base 1800 -> +3600 · decay 130 -> +10
HALLAZGO AL VERIFICAR — D.7 y S-07 son EL MISMO DEFECTO EN DOS CAPAS. El primer
arreglo usaba `soundTuning('commit.soft', {...})` y el guard lo tumbo: un
afinado de escalera trae su `gain` como NUMERO DESNUDO (`commit.soft` =
`{ gain: 0.05 }`), y un numero desnudo tambien se aplica en modo `replace`. Es
decir, cambiar el sample por un tuning dejaba el intent igual de aplastado. Las
9 reglas COMPONEN el nivel (`gain: { op: 'add', value: -0.25 }` sobre la base
0.3) en vez de fijarlo. S-07 alcanza a las 14 claves del catalogo y sigue
ABIERTO fuera de estas reglas.
ENMIENDA A D.7 — `handle` sale de la lista de familias prohibidas, por su
motivo y no por costumbre: la prohibicion existe para preservar `intent.deltas`,
y en una familia SIN `base.sound` no hay firma que aplastar ni deltas que
borrar. `handle` declara solo `haptic` en su base, y sus 14 usos de `sound()`
en 12 packs son la UNICA fuente sonora del gesto — aplicar la regla al pie de
la letra dejaria mudo todo el arrastre del framework. Igual `sustain` y
`delegate`. La regla queda enunciada por su razon: no se canoniza un sample
sobre un evento cuya familia TENGA `base.sound`.
GUARD `d7-intent-survives.test.ts`: para color-picker, gradient-picker y
proof-of-human comprueba que `fulfill` sube pitch y gain y que `risk` sube
roughness respecto a neutral — es decir, que el intent sigue siendo audible a
traves del pack — y que el caracter del sample sobrevive (centroid por encima
de la base). ⚠️ Nacio en ROJO por partida doble: primero con el mock que hacia
casar TODAS las reglas del pack a la vez (artefacto mio, acotado al evento
despues), y luego con el `gain` desnudo del tuning, que es el hallazgo de
arriba.
VERIFICADO: sema+morfo+sound 374/374 · docs:check 0/616 · check 76 = base 75 + 1
del fichero huerfano · prettier limpio en lo tocado.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
b9d73c19b5 |
feat(sema): los toggles recuperan su CONTACTO — la voz vuelve al momento que el libro le da
Tras leer el corpus entero de sema y del motor de sonido (los tres planes, el
handoff vivo, sema.md, CANON, channels, el README del art y D.7/D.8 literales),
que es la precondicion que me estaba saltando.
1) EL CONTACTO EXISTE (directiva del autor: «1 su contact-press»)
El libro le da a un Toggle DOS momentos, y el codigo solo tenia uno:
- cap. 23 §9 empareja el Toggle con `contact.press` Y `commit.toggle`;
- cap. 22 nombra «un switch que muestra estado pressed» como contact;
- CANON §8 regla 1: «contact precede al resultado cuando hay accion directa».
switch, toggle y toggle-group declaran ahora `contact-press` (family contact,
verbo press, sequence 'pre', SIN intent por cap. 22 §11) ademas de su
`commit-toggle`. Los providers los disparan en ese orden. Solo 4 de 91 morfos
con eventos expresaban `contact`; ahora 7.
Y el reparto de la voz cambia de sitio, que es el fondo del asunto:
- el CONTACTO habla — `contact.soft` (gain 0.08, clave nueva): el «click breve
opcional» de la tabla de canales del cap. 22, un tercio de una pulsacion de
boton para que un flip nunca pese mas que una activacion;
- el COMMIT calla — `SILENT`. Es lo que pide la tabla de canales del cap. 23
(«sonido: cierre opcional»), lo que exige el Criterio 12 (ch. 7 §13) de un
control que se pulsa docenas de veces por sesion, y lo que D.5 decidio en
mayo. La consecuencia la lleva `data-state` + color, la huella de ch. 23 §3.3.
`commit.medium` —la clave que invente ayer para que sonaran— queda RETIRADA:
huerfana en cuanto la voz se mudo al contacto.
MEDIDO en navegador: un clic emite `contact-press` -> `commit-toggle`, el estado
pasa a checked, y se levantan 2 osciladores con pico 0.08. UNA sola voz, la del
contacto; el commit no sintetiza nada.
⚠️ REVERSION DOCTRINAL DECLARADA: `compile.test.ts` afirmaba, citando el ANEXO
Tabla 1, que «Toggle/Switch declara un UNICO evento; el contacto es IMPLICITO en
el gesto». El cuerpo del libro dice lo contrario donde trata el caso. El test se
actualiza dejando escrita la reversion y su procedencia, para que quien lea el
anexo sepa por que el codigo diverge.
2) D.7 VUELVE A SER CANON SIN VERSOS SUELTOS (directiva del autor)
`proof-of-human` canonizaba samples en familia `commit`: `sound()` devuelve una
firma COMPLETA y la cascada la aplica en `replace`, asi que aplastaba la
modulacion por intent que la propia entrada existe para proteger — un `fulfill`
perdia su +300 Hz / ascending / +0.05, y el fallback sintetico sonaba ciego al
intent tambien. Su cabecera afirmaba literalmente lo contrario de lo que hacia.
Ahora compone tunings con `{ op: 'add' }`: el intent conserva sus primitivos y
la regla solo aporta caracter. Un producto que quiera el ping lo registra desde
la cascada de app (capa 5b), donde un sample SI es recurso legitimo.
3) D.8 REESCRITA Y FIRMADA
La entrada decia «los packs DEBEN respetar el activeChannels del family … anadir
`sound` a `handle` tambien [es incoherente]», y el framework hace justo eso,
prescrito por sema.md §componentes continuos. La ley queda:
- `activeChannels` es el DEFAULT perceptual de la familia, no una prohibicion.
- `channels` es la palanca por evento/regla y ajusta en las DOS direcciones.
Restringir: dialog `close-after-fail`, transporte del player, VirtualList.
Ampliar: el pack del slider sobre `handle`.
- Ampliar exige justificacion perceptual escrita. La del slider es canonica: el
sonido del arrastre no es firma fija sino payload por emision — por eso
`handle` no declara `base.sound`, y por eso sema conserva TRES resolvers de
gesto cuyo unico proposito es ese sonido.
- PROHIBIDO: declarar una firma de canal que la activacion no incluye. Deja
reglas INERTES. Es comprobable por guard. Infractoras: 2 del dialog + 3 mas.
Y Announce: la propia D.8 dejo la puerta abierta («si en el futuro necesita ser
pluggable … entonces vale convertirlo en canal formal. Hoy no»). Ese futuro
llego el 2026-07-04. No fue deriva; fue no volver a escribirlo aqui.
VERIFICADO: sema+morfo 337/337 · docs:check 0/616 · check 76 = base 75 + 1 del
fichero huerfano de la auditoria · `morfo:vocabulary` limpio · prettier: el
unico aviso mio formateado, los demas ya sucios en HEAD.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
432106f1f6 |
docs(sema): la doctrina deja de mentir — deriva propia, copias podridas y dos leyes muertas
Pasada de saneamiento documental tras los tres commits de sonido, con el
informe de AUDIT-sema-2026-08-05 como mapa. Tres clases de defecto.
1) DERIVA QUE YO MISMO DEJE (lo mas urgente)
El 05 renombre las claves silenciadoras a `commit.silent` / `emerge.silent` y
el 06 las elimine del catalogo — dejando media docena de docs citando claves
que ya no existen, y afirmando ademas comportamientos que ya no ocurren:
- switch / toggle / toggle-group README: decian «silent-by-default
(`commit.silent`)». Hoy es `commit.medium` (gain 0.1) y suenan. Corregidos
con el nivel real y su razon (un tercio de una pulsacion de boton).
- tooltip README + docblock del pack: decian que el tuning «resta el gain de
la familia, asi que neutral emite a 0» y que «threat / fulfill siguen
aflorando». Ambas cosas son falsas desde el 06: es `SILENT`, el resolver
retira el canal y NADA aflora, porque no queda ganancia que subir.
- toggle-group apuntaba a `LIBRO_VARIACIONES_Y_EXTENSIONES.md`, que es un stub
movido; ahora apunta a book-deviations.
- La nota del renombrado en D.5 decia «hoy es `commit.silent`»: una clave que
vivio UN DIA. Marcada como tal, con la lista de las que siguen vivas.
2) COPIAS PODRIDAS — la ley del corpus es enlazar, no copiar, y estas dos
entradas la incumplian
- D.9 transcribia `SEMA_HOLDS_BY_INTENT` entera y se habia quedado atras: D.12
corrigio DOS valores el 2026-07-06 (`commit.fulfill` noticed→settled,
`signal.loss` noticed→brief, ambos contra el texto del libro) y la copia
siguio afirmando los viejos un mes. Sustituida por el puntero a holds.ts +
el enumerado generado, con la leccion escrita en el sitio.
- sema.md transcribia `SEMA_VERBS` y le faltaban DOS verbos vivos:
`commit.unselect` y `handle.zoom`. Retirada; queda el puntero a verbs.ts y a
vocabularies.md, que si se genera y tiene guard de frescura.
3) LEYES QUE LA PRACTICA YA HABIA DEROGADO, Y NADIE REGISTRO
- El contrato `emit` publicaba `Promise<void>`; devuelve `Promise<string>`
desde D.9. Corregido, y explicado que ese string es el id de la ocurrencia —
el unico asidero para cerrar una senal persistente con `clear`.
- channels.md fijaba «3 canales runtime» y D.8 cerraba la puerta a Announce
(«hoy no»). El `AnnounceChannel` existe desde el 2026-07-04: built-in,
opt-in y exportado. channels.md pasa a 4 con la distincion que importa
(visual/sound/haptic EXPRESAN; announce SUSTITUYE) y D.8 queda marcada
PARCIALMENTE SUPERSEDED.
- sema.md prescribia `{ announce: uix.announce }` — y eso NO COMPILA: las dos
firmas no casan (bolsa de opciones vs posicional). Ahora ensena el adaptador
de una linea que si compila, y declara que ninguna raiz cablea el canal hoy,
asi que activarlo cae en el fallback que anade un SEGUNDO par de live
regions. El arreglo de codigo queda sin tomar: es decision de diseno.
- La cabecera de engine-sound.ts y la fila de arts/README seguian afirmando
como MECANISMO que «`prefs.sound` mapea al bus ui», que la auditoria AU-4 ya
habia corregido en el README del propio arte: lo garantizado por
construccion es el NEGATIVO (ninguna politica de UI escribe el bus content).
AMBIGUEDADES QUE NO RESUELVO PORQUE SON TUYAS, pero que dejan de estar
escondidas: D.7 prohibe canonizar samples fuera de `signal` y
`proof-of-human` los usa en dos eventos `commit`, aplastando la modulacion por
intent que esa misma entrada existe para proteger —y el comentario del pack
afirma literalmente lo contrario de lo que hace—; y D.8 declara que los packs
deben respetar el `activeChannels` de la familia mientras sema.md prescribe lo
contrario y el pack de dialog deja dos reglas inertes. Ambas quedan marcadas
con su estado real y los dos caminos excluyentes, pendientes de tu firma.
VERIFICADO: docs:check 0/616 · sonido + sema 244/244 · cero referencias vivas a
las tres claves muertas (las que quedan en book-deviations son historia
declarada como tal, y las de cronica no se reescriben) · prettier: los 6 docs
con avisos ya estaban sucios en HEAD, no los toco.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
316452263f |
fix(sound): la rugosidad pasa a ser un tremolo EN SERIE — deja de sumarse a la envolvente
Conectar un nodo a un AudioParam SUMA a su automatizacion; no la escala. El modulador de AM colgaba de `envelope.gain`, asi que su profundidad se mantenia CONSTANTE mientras la envolvente se movia: el tremolo ni crecia con el ataque ni moria con la caida, y alli donde la automatizacion bajaba de la profundidad el parametro CRUZABA CERO — modulacion en anillo, no tremolo. MEDIDO con render offline del grafo real, viejo contra nuevo: caso cuerpo RMS cola PICO corte A commit neutral (control) 0.242 -> 0.242 0.149 -> 0.149 0.0002 (igual) B signal neutral 0.332 -> 0.275 0.145 -> 0.088 0.0666 -> 0.0002 C commit + risk 0.243 -> 0.200 0.263 -> 0.103 ~0 D commit.subtle + threat 0.143 -> 0.018 0.333 -> 0.009 ~0 - B: el clic del cierre de cada aviso/error desaparece (x333 menos amplitud en el corte). Era el tick audible al final de TODOS los earcons de `signal`. - C: la cola dejaba +76% de pico sobre el control; ahora muere por debajo, que es lo que una cola debe hacer. El sustain no se toca (ahi el tremolo ES el percepto de rugosidad buscado). - D: el caso grave. `commit.subtle` (gain 0.03) cargado con `threat` (roughness 0.5) salia a RMS 0.143 — SIETE VECES su nivel declarado, con inversiones de polaridad. Ahora sale a 0.018, su nivel de diseño. Escenario real: un item de menu destructivo con intent="threat". - A: el control sin AM no se mueve ni un digito. El fix no toca lo que no debe. LA FORMA. Etapa de ganancia en serie entre el filtro y la envolvente, con valor base `1 - depth` y el modulador sobre su propio `.gain`: el factor es `(1 - depth) + depth*sin`, que hace pico en 1 —el tremolo nunca ANADE nivel— y multiplica la envolvente, asi que silencio por cualquier cosa es silencio. `depth` clampeada a 0.5 para que `1 - 2*depth >= 0`: invertir polaridad es otro efecto. Y deja de escalarse por `gainScale`: siendo un ratio ya es relativa por construccion, que es justo lo que el comentario viejo perseguia a mano («scaling both keeps the timbre and only moves the volume»). CONTRAPARTIDA DECLARADA: los earcons con rugosidad bajan 1.6-2.7 dB de nivel medio (B: -1.6 dB de cuerpo, -2.7 dB de pico), porque el AM ya no suma nivel. Es correccion, no perdida — pero es un cambio perceptual real en la familia `signal` y en todo `risk`/`threat`. Si el autor quiere recalibrar los gains base, es una decision de catalogo aparte. GUARD. `engine-sound.test.ts` sustituye el test que fijaba la propiedad vieja (la profundidad escalada por gainScale) por el que fija la invariante que de verdad importa: NADA puede conectarse al AudioParam de la envolvente, y la etapa en serie vale `1 - depth`. Verificado en ROJO (al reconectar el modulador a `envelope.gain` falla con su mensaje). El fallo colateral que aparecio al hacerlo —el test de la voz desconocida avisando dos veces— era cascada: al fallar el assert no corre `dispose()`, el AudioContext queda vivo y el siguiente test recibe el aviso de «segundo contexto». Doctrina: `sema.md` (sintesis) y `arts/sound/README.md` (voces) describen ahora la etapa en serie y por que nunca se parchea un modulador sobre `envelope.gain`. VERIFICADO: sonido + sema 244/244 · check 76 = base 75 + 1 del fichero huerfano `__adv-verify-haptic-dead.test.ts` (sin trackear, sin borrar sin instruccion) · docs:check 0/616 · el dev server sirve el fix (comprobado por fetch del modulo) · prettier: los dos ficheros eran limpios en HEAD y quedan limpios. Cierra el hallazgo S-06 de AUDIT-sema-2026-08-05. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
24d30ee1a8 |
fix(sema): el silencio deja de ser una resta y pasa a ser un valor — SILENT
Directiva del autor: «el silencio no admite verbos ni intents, es universal,
por lo tanto es un valor canonico que cuando se le envia al canal semantico
este simplemente lo ignora y no envia nada al soundengine».
LO QUE ERA. `commit.silent`, `emerge.silent` y `contact.silent` no eran
silencio: eran una RESTA del gain base de su familia (-0.3, -0.2, -0.25). Por
eso hacian falta tres —cada una tenia que saber que base cancelaba— y por eso
el silencio tenia familia, que es el absurdo que el autor senalo. Y un cero es
un NUMERO, y las capas posteriores mueven numeros.
LA MEDICION QUE LO CIERRA. Los deltas de intent suman RUGOSIDAD (`risk` +0.2,
`threat` +0.4), lo que cruza el umbral del AM del motor; y el modulador se
conecta al AudioParam de la envolvente —y conectar a un AudioParam SUMA, no
multiplica—, asi que con la envolvente en cero el tremolo pasaba a ser la senal
entera. Renderizado offline con el grafo real del motor:
toggle "silenciado" neutral -77.0 dBFS (mudo de verdad)
toggle "silenciado" risk -13.9 dBFS <- con gain declarado 0
toggle "silenciado" threat -6.7 dBFS
boton normal, referencia -9.3 dBFS
El "silencio" era mas fuerte que un boton, y lo que sonaba no era el earcon:
era un zumbido sin ataque ni caida, cortado en seco. La medicion que yo mismo
di por buena esta manana (0.0001) solo cubria el caso neutral.
LO QUE ES AHORA. `SILENT` — un valor canonico exportado por `$uix/sema`. Se
declara en el slice del canal y el RESOLVER lo honra retirando ese canal de la
firma. Un solo mecanismo para todos los canales, y nada aguas abajo ve jamas un
string donde espera una firma. Es la forma que el propio motor ya prescribia y
se estaba saltando: «muting DROPS the channel rather than scaling to zero,
because a `0` still buzzes» (engine.ts).
- universal por construccion: ninguna familia, ningun verbo, ningun intent
puede resucitarlo, porque no queda ningun numero que mover;
- liftable solo por REEMPLAZO: declarar sonido es una decision, no un delta;
- `SoundOverride` lo EXCLUYE por tipo — un afinado es un delta, nunca una
ausencia.
Migrado: tooltip (1) y media-player (6). Los tres tunings mueren del catalogo.
GUARDS. `resolver.test.ts` fija las cuatro mitades del contrato (retira el
canal · universal sobre 4 familias x 4 intents · vale igual para haptic ·
liftable por reemplazo), verificado en ROJO. `sounds-grammar.test.ts` cambia su
invariante muerta por las dos que ahora importan: ninguna clave `*.silent`, y
ningun afinado que deje su familia en `gain <= 0` — silencio disfrazado.
Doctrina al dia: sema.md gana la seccion «Silence is a value, not a tuning» con
la medicion; D.5 marca DEROGADA su solucion de mayo y registra el porque;
vocabularies.md deja de enumerar silenciadores y explica donde vive el silencio.
VERIFICADO: sema 211/211 · check 76 = la base 75 + 1 error del fichero huerfano
`__adv-verify-haptic-dead.test.ts` que dejo un agente de la auditoria (sin
trackear, no se borra sin instruccion) · docs:check 0/616 · navegador: el
transporte del player EMITE (`contact-activate`, `commit-toggle-play`) y
levanta CERO osciladores · prettier limpio (el unico aviso mio, resolver.ts,
formateado: +17 lineas, sin ruido).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
d05ecc1aa8 |
fix(sema): los toggles recuperan voz, y el silencio deja de sintetizarse — muere el prefijo form.
Tres cosas que el mismo hilo destapo, de la mas visible a la mas de fondo. 1) LOS TOGGLES SUENAN OTRA VEZ (directiva de autor 2026-08-05) switch, toggle y toggle-group llevaban SILENCIADOS desde el 2026-05-26: su pack aplicaba un tuning que restaba exactamente el gain base de la familia commit (0.3 - 0.3 = 0). Medido en navegador: pico 0.0001, el suelo de la rampa. Un silencio por defecto no se percibe como sobriedad sino como componente roto. Pasan a `commit.medium` (gain 0.1): audible, y un tercio de una pulsacion de boton (contact base 0.25) para que un flip no pese mas que una activacion. Los deltas de intent siguen montando encima (threat +0.1, fulfill +0.05). El haptico ligero no cambia. Medido despues: switch pico 0.1. Conservan el silencio, con su justificacion intacta, solo los dos donde el silencio ES la firma: tooltip (revela al pasar el raton, sonaria en cada cruce) y media-player (su propia salida ES audio — D-AP2.7, decision del hilo de audio). 2) EMITIR SILENCIO YA NO CUESTA `SoundChannel.handle` cortaba DESPUES de resolver la firma, asi que un tuning silenciador levantaba dos osciladores, un filtro y una envolvente programada para no sonar, en cada interaccion. Ahora corta antes de sintetizar cuando la ganancia resuelta es <= 0. El corte mira la ganancia RESUELTA, no el tuning, asi que un intent que la devuelve por encima de 0 (threat, fulfill) suena igual. Fijado con test propio, verificado en ROJO quitando el corto. 3) EL AFINADO SE NOMBRA POR LA FAMILIA QUE MODIFICA El catalogo mezclaba tres ejes en el primer segmento —familia (emerge.*, contact.silent), componente (tooltip.*, tabs.*) y la categoria huerfana form.*— sin regla declarada y sin guard que mirara. `form.commit.soft/subtle` entro el 2026-05-19 ( |
2 months ago |
|
|
2870874cbb |
docs(opts): la auditoria exhaustiva del corpus — el canon deja de mentir
Barrido mecanico sobre los 673 .md del repo (1485 bloques de codigo en 319
docs): cada import contra la tabla de alias real, cada *Provider/*Opts contra
11481 simbolos del codigo, cada tabla de props de los 284 READMEs contra su
types.ts.
Causado por el eje de opts:
- active-architecture §8 (canon) daba OptsFromProps SIN el 4o parametro —el que
salva el `undefined`— y bindProps en forma v1; partOpts no aparecia pese a
ser el mecanismo de 288 wrappers. Tabla reescrita + las dos notas que la
tabla no puede llevar.
- architecture/soma.md tenia el UNICO bloque con bolsa manual que quedaba en
los 673, y sus DOS imports eran rutas fantasma ('../../reactive',
'../../props'). Ejemplo reescrito al canon.
- CONTINUE-direction afirmaba que switch no podia usar OptsFromProps y que
bindProps no acepta Active: falso por ambas mitades desde el eje. Marcado.
- CONTINUE-opts-canonicas se leia como plan VIVO con seis decisiones sin tomar
—una sesion futura habria rehecho 353 ficheros—: banner de cierre con lo que
cada decision acabo siendo.
- El invariante de catalogo sube al estrato arquitectura y gana filas de
aceptacion E-3.7 / E-3.8. drawer documenta que el snap SOBREVIVE al cierre;
knob y cropper precisan que su callback cubre reset y clamp.
Preexistentes que solo aparecen con el metodo exhaustivo:
- carousel documentaba `value`/`onValueChange`; son `index`/`onIndexChange` —y
el mismo README ya decia `index` doce lineas mas abajo.
- navigation-menu seguia con `skipDelayDuration`, renombrado a `groupSkipDelay`
en la pasada N6 (el propio types.ts registra el rename).
- textarea documentaba `submitShortcut` con **el default invertido**: el prop
es `submitOn` y por defecto es `false`, no 'mod+enter' —un consumidor
esperaba que Ctrl+Enter enviara de fabrica—, y `minLength` no existe.
- search-field ensenaba a envolver el callback en un debounce a mano (con el
alias `$lib`, retirado del proyecto) teniendo `debounceMs` propio: era
debounce sobre debounce.
- GESTURES nombraba SplitterTriggerProvider; la clase es
SplitterResizeTriggerProvider.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
095841ca4e |
fix(opts): los divergentes se normalizan — ninguna escritura interna en silencio
knob y cropper escribian sin avisar y divergian de sus propios hermanos: knob callaba el commit-reset que slider SI notifica, y el clamp del efecto de cropper corregia el rect a espaldas del callback. Ambos pasan al setter (el guard epsilon del clamp ya impedia el bucle). En drawer la pregunta correcta no era si notificar el reset del snap sino si debia resetearse: NO — el snap sobrevive al cierre como una posicion de scroll, y quien quiera empezar limpio lo anula. search-field conserva su debounce (romperlo vaciaria debounceMs de sentido) y encapsula la emision en un unico metodo privado. La guia enuncia el invariante de catalogo: toda escritura interna a un bindable notifica por construccion, con UNA excepcion nombrada — la notificacion coalescida (debounce), que nunca se pierde. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
46ce557836 |
docs(opts): la guia enseña el puente v2, partOpts y el reparto de callbacks
Wrapper Pattern con bindProps<XOpts> target-typed y partOpts para la parte trivial; nueva seccion «Callback conventions» (bindable→setter por construccion, evento puro→aparte); la via OptsFromProps+Preserve en Types. La nota del contrato de direccion deja de exigir el cable aparte: dir entra en la bolsa como pass-through con la llamada visible en la init. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
30e2a027fe |
docs(opts): handoff del eje de opts canonicas — el plan de manana
La forma de las opts es la ultima artesania masiva del catalogo: 2286 readableActive + 721 writableActive a mano en 593 wrappers, 524 interfaces con 93 re-declaraciones de id/ref, y dos helpers cuya adopcion es 3/101 CON cast en el 100% de los adoptantes. El handoff lleva el censo re-medido HOY (no el del agente de ayer), los 4 defectos de opts.ts con linea, las dos convenciones en guerra, las SEIS decisiones que abren el eje (con recomendacion y sin firmar), los movimientos M1-M4 con el orden (M1 primero: no depende de ninguna decision), la linea base exacta post-endgame (check=75) y las trampas pagadas del eje hermano. Deja claro lo que NO se toca: el mecanismo de dir ya es del morfo y la clase base Provider se retiro a proposito. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
6c65caf3f8 |
feat(direction): el censo cierra — picker, gradient-picker y chat-log aceptan dir
Los tres ultimos del catalogo sin la prop, pospuestos en D4 y firmados hoy:
- `picker` (el generico transaccional): `dir?: Direction` publico; el wrapper
corre `activeDir(() => dir)` en vez del `() => undefined` que solo seguia al
ancestro. `PickerOpts` gana la opt y el TIPO arrastra a sus dos composers
(gradient-picker y natural-time-picker pasan su afirmacion) — el generico
condicional de P4 haciendo exactamente su trabajo.
- `gradient-picker`: prop + cadena + morfo `direction: {}` + runtime.
- `chat-log`: prop + cadena + morfo, y reenvia la afirmacion a los
`FeedProvider`/`VirtualListProvider` que compone en triple registro — que la
exigian en OPTS desde la normalizacion y nadie les pasaba nada. **Sus 2
errores de linea base MUEREN: check pasa de 77 a 75.** Solo la bolsa del
chat-log se spreadea en el elemento compartido, asi que el estampado es suyo
(morfo direction) y Feed/VirtualList reciben el valor para su matematica.
El censo-guard (direction-census.test.ts) los cuenta sin excepcion nueva: prop
y morfo declaran el mismo hecho en los tres. Demos: gradient-picker y chat-log
tenian el control del arnes sin cablear — ahora pasan `{dir}`.
Verificado en Chrome, pagina en LTR y direccion por el chip de la demo:
gradient-picker root dir="rtl" · panel portalizado rtl · 0 islas
chat-log auto -> atributo AUSENTE (hereda) · prop rtl -> estampa rtl
picker via sus composers (no tiene demo propia)
check 75 = 77 - 2 (los de chat-log, desviacion explicada: la base MEJORA) ·
censo + compile + suites de los ambitos 73/73 · rtl:check 1 (palabras) ·
docs:check 0/566. El handoff marca la cola D4 como CERRADA.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
f516b29e90 |
docs(direction): la cola del censo estaba inflada — quedan tres, no seis
grid-list, tag-group, drawer, virtual-grid y virtual-list YA aceptan dir (la ola 9.16-9.19); el handoff arrastraba el censo rancio de 9.15. Verificado por grep de types.ts. Quedan: gradient-picker, picker y chat-log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
8d1909b166 |
feat(direction): eidos comparte el contexto, y el corpus registra el endgame
La entrada eidos se parte igual que la de soma: `activeEidosDir(dir)` devuelve la AFIRMACION (prop -> ancestro) y publica en el MISMO DirectionContext — un chart eidos-only dentro de un subarbol soma afirmado (o al reves) resuelve el mismo hecho. La cola matematica va a `resolveEidosDir(dir, eidos.prefs)`, con la vista de prefs cacheada por instancia (WeakMap): construirla dentro de una funcion pura llamada por $derived era una alocacion por pasada. createChartRtl consume las dos mitades por su lado: `attr` estampa la afirmacion cruda (ausente hereda del <html> proyectado), `current`/`anchor` resuelven la cola. Corpus: - direction-contract.md §2 pasa del campo obligatorio de 4.1 al mecanismo del morfo (declaracion -> tipo condicional -> estampado; secundarios del mismo morfo; el censo como guard), §7 documenta el contexto compartido y la tabla de entradas partidas, y el checklist §8.3 pide morfo + cable. - docs/decisions.md registra la ratificacion D1-D4 del 2026-08-05 con las cuatro decisiones y su porque. - CONTINUE-direction-runtime.md §11 cierra el handoff: tabla de fases con commits, verificacion final medida, la cola pospuesta por D4 y las trampas nuevas (pathspec SIEMPRE en rama compartida; el detector de huerfanos y la ruta del import; la vista en $derived). check 77 = linea base · morfo+direction suites 131/131 · docs:check 0/566. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
01cc13d412 |
feat(direction): el flip — el atributo es la afirmacion, y undefined ACTUA
La cadena se parte en la afirmacion. `activeDir` devuelve ahora SOLO lo que
alguien eligio (prop -> contexto del ancestro) y ahi se detiene: eso es lo que
el runtime estampa y lo que cruza portales. La preferencia de la app ya no
viaja en el atributo de cada componente — llega a la pagina UNA vez, por la
proyeccion automatica de P2, y todo lo demas HEREDA.
Por que: con prefs dentro de la cadena por-componente, "nadie afirmo" era
inalcanzable — el boot por defecto siempre tiene la dimension direction, asi
que getDir() casi nunca era undefined y cada raiz del canon estampaba
dir="ltr" en una pagina sin afirmaciones. El atributo nunca llegaba a estar
AUSENTE, la herencia del DOM nunca llegaba a trabajar, y de ahi la clase
entera de islas y reenvios que este eje llevo meses parcheando.
La matematica conserva la preferencia como VALOR (no puede leer el DOM) en un
solo helper:
afirmacion -> soma.prefs.getDir() -> 'ltr' resolveDir()
- activeDir(dir) pierde el parametro soma: 57 wrappers, y 49 quedan sin
`const soma = Soma.get()` huerfano (limpiados con deteccion de identificador,
no de ruta — la primera pasada se tropezaba con 'core/soma.svelte').
- 45 colas `opts.dir.current ?? 'ltr'` -> resolveDir(this.opts.dir, this.soma).
- Los 4 getDirectionalKeys de submenus que leian opts.dir.current directo
pasan a resolvedDir — con undefined degradaban a ltr EN SILENCIO bajo rtl
ambiental.
- command: 4.1 le habia dejado `dir: null` en el runtime y el estampado manual
inline en el assert (la linea no estaba sola y el barrido no la vio). Ahora
el runtime estampa y el inline se va.
- listbox / navigation-menu: el alias assertedDir muere — tras el flip,
opts.dir ES la afirmacion.
- date-range-field-input / time-range-field-input: el reenvio dir= al Field
compuesto en sitio sobra — DirectionContext lo lleva.
- 7 wrappers renombran `resolvedDir` -> `assertedDir`: el nombre mentia.
- picker / gradient-picker: activeDir(() => undefined) = la afirmacion del
ancestro o nada — mejor que el prefs-siempre-concreto de antes.
MEDIDO en Chrome, el modelo entero:
auto UN solo [dir] en toda la pagina: el <html> de la proyeccion
(antes lo llevaba casi cada raiz del canon)
prefs rtl select sin atributo, computa rtl por herencia; el panel
portalizado SIN atributo y computa rtl (heredo del html a
traves del body)
prop rtl trigger del dropdown estampa rtl, el panel portalizado
estampa rtl (la afirmacion cruza), submenu data-side=left,
cero islas dentro
matematica slider SIN atributo con pagina rtl: click al 25% fisico da
75 y ArrowRight BAJA — resolveDir lee prefs sin que el
atributo lo haga
SSR curl de select y accordion: cero dir= en el payload
El contrato: §1 documenta la cadena partida (activeDir -> afirmacion;
resolveDir -> matematica), §6 la proyeccion automatica + la semilla y el
parrafo de SSR — el <html dir> del primer render pertenece a app.html o al
servidor (modelo react-aria).
check 77 = linea base · suites soma+active-uix+prefs+contracts 1362 tests:
los 6 de contracts y soma-attr-audit son el conjunto preexistente (A/B dos
veces en fases previas; attr-audit pasa aislado) · media-player 17/17 tras
hacer resolveDir defensivo con mocks sin prefs · rtl:check 1 (palabras) ·
docs:check 0/566.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
9158d07964 |
feat(direction): el eslabon de contexto — la composicion hereda por fisica
La cadena gana el eslabon que el usuario dejo aparcado y ahora ratifica
(2026-08-05): prop -> afirmacion del ancestro (DirectionContext) -> prefs.
Cada activeDir PUBLICA su afirmacion (prop ?? heredada) y consulta la del
ancestro antes de caer a prefs.
Por que: la cadena por-componente metia prefs en la ruta del ATRIBUTO, y el
boot por defecto SIEMPRE tiene la dimension direction (deriva del idioma,
fallback ltr) — asi que "nadie afirmo" era inalcanzable en la practica y cada
hijo del canon estampaba ltr dentro de un subarbol afirmado rtl, cortando la
herencia. La otra sesion lo midio dos veces en el campo: un menu compuesto en
sitio aterrizaba en la preferencia global (LTR chrome dentro de un player RTL),
y el panel portalizado resolvia por su cuenta aunque arreglaras lo primero. Un
solo eslabon cierra los dos agujeros, porque la capa flotante ya lleva el
opts.dir del dueno — que ahora incorpora el contexto.
Con la fisica, las convenciones se borran:
- los 3 reenvios a mano de 4.3 (natural-time-picker-panel, emoji-picker-content
x3, color-field-format-select) — el contexto los hace
- el enlace manual del submenu (dir ?? parentMenu?.opts.dir.current) — idem
- los 3 canarios dir= de media-player que la otra sesion dejo puestos a
proposito ("si al quitarlos el panel vuelve a salir LTR, el mecanismo no
llego al portal")
El canario canta: su repro exacto (media-player, manual parts, direction rtl,
float de subtitulos) da panel rtl SIN el dir= — y el volume float igual.
Medido ademas en Chrome: islas 0 en emoji-picker, natural-time-picker y
color-picker sin ningun reenvio; sliders de canal en espejo exacto (hue 210 a
0.417 del borde fisico); submenu de dropdown con data-side=left en RTL
derivado del contexto.
Regla de colocacion (documentada en direction.ts y el contrato): activeDir lee
y publica contexto de Svelte, asi que corre SIEMPRE en la init del wrapper —
nunca en constructores de provider. Los tests de provider construyen directo y
no se enteran; los 91 mocks de Soma.require() sobreviven porque getOr(undefined)
cae a traves de prefs.
Test nuevo (direction.svelte.test.ts + harnesses reales, 5/5): el hijo hereda
la afirmacion, su prop la pisa, sin afirmacion queda undefined (nunca un
default), sin ancestro cae a prefs, y la publicacion es REACTIVA.
check 77 = linea base · 71/71 en los 9 ambitos tocados · rtl:check 1
(palabras, preexistente) · docs:check 0/566. El contrato §1 pasa a cuatro
eslabones y §1 "Composition carries the assertion" documenta la fisica.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
1d442c59dc |
fix(media-player): `c` ya no descarta el idioma elegido, y el menu deja de robarle teclas al player
Cierra los cuatro que la revision adversarial dejo vivos y que dependian del
provider. La otra sesion commiteo su refactor (
|
2 months ago |
|
|
5983b00d5f |
feat(direction): el estampado pertenece al runtime — y olvidarlo ya no compila
Era la ultima convencion manual del eje: una linea por proveedor, 49 copias de
ella, y 20 de 55 componentes que simplemente la habian olvidado. Una convencion
que hay que repetir 49 veces y que un tercio del catalogo incumple no es una
convencion — es una abstraccion que falta.
VEHICULO A, y el handoff tenia razon desde el principio. Yo habia recomendado B
(un AttrPlan del compilador de morfo) con el argumento de que A deja `dir`
invisible para `contracts.cssSelectors` y `eidos-lint`. Medido: FALSO. El linter
sólo clasifica selectores `[data-*]`, y las recetas leen la direccion con
`:dir()`, una pseudo-clase que no toca ninguno de los dos guards. El coste que
le achacaba a A no existe.
Y B no llegaba donde yo decia. Reparto real de los 49 sitios segun la bolsa que
expanden:
renderProps() 21 un AttrPlan les llegaria
runtimePart.props 27 NO les llegaria
ninguna 1
O sea B aterriza en 21 de 49 y exige migrar 28 componentes de `.props` a
`renderProps()` — que no es cosmetico: `renderProps()` emite TODOS los attrs del
morfo de esa parte. A, en cambio, inyecta en `partPropsForRegistration`, que
incluyen LAS DOS bolsas, y llega a 48 de 49 desde un solo punto. Su precedente
es literal: `id`, el marcador, `data-archetype` y `reg.attachment` ya se
inyectan ahi sin declaracion en morfo.
EL GATE ES DE TIPOS, que es el premio de verdad:
dir: SomaRuntimeDir | null // OBLIGATORIO en SomaRuntimeSources
`null` es la unica salida y dice algo cierto: este runtime no estampa direccion.
Olvidarlo es un error de compilacion, no un defecto que nadie ve hasta que
alguien abre el componente en RTL.
`dir.parts` responde la otra mitad de la pregunta del contrato — QUE elemento
lleva la pintura — por defecto `['provider']`. El censo dice que 44 de 49 son la
raiz; los cinco que no: dropdown-menu ['trigger'] (su raiz no renderiza
elemento), drawer y float-panel ['content'] (pintan en el portal), number-field
y css-field ['provider','input'].
VERIFICADO en Chrome real, pagina en LTR y direccion movida por el control de la
demo:
tabs auto -> ltr prop rtl -> rtl vuelta a auto -> ltr
dropdown-menu el TRIGGER pasa a rtl y el panel portalizado tambien
number-field raiz e input, los dos a rtl
drawer el content portalizado a rtl por la cadena de prefs
accordion sigue la cadena y vuelve, sin una sola linea en su proveedor
check: 77 con mis cambios vs 79 sin ellos (A/B con stash). El -2 esta explicado:
uno era un error preexistente de `date-range-picker-provider.svelte.test.ts` que
indexaba el tipo `{ readonly dir }` de unos props que ahora son mas anchos; el
otro es que `media-player` ya no puede compilar sin el campo (su unica linea
tocada, +1/-1, es mecanica).
Tests: 1271/1278 en `src/uix/soma` + contracts. Los 7 fallos son el conjunto
preexistente, A/B contra arbol limpio: los 6 de `contracts.test.ts` salen
identicos sin mis cambios y `soma-attr-audit` pasa aislado (flaky bajo carga).
⚠️ Un fallo mio que cazaron los tests y conviene no repetir: los proveedores con
la forma de una linea `soma.runtime(morfo, {})` recibieron `dir: null` en vez del
getter, y 12 componentes con direccion se quedaron sin estampar. Lo vieron cinco
tests de "exposes root props". Un barrido con dos formas sintacticas necesita
las dos en el mapa, no una.
Estampados a mano restantes en el catalogo: 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
482cea3bee |
docs(media-player): las cuatro variantes de audio estan aprobadas, no dos
row e inline las dio por buenas el usuario y yo las seguia listando como pendientes de su mirada. El eje visual del modo audio queda CERRADO. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
35ef18ed02 |
docs(media-player): los cuatro arreglos que bloquea el refactor en vuelo
La revision adversarial dejo cuatro hallazgos ciertos que viven todos en media-player-provider.svelte.ts, que la otra sesion tiene modificado sin commitear. Tocarlo significaria llevarme su trabajo en mi commit, asi que quedan escritos con el arreglo concreto de cada uno. Incluye una observacion sobre SU arbol que no he tocado: con el refactor en vuelo el player deja de estampar el atributo dir crudo aunque la prop lo afirme. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
b6caaaa178 |
docs(media-player): el handoff recoge el selector de pista y lo que NO pude verificar
Anade las entregas 5 y 6 (CaptionFloat y la especificacion de la calidad) y, mas importante, una seccion nueva de limites. El panel de navegador embebido dejo de componer frames a media tarde (`document.visibilityState === 'hidden'`) y con el dejaron de llegar las teclas. Llegue a creer que habia un defecto —«Escape no cierra el float dentro del player»— y era FALSO: instrumente un listener y una pulsacion real registro cero eventos. Lo roto era mi via de entrada, no el componente. Queda escrito porque es la misma familia de trampa que las transiciones CSS congeladas, y porque la proxima sesion no debe salir a cazar ese fantasma. Lo que si quedo verificado con eventos despachados: keydown de Enter/Espacio abre el menu, y el click que el navegador sintetiza detras no toca los subtitulos — el preventDefault() de la costura aguanta. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
e0206f6b21 |
docs(media-player): la calidad de reproduccion, especificada sin implementar
Decision del usuario: escribir el contrato antes de tocar codigo. El documento existe porque `settings-button` lleva declarada desde el principio prometiendo «velocidad / calidad / pista», y al ir a implementarla resulta que dos de los tres ejes ya no le corresponden y el tercero no existe. Auditado sobre el codigo: grep de `quality|bitrate|resolution|level| representation` sobre el puerto da CERO. Ni `MediaSnapshot` (0 de 14 campos), ni `MediaProvider` (0 de 9 miembros), ni los espejos de `$arts/sound`. Y no es un descuido: la calidad NO es intrinseca al `<video>`. Un fichero no tiene niveles; los niveles aparecen con un manifiesto HLS/DASH y un motor que elige representacion. Este framework no vendoriza motores, asi que la lista solo puede venir del consumidor. De ahi que el contrato, si se abre, sera una lista VACIA para el 90 % de los usos — lo que decide la forma: el control se AUSENTA, no sale vacio. Misma doctrina que el waveform sin picos. Queda escrito el contrato minimo (3 campos de snapshot con `qualityId` separado de `qualityAuto`, `setQuality`, los dos espejos, el evento de morfo que rompe el censo de 11, los textos) y la trampa que ya nos mordio con la velocidad: el commit cabalga el RESULTADO, no el clic — pedir 1080p no es haberlo conseguido. Registrado en el indice `docs/decisions.md`. docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
c73669cde7 |
feat(direction): la composicion lleva la afirmacion — tambien en sitio
4.2 cerro el caso del portal. Faltaba el gemelo: un componente que MONTA otro componente del canon arranca dentro de si una segunda cadena. Sin la afirmacion del dueño esa cadena salta la prop, aterriza en prefs y estampa un `dir` propio — y un `dir` estampado CORTA la herencia de todo lo que cuelga. El padre afirma rtl, el hijo estampa ltr, y el subarbol se parte. Medido con las demos ya cableadas, escaneando cada panel en RTL en busca de elementos que estampen una direccion distinta a la del envoltorio flotante: natural-time-picker 1 isla [data-slider] emoji-picker 3 islas [data-command] + [data-toggle-group] x2 color-field 1 isla [data-color-field-format-select] (un Select) Preexistente: mientras el panel entero era LTR la isla no se veia. Lo que hizo 4.2 fue volverla visible. El canon pasa a enunciar UNA regla con DOS vehiculos, elegidos por geometria y no por gusto (direction-contract.md §1, "Composition carries the assertion"): a traves de un portal -> la capa flotante, una vez por overlay composicion en sitio -> la prop `dir`, en el punto de llamada Lo que se entrega es siempre la afirmacion CRUDA (`opts.dir.current`), nunca `resolvedDir`: asi "nadie afirmo" sigue siendo distinguible y la cola de prefs del hijo sigue corriendo. Verificado en Chrome real, pagina en LTR y direccion movida por el control de la demo: los tres paneles pasan a 0 islas y el area del color-picker computa por fin `rtl`. Los dos sliders de canal siguen en espejo exacto — hue 210 cae a 0.583 del borde izquierdo en LTR y a 0.417 en RTL, alpha al maximo pasa de 1.0 a 0.0 — o sea §9.12 no se ha movido. check 78 = linea base · 59/59 en los 8 ambitos · rtl:check 1 (palabras) · docs:check 0/565. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
d3c8fcfd37 |
docs(direction): los charts ya no estan en la cola — la linea llevaba rancia
§10.2 seguia diciendo que la familia chart "no tiene prop ni provider y resuelve
leyendo getComputedStyle(node).direction". Eso lo cerro 43a788083: los cinco que
necesitan direccion declaran dir?: Direction y resuelven por
createChartRtl(eidos, () => dir) -> activeEidosDir. Verificado por grep que no
queda un solo getComputedStyle(...).direction en la familia.
La linea me indujo a repetirlo. El registro historico de §9.20 se queda como
estaba — describe lo que era cierto entonces —; lo corregido es lo que apunta
hacia adelante.
Re-hace el cambio que
|
2 months ago |
|
|
6f0f438f30 |
docs(media-player): el handoff recoge las cuatro entregas de la tarde
Waveform visible, velocidad bajable, API de eventos y transporte de lista. Con la leccion reutilizable: componer un primitivo desde soma deja su receta fuera, la carga el componente que lo compone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
64df3d7b37 |
feat(direction): la propagacion pertenece a la capa flotante
Un panel portalizado se renderiza FUERA del subarbol de su dueño, asi que la
herencia no lo alcanza: la afirmacion del dueño solo llega si alguien la lleva.
Eso estaba delegado en cada consumidor — siete envoltorios Content componian el
enlace al padre a mano — y quien COMPONE un popover no tenia por donde hacerlo.
Medido con <DatePicker dir="rtl"> sobre una pagina LTR: el wrapper flotante
estampaba dir="ltr", el picker-shell y su pie se disponian en LTR, y solo el
calendario espejaba. Panel partido por la mitad, el fallo del §2 del contrato.
La capa lo compone ahora una sola vez:
el dir propio de la superficie -> el dir afirmado por el proveedor DUEÑO -> omitir
- FloatingProviderOpts.dir el dueño baja su afirmacion (ya pasada por
activeDir, o sea prop -> prefs)
- FloatingContent.assertedDir donde se encuentran; lo leen los DOS estampados
del wrapper (rama nativa y rama JS)
- createFloatingShellRoot lo reenvia; es la puerta de 8 de los 10
FloatingProvider. Los 2 submenus toman el del
menu padre.
FloatingContentOpts.dir pasa a ser la prop CRUDA del Content. Resolverla ahi la
haria casi siempre concreta (prefs) y el dueño no ganaria nunca el fallback.
Se borran los 7 enlaces compuestos a mano con su soma / activeDir / parentRoot.
Sobrevive uno a proposito: dropdown-menu-sub-content lo necesita para
effectiveSide, que es matematica y quiere valor resuelto.
popover, tooltip y link-preview aceptan dir en su raiz. No estampan nada — no
renderizan elemento —; el valor existe para cruzar el portal, y es lo que
permite que los 9 componentes que componen un popover empujen su direccion al
panel.
Borrados 9 resolvedDir muertos en los proveedores Content: nadie los leia (toda
la matematica direccional consume el de la RAIZ) y el cambio de semantica de
opts.dir dejaba su docblock mintiendo.
Demos: las 7 que aceptan dir lo pasan ahora a la instancia — time-picker,
time-range-picker y color-picker no tenian control ninguno; emoji-picker y
natural-time-picker lo tenian solo en el arnes. Sin eso el camino de la PROP,
el unico que ejercita el portal, no era observable.
Verificado en Chrome real, direccion movida por el control de la demo:
date-picker Clear/Cancel/Close 15/102/209 -> 200/92/15 espejo exacto
date-range-picker 448/217/17
sin regresion dropdown-menu (raiz y submenu, data-side=left en RTL),
context-menu, menubar, select, sidebar (flyout), y tooltip
en auto sigue estampando ltr — la cola de prefs sobrevive
check 78 = linea base (A/B con stash) · 158/158 en los 20 ambitos tocados ·
rtl:check 1 (palabras, preexistente) · docs:check 0/565.
Queda documentado en el handoff un defecto PREEXISTENTE de otra familia que
esto vuelve visible: un componente eidos que monta otro componente del canon en
SITIO no le reenvia el dir, y el hijo estampa ltr cortando la herencia del
panel (natural-time-picker/Slider, emoji-picker/Command+ToggleGroup,
color-field/Select). No hay precedente de ese reenvio en eidos: es doctrina
nueva y movimiento propio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
7b0556a955 |
docs(direction): handoff del refactor — la direccion deja de ser una convencion
El usuario preguntó si lo entregado era lo mas logico y profesional. No lo es:
el contrato es correcto y el MECANISMO para cumplirlo es teclear la misma linea
en cada componente. 54 estampados a mano, 7 enlaces al padre a mano, 9 Content
que re-arrancan la cadena por su cuenta, y 20 de 55 componentes que lo
incumplian hasta `21055bd3c`. Una convencion que hay que repetir 54 veces y que
20 de 55 incumplian no es una convencion — es una abstraccion que falta.
`docs/process/CONTINUE-direction-runtime.md`, para abrir en sesion nueva. Lleva
LAS DOS COMPROBACIONES DE VIABILIDAD YA HECHAS, que es lo que separa un handoff
util de una idea:
- El runtime YA omite un atributo cuyo valor resuelve a `undefined`
(`runtime.svelte.ts:519-522`). Es exactamente la semantica del estampado
crudo. No hay que construir nada para eso.
- El runtime YA inyecta atributos sin declaracion en morfo — `id`, el marcador,
`data-archetype`, `reg.attachment` (`:538-551`), con el comentario que lo
justifica. Asi que el movimiento NO exige tocar 55 ficheros de morfo.
- La capa flotante tiene acceso al dueño: los sub-proveedores llevan
`this.provider`. Y el reparto de los 11 consumidores es el diagnostico —
8 pasan el `dir` DEL PROPIO Content (la fuga), 3 el de la raiz (lo correcto).
Dos movimientos, con el orden razonado (la propagacion primero: mas acotada,
verificable de un vistazo, y BORRA codigo, asi que el segundo llega a un arbol
mas limpio). Dos vehiculos posibles para el primero, sin decidir a proposito.
Lleva ademas la linea base exacta de verificacion y las trampas ya pagadas: el
crudo nunca resuelto, `dir` en un `<svg>` no hace nada, `OptsFromProps` quita el
`undefined`, clasificar por grep se equivoca (me paso con `avatar`), y una
migracion mecanica hereda los defectos que traduce.
⚠️ Anota lo que el refactor NO debe romper: los 4 que correctamente NO estampan
(popover, tooltip, link-preview, context-menu) y la excepcion firmada por el
usuario de `avatar`/`image`, que no aceptan `dir` a proposito.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
21055bd3cc |
fix(direction): la regla de estampado era demasiado estrecha — 16 componentes la incumplian
Decision del usuario (opcion B). La regla de §2 decia «estampa si el recipe usa `:dir()`», y esa premisa es FALSA: `:dir()` no es el unico lector. Tambien leen la direccion computada del elemento las propiedades logicas (`margin-inline-*`, `text-align: start`) y —lo que lo hace casi universal— `flex-direction: row`, que se INVIERTE. En la practica, cualquier componente con maquetacion horizontal depende de la direccion. La regla honesta, ya escrita: quien acepta `dir` lo estampa. La pregunta no es SI, es EN QUE ELEMENTO — el que lleva la pintura. Y para eso hay cuatro casos, ahora documentados: raiz normal · superficie PORTALIZADA (el wrapper flotante, que la capa ya estampa porque fuera del subarbol no puede heredar) · componente con LAS DOS mitades, que necesita las dos · y geometria aplicada desde JS, que no necesita atributo. Censo: de 55 que aceptan `dir`, 20 no estampaban. Clasificados uno a uno leyendo recipe + wrapper + donde renderiza cada parte: - 16 ESTAMPAN ahora, cada uno en la parte que lleva la pintura. Los sub-partes (drawer→Content, float-panel→Content, dropdown-menu→Trigger porque su raiz no renderiza elemento) llevan comentario nombrando la pintura que cubren. - 4 NO estampan y es CORRECTO: popover, tooltip, link-preview y context-menu. Su unica pintura direccional es el panel portalizado, que ya estampa la capa flotante; y el root de context-menu no renderiza elemento ninguno. DOS DEFECTOS QUE EL BARRIDO DESTAPO, uno de ellos MIO: 1. `sidebar` pasaba el valor RESUELTO al motor flotante. Esa linea la escribi yo en §9.19: arregle que se saltara la prop y deje el otro defecto, que siempre es concreto. La capa estampa lo que recibe, asi que un flyout SIN afirmacion recibia `dir="ltr"` — forzando su subarbol a LTR dentro de una pagina RTL, que es exactamente lo que el crudo existe para impedir. Era el UNICO de once consumidores flotantes que lo hacia. De paso desaparece su `resolvedDir`: sidebar no hace matematica direccional, toda su pintura es CSS. 2. La afirmacion de la raiz NO LLEGABA al panel portalizado. `select`, `combobox` y `context-menu` resolvian su Content con `activeDir(() => dir, soma)`, sin paso por el padre — asi que `<Select dir="rtl">` movia el trigger y dejaba la lista atras. Antes del barrido no se notaba porque NINGUNA de las dos mitades estaba afirmada y coincidian por heredar las dos del `<html>`; estampar el trigger lo habria hecho VISIBLE. Compuesto el enlace en el punto de llamada, que es la forma que §1 imprime y que `dropdown-menu` y `menubar` ya usaban. El resolutor sigue sin paso por el padre. Y `color-picker`, que es el mismo caso con una regla `:dir()` REAL varada: el area mirroring vive en el panel portalizado (`color-picker.css:298,335`) mientras la matematica del thumb y el arrastre salen de la raiz. Sin el enlace, el thumb se voltea y el gradiente de debajo no — justo lo que avisa el comentario de la receta. Su wrapper ya reestampaba `data-color` por esta misma razon; le faltaba `dir`. MEDIDO en Chrome (select): la raiz pasa de NO tener atributo a estamparlo, y trigger y panel coinciden en las tres posiciones del toggle. Estructuralmente ya no pueden divergir: ambos leen `opts.dir` de la misma raiz. `check` sin errores en ninguno de los 20 (el total 80 son 77 de base mas 3 de `media-player`, que edita otra sesion). 90 tests de los componentes tocados en verde. `rtl:check` 1, el de `palabras`. `docs:check` 0/0. QUEDA, reportado y sin tocar: ningun picker de eidos reenvia `dir` a su `PopoverContent`, asi que el cromo del `picker-shell` dentro del portal (`picker-shell.css:78`, el split del pie) queda sin cubrir en los siete. Los controles interiores (Calendar, los Slider) se salvan porque reciben el crudo por su cuenta. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
64043ae2bd |
fix(media-player): una flecha hacia el slider disparaba DOS acciones
Con el pulgar del volumen enfocado, ArrowRight subia el volumen Y ademas saltaba el playhead un seekStep. Con el pulgar del scrubber, ArrowUp scrubbeaba Y cambiaba el volumen. El Slider hace preventDefault de las cuatro flechas pero no detiene la propagacion, y la guarda de hotkeys del player solo eximia INPUT/TEXTAREA/contentEditable — asi que las dos manos actuaban sobre la misma pulsacion. Arreglado en la guarda del player, no en el Slider: la exencion es por PROPIEDAD, no por etiqueta. Un control que se declara slider maneja sus propias teclas, y el player deja de reclamarlas cuando la pulsacion nace dentro de un [data-slider]. Tocar el Slider habria afectado a todos sus consumidores. CORRIJO UNA AFIRMACION MIA DE ESTA MISMA JORNADA. Al verificar el teclado direccional escribi que raiz y pulgar estaban de acuerdo, un solo seekStep sin doble salto. Era falso: ambos escriben currentTime, asi que el seekBy del player PISABA el paso mas pequeno del Slider y el doble manejo quedaba enmascarado. Se destapo cruzando ejes — ArrowUp sobre el scrubber movia tiempo Y volumen, que son magnitudes distintas. La leccion queda escrita en el handoff: dos manejadores que escriben la MISMA magnitud son indistinguibles. Medido en Chrome antes y despues. Antes: volumen+ArrowRight daba dVol=+0.01 y dTiempo=+8. Ahora: dVol=+0.01, dTiempo=0; scrubber+ArrowUp dTiempo=+0.1, dVol=0; y la raiz conserva sus hotkeys (ArrowRight busca, ArrowUp sube volumen). Guard nacido en rojo por mutacion. player 14/14 · docs:check 0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
bd7853b59e |
docs(sound-engine): mutear antes de reproducir, y parar al terminar
Medir el escenario mixto y los earcons exige reproducir de verdad, pero el Chrome que suena es el del usuario. Dejar una pista de 6 minutos a volumen 1 mientras se hacen otras cosas es una molestia real, y paso hoy. Casi todo lo que se mide —contextos, stamps, envolventes, geometria— no depende de que se oiga, asi que muted=true basta; el volumen solo hace falta para la escucha, que es del usuario. Y pause() al acabar cada medicion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
e9454bebe7 |
docs(direction): la excepcion que el usuario veto — :dir() que traduce una prop logica
«NO añadas la direccion al avatar porque tambien depende de donde se situa el badge». Tiene razon, y mi regla de §2 —«si el recipe usa :dir(), acepta y estampa `dir`»— es demasiado ancha: mandaba al siguiente a hacer justo eso. NO toda regla `:dir()` afirma una direccion. `Avatar.Badge` expone la esquina con `position` en terminos LOGICOS (`top-start` / `top-end`), asi que el ancla (`inset-inline-*`) ya se voltea sola; `:dir(rtl)` solo invierte el SIGNO del `translate` fisico que hace sobresalir la insignia —por eso las cuatro reglas llevan su marcador `rtl-physical:`, es el caso sancionado de RTL-1, no un selector de lado—. `Image` es lo mismo con otro mecanismo: `object-position` no tiene palabra clave logica, asi que `:dir(rtl)` traduce a `left`/`right` el `start`/`end` que el consumidor ya eligio. En los dos, una prop `dir` seria un SEGUNDO mando para lo que el primero ya decide, y pelearia en vez de componer: el ancla logica resuelve por la direccion ambiente mientras la prop dice fijarla. Esa regla `:dir()` necesita la direccion que REALMENTE maqueto el elemento, que es la heredada, por diseño y no por descuido. El test que queda escrito: si el componente tiene vocabulario logico propio para el eje, no lleva `dir`. Un `Switch` no lo tiene —su pulgar viaja en un sentido y la direccion es el unico input—, asi que ahi si es significativo. Un `Avatar.Badge` tiene `position`, asi que no. Barrido: de los 13 recipes con `:dir()`, SOLO `avatar` e `image` tienen ademas prop logica start/end. La excepcion son exactamente esos dos. Corrige tambien lo que reporte como hallazgo: «avatar e image usan `:dir()` sin aceptar `dir`» NO es un defecto ni una decision pendiente, es el diseño correcto. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
43a7880832 |
feat(direction): las charts aceptan `dir` y corren la cadena canonica
Decision del usuario. Yo habia recomendado lo contrario —dejarlas y ablandar el
contrato— y me equivocaba en el diagnostico: no era que la cadena no valiese
para eidos, era que **le faltaba el punto de entrada**.
`activeDir(getter, soma)` pide un `Soma`, y una chart solo tiene un
`ActiveEidos`. Y `ActiveEidos.prefs` es un `ActivePrefs` crudo, que no tiene
`getDir()` —ese metodo vive en la vista que mira a soma—. Por eso la cadena
"no era ejecutable" y por eso alguien puso ahi un `getComputedStyle`. La
adaptacion entre las dos superficies es TODA la diferencia:
src/uix/eidos/direction.ts → activeEidosDir(getter, prefs)
Mismos dos eslabones, mismo `Direction | undefined`, misma cola `'ltr'` en el
consumidor. Abre la puerta tambien a `avatar` e `image`, que son el mismo caso.
Fuera la lectura del DOM y fuera la copia byte-a-byte que `chart.svelte` tenia
del mismo mecanismo —dos fuentes de verdad para un hecho, y ademas el observer
vigilaba `<html dir>` mientras la lectura era del elemento, asi que un ancestro
que volteara en caliente no se veia nunca—.
⚠️ EL HALLAZGO QUE SOLO SALE MIDIENDO: **estampar `dir` en un `<svg>` no hace
nada.** El atributo lo mapea a `direction` la hoja UA de HTML, que no alcanza a
los elementos SVG: medido en Chrome, un `<svg dir="ltr">` bajo un ancestro
`dir="rtl"` sigue computando `rtl`. Yo lo habia puesto ahi y el comentario
afirmaba que servia. Va en el envoltorio HTML (`<figure data-chart-figure>`,
`<div data-chart-frame>`). Importa porque `text-anchor` es LOGICO: con el
estampado en el nodo equivocado la matematica espeja y las etiquetas no —el
fallo de §2 con otro disfraz—.
MEDIDO en Chrome, heatmap: LTR `ene/feb/mar` en x=26/90/154, celdas 26‥858;
RTL en 851/787/723, celdas 6‥838. Funnel: poligonos de 0‥226 a 193‥420,
etiquetas de 249 a 171. El toggle del topbar sigue volteandolas, ahora por
prefs y no por el DOM.
COSTE ACEPTADO, verificado: envolver una chart en `<div dir="rtl">` sin pasar la
prop ya no la voltea. Falla de forma CONSISTENTE —el estampado del envoltorio
gana sobre el ancestro, asi que matematica y pintura siguen de acuerdo— y no
con las etiquetas cruzando el grafico.
§7 del contrato deja de ser una divergencia declarada y pasa a ser la tabla de
los dos puntos de entrada. Su justificacion era ademas falsa en dos puntos: no
era "el unico sitio del catalogo" (quedan 4 mas, uno vivo en
`field-langs-switcher`), y decia que una chart no se podia voltear por subarbol
cuando `direction` se HEREDA y si se podia.
`check` 77 = linea base. `docs:check` 0/0. Tests de eidos + libs/plots: 424/425,
el fallo es `audio-player` sin morfo, preexistente y ajeno.
⚠️ No pude ver los pixeles: el panel del navegador no compone frames.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
6de53519d1 |
fix(audio-player): el track de la barra era un punto — el suelo estaba anulado
El usuario lo vio: en la variante `bar` el scrubber quedaba reducido a un pulgar entre 0:00 y 6:12, mientras el volumen conservaba sus 64px al lado. La causa era de una linea. media-player.css le da a todo scrubber un suelo (min-width: --space-8) y la regla de `bar` lo anulaba EXPLICITAMENTE con min-inline-size: 0 y mayor especificidad, asi que flex podia encogerlo hasta la nada. Un control que desaparece en silencio es peor que una fila que admite que no tiene sitio. Dos decisiones de diseno del usuario, aplicadas: - El scrubber nunca se dibuja mas estrecho que el volumen. El ancho del volumen sale a token (--_mp-volume-width) y el scrubber de `bar` se apoya en el como suelo, para que no puedan divergir cuando alguien retoque uno. - El artista se oculta en `bar`; `card` y `row` lo conservan, que tienen sitio. Medido tras el arreglo (variante verificada en la misma pasada): scrubber 64 a 560/720px contra volumen 64, y 229/529 a 900/1200px. Sin desborde en ninguno. Visto en Chrome: los dos sliders se leen del mismo tamano y el titulo gana el espacio del artista. Queda vivo y declarado: el mute-button se estruja a 20-25px bajo 800px. No se toca porque ponerle flex:none empuja el problema a otro control, y esa es una decision de diseno. check 0 errores propios · player 14/14 · eidos-lint invalid 0 · docs:check 0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
92c1ee91a9 |
docs(sound-engine): bar solo colapsa bajo 800px, y la mezcla la elige el disenador
El barrido de anchos reencuadra el defecto: bar esta sano de 900px en adelante (scrubber 147/347/1047) y solo colapsa a 720px, que es el ancho al que la demo encierra al player, no un ancho propio de una barra de app persistente. Corrige ademas la pregunta que yo habia planteado. El usuario zanjo que la mezcla es composicion y la elige el disenador, lo que encaja con la doctrina que audio-player.css ya declara (un variant decide que se muestra y como fluye, nunca comportamiento) y con la salida que ya existe: componer MediaPlayer media=audio a mano mantiene el flujo sin opinion, porque el CSS de variantes cuelga de data-variant y eso solo lo estampa AudioPlayer. Lo que no se disuelve en composicion es que el preset colapse EN SILENCIO. El arreglo pendiente no arbitra entre scrubber y volumen: min-inline-size en el scrubber para que nunca llegue a 0, y el ancho minimo documentado. Y registra dos veredictos del usuario: waveform F6 APROBADA (cierra el pendiente del 2026-08-01) y variante card APROBADA. Quedan row e inline. docs:check 0 sobre 564 documentos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
5ff5202038 |
docs(sound-engine): el stamp de sema y la matriz del modo audio, medidos
Cierra los dos pendientes del hilo que no exigían los ojos del usuario, y sanea el documento, que afirmaba cosas ya cumplidas. STAMP DE SEMA — cerrado por las dos mitades. Dónde aterriza: con un MutationObserver sobre el subárbol del player, un clic en play estampa contact-activate y commit-toggle-play (commit · neutral) sobre el PROPIO play-button, no sobre el provider, y los limpia al cerrarse el hold. Que la regla del pack casa: A/B del pico de la envolvente dentro del mismo componente — transporte (play, mute) 0.0001, o sea silenciado; commit-complete 0.05, audible. Los osciladores sí se construyen, la ganancia nunca sube: es la resta de gain de D-AP2.7 funcionando. Queda anotada la trampa en la que caí, comparar ese pico con el 0.25 de un Button de otra página y creer ver una contradicción: el A/B tiene que ser dentro del mismo componente. F6, MATRIZ DEL MODO AUDIO — las 16 combinaciones (4 variantes × claro/oscuro × LTR/RTL) recorridas buscando partes colapsadas, botones estrujados y desbordes. Un solo defecto: en `bar` el time-slider colapsa a ancho 0 y el mute a 20px, en las CUATRO combinaciones, así que no es del eje ni del tema. Visto: el scrubber queda reducido a un punto ámbar entre 0:00 y 6:12 — una barra de app sin barra de progreso — mientras el volumen conserva su ancho completo. `card` y `row` limpias; lo que oculta `inline` es lo que su receta declara. HIGIENE — el documento decía «sin commitear» del rediseño y del waveform, y pedía un re-plan del player que ya está hecho. Verificado contra git: rediseño |
2 months ago |
|
|
1cb61567e5 |
feat(direction): RTL-2 — el guard del doble volteo, la forma que se me colo dos veces
Cierra §10.1, la ultima deuda del eje. `dee2c9e3a` arreglo los dos defectos pero
dejo la FORMA sin guard, y es la que sobrevivio a la migracion §6.3 y a que yo
diera el eje por cerrado en §9.19.
LA FIRMA — dentro de un bloque cuyo prelude tiene `:dir()`, la misma familia
logica (`border`/`padding`/`margin`/`inset`-`inline`) declarada en LAS DOS caras,
EXACTAMENTE UNA con valor neutro (`0`, `auto`, `none`, `initial`, `unset`,
`revert`). Ese desequilibrio ES el espejo cancelado: la propiedad ya se habia
volteado cuando la regla matchea, asi que reubicarla la devuelve al punto de
partida y la deja en el borde opuesto al de sus hermanas.
Es mas estrecha que «bloque `:dir()` con puras logicas» a proposito. Una regla
que CAMBIA un valor bajo RTL sin reubicarlo es legitima —asimetrico por diseño
existe—, y las dos caras con valor son un autor describiendo dos bordes reales.
Lo que delata el defecto es el PAR, una cara apagada. Hay un test negativo por
cada uno de esos casos, que son los que evitan que la regla se vuelva ruido.
LAS TRES DECISIONES que el handoff dejaba abiertas:
- `:dir(ltr)` tambien entra. Igual de sospechosa, y no anade ruido.
- Marcador PROPIO, `rtl-mirror: <reason>`. Reutilizar `rtl-physical:` mentiria:
aqui no hay nada fisico, y un marcador que miente es peor que ninguno. Mismo
mecanismo (`exemptLines` toma ahora el patron por parametro), dos vocabularios.
Un test comprueba que el marcador de RTL-1 NO exime a RTL-2.
- Regla aparte, `lintRtlMirror()` con su propio `RtlMirrorFinding`. Los 14 tests
de RTL-1 quedan intactos y el tipo lleva los campos que importan
(`family`/`neutral`/`payload`) en vez de forzar los de RTL-1.
VERIFICACION, en este orden:
- `rtl-lint.test.ts` 27/27 (14 de RTL-1 intactos + 13 nuevos).
- RECALL con el runner COMPLETO contra el arbol pre-arreglo: restaure los dos
ficheros de `dee2c9e3a^` sobre el arbol, corri `rtl:check` y los devolvi con
`git checkout` en el mismo bloque. **2/2 cazados** (`feed.css:147`,
`tree-view.css:211`). Probar la funcion no basta: el runner es lo que corre.
- `rtl:check` sobre HEAD: 1 error, el de `palabras`, preexistente y excluido.
- `docs:check` 0/0 sobre 564 docs. `check` 77 = linea base.
Un defecto de presentacion salio al hacerlo: el `calc()` multilinea de tree-view
partia el mensaje por el primer salto. Los campos que RTL-2 emite colapsan el
whitespace; con test.
Actualizados los textos que el handoff avisaba que quedarian obsoletos: el
`enforcement:` y §4 del contrato, la fila RTL del build contract, la fila X-1.6
del checklist, y los docblocks de `rtl-lint.ts` y `rtl-check.ts` — que son
doctrina, no adorno.
⚠️ Sigue siendo cierto lo que NINGUNA de las dos reglas ve: leen texto CSS. Un
`transform` inline escrito por JS, un preset de motion compartido y la geometria
SVG siguen necesitando el ojo en RTL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
7aee33acda |
docs(direction): §10, la cola para manana — RTL-2 ya viene especificada y medida
La unica deuda del eje es el guard del doble volteo, y es la forma que se me
escapo DOS veces (en la migracion §6.3 y en el cierre §9.19). El handoff no la
deja como idea: la deja lista para implementar.
LA FIRMA, mas estrecha que «bloque `:dir()` con puras logicas» —eso daria falsos
positivos sobre reglas legitimas que cambian un valor bajo RTL—: dentro de un
bloque con `:dir()`, la MISMA familia logica aparece como `-start` y como `-end`,
una con valor NEUTRO (`0`/`auto`/`none`) y la otra cargando el valor. Ese es el
gesto «apago un lado y lo repinto en el otro», que cancela un espejo en vez de
crearlo.
MEDIDA ANTES DE ESCRIBIRLA, que es lo que la hace accionable:
- contra `dee2c9e3a^` (antes del arreglo): 2/2 cazados
- contra HEAD: 0 hallazgos sobre 18 bloques `:dir()`
Nace verde y con recall demostrado sobre defectos REALES. Si al implementarla
sale distinto de 0, es un hallazgo nuevo, no un fallo de la regla.
Con las cuatro piezas a tocar (`rtl-lint.ts` y su docblock —que es doctrina—,
`scripts/rtl-check.ts`, y los casos de test, positivos Y negativos), tres
decisiones que NO dejo tomadas (`:dir(ltr)` tambien; escape hatch propio o
reutilizar `rtl-physical:`; regla aparte o ensanchar RTL-1), y el aviso de que
al aterrizar quedan DOS textos obsoletos el mismo dia — los escribi diciendo que
el guard no ve esta forma.
⚠️ NO copiar mi prototipo: usa una regex ingenua sin anidamiento ni comentarios.
`rtl-lint.ts` ya tiene `parseBlocks()`, que da exactamente lo que hace falta.
§10.2 lleva el resto por valor (los charts deberian aceptar `dir` — la
divergencia declarada en §7 del contrato; la tabla de props ausente de
number-field; la numeracion duplicada preexistente del checklist) y §10.3 el
estado del arbol.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
961c1bf8be |
docs(direction): los 55 componentes que aceptan `dir` ya lo documentan
De los que aceptan la prop, la mayoria no la mencionaba en su README, y los que
si tenian seis redacciones distintas para el mismo hecho: «from Soma», «prefs»,
«soma config», «soma», «Override Soma defaults» y —la peor— «Inherited from Soma
when omitted», que se lee como herencia del PADRE, que es justo lo que la cadena
no hace.
Cada uno dice ahora QUE cambia la direccion EN ESE componente, leido de su propio
codigo (el JSDoc de la prop, los usos de `resolvedDir` en el provider, las reglas
`:dir(` de su receta), no una frase de plantilla. Y cada uno enlaza el contrato
una vez, en la primera mencion.
DOS AFIRMACIONES FALSAS, cazadas por el verificador adversarial:
- `feed` decia que la receta «mueve el rail del hilo al otro lado». No lo movia:
lo CANCELABA. Ese era el defecto arreglado en `dee2c9e3a`; el README describe
ahora lo que el codigo hace.
- `waveform` daba `prefs` como default. Falso: waveform es el caso DELEGANTE,
no llama a `activeDir`, y sin prop no hay atributo ni lectura de prefs —
hereda. Su Slider compuesto es quien corre la cadena.
Y dos que estaban mal COLOCADAS: `calendar` listaba `dir` bajo `## ARIA` (no es
un atributo ARIA) y `accordion` bajo `## HTML Attributes` describiendolo como
«la prop cruda», cuando lo estampado es prop O prefs.
CORRECCION AL PRIMER BARRIDO, por la ley de autoria: escribio la cadena literal
`prop → prefs → 'ltr'` en 54 ficheros. Eso es la forma restate-instead-of-link
que `docs/authoring.md` §1 prohibe citando este mismo fallo («'7 families'
sobrevivio en tres docs semanas despues de que el canon pasara a 8»). Retirada:
la cadena se enuncia UNA vez, en el contrato; el README conserva su efecto.
Tres READMEs de eidos ensenaban todavia el selector prohibido —y los tres se
equivocaban sobre su PROPIO CSS, que ya usa `:dir(rtl)`—: waveform, nav-tree y
toolbar (este ademas invertia la precedencia: «prefs o prop»; gana la prop).
⚠️ `number-field` no tiene tabla de props que ampliar; su `dir` sigue en su
seccion `### Direction resolution`, ahora enlazando el contrato. Fabricar una
tabla de una fila diria «`dir` es su unica prop».
Handoff §9.20 con el cierre, el doble volteo y como se colo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
b80ccb33cb |
docs(direction): el contrato pasa a ser canon, y el corpus deja de contradecirlo
El eje estaba cerrado en CODIGO y no en DOCUMENTACION. El contrato vivia solo en
`CONTINUE-direction.md`, un handoff que `docs/README.md` declara «never a source
of truth». Un capitulo E2 lo fija ahora: `docs/canon/direction-contract.md`.
Va a canon y no a arquitectura por la misma razon que `recipe-contract.md`: es
normativo y tiene guard ejecutable. Cubre la cadena y donde corre cada eslabon;
por que `undefined` no es `'ltr'`; las DOS atributos —`dir` crudo (nativo, lo que
`:dir()` mira) y `data-dir` resuelto (opt-in, para recetas que necesitan un hook
incondicional)—; cuando el estampado es OBLIGATORIO; la doctrina de selector; las
trampas que el guard no ve; que espeja y que no; y la mitad global de prefs.
EL CORPUS SE CONTRADECIA en cuatro sitios, y dos de ellos ENSENABAN mal:
- `component-guide.md:372` daba la cadena como `prefs → 'ltr'`, DOS eslabones,
mandando al wrapper a leer prefs directamente. Eso excluye la prop.
- `soma-architecture.md` §3.4 presentaba el resolutor a pelo (`soma.prefs.getDir()`
dentro del provider) como LA forma de obtener la direccion — justo lo que el eje
retiro del catalogo.
- `html.ts` ensenaba en su JSDoc `dir?: 'ltr' | 'rtl'`, el union a mano que la
regla E-2.5 ahora prohibe. El mecanismo que ensena (extender para estrechar una
clave HTML) es correcto y sobrevive; solo cambia el ejemplo.
- `active-architecture.md:416` no listaba `lang` en la proyeccion, contra
`contracts.ts` y contra su propio test de frontera. Igual `prefs/README.md`.
Y no lo mencionaba en absoluto: el glosario (`activeDir`, `resolvedDir`,
`data-dir`, RTL-1, el escape `rtl-physical:`), `eidos.md` (RTL-1 es el TERCER
guard de deriva y faltaba junto al builder tipado y eidos-lint), `soma.md`,
`building-a-component.md` (§Known traps es exactamente donde va «la prop mueve la
matematica y deja la pintura atras»), y la tabla de atributos de la arquitectura,
que afirma «todo lo que viaja entre capas viaja por atributos» y omitia `dir`.
El checklist gana cuatro reglas y el guard que faltaba (`rtl:check` no estaba en
la matriz de aceptacion pese a que la tabla canon lo nombra), mas un recorte en
E-3.5: «todas las props visuales mapean a `data-{prop}`» leia como mandato de
estampar `data-dir`, que `:dir()` no puede ver.
DIVERGENCIA DECLARADA (§7): la familia `chart` no tiene prop ni provider y
resuelve leyendo `getComputedStyle(node).direction`. Es el unico sitio del
catalogo que hace lo que §1 prohibe. Se registra en vez de esconderse.
Tres verificadores adversariales sobre el barrido; sus hallazgos, corregidos:
RTL-1 estaba anunciado como guard de TODO el capitulo (solo cubre §4) · «nunca
lee al padre» era absoluto y borraba la composicion sancionada en el punto de
llamada · el estampado se afirmaba incondicional en un sitio y condicional en
otro · las cuatro filas nuevas usaban una aplicabilidad que ni la leyenda ni
`component-audit.ts` conocen.
`docs:check` 0 errores sobre 564 docs. `check` 77 = la linea base.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
f9e372e6fe |
feat(media-player): captions propias + el eje de dirección, canonizado
Dos hilos que comparten ficheros, así que van juntos. ## Captions — el player pinta las cues La parte estaba declarada en el morfo desde el principio y nunca implementada. El render nativo no servía: con la pista en mode='showing' el navegador dibuja su caja de cues en bottom:0 del <video> — la misma franja de 49px que la barra de controles — y contra el z-index:4 de la barra al 82% de opacidad llegaba el 18% del texto. ::cue no puede moverlo (la spec no admite propiedades de posición ahí). Ahora la pista se mantiene en 'hidden': la spec deja la pista activa —cues parseadas, activeCues mantenida, cuechange disparándose— y el navegador no pinta nada, así que el texto se monta en la parte Captions, por encima de la barra. Es el movimiento de Plyr, y por el mismo motivo. Tres trampas de la especificación, encaradas por escrito: - Pasar una pista a 'disabled' desactiva sus cues SIN disparar cuechange. Nadie avisa de que hay que borrar, así que se limpia a mano — si no, el último subtítulo se queda congelado sobre el vídeo. - Los eventos de cue no corren mientras está puesto el show-poster flag, así que el primer pintado se siembra leyendo activeCues. - cues/activeCues son null en 'disabled'. De paso cierra un defecto latente: captionsOn era un espejo de una sola dirección que nunca releía track.mode, así que si el SO activaba subtítulos salían cues mientras el botón decía que estaban apagados. Ahora se DERIVA de los modos reales, y una pista que el navegador encienda se adopta (degradada a 'hidden', para que no pinte encima de la nuestra). Decisiones declaradas: el marcado inline se conserva (se monta el DocumentFragment de getCueAsHTML como nodos, nunca por innerHTML — el sumidero de XSS que abre Plyr al serializarlo de vuelta a cadena); la metadata de posición (line/position/align/vertical/snapToLines/regiones) se DESCARTA. Esto no es una implementación de WebVTT y el README lo dice. ## Dirección — el player estaba partido por la mitad Con dir="rtl" por prop y sin dir ambiental, el cromo se disponía en LTR y sus dos Sliders en RTL: el wrapper pasaba la prop cruda y sólo la reenviaba a los hijos, sin resolver la cadena ni estampar. En la demo estaba enmascarado porque el stage estampa dir además de pasar la prop. Canonizado con el patrón de CONTINUE-direction.md §9.16: activeDir en el wrapper, resolvedDir en el provider, dir crudo + data-dir resuelto en los props. El censo del §9.15 no lo tenía — era un cuarto caso que su búsqueda no cubre (acepta dir y no lo escribe en ningún sitio). Y los glifos direccionales no se volteaban: en RTL el transporte se leía ▶▶ ▶ ◀◀, con las dos flechas exteriores apuntando HACIA DENTRO — el síntoma que carousel.css ya documentaba. Se voltean con scale:-1 1 (no rotate:180deg: sólo coinciden en glifos verticalmente simétricos, y rotar el PiP le llevaría el recuadro a la esquina de arriba), anclado a data-dir, el atributo propio del componente. Alcance elegido: seek, play, volumen y PiP; captions y fullscreen NO. El teclado pasa a resolver por getDirectionalKeys, así que las flechas concuerdan con el glifo y con el Slider embebido. ## Verificación check 0 errores propios · vitest del player 14/14, con los tres guards nuevos vistos en ROJO por mutación · eidos-lint invalid 0 (morfo-backed 25 → 29) · morfo:check sin fallos del player · component:audit PASS · smoke PASS · docs:check 0 · prettier limpio en lo propio. A ojo en Chrome real: claro y oscuro, LTR y RTL, y las dos regresiones (el espejo del SO y el subtítulo congelado). Nota: el README de soma se lleva una línea ajena — la fila `dir` de la tabla de props, del barrido de otra sesión, que describe justamente esta cadena. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |