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 }
1780 Commits (973d2c886dbee183a9756780ab0d9778cea3ccf3)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
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 |
|
|
482cea3bee |
docs(media-player): las cuatro variantes de audio estan aprobadas, no dos
row e inline las dio por buenas el usuario y yo las seguia listando como pendientes de su mirada. El eje visual del modo audio queda CERRADO. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
35ef18ed02 |
docs(media-player): los cuatro arreglos que bloquea el refactor en vuelo
La revision adversarial dejo cuatro hallazgos ciertos que viven todos en media-player-provider.svelte.ts, que la otra sesion tiene modificado sin commitear. Tocarlo significaria llevarme su trabajo en mi commit, asi que quedan escritos con el arreglo concreto de cada uno. Incluye una observacion sobre SU arbol que no he tocado: con el refactor en vuelo el player deja de estampar el atributo dir crudo aunque la prop lo afirme. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
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 |
|
|
b6caaaa178 |
docs(media-player): el handoff recoge el selector de pista y lo que NO pude verificar
Anade las entregas 5 y 6 (CaptionFloat y la especificacion de la calidad) y, mas importante, una seccion nueva de limites. El panel de navegador embebido dejo de componer frames a media tarde (`document.visibilityState === 'hidden'`) y con el dejaron de llegar las teclas. Llegue a creer que habia un defecto —«Escape no cierra el float dentro del player»— y era FALSO: instrumente un listener y una pulsacion real registro cero eventos. Lo roto era mi via de entrada, no el componente. Queda escrito porque es la misma familia de trampa que las transiciones CSS congeladas, y porque la proxima sesion no debe salir a cazar ese fantasma. Lo que si quedo verificado con eventos despachados: keydown de Enter/Espacio abre el menu, y el click que el navegador sintetiza detras no toca los subtitulos — el preventDefault() de la costura aguanta. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
e0206f6b21 |
docs(media-player): la calidad de reproduccion, especificada sin implementar
Decision del usuario: escribir el contrato antes de tocar codigo. El documento existe porque `settings-button` lleva declarada desde el principio prometiendo «velocidad / calidad / pista», y al ir a implementarla resulta que dos de los tres ejes ya no le corresponden y el tercero no existe. Auditado sobre el codigo: grep de `quality|bitrate|resolution|level| representation` sobre el puerto da CERO. Ni `MediaSnapshot` (0 de 14 campos), ni `MediaProvider` (0 de 9 miembros), ni los espejos de `$arts/sound`. Y no es un descuido: la calidad NO es intrinseca al `<video>`. Un fichero no tiene niveles; los niveles aparecen con un manifiesto HLS/DASH y un motor que elige representacion. Este framework no vendoriza motores, asi que la lista solo puede venir del consumidor. De ahi que el contrato, si se abre, sera una lista VACIA para el 90 % de los usos — lo que decide la forma: el control se AUSENTA, no sale vacio. Misma doctrina que el waveform sin picos. Queda escrito el contrato minimo (3 campos de snapshot con `qualityId` separado de `qualityAuto`, `setQuality`, los dos espejos, el evento de morfo que rompe el censo de 11, los textos) y la trampa que ya nos mordio con la velocidad: el commit cabalga el RESULTADO, no el clic — pedir 1080p no es haberlo conseguido. Registrado en el indice `docs/decisions.md`. docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
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 |
|
|
d3c8fcfd37 |
docs(direction): los charts ya no estan en la cola — la linea llevaba rancia
§10.2 seguia diciendo que la familia chart "no tiene prop ni provider y resuelve
leyendo getComputedStyle(node).direction". Eso lo cerro 43a788083: los cinco que
necesitan direccion declaran dir?: Direction y resuelven por
createChartRtl(eidos, () => dir) -> activeEidosDir. Verificado por grep que no
queda un solo getComputedStyle(...).direction en la familia.
La linea me indujo a repetirlo. El registro historico de §9.20 se queda como
estaba — describe lo que era cierto entonces —; lo corregido es lo que apunta
hacia adelante.
Re-hace el cambio que
|
2 months ago |
|
|
52fd56c9f3 |
docs(media-player): la pestana API de la demo se habia quedado rancia
Tres desfases, todos de hoy: - La lista de partes se quedo en las 16 originales: le faltaban las seis del modo audio (Artwork · Artist · Identity · Transport · RateButton · LiveIndicator · AudioLayout), los dos floats eidos-only (VolumeFloat · RateFloat) y las dos nuevas del transporte. Ademas ahora dice que RestartButton y SkipButton son OPT-IN, que es la parte que un consumidor no puede adivinar. - La tabla de props no mencionaba ninguno de los seis callbacks nuevos. Van con las dos divergencias declaradas (el `pause` del final natural no se reporta; `onEnded` si dispara para un final por salto) y con la regla de que una direccion sin manejador se pinta deshabilitada. - «The settings menu (speed / quality / track) is the remaining v2 item» ya no era cierto: la velocidad tiene lista propia desde `ee51172d8`, asi que al menu de ajustes solo le quedan calidad y pista. Medido en el navegador: la tabla no desborda su contenedor (864 de 865) y la pagina no gana scroll horizontal. 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 |
|
|
6f0f438f30 |
docs(media-player): el handoff recoge las cuatro entregas de la tarde
Waveform visible, velocidad bajable, API de eventos y transporte de lista. Con la leccion reutilizable: componer un primitivo desde soma deja su receta fuera, la carga el componente que lo compone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
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 |
|
|
7b0556a955 |
docs(direction): handoff del refactor — la direccion deja de ser una convencion
El usuario preguntó si lo entregado era lo mas logico y profesional. No lo es:
el contrato es correcto y el MECANISMO para cumplirlo es teclear la misma linea
en cada componente. 54 estampados a mano, 7 enlaces al padre a mano, 9 Content
que re-arrancan la cadena por su cuenta, y 20 de 55 componentes que lo
incumplian hasta `21055bd3c`. Una convencion que hay que repetir 54 veces y que
20 de 55 incumplian no es una convencion — es una abstraccion que falta.
`docs/process/CONTINUE-direction-runtime.md`, para abrir en sesion nueva. Lleva
LAS DOS COMPROBACIONES DE VIABILIDAD YA HECHAS, que es lo que separa un handoff
util de una idea:
- El runtime YA omite un atributo cuyo valor resuelve a `undefined`
(`runtime.svelte.ts:519-522`). Es exactamente la semantica del estampado
crudo. No hay que construir nada para eso.
- El runtime YA inyecta atributos sin declaracion en morfo — `id`, el marcador,
`data-archetype`, `reg.attachment` (`:538-551`), con el comentario que lo
justifica. Asi que el movimiento NO exige tocar 55 ficheros de morfo.
- La capa flotante tiene acceso al dueño: los sub-proveedores llevan
`this.provider`. Y el reparto de los 11 consumidores es el diagnostico —
8 pasan el `dir` DEL PROPIO Content (la fuga), 3 el de la raiz (lo correcto).
Dos movimientos, con el orden razonado (la propagacion primero: mas acotada,
verificable de un vistazo, y BORRA codigo, asi que el segundo llega a un arbol
mas limpio). Dos vehiculos posibles para el primero, sin decidir a proposito.
Lleva ademas la linea base exacta de verificacion y las trampas ya pagadas: el
crudo nunca resuelto, `dir` en un `<svg>` no hace nada, `OptsFromProps` quita el
`undefined`, clasificar por grep se equivoca (me paso con `avatar`), y una
migracion mecanica hereda los defectos que traduce.
⚠️ Anota lo que el refactor NO debe romper: los 4 que correctamente NO estampan
(popover, tooltip, link-preview, context-menu) y la excepcion firmada por el
usuario de `avatar`/`image`, que no aceptan `dir` a proposito.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
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 |
|
|
bd7853b59e |
docs(sound-engine): mutear antes de reproducir, y parar al terminar
Medir el escenario mixto y los earcons exige reproducir de verdad, pero el Chrome que suena es el del usuario. Dejar una pista de 6 minutos a volumen 1 mientras se hacen otras cosas es una molestia real, y paso hoy. Casi todo lo que se mide —contextos, stamps, envolventes, geometria— no depende de que se oiga, asi que muted=true basta; el volumen solo hace falta para la escucha, que es del usuario. Y pause() al acabar cada medicion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
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 |
|
|
e9454bebe7 |
docs(direction): la excepcion que el usuario veto — :dir() que traduce una prop logica
«NO añadas la direccion al avatar porque tambien depende de donde se situa el badge». Tiene razon, y mi regla de §2 —«si el recipe usa :dir(), acepta y estampa `dir`»— es demasiado ancha: mandaba al siguiente a hacer justo eso. NO toda regla `:dir()` afirma una direccion. `Avatar.Badge` expone la esquina con `position` en terminos LOGICOS (`top-start` / `top-end`), asi que el ancla (`inset-inline-*`) ya se voltea sola; `:dir(rtl)` solo invierte el SIGNO del `translate` fisico que hace sobresalir la insignia —por eso las cuatro reglas llevan su marcador `rtl-physical:`, es el caso sancionado de RTL-1, no un selector de lado—. `Image` es lo mismo con otro mecanismo: `object-position` no tiene palabra clave logica, asi que `:dir(rtl)` traduce a `left`/`right` el `start`/`end` que el consumidor ya eligio. En los dos, una prop `dir` seria un SEGUNDO mando para lo que el primero ya decide, y pelearia en vez de componer: el ancla logica resuelve por la direccion ambiente mientras la prop dice fijarla. Esa regla `:dir()` necesita la direccion que REALMENTE maqueto el elemento, que es la heredada, por diseño y no por descuido. El test que queda escrito: si el componente tiene vocabulario logico propio para el eje, no lleva `dir`. Un `Switch` no lo tiene —su pulgar viaja en un sentido y la direccion es el unico input—, asi que ahi si es significativo. Un `Avatar.Badge` tiene `position`, asi que no. Barrido: de los 13 recipes con `:dir()`, SOLO `avatar` e `image` tienen ademas prop logica start/end. La excepcion son exactamente esos dos. Corrige tambien lo que reporte como hallazgo: «avatar e image usan `:dir()` sin aceptar `dir`» NO es un defecto ni una decision pendiente, es el diseño correcto. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
43a7880832 |
feat(direction): las charts aceptan `dir` y corren la cadena canonica
Decision del usuario. Yo habia recomendado lo contrario —dejarlas y ablandar el
contrato— y me equivocaba en el diagnostico: no era que la cadena no valiese
para eidos, era que **le faltaba el punto de entrada**.
`activeDir(getter, soma)` pide un `Soma`, y una chart solo tiene un
`ActiveEidos`. Y `ActiveEidos.prefs` es un `ActivePrefs` crudo, que no tiene
`getDir()` —ese metodo vive en la vista que mira a soma—. Por eso la cadena
"no era ejecutable" y por eso alguien puso ahi un `getComputedStyle`. La
adaptacion entre las dos superficies es TODA la diferencia:
src/uix/eidos/direction.ts → activeEidosDir(getter, prefs)
Mismos dos eslabones, mismo `Direction | undefined`, misma cola `'ltr'` en el
consumidor. Abre la puerta tambien a `avatar` e `image`, que son el mismo caso.
Fuera la lectura del DOM y fuera la copia byte-a-byte que `chart.svelte` tenia
del mismo mecanismo —dos fuentes de verdad para un hecho, y ademas el observer
vigilaba `<html dir>` mientras la lectura era del elemento, asi que un ancestro
que volteara en caliente no se veia nunca—.
⚠️ EL HALLAZGO QUE SOLO SALE MIDIENDO: **estampar `dir` en un `<svg>` no hace
nada.** El atributo lo mapea a `direction` la hoja UA de HTML, que no alcanza a
los elementos SVG: medido en Chrome, un `<svg dir="ltr">` bajo un ancestro
`dir="rtl"` sigue computando `rtl`. Yo lo habia puesto ahi y el comentario
afirmaba que servia. Va en el envoltorio HTML (`<figure data-chart-figure>`,
`<div data-chart-frame>`). Importa porque `text-anchor` es LOGICO: con el
estampado en el nodo equivocado la matematica espeja y las etiquetas no —el
fallo de §2 con otro disfraz—.
MEDIDO en Chrome, heatmap: LTR `ene/feb/mar` en x=26/90/154, celdas 26‥858;
RTL en 851/787/723, celdas 6‥838. Funnel: poligonos de 0‥226 a 193‥420,
etiquetas de 249 a 171. El toggle del topbar sigue volteandolas, ahora por
prefs y no por el DOM.
COSTE ACEPTADO, verificado: envolver una chart en `<div dir="rtl">` sin pasar la
prop ya no la voltea. Falla de forma CONSISTENTE —el estampado del envoltorio
gana sobre el ancestro, asi que matematica y pintura siguen de acuerdo— y no
con las etiquetas cruzando el grafico.
§7 del contrato deja de ser una divergencia declarada y pasa a ser la tabla de
los dos puntos de entrada. Su justificacion era ademas falsa en dos puntos: no
era "el unico sitio del catalogo" (quedan 4 mas, uno vivo en
`field-langs-switcher`), y decia que una chart no se podia voltear por subarbol
cuando `direction` se HEREDA y si se podia.
`check` 77 = linea base. `docs:check` 0/0. Tests de eidos + libs/plots: 424/425,
el fallo es `audio-player` sin morfo, preexistente y ajeno.
⚠️ No pude ver los pixeles: el panel del navegador no compone frames.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
6de53519d1 |
fix(audio-player): el track de la barra era un punto — el suelo estaba anulado
El usuario lo vio: en la variante `bar` el scrubber quedaba reducido a un pulgar entre 0:00 y 6:12, mientras el volumen conservaba sus 64px al lado. La causa era de una linea. media-player.css le da a todo scrubber un suelo (min-width: --space-8) y la regla de `bar` lo anulaba EXPLICITAMENTE con min-inline-size: 0 y mayor especificidad, asi que flex podia encogerlo hasta la nada. Un control que desaparece en silencio es peor que una fila que admite que no tiene sitio. Dos decisiones de diseno del usuario, aplicadas: - El scrubber nunca se dibuja mas estrecho que el volumen. El ancho del volumen sale a token (--_mp-volume-width) y el scrubber de `bar` se apoya en el como suelo, para que no puedan divergir cuando alguien retoque uno. - El artista se oculta en `bar`; `card` y `row` lo conservan, que tienen sitio. Medido tras el arreglo (variante verificada en la misma pasada): scrubber 64 a 560/720px contra volumen 64, y 229/529 a 900/1200px. Sin desborde en ninguno. Visto en Chrome: los dos sliders se leen del mismo tamano y el titulo gana el espacio del artista. Queda vivo y declarado: el mute-button se estruja a 20-25px bajo 800px. No se toca porque ponerle flex:none empuja el problema a otro control, y esa es una decision de diseno. check 0 errores propios · player 14/14 · eidos-lint invalid 0 · docs:check 0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
92c1ee91a9 |
docs(sound-engine): bar solo colapsa bajo 800px, y la mezcla la elige el disenador
El barrido de anchos reencuadra el defecto: bar esta sano de 900px en adelante (scrubber 147/347/1047) y solo colapsa a 720px, que es el ancho al que la demo encierra al player, no un ancho propio de una barra de app persistente. Corrige ademas la pregunta que yo habia planteado. El usuario zanjo que la mezcla es composicion y la elige el disenador, lo que encaja con la doctrina que audio-player.css ya declara (un variant decide que se muestra y como fluye, nunca comportamiento) y con la salida que ya existe: componer MediaPlayer media=audio a mano mantiene el flujo sin opinion, porque el CSS de variantes cuelga de data-variant y eso solo lo estampa AudioPlayer. Lo que no se disuelve en composicion es que el preset colapse EN SILENCIO. El arreglo pendiente no arbitra entre scrubber y volumen: min-inline-size en el scrubber para que nunca llegue a 0, y el ancho minimo documentado. Y registra dos veredictos del usuario: waveform F6 APROBADA (cierra el pendiente del 2026-08-01) y variante card APROBADA. Quedan row e inline. docs:check 0 sobre 564 documentos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
5ff5202038 |
docs(sound-engine): el stamp de sema y la matriz del modo audio, medidos
Cierra los dos pendientes del hilo que no exigían los ojos del usuario, y sanea el documento, que afirmaba cosas ya cumplidas. STAMP DE SEMA — cerrado por las dos mitades. Dónde aterriza: con un MutationObserver sobre el subárbol del player, un clic en play estampa contact-activate y commit-toggle-play (commit · neutral) sobre el PROPIO play-button, no sobre el provider, y los limpia al cerrarse el hold. Que la regla del pack casa: A/B del pico de la envolvente dentro del mismo componente — transporte (play, mute) 0.0001, o sea silenciado; commit-complete 0.05, audible. Los osciladores sí se construyen, la ganancia nunca sube: es la resta de gain de D-AP2.7 funcionando. Queda anotada la trampa en la que caí, comparar ese pico con el 0.25 de un Button de otra página y creer ver una contradicción: el A/B tiene que ser dentro del mismo componente. F6, MATRIZ DEL MODO AUDIO — las 16 combinaciones (4 variantes × claro/oscuro × LTR/RTL) recorridas buscando partes colapsadas, botones estrujados y desbordes. Un solo defecto: en `bar` el time-slider colapsa a ancho 0 y el mute a 20px, en las CUATRO combinaciones, así que no es del eje ni del tema. Visto: el scrubber queda reducido a un punto ámbar entre 0:00 y 6:12 — una barra de app sin barra de progreso — mientras el volumen conserva su ancho completo. `card` y `row` limpias; lo que oculta `inline` es lo que su receta declara. HIGIENE — el documento decía «sin commitear» del rediseño y del waveform, y pedía un re-plan del player que ya está hecho. Verificado contra git: rediseño |
2 months ago |
|
|
1cb61567e5 |
feat(direction): RTL-2 — el guard del doble volteo, la forma que se me colo dos veces
Cierra §10.1, la ultima deuda del eje. `dee2c9e3a` arreglo los dos defectos pero
dejo la FORMA sin guard, y es la que sobrevivio a la migracion §6.3 y a que yo
diera el eje por cerrado en §9.19.
LA FIRMA — dentro de un bloque cuyo prelude tiene `:dir()`, la misma familia
logica (`border`/`padding`/`margin`/`inset`-`inline`) declarada en LAS DOS caras,
EXACTAMENTE UNA con valor neutro (`0`, `auto`, `none`, `initial`, `unset`,
`revert`). Ese desequilibrio ES el espejo cancelado: la propiedad ya se habia
volteado cuando la regla matchea, asi que reubicarla la devuelve al punto de
partida y la deja en el borde opuesto al de sus hermanas.
Es mas estrecha que «bloque `:dir()` con puras logicas» a proposito. Una regla
que CAMBIA un valor bajo RTL sin reubicarlo es legitima —asimetrico por diseño
existe—, y las dos caras con valor son un autor describiendo dos bordes reales.
Lo que delata el defecto es el PAR, una cara apagada. Hay un test negativo por
cada uno de esos casos, que son los que evitan que la regla se vuelva ruido.
LAS TRES DECISIONES que el handoff dejaba abiertas:
- `:dir(ltr)` tambien entra. Igual de sospechosa, y no anade ruido.
- Marcador PROPIO, `rtl-mirror: <reason>`. Reutilizar `rtl-physical:` mentiria:
aqui no hay nada fisico, y un marcador que miente es peor que ninguno. Mismo
mecanismo (`exemptLines` toma ahora el patron por parametro), dos vocabularios.
Un test comprueba que el marcador de RTL-1 NO exime a RTL-2.
- Regla aparte, `lintRtlMirror()` con su propio `RtlMirrorFinding`. Los 14 tests
de RTL-1 quedan intactos y el tipo lleva los campos que importan
(`family`/`neutral`/`payload`) en vez de forzar los de RTL-1.
VERIFICACION, en este orden:
- `rtl-lint.test.ts` 27/27 (14 de RTL-1 intactos + 13 nuevos).
- RECALL con el runner COMPLETO contra el arbol pre-arreglo: restaure los dos
ficheros de `dee2c9e3a^` sobre el arbol, corri `rtl:check` y los devolvi con
`git checkout` en el mismo bloque. **2/2 cazados** (`feed.css:147`,
`tree-view.css:211`). Probar la funcion no basta: el runner es lo que corre.
- `rtl:check` sobre HEAD: 1 error, el de `palabras`, preexistente y excluido.
- `docs:check` 0/0 sobre 564 docs. `check` 77 = linea base.
Un defecto de presentacion salio al hacerlo: el `calc()` multilinea de tree-view
partia el mensaje por el primer salto. Los campos que RTL-2 emite colapsan el
whitespace; con test.
Actualizados los textos que el handoff avisaba que quedarian obsoletos: el
`enforcement:` y §4 del contrato, la fila RTL del build contract, la fila X-1.6
del checklist, y los docblocks de `rtl-lint.ts` y `rtl-check.ts` — que son
doctrina, no adorno.
⚠️ Sigue siendo cierto lo que NINGUNA de las dos reglas ve: leen texto CSS. Un
`transform` inline escrito por JS, un preset de motion compartido y la geometria
SVG siguen necesitando el ojo en RTL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
7aee33acda |
docs(direction): §10, la cola para manana — RTL-2 ya viene especificada y medida
La unica deuda del eje es el guard del doble volteo, y es la forma que se me
escapo DOS veces (en la migracion §6.3 y en el cierre §9.19). El handoff no la
deja como idea: la deja lista para implementar.
LA FIRMA, mas estrecha que «bloque `:dir()` con puras logicas» —eso daria falsos
positivos sobre reglas legitimas que cambian un valor bajo RTL—: dentro de un
bloque con `:dir()`, la MISMA familia logica aparece como `-start` y como `-end`,
una con valor NEUTRO (`0`/`auto`/`none`) y la otra cargando el valor. Ese es el
gesto «apago un lado y lo repinto en el otro», que cancela un espejo en vez de
crearlo.
MEDIDA ANTES DE ESCRIBIRLA, que es lo que la hace accionable:
- contra `dee2c9e3a^` (antes del arreglo): 2/2 cazados
- contra HEAD: 0 hallazgos sobre 18 bloques `:dir()`
Nace verde y con recall demostrado sobre defectos REALES. Si al implementarla
sale distinto de 0, es un hallazgo nuevo, no un fallo de la regla.
Con las cuatro piezas a tocar (`rtl-lint.ts` y su docblock —que es doctrina—,
`scripts/rtl-check.ts`, y los casos de test, positivos Y negativos), tres
decisiones que NO dejo tomadas (`:dir(ltr)` tambien; escape hatch propio o
reutilizar `rtl-physical:`; regla aparte o ensanchar RTL-1), y el aviso de que
al aterrizar quedan DOS textos obsoletos el mismo dia — los escribi diciendo que
el guard no ve esta forma.
⚠️ NO copiar mi prototipo: usa una regex ingenua sin anidamiento ni comentarios.
`rtl-lint.ts` ya tiene `parseBlocks()`, que da exactamente lo que hace falta.
§10.2 lleva el resto por valor (los charts deberian aceptar `dir` — la
divergencia declarada en §7 del contrato; la tabla de props ausente de
number-field; la numeracion duplicada preexistente del checklist) y §10.3 el
estado del arbol.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
961c1bf8be |
docs(direction): los 55 componentes que aceptan `dir` ya lo documentan
De los que aceptan la prop, la mayoria no la mencionaba en su README, y los que
si tenian seis redacciones distintas para el mismo hecho: «from Soma», «prefs»,
«soma config», «soma», «Override Soma defaults» y —la peor— «Inherited from Soma
when omitted», que se lee como herencia del PADRE, que es justo lo que la cadena
no hace.
Cada uno dice ahora QUE cambia la direccion EN ESE componente, leido de su propio
codigo (el JSDoc de la prop, los usos de `resolvedDir` en el provider, las reglas
`:dir(` de su receta), no una frase de plantilla. Y cada uno enlaza el contrato
una vez, en la primera mencion.
DOS AFIRMACIONES FALSAS, cazadas por el verificador adversarial:
- `feed` decia que la receta «mueve el rail del hilo al otro lado». No lo movia:
lo CANCELABA. Ese era el defecto arreglado en `dee2c9e3a`; el README describe
ahora lo que el codigo hace.
- `waveform` daba `prefs` como default. Falso: waveform es el caso DELEGANTE,
no llama a `activeDir`, y sin prop no hay atributo ni lectura de prefs —
hereda. Su Slider compuesto es quien corre la cadena.
Y dos que estaban mal COLOCADAS: `calendar` listaba `dir` bajo `## ARIA` (no es
un atributo ARIA) y `accordion` bajo `## HTML Attributes` describiendolo como
«la prop cruda», cuando lo estampado es prop O prefs.
CORRECCION AL PRIMER BARRIDO, por la ley de autoria: escribio la cadena literal
`prop → prefs → 'ltr'` en 54 ficheros. Eso es la forma restate-instead-of-link
que `docs/authoring.md` §1 prohibe citando este mismo fallo («'7 families'
sobrevivio en tres docs semanas despues de que el canon pasara a 8»). Retirada:
la cadena se enuncia UNA vez, en el contrato; el README conserva su efecto.
Tres READMEs de eidos ensenaban todavia el selector prohibido —y los tres se
equivocaban sobre su PROPIO CSS, que ya usa `:dir(rtl)`—: waveform, nav-tree y
toolbar (este ademas invertia la precedencia: «prefs o prop»; gana la prop).
⚠️ `number-field` no tiene tabla de props que ampliar; su `dir` sigue en su
seccion `### Direction resolution`, ahora enlazando el contrato. Fabricar una
tabla de una fila diria «`dir` es su unica prop».
Handoff §9.20 con el cierre, el doble volteo y como se colo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
b80ccb33cb |
docs(direction): el contrato pasa a ser canon, y el corpus deja de contradecirlo
El eje estaba cerrado en CODIGO y no en DOCUMENTACION. El contrato vivia solo en
`CONTINUE-direction.md`, un handoff que `docs/README.md` declara «never a source
of truth». Un capitulo E2 lo fija ahora: `docs/canon/direction-contract.md`.
Va a canon y no a arquitectura por la misma razon que `recipe-contract.md`: es
normativo y tiene guard ejecutable. Cubre la cadena y donde corre cada eslabon;
por que `undefined` no es `'ltr'`; las DOS atributos —`dir` crudo (nativo, lo que
`:dir()` mira) y `data-dir` resuelto (opt-in, para recetas que necesitan un hook
incondicional)—; cuando el estampado es OBLIGATORIO; la doctrina de selector; las
trampas que el guard no ve; que espeja y que no; y la mitad global de prefs.
EL CORPUS SE CONTRADECIA en cuatro sitios, y dos de ellos ENSENABAN mal:
- `component-guide.md:372` daba la cadena como `prefs → 'ltr'`, DOS eslabones,
mandando al wrapper a leer prefs directamente. Eso excluye la prop.
- `soma-architecture.md` §3.4 presentaba el resolutor a pelo (`soma.prefs.getDir()`
dentro del provider) como LA forma de obtener la direccion — justo lo que el eje
retiro del catalogo.
- `html.ts` ensenaba en su JSDoc `dir?: 'ltr' | 'rtl'`, el union a mano que la
regla E-2.5 ahora prohibe. El mecanismo que ensena (extender para estrechar una
clave HTML) es correcto y sobrevive; solo cambia el ejemplo.
- `active-architecture.md:416` no listaba `lang` en la proyeccion, contra
`contracts.ts` y contra su propio test de frontera. Igual `prefs/README.md`.
Y no lo mencionaba en absoluto: el glosario (`activeDir`, `resolvedDir`,
`data-dir`, RTL-1, el escape `rtl-physical:`), `eidos.md` (RTL-1 es el TERCER
guard de deriva y faltaba junto al builder tipado y eidos-lint), `soma.md`,
`building-a-component.md` (§Known traps es exactamente donde va «la prop mueve la
matematica y deja la pintura atras»), y la tabla de atributos de la arquitectura,
que afirma «todo lo que viaja entre capas viaja por atributos» y omitia `dir`.
El checklist gana cuatro reglas y el guard que faltaba (`rtl:check` no estaba en
la matriz de aceptacion pese a que la tabla canon lo nombra), mas un recorte en
E-3.5: «todas las props visuales mapean a `data-{prop}`» leia como mandato de
estampar `data-dir`, que `:dir()` no puede ver.
DIVERGENCIA DECLARADA (§7): la familia `chart` no tiene prop ni provider y
resuelve leyendo `getComputedStyle(node).direction`. Es el unico sitio del
catalogo que hace lo que §1 prohibe. Se registra en vez de esconderse.
Tres verificadores adversariales sobre el barrido; sus hallazgos, corregidos:
RTL-1 estaba anunciado como guard de TODO el capitulo (solo cubre §4) · «nunca
lee al padre» era absoluto y borraba la composicion sancionada en el punto de
llamada · el estampado se afirmaba incondicional en un sitio y condicional en
otro · las cuatro filas nuevas usaban una aplicabilidad que ni la leyenda ni
`component-audit.ts` conocen.
`docs:check` 0 errores sobre 564 docs. `check` 77 = la linea base.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
dee2c9e3a6 |
fix(direction): dos reglas :dir(rtl) volteaban lo que ya estaba volteado
Salieron documentando el eje, no auditandolo, que es lo que las hace utiles:
son una forma que yo no habia visto y que el guard no ve.
EL DOBLE VOLTEO — una regla `:dir(rtl)` cuyo cuerpo solo reasigna propiedades
LOGICAS. Cuando esa regla matchea, la propiedad YA se ha espejado, asi que
moverla de `inline-start` a `inline-end` la devuelve al punto de partida; y como
las declaraciones hermanas se quedan quietas, aterriza en el borde OPUESTO al de
la cosa a la que pertenece.
- `feed.css` — el hilo anidado sangraba por un borde y dibujaba su rail en el
otro. El rail marca el borde DESDE el que se sangra, asi que viaja en el mismo
lado logico que el padding y se espeja con el.
- `tree-view.css` — la guia de indentacion quedaba aparcada al otro lado del
subarbol que marca.
El arreglo es RETIRAR las dos reglas: las propiedades logicas ya hacian lo
correcto solas. En su lugar queda un comentario, porque el proximo que lea el
recipe va a tener la misma tentacion.
Medido en Chrome: `feed` da padding-left:20px + border-left:2px en LTR y
padding-right:20px + border-right:2px en RTL — los dos en el mismo borde. La
guia de `tree-view` resuelve a left:16px en LTR y right:16px en RTL.
⚠️ NO pude ver los pixeles: el panel del navegador no estaba visible.
COMO SE COLARON: la migracion de §6.3 (`[dir='rtl']` → `:dir(rtl)`, `ec533845d`)
cambio el SELECTOR de las 12 reglas sin preguntarse si el CUERPO era correcto.
Una migracion mecanica hereda los defectos que traduce. RTL-1 tampoco las ve —
busca un desplazamiento FISICO contra un ancla logica, y aqui todo es logico.
Extender el guard a esta forma queda pendiente.
Ademas: el JSDoc de `dir` en waveform prometia «@default from Soma config». Es
falso — waveform es el caso delegante, no llama a `activeDir`, y sin prop no hay
atributo ni lectura de prefs: hereda.
`eidos-lint` feed/tree-view: invalid 0. Ningun test afirma sobre las dos reglas.
`rtl:check` sigue en 1 error, el de `palabras`, preexistente y excluido.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
f9e372e6fe |
feat(media-player): captions propias + el eje de dirección, canonizado
Dos hilos que comparten ficheros, así que van juntos. ## Captions — el player pinta las cues La parte estaba declarada en el morfo desde el principio y nunca implementada. El render nativo no servía: con la pista en mode='showing' el navegador dibuja su caja de cues en bottom:0 del <video> — la misma franja de 49px que la barra de controles — y contra el z-index:4 de la barra al 82% de opacidad llegaba el 18% del texto. ::cue no puede moverlo (la spec no admite propiedades de posición ahí). Ahora la pista se mantiene en 'hidden': la spec deja la pista activa —cues parseadas, activeCues mantenida, cuechange disparándose— y el navegador no pinta nada, así que el texto se monta en la parte Captions, por encima de la barra. Es el movimiento de Plyr, y por el mismo motivo. Tres trampas de la especificación, encaradas por escrito: - Pasar una pista a 'disabled' desactiva sus cues SIN disparar cuechange. Nadie avisa de que hay que borrar, así que se limpia a mano — si no, el último subtítulo se queda congelado sobre el vídeo. - Los eventos de cue no corren mientras está puesto el show-poster flag, así que el primer pintado se siembra leyendo activeCues. - cues/activeCues son null en 'disabled'. De paso cierra un defecto latente: captionsOn era un espejo de una sola dirección que nunca releía track.mode, así que si el SO activaba subtítulos salían cues mientras el botón decía que estaban apagados. Ahora se DERIVA de los modos reales, y una pista que el navegador encienda se adopta (degradada a 'hidden', para que no pinte encima de la nuestra). Decisiones declaradas: el marcado inline se conserva (se monta el DocumentFragment de getCueAsHTML como nodos, nunca por innerHTML — el sumidero de XSS que abre Plyr al serializarlo de vuelta a cadena); la metadata de posición (line/position/align/vertical/snapToLines/regiones) se DESCARTA. Esto no es una implementación de WebVTT y el README lo dice. ## Dirección — el player estaba partido por la mitad Con dir="rtl" por prop y sin dir ambiental, el cromo se disponía en LTR y sus dos Sliders en RTL: el wrapper pasaba la prop cruda y sólo la reenviaba a los hijos, sin resolver la cadena ni estampar. En la demo estaba enmascarado porque el stage estampa dir además de pasar la prop. Canonizado con el patrón de CONTINUE-direction.md §9.16: activeDir en el wrapper, resolvedDir en el provider, dir crudo + data-dir resuelto en los props. El censo del §9.15 no lo tenía — era un cuarto caso que su búsqueda no cubre (acepta dir y no lo escribe en ningún sitio). Y los glifos direccionales no se volteaban: en RTL el transporte se leía ▶▶ ▶ ◀◀, con las dos flechas exteriores apuntando HACIA DENTRO — el síntoma que carousel.css ya documentaba. Se voltean con scale:-1 1 (no rotate:180deg: sólo coinciden en glifos verticalmente simétricos, y rotar el PiP le llevaría el recuadro a la esquina de arriba), anclado a data-dir, el atributo propio del componente. Alcance elegido: seek, play, volumen y PiP; captions y fullscreen NO. El teclado pasa a resolver por getDirectionalKeys, así que las flechas concuerdan con el glifo y con el Slider embebido. ## Verificación check 0 errores propios · vitest del player 14/14, con los tres guards nuevos vistos en ROJO por mutación · eidos-lint invalid 0 (morfo-backed 25 → 29) · morfo:check sin fallos del player · component:audit PASS · smoke PASS · docs:check 0 · prettier limpio en lo propio. A ojo en Chrome real: claro y oscuro, LTR y RTL, y las dos regresiones (el espejo del SO y el subtítulo congelado). Nota: el README de soma se lleva una línea ajena — la fila `dir` de la tabla de props, del barrido de otra sesión, que describe justamente esta cadena. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
76c807d3c7 |
docs(direction): 9.19, el alias y los cuatro de solo-CSS — el eje queda cerrado
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
d490f53e95 |
feat(direction): los cuatro que pintaban con :dir() no dejaban afirmarla
feed, nav-tree, sidebar y switch tienen reglas `:dir(rtl)` en su receta pero
no aceptaban `dir`. Funcionaban —`:dir()` lee la direccion HEREDADA, y la
proyeccion de prefs pone `dir` en el `<html>`— pero solo para la pagina
entera: no habia forma de voltear UNO. Ahora corren la cadena canonica
`prop → soma.prefs.getDir() → 'ltr'` con `activeDir` en el envoltorio, como
los otros 49.
Estampan el CRUDO (`opts.dir.current`, no `resolvedDir`) por la razon de
siempre: `:dir()` lee la direccion RESUELTA del elemento, asi que una
afirmacion que no llegue al DOM movería el contrato y dejaria la pintura
atras — el mismo partido-por-la-mitad que costo el arreglo de tree-grid y
float-panel.
`switch` no pudo ir por `OptsFromProps`: ese helper quita el `undefined` del
prop publico y da `Active<'ltr' | 'rtl'>`, cuando «nadie afirmo nada» es
justo el valor que el estampado crudo necesita distinguir. Va aparte, con
`ActiveProps<{ dir: Direction | undefined }>`, y en el envoltorio fuera del
`bindProps` (que toma getters, no Actives).
sidebar tenia ademas un resolutor propio en el motor flotante
(`soma.prefs?.getDir() ?? 'ltr'` a pelo, linea 662) que se saltaba el prop:
ahora consume `resolvedDir`. Ese si necesita valor concreto —el motor espeja
la colocacion del flyout— a diferencia de la receta, que lee `:dir()`.
Verificado: `check` sin errores nuevos en los cuatro (77 total = la base con
los ficheros de media-player, que son de otra sesion). Tests de feed y switch
en verde tras anadir `dir` a sus helpers de opts. En SSR los cuatro estampan
`dir="ltr"`; en Chrome el switch pasa a `dir="rtl"` al girar la preferencia y
la regla dispara —`--_switch-thumb-offset` va de `16px` a `calc(-1 * 16px)`.
La transicion del pulgar no se pudo medir: el panel del navegador estaba
oculto y las transiciones CSS quedan congeladas ahi.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
1884500725 |
refactor(direction): ocho componentes escribian el union a mano en vez del alias
`Direction` existe en `soma/types.ts` y es lo que consume el resto de la cadena — `activeDir`, las opts de los proveedores, `resolvedDir`. Estos ocho declaraban `dir?: 'ltr' | 'rtl'` literal, que compila igual pero deja el contrato fuera del alias: renombrar o ensanchar `Direction` no los tocaria y la deriva no rompe en tipos, que es justo lo que el alias esta para garantizar. Son calendar, command, emoji-picker, field-langs (props Y el spec de lengua del proveedor), month-grid, range-calendar y year-grid. Cambio de tipo puro, sin efecto en runtime. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
bfb58b9c78 |
docs(direction): 9.18, la auditoria de la canonizacion
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
3b4340d2ad |
fix(direction): auditoria de la canonizacion — 2 defectos propios y 1 artefacto
Repaso critico del refactor de los 11, con el criterio CORREGIDO que la otra sesion demostro que faltaba: buscar tambien quien ACEPTA `dir` y cruzarlo con quien lo resuelve. DEFECTO 1 — anadi la prop pero NO el estampado. `tree-grid` y `float-panel` usan `:dir()` en su recipe y no estampaban el atributo, asi que un consumidor que afirmase `dir` por prop movia la MATEMATICA y dejaba la PINTURA atras: el `:dir()` lee la direccion efectiva, que sin atributo es la heredada. Es el mismo partido-por-la-mitad que la otra sesion hallo en `media-player`. Verificado tras el arreglo: `tree-grid` estampa ltr/rtl/auto correctamente y el grip del `float-panel` pasa de `nwse-resize` a `nesw-resize` con el atributo. Los otros 5 del grupo B no llevan `:dir()` en su recipe, asi que no estampar no rompe nada; se dejan como estan para no ampliar el DOM sin motivo. DEFECTO 2 — artefacto de mi regex: cinco ficheros quedaron con un espacio antes de la coma (`OnChangeFn , Direction`). Prettier no lo corrigio porque ya estaban sin formatear en HEAD, asi que no pasaba por su escritura. Corregidos a mano. FALSO POSITIVO revisado y NO tocado — `waveform` acepta `dir` y no lo resuelve, pero **no esta roto**: no tiene matematica direccional propia. El recipe espeja con `:dir(rtl)` (medido: la onda pasa a `scaleX(-1)`) y el Slider que compone corre la cadena por su cuenta; el wrapper solo reenvia el crudo, que es lo que mantiene ambas mitades de acuerdo. Lo unico corregido ahi es el COMENTARIO, que afirmaba que el recipe se apoya en `[dir='rtl']` —prohibido desde 6.3— cuando usa `:dir(rtl)`. `check` sin errores en ninguno de los componentes tocados. 16/16. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |