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 }
1768 Commits (52fd56c9f362dba5edeea500bfe637e02ba67d48)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
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 |
|
|
5269c4f6a9 |
feat(direction): `dir` pasa a ser prop en los 3 ultimos del grupo B
`float-panel` · `virtual-grid` · `virtual-list`. Cierra el censo: los 49 componentes con logica direccional resuelven ya por la MISMA cadena, en el MISMO sitio. Mismo patron que 9.16 y que los cuatro anteriores: `dir?: Direction` en types, `activeDir(() => dir, soma)` en el wrapper, `resolvedDir = opts.dir.current ?? 'ltr'` en el provider. Las lecturas sueltas de `soma.prefs.getDir()` que quedaban dentro de la matematica —el calculo de la semilla anclada y el edge fisico del resize en float-panel, el signo del translate de las celdas en los dos virtualizadores— pasan a leer ese `resolvedDir`. Tres factorias de test recibieron el campo; el compilador enumero el trabajo en cada paso, que es justo para lo que el handoff recomienda ensanchar el tipo primero. `check` sin errores en ninguno de los 11 componentes tocados. 22/22. NOTA: el conteo global fluctua entre 74 y 77 durante la sesion por cambios de `media-player`, que pertenecen a OTRA sesion sobre esta misma rama y quedan fuera de todos estos commits. |
2 months ago |
|
|
23315b711e |
feat(direction): `dir` pasa a ser prop en 4 de los 7 que no la exponian
Grupo B del censo (9.17). No usaban otro mecanismo: usaban el MISMO con el
primer eslabon ausente —leian `soma.prefs.getDir()` porque no habia prop que
consultar—, que es lo que el contrato prescribe sin prop. Lo que se subsana es
la ASIMETRIA de superficie: 42 componentes dejaban fijar la direccion de su
subarbol y estos no.
Hechos aqui: `tag-group` · `grid-list` · `tree-grid` · `drawer`.
El patron, identico en los cuatro y ya probado en 9.16:
types.ts `dir?: Direction` documentado con la cadena canonica
wrapper `dir: activeDir(() => dir, soma)`
provider `resolvedDir = opts.dir.current ?? 'ltr'` (solo el fallback)
atributo el CRUDO (`opts.dir.current`), omitido si nadie afirmo
⚠️ `drawer` merece cuidado: ya exponia `direction` (el BORDE al que se pega:
top/right/bottom/left/start/end). Ahora convive con `dir` (la direccion de
LECTURA, ltr/rtl), que es justo lo que resuelve `start`/`end` a un lado fisico.
Ambas quedan documentadas apuntandose la una a la otra para que nadie las
confunda.
Cinco tests apoyaban su expectativa en el mecanismo viejo (el harness devolvia
una direccion por `prefs.getDir()` y esperaban verla resuelta dentro del
provider). Actualizados: el provider recibe la direccion YA resuelta, que es lo
que el wrapper le entrega. El del drawer gana ademas la comprobacion de que un
borde FISICO (`left`) ignora la direccion de lectura.
`check` = 74 = linea base (medido con el arbol limpio; los 2 errores extra que
aparecen en el conteo son de `media-player`, tocado por otra sesion y ajeno a
este trabajo). 15/15.
QUEDAN 3 del grupo B: `float-panel`, `virtual-grid`, `virtual-list`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
3c73af90a3 |
docs(direction): 9.16 grupo A canonizado, 9.17 el grupo B es decision de API
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
b73d6baf8c |
refactor(direction): la cadena de `dir` baja al wrapper en los 4 que la resolvian en el provider
`013ceac57` unifico DIEZ formas de resolver la direccion en una: la cadena `prop -> soma.prefs.getDir() -> 'ltr'` vive en el WRAPPER (`activeDir`) y el provider solo pone el ultimo fallback. El censo de 6.12 dio por hecho que los 36 restantes ya la seguian, pero contaba los wrappers con default duro y no los que resuelven en OTRO sitio. Cuatro se habian quedado fuera: css-field · listbox · navigation-menu · number-field Todos pasaban la prop CRUDA (`readableActive(() => dir)`) y duplicaban la cadena dentro del provider. Dos de ellos —`listbox` y `navigation-menu`— lo documentaban como desviacion consciente («Unlike the other components…»). Funcionalmente eran equivalentes HOY: verificado que `number-field` y el canonico `scroll-area` estampan lo mismo con el toggle en `auto`. Pero la divergencia era real y latente en dos: css-field y number-field estampaban el valor RESUELTO en el atributo. Con un esquema de prefs sin dimension de direccion, `getDir()` devuelve undefined: los canonicos omiten el atributo y esos dos estampaban `'ltr'` — el mismo defecto que `013ceac57` corrigio en 33 componentes, superviviente en 2. Ahora estampan el crudo (`opts.dir.current`), como el resto. Tres tests se apoyaban en el mecanismo viejo (el harness devolvia una direccion por `prefs.getDir()` y esperaban verla en el provider). Actualizados para reflejar el contrato: sin afirmacion, el atributo se OMITE; con ella, se estampa. `listbox` gana ademas la comprobacion explicita de ese par. Verificado en Chrome en las tres posiciones del toggle (`ltr` / `rtl` / `auto`+arabe): `number-field` y `listbox` estampan y computan igual que antes. `check` 74 = linea base, 25/25. QUEDA (grupo B, 7 componentes): `drawer`, `float-panel`, `grid-list`, `tag-group`, `tree-grid`, `virtual-grid`, `virtual-list` NO exponen prop `dir` y leen prefs directamente. No estan rotos —es la cadena con el eslabon de prop ausente— pero son una asimetria de API: 42 componentes dejan fijar la direccion de su subarbol y esos 7 no. Anadir la prop es superficie publica. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
8cda5fd252 |
docs(direction): 9.15, censo de canonizacion — 38 canonicos, 11 fuera de la norma
El censo de 6.12 estaba incompleto: contaba los wrappers con default duro pero no los que resuelven en el provider (4) ni los que no exponen dir (7). Ninguno esta roto hoy — verificado que number-field y scroll-area estampan lo mismo — pero quedan dos decisiones: mover la cadena al wrapper, y si los 7 sin prop deben exponerla. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
7b306ae5d7 |
docs(direction): 9.13 decidida (teclado fisico) y 9.14, el resize del float-panel en RTL
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
552f4f2c77 |
fix(float-panel): el resize se calculaba al reves en RTL, y se salia de sus bounds
Reportado por el usuario con una captura del grip en la esquina inferior
IZQUIERDA. Ahi es donde debe estar en RTL —el recipe lo coloca con
`inset-inline-end`, logico— pero la matematica no acompanaba.
`onResizeMove` leia el edge como si fuera FISICO:
if (edge.includes('e')) width = start.width + dx; // dx es fisico
Los handles se colocan con insets LOGICOS, asi que el marcado `e` es el borde
inline-END: fisicamente derecho en LTR y fisicamente IZQUIERDO en RTL. El
resultado, medido: arrastrar el grip hacia FUERA encogia el panel — 436 -> 327
donde debia crecer a 545. El eje de bloque si estaba bien (292 -> 346).
Arreglado resolviendo el edge logico a su lado FISICO una sola vez
(`physicalResizeEdge`), de modo que toda la geometria de abajo sigue en
pixeles fisicos — la misma forma que usa `drawer.resolveDirection()`, que 6.9
senalo como el ejemplo a imitar.
Y eso destapo un SEGUNDO defecto, latente: crecer desde `w`/`n` mantiene el
lado opuesto fijo, asi que el origen del panel viaja hacia fuera, y ese caso
no se acotaba contra los bounds — el panel se salia del contenedor (medido
`POS -85` con el stage empezando en 0). En LTR la rama era inalcanzable: solo
montan `e`, `s` y el grip `se`. Acotado por el hueco disponible hasta el
limite.
El grip ademas mentia visualmente en RTL: `cursor: nwse-resize` (flecha ↘↖) y
los chevrones a `-45deg / bottom right` son fisicos y no tienen forma logica.
Par `:dir(rtl)` con `nesw-resize` y `45deg / bottom left`.
Verificado con raton real y a ojo en las dos direcciones:
RTL arrastre hacia fuera 300 -> 409 de ancho, borde DERECHO fijo en 803
RTL arrastre largo se detiene exacto en el borde del stage (left 0)
LTR sin cambios 300 -> 436, borde izquierdo fijo en 24
DECISION documentada (el usuario la dejo a mi eleccion): el movimiento por
teclado del panel —flechas y `Home`/`End`— se queda FISICO. No hay patron APG
para paneles flotantes movibles; el analogo, el Window Splitter, define las
teclas por VALOR («la posicion que da al panel primario su menor/mayor
tamano») y no menciona RTL, pero un panel que se arrastra libre por un plano
no tiene valor, solo posicion. Sin semantica que respetar, el vocabulario
fisico es coherente CONSIGO MISMO —`bounds.left`/`bounds.right` ya lo son— y
esa es la misma excepcion legitima que dejo intacto al `toast` en 6.10.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
013af67d7b |
docs(direction): 9.12 color-picker cerrado por las referencias, 9.13 float-panel sin precedente
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
46ffe1c74b |
fix(color-picker): el area 2D y las rampas de canal no se espejaban en RTL
El eje X del area lleva un CANAL de color repartido por la dimension inline —
un eje de lectura— asi que espeja, igual que el `ColorArea` de react-aria
(«orientation of the gradient background, positioning of the thumb, and
dragging behavior is automatically mirrored»). Una rueda radial no lo haria:
es el mismo corte lectura-vs-radial que ya usa la regla de las graficas. Mi
analogia previa con el `cropper` era mala y queda retirada — alli paneas una
imagen, aqui recorres un canal.
Medido antes: pinchar el MISMO punto fisico daba el mismo valor en las dos
direcciones, y el degradado no seguia al thumb.
Lo que faltaba, por pieza:
area · puntero la fraccion fisica se espeja sobre el canal
area · thumb ancla PHYSICA (`left`) alimentada ya espejada
area · flechas ArrowRight mueve el thumb a la derecha ⇒ BAJA el canal
area · degradados saturacion y arcoiris de hue voltean con `:dir(rtl)`
sliders de canal solo el degradado
⚠️ El thumb conserva `left` FISICO a proposito: el recipe lo centra con
`translate(-50%,-50%)`, y ancla logica + translate fisico es justo la trampa
que el contrato de RTL prohibe. Soma le pasa la fraccion ya espejada
(`physicalXProgress`).
Los sliders de canal casi no necesitaban nada: desde el refactor de 2026-05-21
componen el `SliderProvider` generico, que ya espeja thumb, puntero y teclado.
Pero su rampa estaba clavada a `to right`, asi que el color BAJO el thumb
dejaba de ser el valor — medido con hue 210: thumb a 123px del borde derecho y
degradado contando aun desde la izquierda.
Verificado en Chrome en las dos direcciones: pinchando al 10% del borde
izquierdo fisico, LTR da saturation 10 y RTL da 90; el blanco del area y el
rojo del hue quedan a la derecha en RTL; ArrowRight mueve el thumb a la derecha
en ambas. Los dos tests nuevos salen ROJOS con el provider anterior.
`Home`/`End` se quedan en min/max del CANAL (un valor, no un borde fisico),
igual que en un slider.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
ac2ca97444 |
docs(direction): 9.11 ejecutada — el lang del documento ya sigue al idioma
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
41e1f28306 |
feat(prefs): el `lang` del documento sigue al idioma, como ya hacia el `dir`
`src/app.html` trae `<html lang="en">` hardcodeado y NADIE lo actualizaba: con arabe seleccionado la pagina quedaba correctamente espejada (`dir="rtl"`, que la proyeccion si escribe) pero anunciada en el idioma equivocado. El `lang` gobierna seleccion de fuentes, corte de palabras, glifos de comilla y lo que lee CUALQUIER lector de pantalla. `lang` viaja con `dir` porque responden a la misma pregunta sobre el documento y el navegador lee LOS DOS del DOM. La dimension `language` ya estaba en el esquema —es de donde `prefs` deriva la direccion— asi que el slot ya estaba disponible: el valor efectivo es un tag BCP-47, que es justo lo que el atributo toma. Un esquema SIN la dimension `language` no produce slot y no se proyecta nada, asi que una app que gobierne el `lang` desde el servidor (i18n por routing) queda intacta. Cambia el contrato declarado: `prefsDomProjection.ownsAttrs` pasa a `['dir','lang','data-motion','data-sound','data-haptic']`, con su guard en `contracts.test.ts` y el README de prefs. Medido en Chrome por el camino real (el selector de idioma de la shell): ar -> lang=ar/dir=rtl · en -> lang=en/dir=ltr · es -> lang=es/dir=ltr. Un solo `language.set` mueve los dos: el `dir` por derivacion, el `lang` directo — y eso queda fijado en `dom-projection.test.ts`. Sin tocar el `<main lang="en">` de las demos: NO es el defecto, es correcto — la prosa de las demos esta en ingles (decision de 6.4). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
23d2e94783 |
docs(direction): el dir vacio era el shorthand {dir} de Svelte, y el diagnostico del html lang
9.10 cierra el dir=\\` que 6.5 dejo sin investigar: el shorthand emite una asignacion de PROPIEDAD ademas del atributo, y con undefined queda la cadena vacia. No estaba en el SSR — lo escribia el cliente al hidratar. 9.11 deja el html lang diagnosticado pero NO ejecutado: la causa es app.html con lang=en hardcodeado, el arreglo seria corto (la shell ya registra language en prefs) pero cambia el contrato ownsAttrs de la proyeccion, y hay un argumento en contra — en apps con i18n por routing el lang lo fija el servidor. Decision de arquitectura, pendiente de criterio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |