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 }
1538 Commits (f7a7564b393c9f7c5e6a92dfa0c08388366dfb36)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
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 |
|
|
ccfea0f103 |
fix(sema): una regla de cascada casa con SU objetivo, nunca con un ancestro
`safeMatches` aceptaba `target.closest(selector)` como coincidencia, lo que convertia toda regla de pack en regla de ancestro. Parecia inocuo porque los `data-event-*` son transitorios: la regla necesita que el ancestro lleve puesto justo el evento que ella nombra, y un hold dura ~240 ms. Salvo cuando NO son transitorios. Un `signal.warn + risk` resuelve a `persistence: 'untilFix'` (holds.ts), asi que un formulario que fallo la validacion conserva `data-event="signal-warn-invalid"` INDEFINIDAMENTE — y desde ese momento cada click y cada commit dentro del formulario casaba con la regla del propio formulario y reproducia su earcon de error, hasta el siguiente submit. El usuario oia un error mientras corregia el error. Es el hallazgo S-01 de AUDIT-sema-2026-08-05, el unico de severidad ALTA, y los 256 tests de sema pasaban con la fuga puesta. EL ARREGLO NO ROMPE EL ACOTADO POR DESCENDENCIA, porque ese vive en el SELECTOR y `matches()` lo evalua contra el elemento en su arbol real. Es como `media-player` acota sus reglas sobre los morfos de Button y Slider (`[data-media-player] [data-button][data-event="…"]`). Medido antes de tocar: CERO reglas del catalogo se declaran sin conjunto de evento y CERO usan el matcher `ancestor`, asi que nada dependia del `closest()`. GUARD `cascade-scope.test.ts`, nacido en ROJO con el escenario real (un formulario con la estampa persistente + un boton dentro): la regla del ancestro alcanzaba al descendiente. Fija las tres mitades — no alcanza al descendiente, sigue honrando la descendencia escrita en el selector, y sigue casando con el objetivo propio. Y de paso se retira `__adv-verify-haptic-dead.test.ts`, andamio que dejo un agente de la auditoria (autorizado por el usuario). `check` vuelve a 75 = la baseline limpia, sin el +1. VERIFICADO: sema+morfo+sound 369/369 · check 75 (baseline exacta) · navegador: el transporte del player sigue callado, es decir la descendencia sigue acotando · prettier limpio. 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 |
|
|
a5c2ad7429 |
docs(date-range-field): el ultimo fantasma — readonlySegments nunca fue simetrico
El README documentaba `readonlySegments` "aplicado simetricamente a ambos extremos"; esa prop no existe. La API real es por-extremo desde types.ts: `startReadonlySegments` / `endReadonlySegments` (el input deriva `endpointReadonlySegments` segun su type). La fila de la tabla de props y la fila comparativa quedan alineadas con el codigo. Era el unico fantasma real de los 5 que dejo el barrido exhaustivo: los otros 4 eran falsos positivos del extractor por indireccion de tipos (props importadas + rest-forwarding) — la leccion que dicta la resolucion a un salto del invariante I10 del guard. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
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 |
|
|
aea2c9f902 |
fix(opts): el callback de escritura viaja en el setter — dobles disparos fuera
16 parejas (15 componentes) pasan a la convencion del setter: el provider solo escribe y la notificacion es imposible de saltar por construccion. Seis doble-disparaban (setter + llamada manual): field-input notificaba DOS veces por tecla; file-upload, grid-list, tag-group y tree-grid (value y expanded) igual. chat-list encadena ahora por setters anidados (el reenvio al listbox interno sobra) y chat-composer pierde su forwarding no-op. Seis divergentes quedan señalados SIN tocar (clear/clamp/reset/debounce deliberados que escriben sin avisar): chat-composer, chat-list ya resuelto, cropper, drawer, knob, search-field. Tests de provider alineados a la forma del wrapper (writableActive con el espia en el setter). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
26cf2a22f5 |
feat(opts): partOpts — la bolsa trivial tiene un solo constructor (288)
La mitad del catalogo copiaba a mano la misma bolsa {id, ref}. partOpts<E>
la construye en un unico sitio: uniformidad por construccion, cero imports de
reactive en wrappers triviales, y el cast de estrechamiento de los refs
element-specific absorbido dentro del helper (7 call-sites limpiados).
29 del piloto + 255 del censo + 4 de sidebar (create con parte string, que el
censo no veia). palabras queda fuera (solo-escritura).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
0ea2c22f37 |
feat(opts): el puente se vuelve target-typed — la bolsa comprobada, casts 3→0
bindProps<O>(config: ConfigFor<O>): O — la interfaz DECLARADA computa el tipo del config; nada se infiere desde la bolsa, asi que el cuerpo del setter queda comprobado (el spike probo que las variantes por inferencia lo degradan a any) y las claves sobrantes/ausentes fallan. Boxes pass-through: dir viaja en la bolsa con la llamada a activeDir visible en la init. OptsFromProps gana el 4o parametro Preserve (el undefined significativo sobrevive) y el parche Omit+ActiveProps de switch muere. button/toggle/switch migrados con tipos verdaderos; onPressedChange/onCheckedChange viajan en el setter del bindable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
337dde8575 |
feat(opts): M1 — 49 interfaces de opts se funden en las bases
Las 49 redeclaraciones verbatim de id/ref en 7 providers pasan a heredar de WithRefOpts (44) / ProviderOpts (5 raices con ref opcional). Guard de censo por linea cruda CASE-SENSITIVE en opts-census.test.ts (un parser de llaves se traga los extends con genericos inline; menubar tiene `ref: state<...>` en un call-site que un scan insensible flagearia), verificado en rojo y en verde. 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 |
|
|
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 |
|
|
fc84305c22 |
feat(direction): el mecanismo pertenece al morfo — la declaracion exige el cable
El campo obligatorio `dir: SomaRuntimeDir | null` de 4.1 conservaba el gate a
costa de ~116 lineas de ceremonia: 67 `dir: null` en fuente + 47 en tests
obligaban a ~75 componentes SIN direccion a pronunciarse sobre un eje que no
les concierne, y `parts` era un `readonly string[]` sin validar — un typo
estampaba nada, en silencio (fail-open). El usuario lo dijo antes que la
auditoria: "los dir no deben ser obligados de pasar".
Ahora lo declara el MORFO, que es donde viven los contratos:
direction: {} // 46 morfos — estampa en 'provider'
direction: { parts: ['trigger'] } // dropdown-menu (su raiz no renderiza)
direction: { parts: ['content'] } // drawer · float-panel (pintan en portal)
direction: { parts: [...,'input'] } // css-field · number-field (dos caras)
y el TIPO se computa de la declaracion, con el generico que ya existia inerte
en soma.runtime<M>:
morfo declara direction => dir: Active<Direction|undefined> REQUERIDO
no declara => dir?: never PROHIBIDO
Olvidar el cable no compila; cablear un eje a un componente sin direccion
tampoco. Y compileMorfo valida direction.parts contra el arbol declarado —
fail-CLOSED: el typo que antes estampaba nada ahora revienta al registrar.
El colapso: 49 getters `{ get: () => this.opts.dir.current }` -> `dir:
this.opts.dir`; los 67+47 `dir: null` MUEREN. El estampado gana su sitio en el
tipo de la superficie (`SomaRuntimePart.props` incluye `dir?: Direction`), asi
que los casts `(props as Record...).dir` de los tests sobran.
El gate cazo en el acto, y las dos presas eran reales:
- `command`: 4.1 le dejo `dir: null` en el runtime y el estampado manual
inline en el assert (la linea no estaba sola y el barrido no la vio) — un
componente CON direccion que habia quedado fuera del mecanismo.
- 5 runtimes SECUNDARIOS del mismo morfo (accordion item, nav-tree item,
emoji-picker cell, context-menu root-runtime, feed sentinel): mismo morfo,
misma exigencia — reciben la afirmacion del dueno (el estampado solo aterriza
en direction.parts, que esos runtimes no registran).
Guards nuevos:
- compile.test: normalizacion a ['provider'], partes explicitas, y el
fail-closed del typo (4 casos).
- direction-census.test: prop publica `dir?: Direction` <=> morfo.direction,
en ambos sentidos, con las excepciones FIRMADAS y sus razones (field-langs:
LangSpec; waveform: delega en el Slider; popover/tooltip/link-preview:
overlays puros cuya pintura estampa la capa flotante en el wrapper
portalizado — declararles direction seria el atributo inutil contra el que
4.1 avisaba). El propio censo cazo a esos tres: la excepcion quedo escrita
el mismo dia que el guard.
Verificado en Chrome con la fuente nueva (el morfo): dropdown-menu estampa en
el TRIGGER, css-field en raiz E input, drawer con pagina rtl por prefs deja el
content SIN atributo y computa rtl por herencia — el modelo del flip intacto.
check 77 = linea base · morfo+soma+active-uix 1401/1402 (el 1 es
soma-attr-audit, flaky bajo carga, pasa aislado) · rtl:check 1 (palabras) ·
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 |
|
|
973d2c886d |
feat(direction): la proyeccion prefs->DOM es del boot, y el DOM la siembra
La proyeccion cross-modal (dir · lang · data-motion/sound/haptic sobre <html>)
era cableado manual de cada app: solo 4 de ~13 boots de la demo la tenian, y el
estampado per-componente del valor de prefs actuaba de SUSTITUTO de la
proyeccion que faltaba. Para que el flip de P3 pueda soltar prefs del atributo,
el ambiente tiene que llegar al DOM una vez y siempre:
- createActiveUix la crea por defecto (opt-out `projectPrefs: false` cuando la
app posee <html>: i18n por routing, proyeccion propia). En attach es opt-IN —
el inverso — porque el host puede gobernar el documento.
- Se dispone la PRIMERA en dispose(), antes de que prefs/dom mueran debajo.
- Los 3 cableados manuales commiteables se borran EN ESTE MISMO commit (uix,
blocks, BootUix); el de web/routes/alpha se edita en arbol pero no se
commitea nunca (regla de la rama). Borrarlos junto a la automatica evita el
agujero medido del doble montaje: dispose() BORRA los attrs gestionados sin
restaurar.
Y LA SEMILLA, que es lo que hace la automatica segura: sin ella, el boot
REESCRIBIRIA un <html dir="rtl"> puesto a mano (el escenario
|
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 |
|
|
aaced3ebec |
fix(media-player): los tres floats no estaban portalizados ni llevaban el dir del player
Una revision adversarial del trabajo del dia (5 lentes, 3 escepticos por hallazgo) devolvio 15 supervivientes de 19. Estos son los que resulto que eran ciertos y son mios; los verifique uno a uno antes de tocar nada. ## 1. Fuga de teclas — el panel no estaba portalizado MEDIDO: con el menu de subtitulos abierto, ArrowDown bajaba el volumen del player de 1 a 0.95. El `<DropdownMenu.Content>` de eidos NO portaliza por si solo — hay que componer `DropdownMenu.Portal`, exactamente como VolumeFloat ya hacia con `Popover.Portal`. Sin el, el panel colgaba DENTRO de `[data-media-player]` y cada tecla del menu subia al `onkeydown` del player. Portalizados los dos menus. Medido despues: cuatro teclas (ArrowDown x2, Espacio, ArrowUp) y el volumen sigue en 1, sin arrancar la reproduccion. Eso arregla de paso otras dos cosas que la revision reporto aparte: el auto-hide de la barra ya no se lleva el menu abierto a opacity 0, y los tres comentarios que decian «the panel is PORTALLED» pasan a ser CIERTOS — lo eran solo en VolumeFloat. ## 2. El `dir` del player no llegaba al menu Los tres floats montan un componente del catalogo EN SITIO y no le pasaban `dir`. El resolutor no tiene paso de padre, asi que cada menu arrancaba su propia cadena y aterrizaba en la preferencia global: lista LTR dentro de un player RTL. Es justo lo que `c73669cde` canonizo horas antes (direction-contract §1, «la composicion lleva la afirmacion») — y el TimeSlider del MISMO componente ya lo hacia bien en dos sitios. `dir` va ANTES del spread para que el valor explicito del consumidor siga ganando. MEDIDO con el player en RTL: el panel portalizado sale `rtl` y coincide con el `data-dir` del player. Antes habria salido `ltr`. ## 3. La demo mentia en cuatro sitios El `eidosSnippet` se habia quedado NUEVE partes por detras del preview vivo (paridad D-3.1): sin transporte, sin tiempos, sin captions, sin PiP, sin los tracks. La rama de audio ignoraba `peaks`, que es justo el control nuevo. La prosa decia «two eidos-only floats» siendo tres, y que al engranaje le quedaban «quality and track» cuando la pista ya tiene lista propia. Comprobada la paridad midiendo el DOM contra el snippet: coinciden. ## 4. La tabla de eventos sema del README (esto era ANTERIOR a hoy) Publicaba `commit-set-time`, retirado en D-AP2.13, y cuatro nombres que ya no existen (`enter-fullscreen`, `exit-fullscreen`, `enter-pip`, `exit-pip`), le faltaba `commit-set-rate`, y daba «provider» como diana de todos cuando el morfo apunta a la parte concreta. Reescrita desde el morfo, los once. 43/43 · eidos-lint invalid 0 · class-hooks 0 · docs:check 0 · check 0 errores propios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
21d817c1f6 |
feat(media-player): con dos idiomas de subtitulos no habia forma de llegar al segundo
`toggleCaptions()` solo alcanza la PRIMERA pista, y a proposito: es un toggle, y las demas se quedan en `disabled` porque poner una en `hidden` OBLIGA al navegador a descargar su VTT — encenderlas todas bajaria todos los ficheros de golpe. La consecuencia es que un video con dos idiomas no tenia ninguna forma de llegar al segundo. `<MediaPlayer.CaptionFloat>` es la lista: `Off` + una entrada por pista, mismo patron que el `RateFloat` de hoy (DropdownMenu + RadioGroup, con el `CaptionButton` real de disparador y su toggle anulado con `preventDefault()` en la costura). OPT-IN: el boton solo sigue siendo el control correcto para una fuente de una sola pista. Por debajo, dos anadidos a soma y ninguno al puerto: - `selectCaptionTrack(track | null)` — pone UNA en `hidden` y el resto en `disabled`, por la misma razon de descarga. - `captionTrackList` + `activeCaptionTrack` — espejos reactivos. El `TextTrackList` vivo es una coleccion del DOM y NO notifica, asi que un chrome que LISTA las pistas no se enteraba de una que llegase tarde. Los refresca `syncCaptions`, que ya corria en addtrack / removetrack / change. Cambiar de idioma NO dispara `commit-toggle-captions`: solo lo hace una transicion real de encendido/apagado. Pasar de ingles a espanol con los subtitulos ya puestos no es un toggle — la misma distincion que el commit de mute hace con `s.muted !== wasMuted`. De paso, tres claves de texto que se me quedaron ayer solo en el LANGS del provider y no en el `texts` del morfo (restart, skip-previous, skip-next), mas `captions-none` para la entrada «Off». El `texts` del morfo ES la superficie i18n que pinta la demo, asi que faltar ahi es faltar de verdad. Verificado en Chrome con dos pistas VTT reales: abrir no toca los modos · exacto UNA pista en `hidden` y el resto en `disabled` · las cues PINTAN el idioma elegido (English «Inline captions demo track», Espanol «Pista de subtitulos de prueba») y Off deja la caja vacia, no congelada · panel 94px. Test nacido en rojo. 17/17 player · morfo:check media-player PASS · eidos-lint invalid 0 · class-hooks 0 · check 0 errores propios (78, los mismos de antes). 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 |
|
|
76f441882c |
fix(eidos): audio-player no es un hueco de morfo, es una receta sobre el ajeno
El guard `has a morfo file for every eidos CSS component` llevaba en rojo por
`audio-player`, y la duda era donde archivarlo. `KNOWN_MISSING_MORFO` habria
sido mentir: ese conjunto esta documentado como «violacion real del morfo-first,
seguida para una pasada de construccion — quitar la entrada cuando se construya»,
y aqui no hay nada que construir.
La evidencia lo zanja: las 30 reglas de primer nivel de `audio-player.css`
seleccionan `[data-media-player…]`, y no existe ningun `data-audio-player` en
todo el arbol. `<AudioPlayer>` es una RAIZ COMPETIDORA sobre el contrato del
media-player (D-AP2.2, precedente Toast/Toaster): viste el morfo de otro y por
eso no tiene ni tendra el suyo.
Asi que categoria propia, `COMPETING_ROOTS`.
Y como una exencion solo vale mientras su premisa siga siendo cierta, la premisa
pasa a estar COMPROBADA en vez de confiada: un segundo test afirma que la receta
de una raiz competidora nunca selecciona su propio nombre. En cuanto lo haga
habra dejado de ser una raiz competidora y debera un morfo como todo el mundo.
Comprobado que el guard nuevo muerde: anadiendo `[data-audio-player] {…}` a la
receta, falla. Restaurada, 18/18 en verde.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
dbd239bcc2 |
feat(media-player): volver al inicio y saltar de pista, sin meterle una cola dentro
Dos partes nuevas del morfo, 25 en total: `RestartButton` (vuelve al inicio del medio ACTUAL — un seek a 0) y `SkipButton` (`direction='previous'|'next'`, pide otra FUENTE). Las dos OPT-IN: `AudioLayout` no las compone, asi que ninguna botonera existente crece un boton. ## El player no tiene cola, y es deliberado El `SkipButton` solo emite: la raiz recibe `onPrevious` / `onNext` y la app cambia el `src`. Cuando lo hace, hablan los callbacks de ciclo de vida que se abrieron en el commit anterior. Meter una playlist dentro del componente habria sido un ensanchamiento que nadie pidio, y es lo que hacen Media Chrome y Vidstack. De ahi sale una regla que ahorra trabajo al consumidor: una direccion SIN manejador se pinta deshabilitada (`data-disabled` + `disabled`). Es el mismo aspecto que los extremos de una cola, asi que la app no tiene que hacer nada especial para el primer y el ultimo elemento — le basta con no pasar el manejador. ## Ninguna de las dos suena Cero eventos semanticos nuevos, igual que el `SeekButton` que ya existia y que tampoco declara ninguno: moverse por el material es navegacion, no consecuencia. Lo que suena es el resultado (arranque, final, error), ya cubierto. Decision del usuario tras plantearle las tres opciones. Los glifos de salto se voltean en RTL junto a los de seek — previous/next leen como atras/adelante sobre el mismo eje. El de reinicio no: su flecha circular habla de volver, no de una direccion de la fila. Verificado en Chrome: `Previous` sale inerte por no tener manejador, `Restart` busca a 0, y `Next` cambia la fuente de verdad (Big Buck Bunny -> trailer.mp4). Test nacido en rojo. morfo:check media-player PASS · 28/28 (player + morfo + waveform) · eidos-lint invalid 0 · class-hooks 0 · docs:check 0 · check 0 errores propios (78, los mismos de antes). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
b41669c438 |
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. 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 |
|
|
5cbb03904d |
feat(media-player): el player ya le cuenta a la app cuando empieza y cuando acaba
Hasta ahora el componente no exponia NINGUNA api de eventos: cero props `onX` en la raiz. La app solo podia enterarse de la reproduccion por dos vias malas — observar mutaciones de los `data-*` del provider, o enchufar un `MediaProvider` entero por `createProvider`, que es una escotilla pesada para «avisame cuando acabe». Y sin embargo el player YA distinguia esos momentos por dentro para sonarlos: `commit-toggle-play`, `commit-complete`, `commit-fail`. Faltaba sacarlos. Se abren cuatro, espejo de esos: `onPlay` · `onPause` · `onEnded` · `onError`. Montan sobre los eventos del ELEMENTO, no sobre los botones, asi que la tecla, el gesto, las teclas de medios del sistema y un `play()` programatico caen todos aqui una sola vez, cuando el medio cambio de verdad. ## Dos divergencias deliberadas respecto a la capa perceptual - El `pause` que acompania al final natural NO se reporta. El elemento lo emite justo antes del `ended`, y contar los dos haria que cada pista terminase dos veces. Es la misma guarda `!s.ended` que ya usa el commit. - `onEnded` SI dispara cuando el final viene de un salto al final, aunque `commit-complete` se lo salte. Esa exencion existe para que un salto no SUENE como una consumacion; una app que encola la siguiente pista necesita el final venga de donde venga. Las dos reglas van en el test. Leidos tarde (`Active`, como `onValueCommit` del Slider) para que cambiar el handler a mitad de reproduccion se respete. La demo los pinta en una lectura `lifecycle` al lado de `trace`, a proposito: `trace` es lo que el player SUENA, `lifecycle` es lo que le CUENTA a la app. Verificado en Chrome con un <video> real: play → onPlay, pause → onPause, final natural → onEnded SIN un onPause espurio delante. Test nacido en rojo (desconectadas las 4 llamadas: 0 invocaciones). player + waveform 19/19 · check 0 errores propios (78, los mismos de antes). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
ee51172d8f |
fix(media-player): de 2x no se podia bajar, y la etiqueta movia la botonera
El `<RateButton>` sólo sube: su `onclick` es `cycleRate(1)` y el paso recorta contra el último preset, así que quien se pasaba de `2×` se quedaba ahí — bajar era exclusivo de las teclas `<` / `>`. Con ratón no había vuelta. `AudioLayout` pasa a componer `<RateFloat>`: el DropdownMenu del catálogo con un RadioGroup, porque la velocidad es una elección de estado dentro de un conjunto y la actual debe anunciarse `aria-checked`, no sólo parecerlo. Es lo que abren YouTube, Vimeo y Plyr. El `<RateButton>` sigue exportado para quien arme la botonera a mano. El disparador sigue siendo el RateButton de verdad — conserva su parte del morfo, su `aria-label` y el `data-rate` del que sale la etiqueta. Su ciclado se anula en la costura: `composeHandlers` se salta el handler de soma cuando el `onclick` del consumidor llama a `preventDefault()`, así que el clic abre la lista en vez de empujar la velocidad. Medido: 4 ciclos de apertura y cierre con la velocidad intacta. Los presets salen de `MediaPlayerProvider.RATE_PRESETS` (ahora público, y la clase se exporta como ya hace el calendar) — los mismos que recorren las teclas. Dos copias a mano habrían derivado. ## Los dos defectos del panel portado Mismo par que destapó el float de volumen, y por la misma razón: lo que se porta sale del ámbito del player. - El menú traía un suelo de `12rem` pensado para prosa («Show notifications»), con la etiqueta más ancha en `1.25×`: 192px de panel para 33px de texto. El marcador `data-media-player-rate-float` viaja con el panel y baja el token a cero → 77px. - El botón pasa a ancho fijo. Medido al tipo del control, `1×` ocupa 0,90em y `0.75×` 2,41em: cambiar de velocidad redimensionaba el botón y DESPLAZABA toda la botonera — el mismo defecto que tumbó el hover horizontal del volumen. En `em` para que siga a `data-size` sin una entrada por tamaño. Medido en los cuatro: 64 · 74 · 88 · 121 px, ancho único por tamaño y el mute clavado. Verificado en navegador: abrir no toca la velocidad · 2× → 0.5× → 1.25× con el elemento obedeciendo · panel 77px · botonera inmóvil en sm/md/lg/xl. check 0 errores propios (78, los mismos de antes) · player + waveform 18/18 · eidos-lint invalid 0 · class-hooks 0 · docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
127c4c95b4 |
feat(media-player): con peaks el scrubber es onda; sin peaks, ninguna onda
Pasar `peaks` al TimeSlider convierte el scrubber en waveform; sin ellos queda el raíl. Por debajo son el MISMO Slider sobre el mismo dominio (segundos), así que sólo cambia la pintura — ni el valor, ni el buffered, ni el teclado, ni la dirección. La ausencia de `peaks` es la señal, y no hace falta una prop aparte que declare que la fuente se streamea: da igual el motivo por el que no haya datos. ## Por qué NO hay onda genérica Se propuso pintar una sintética cuando la fuente se streamea. Se investigó el sector antes de implementarlo, con cada afirmación verificada contra fuente primaria, y no tiene UN SOLO precedente: - SoundCloud sirve un JSON de 1800 valores (~6,5 KB; O(1) en tamaño, no O(duración)). - WhatsApp y Telegram los calculan en el EMISOR y viajan dentro del mensaje. Telegram es el único con contrato público: 100 muestras de 5 bits, LSB-first sin alinear, 63 bytes, valores 0-31. - Spotify y Apple Podcasts no ponen onda: barra normal. - wavesurfer.js lo zanja en su FAQ: streaming sólo funciona si le entregas peaks + duration. No hay decodificación progresiva; para ficheros grandes recomienda precálculo en servidor con bbc/audiowaveform. O sea que una fuente que se streamea no plantea un modo de pintado, sino un problema de suministro de datos que se empuja al llamador — lo que D-WF.2 ya decía. Y una forma inventada MIENTE sobre el sonido: sugiere dónde hay silencio y dónde clímax sin saberlo. La caída honesta es el raíl que el player ya tiene. La investigación entera queda en el README de eidos, incluida la afirmación que se CAYÓ al verificarla: que SoundCloud precalcula al transcodificar y no computa nada en cliente. El blog citado argumentaba lo contrario, y como el JSON viene cuantizado a una rejilla fija de 1800x140, sí hay reescalado en cliente. Vía lateral anotada para cuando se retome: Apple Podcasts da textura al scrubber con capítulos y detentes hápticos — significado, no amplitud. check 0 errores propios · player + waveform 18/18 · eidos-lint invalid 0 · docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
4755d97d1b |
fix(media-player): aire vertical del volumen flotante, sin tocar el ancho
Relleno de bloque a --space-6 (24px); el inline se queda en --space-2 y el panel en 48px de ancho. Ajustado con el usuario mirando: 16px se quedaba corto, 32 pasaba, y ensanchar tambien el panel fue un desvio mio que rechazo — el aire que pedia era vertical. Panel 48x146, pulgar a 13px del borde interior en el extremo, sin scroll. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
fafe98bcd8 |
fix(media-player): el pulgar del volumen flotante sobresalia del panel
Con 8px de relleno el pulgar se pasaba 2px del area de relleno en los extremos: mide 20px y va CENTRADO en el punto del rail, asi que en el maximo la mitad cuelga por arriba. No era decoracion lo que faltaba, era despejar el voladizo. El relleno de bloque pasa a --space-4 (16px), que cubre la mitad del pulgar en todos los tamanos (10px en md, 14px en xl segun --slider-thumb-size-*). El inline se queda en --space-2: ahi solo ensancharia un panel de 48px, por eso los dos ejes difieren. Medido: panel 114 -> 130 de alto, holgura del pulgar al borde -2 -> +5px, sigue sin scrollear y conserva los 16px de separacion del boton. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
8236d225cb |
fix(media-player): el portal se llevaba la sema del volumen, y el panel scrolleaba
Tres defectos que el usuario vio y yo no, porque afirme haber verificado sobre una captura donde el panel media 48px. 1. SONABA. Las reglas que silencian los sliders del player son de descendencia (inPlayer = onProvider() + descendiente), asi que dejan de casar en cuanto el control sale PORTALADO y el slider recupera su voz entera. El pack anade las mismas tres colgadas de la PARTE (inVolume), que es el marcador que viaja con ella este donde este. Misma clase de fuga que los tokens --_mp-* que tampoco cruzan el portal — y que yo mismo habia encarado veinte minutos antes en este componente sin ver que la sema tenia el mismo agujero. 2. SCROLLEABA. El viewport del Popover es un contenedor de scroll con relleno para prosa: alrededor de un rail de 32px saca barras y se come medio panel. Lo que se veia como un pulgar deforme era la barra de scroll. Acotado por el marcador propio de la composicion (data-media-player-volume-float, eidos-only), ya ni scrollea ni rellena de mas: 129px de alto -> 114. 3. PEGADO AL BOTON. sideOffset NO es la separacion literal — medido, 24 da 16px, hay un desfase constante de 8. Y pasarlo SACA del canon de separacion flotante (--space-1-5, 6px), demasiado justo para despegar un panel de un boton de 36px sobre video. Desviacion deliberada, declarada en el README. Medido: relleno del vertical correcto (de abajo arriba, 96/72/24px para 1/0.75/0.25), scrollea=false, separacion 16px. check 0 errores propios · sema + player 206/206 · eidos-lint invalid 0 · docs:check 0. NO VERIFICADO A OJO: la extension de Chrome esta caida. Los numeros dicen que las tres cosas estan, pero no lo he visto. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
9fd0de84d6 |
Reapply "feat(media-player): VolumeFloat — el volumen en panel, sin mover la botonera"
This reverts commit
|
2 months ago |
|
|
c798f7465a |
Revert "feat(media-player): VolumeFloat — el volumen en panel, sin mover la botonera"
This reverts commit
|
2 months ago |
|
|
26ddbaa1c9 |
feat(media-player): VolumeFloat — el volumen en panel, sin mover la botonera
Opcional: componer MuteButton + VolumeSlider al lado sigue siendo la forma en linea; esta es la otra y la elige el desarrollador. POR QUE UN FLOAT Y NO UN DESPLIEGUE EN LA FILA. Se probo lo segundo —el patron de YouTube, rail a inline-size 0 creciendo al hover— y se descarto con el usuario delante: un slider que se expande dentro de una fila flex EMPUJA todo lo que va detras (tiempo, scrubber, subtitulos, PiP, pantalla completa) y en una fila apretada desborda. YouTube se lo permite porque su barra ocupa la ventana entera; la nuestra no. El panel va fuera de flujo. VERIFICADO que no mueve nada: se compararon las posiciones de los DIEZ controles con el panel cerrado y abierto — identicas, cero diferencias. Es la promesa que incumpli con el hover y que esta vez se midio. Todo compuesto del catalogo, sin reinventar el flotado: Popover con openOnHover (ya existia; solo se acorto la apertura a 120ms —esto es un control, no un tooltip— y se alargo el cierre a 400ms para poder viajar hasta el), MuteButton como trigger via child (sigue siendo el boton de verdad: conserva aria-pressed, el atajo m y el glifo por nivel) y VolumeSlider vertical, el eje que el morfo declaraba desde el principio. El gesto: hover abre, CLIC SILENCIA y cierra, que es el remate natural de esa accion. En tactil no hay hover, asi que el toque hace las dos cosas; es ademas la unica via por la que un usuario tactil llega al slider. TRAMPA ENCARADA: los tokens --_mp-* NO cruzan el portal (se declaran en [data-media-player] y el contenido sale fuera), asi que el tinte del slider lleva fallback al rol que envuelve. Sin el, el control sale sin color dentro del panel. Demo con chip volume inline|float para compararlos en la misma barra. check 0 errores propios · player 14/14 · eidos-lint invalid 0 · docs:check 0 · prettier limpio. Panel 48x131, slider 32x96 vertical, visto en Chrome. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
1061e94a59 |
feat(media-player): el volumen acepta orientacion — el sustrato del float
El morfo declara data-orientation ['horizontal','vertical'] en volume-slider desde el principio y nadie lo cableo nunca: soma estampaba el literal 'horizontal' y el Slider compuesto no recibia orientacion ninguna. Un consumidor tampoco podia sortearlo pasando el atributo, porque los props del provider se mezclan los ultimos y ganan. Son DOS estampados y hacian falta los dos: el data-orientation del wrapper (que solo ANUNCIA el eje) y la prop orientation del Slider de dentro (que lo aplica). La clave nueva de opts va OPCIONAL a proposito — una requerida en ActiveProps rompe todos los call-sites, que es la leccion de F1. Sustrato para el volumen flotante: el eje es asunto de la propia parte, asi que viaja como prop en vez de decidirlo quien la flote. Horizontal sigue siendo el default y esta verificado en Chrome sin cambios (wrapper y Slider en horizontal, 64x32). check 0 errores propios · player 14/14 · prettier limpio. Falta encima: la receta del caso vertical y la composicion del float. 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 |
|
|
b91eb9675e |
fix(media-player): dos errores de tipos que colé en el test del doble manejo
mockClear() sobre fake.engine.seek/setVolume, que estan tipados como funciones del puerto MediaProvider, no como espias. vi.mocked() para el cast. Mios y recien introducidos en 64043ae2b: check vuelve a 0 errores propios. player 14/14. 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 |
|
|
3b5eab5b68 |
feat(media-player): el altavoz dice CUANTO volumen, no solo si esta mudo
El icono mostraba las mismas ondas llenas al 5% que al 100%: solo alternaba Volume2 y VolumeX segun data-state. Ahora lee una banda de sonoridad. morfo: data-level en la parte mute-button, enum ['muted','low','mid','high']. Contract-only (sin fuente resoluble), asi que soma pone el valor — misma forma que data-orientation en volume-slider. Declarar values es lo que evita que el compilador lo degrade a bandera de presencia, la trampa que data-rate documenta dos partes mas abajo. soma: la banda se deriva de volume + muted. Mudo gana a cualquier nivel (un player muteado a volumen 1 es silencio, y el icono debe decirlo), y volumen 0 sin la bandera tambien lee 'muted'. Las tres bandas audibles son tercios iguales porque hay exactamente tres glifos no-mudos que mapear. eidos: cuatro bandas, cuatro glifos — Volume / Volume1 / Volume2 / VolumeX, que el set de iconos ya exportaba. Sin assets nuevos y sin icon-swap en CSS. Medido en Chrome, con el recuento de trazos SVG confirmando cuatro glifos distintos: 1.0 y 0.8 high(3) · 0.5 y 0.34 mid(2) · 0.2 y 0.05 low(1) · muteado muted(3). check 0 errores propios · morfo:check PASS media-player · morfo 8/8 · player 14/14 · eidos-lint invalid 0. 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 |
|
|
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 |
|
|
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 |