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 }
288 Commits (56d7f744e993c8cabda4b02c6e2508923dfe4d93)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
a542f9f10d |
feat(eidos): chronos abre su estructura, y el cuerpo por defecto la estrena
C5 rebanada 1 de 4. Que partes son publicas no es cuestion de gusto: es `kind` en el morfo (21 publicas, 12 privadas). Namespace `Chronos.*` con la forma de Table/Calendar, y la raiz gana ranura de composicion — pasar `children` REEMPLAZA el cuerpo por defecto. Antes se renderizaban ADEMAS de la vista completa, un segundo arbol colgando de un calendario entero; ningun consumidor los pasaba. Ocho partes de toolbar (`Toolbar`, `Heading`, `Prev`/`Next`/`Today`/`Undo`/ `Redo`/`SearchButton`), ergonomicas al estilo `Calendar.PrevButton`: componen el IconButton/Button del sistema, su glifo y la llamada al provider, asi que el consumidor recibe un control que FUNCIONA y no un gancho vacio. El cuerpo por defecto se recompone desde ellas — no es un camino privilegiado, es el primer consumidor de la API compuesta. `searchOpen` sube al provider: en un toolbar compuesto el boton y la paleta viven en subarboles distintos y el provider es el unico sitio que alcanzan los dos. Y la prueba compuesta destapo un hueco real — el SearchButton compuesto no abria nada porque la paleta vivia en el cuerpo por defecto. Los overlays pertenecen a la RAIZ: extraida a `chronos-search-palette.svelte`. El Dialog del editor tiene el mismo hueco y sigue dentro de la vista; es lo primero de la rebanada 2. Dos declaraciones falsas mas, del patron de siempre: `toolbar` declaraba `header` y se pintaba un `div`; `heading` declaraba `div` y se pintaba un `span`. Los wrappers renderizan lo DECLARADO, medido sin desplazamiento. Medido con dos instancias en la misma pagina: next en la compuesta mueve solo la compuesta (junio -> julio), prev en la de por defecto mueve solo esa (junio -> mayo), ids de heading distintos, y la paleta de cada raiz lista sus 18 eventos por separado. check 74 = base · 458 vitest · morfo:check PASS chronos · docs:check 0/621 · rtl:check 0/177 · eidos-lint invalid 0 · consola limpia en las tres vistas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
05d422ef4a |
docs(web): cuatro paginas del sonido, escritas para quien llega de nuevas
Correccion del autor sobre la primera version: «la redaccion esta mal,
empieza como si la gente supiera ya que es lo que has pasado, la redaccion
tiene que ser didactica para un nuevo desarrollador». Tenia razon — estaban
escritas como notas de version para quien vivio el rediseño: se abrian
presumiendo el vocabulario y presumian de cuantas reglas se habian borrado,
que es mi historia, no documentacion.
Reescritas desde cero, en ese orden: primero la escena («guardas un
documento y oyes un repique»), luego el vocabulario construido pieza a
pieza (familia, verbo, intent) y solo entonces la maquinaria. Cada
concepto se explica antes de usar su palabra.
/uix/docs/sound como suena una interfaz — el modelo, con la
resolucion en vivo (familia + verbo + intent)
/uix/docs/sound/catalogo cada sonido que existe, audible, con la razon
de por que suena asi y que significa cada eje
/uix/docs/sound/packs traer tu propia voz: sintesis, wav, mp3,
registrar nombres nuevos, cambiar en caliente
/uix/docs/sound/gestos por que un arrastre suena por repeticion, con
un simulador de tres velocidades para oirlo
DERIVADAS DEL CODIGO: los sonidos, familias, intents y verbos salen de
`SEMA_MAP` y del resolver real. Solo la prosa esta escrita a mano, asi que
añadir una entrada al catalogo la hace aparecer con sus medidas.
Lo historico no desaparece, cambia de sitio: donde aporta —por que los
sonidos son tan distintos entre si, que se gana y que se pierde con el
trinquete— va al final de su seccion como razon, nunca como apertura.
VERIFICADO: las cuatro responden 200 en SSR con sus secciones · check sin
errores nuevos en las paginas.
⚠️ Sigo sin poder mirarlas: el panel del navegador no compone en esta
sesion, asi que la composicion visual queda pendiente de tu ojo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
6c65caf3f8 |
feat(direction): el censo cierra — picker, gradient-picker y chat-log aceptan dir
Los tres ultimos del catalogo sin la prop, pospuestos en D4 y firmados hoy:
- `picker` (el generico transaccional): `dir?: Direction` publico; el wrapper
corre `activeDir(() => dir)` en vez del `() => undefined` que solo seguia al
ancestro. `PickerOpts` gana la opt y el TIPO arrastra a sus dos composers
(gradient-picker y natural-time-picker pasan su afirmacion) — el generico
condicional de P4 haciendo exactamente su trabajo.
- `gradient-picker`: prop + cadena + morfo `direction: {}` + runtime.
- `chat-log`: prop + cadena + morfo, y reenvia la afirmacion a los
`FeedProvider`/`VirtualListProvider` que compone en triple registro — que la
exigian en OPTS desde la normalizacion y nadie les pasaba nada. **Sus 2
errores de linea base MUEREN: check pasa de 77 a 75.** Solo la bolsa del
chat-log se spreadea en el elemento compartido, asi que el estampado es suyo
(morfo direction) y Feed/VirtualList reciben el valor para su matematica.
El censo-guard (direction-census.test.ts) los cuenta sin excepcion nueva: prop
y morfo declaran el mismo hecho en los tres. Demos: gradient-picker y chat-log
tenian el control del arnes sin cablear — ahora pasan `{dir}`.
Verificado en Chrome, pagina en LTR y direccion por el chip de la demo:
gradient-picker root dir="rtl" · panel portalizado rtl · 0 islas
chat-log auto -> atributo AUSENTE (hereda) · prop rtl -> estampa rtl
picker via sus composers (no tiene demo propia)
check 75 = 77 - 2 (los de chat-log, desviacion explicada: la base MEJORA) ·
censo + compile + suites de los ambitos 73/73 · rtl:check 1 (palabras) ·
docs:check 0/566. El handoff marca la cola D4 como CERRADA.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
aaced3ebec |
fix(media-player): los tres floats no estaban portalizados ni llevaban el dir del player
Una revision adversarial del trabajo del dia (5 lentes, 3 escepticos por hallazgo) devolvio 15 supervivientes de 19. Estos son los que resulto que eran ciertos y son mios; los verifique uno a uno antes de tocar nada. ## 1. Fuga de teclas — el panel no estaba portalizado MEDIDO: con el menu de subtitulos abierto, ArrowDown bajaba el volumen del player de 1 a 0.95. El `<DropdownMenu.Content>` de eidos NO portaliza por si solo — hay que componer `DropdownMenu.Portal`, exactamente como VolumeFloat ya hacia con `Popover.Portal`. Sin el, el panel colgaba DENTRO de `[data-media-player]` y cada tecla del menu subia al `onkeydown` del player. Portalizados los dos menus. Medido despues: cuatro teclas (ArrowDown x2, Espacio, ArrowUp) y el volumen sigue en 1, sin arrancar la reproduccion. Eso arregla de paso otras dos cosas que la revision reporto aparte: el auto-hide de la barra ya no se lleva el menu abierto a opacity 0, y los tres comentarios que decian «the panel is PORTALLED» pasan a ser CIERTOS — lo eran solo en VolumeFloat. ## 2. El `dir` del player no llegaba al menu Los tres floats montan un componente del catalogo EN SITIO y no le pasaban `dir`. El resolutor no tiene paso de padre, asi que cada menu arrancaba su propia cadena y aterrizaba en la preferencia global: lista LTR dentro de un player RTL. Es justo lo que `c73669cde` canonizo horas antes (direction-contract §1, «la composicion lleva la afirmacion») — y el TimeSlider del MISMO componente ya lo hacia bien en dos sitios. `dir` va ANTES del spread para que el valor explicito del consumidor siga ganando. MEDIDO con el player en RTL: el panel portalizado sale `rtl` y coincide con el `data-dir` del player. Antes habria salido `ltr`. ## 3. La demo mentia en cuatro sitios El `eidosSnippet` se habia quedado NUEVE partes por detras del preview vivo (paridad D-3.1): sin transporte, sin tiempos, sin captions, sin PiP, sin los tracks. La rama de audio ignoraba `peaks`, que es justo el control nuevo. La prosa decia «two eidos-only floats» siendo tres, y que al engranaje le quedaban «quality and track» cuando la pista ya tiene lista propia. Comprobada la paridad midiendo el DOM contra el snippet: coinciden. ## 4. La tabla de eventos sema del README (esto era ANTERIOR a hoy) Publicaba `commit-set-time`, retirado en D-AP2.13, y cuatro nombres que ya no existen (`enter-fullscreen`, `exit-fullscreen`, `enter-pip`, `exit-pip`), le faltaba `commit-set-rate`, y daba «provider» como diana de todos cuando el morfo apunta a la parte concreta. Reescrita desde el morfo, los once. 43/43 · eidos-lint invalid 0 · class-hooks 0 · docs:check 0 · check 0 errores propios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
21d817c1f6 |
feat(media-player): con dos idiomas de subtitulos no habia forma de llegar al segundo
`toggleCaptions()` solo alcanza la PRIMERA pista, y a proposito: es un toggle, y las demas se quedan en `disabled` porque poner una en `hidden` OBLIGA al navegador a descargar su VTT — encenderlas todas bajaria todos los ficheros de golpe. La consecuencia es que un video con dos idiomas no tenia ninguna forma de llegar al segundo. `<MediaPlayer.CaptionFloat>` es la lista: `Off` + una entrada por pista, mismo patron que el `RateFloat` de hoy (DropdownMenu + RadioGroup, con el `CaptionButton` real de disparador y su toggle anulado con `preventDefault()` en la costura). OPT-IN: el boton solo sigue siendo el control correcto para una fuente de una sola pista. Por debajo, dos anadidos a soma y ninguno al puerto: - `selectCaptionTrack(track | null)` — pone UNA en `hidden` y el resto en `disabled`, por la misma razon de descarga. - `captionTrackList` + `activeCaptionTrack` — espejos reactivos. El `TextTrackList` vivo es una coleccion del DOM y NO notifica, asi que un chrome que LISTA las pistas no se enteraba de una que llegase tarde. Los refresca `syncCaptions`, que ya corria en addtrack / removetrack / change. Cambiar de idioma NO dispara `commit-toggle-captions`: solo lo hace una transicion real de encendido/apagado. Pasar de ingles a espanol con los subtitulos ya puestos no es un toggle — la misma distincion que el commit de mute hace con `s.muted !== wasMuted`. De paso, tres claves de texto que se me quedaron ayer solo en el LANGS del provider y no en el `texts` del morfo (restart, skip-previous, skip-next), mas `captions-none` para la entrada «Off». El `texts` del morfo ES la superficie i18n que pinta la demo, asi que faltar ahi es faltar de verdad. Verificado en Chrome con dos pistas VTT reales: abrir no toca los modos · exacto UNA pista en `hidden` y el resto en `disabled` · las cues PINTAN el idioma elegido (English «Inline captions demo track», Espanol «Pista de subtitulos de prueba») y Off deja la caja vacia, no congelada · panel 94px. Test nacido en rojo. 17/17 player · morfo:check media-player PASS · eidos-lint invalid 0 · class-hooks 0 · check 0 errores propios (78, los mismos de antes). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
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 |
|
|
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 |
|
|
9fd0de84d6 |
Reapply "feat(media-player): VolumeFloat — el volumen en panel, sin mover la botonera"
This reverts commit
|
2 months ago |
|
|
c798f7465a |
Revert "feat(media-player): VolumeFloat — el volumen en panel, sin mover la botonera"
This reverts commit
|
2 months ago |
|
|
26ddbaa1c9 |
feat(media-player): VolumeFloat — el volumen en panel, sin mover la botonera
Opcional: componer MuteButton + VolumeSlider al lado sigue siendo la forma en linea; esta es la otra y la elige el desarrollador. POR QUE UN FLOAT Y NO UN DESPLIEGUE EN LA FILA. Se probo lo segundo —el patron de YouTube, rail a inline-size 0 creciendo al hover— y se descarto con el usuario delante: un slider que se expande dentro de una fila flex EMPUJA todo lo que va detras (tiempo, scrubber, subtitulos, PiP, pantalla completa) y en una fila apretada desborda. YouTube se lo permite porque su barra ocupa la ventana entera; la nuestra no. El panel va fuera de flujo. VERIFICADO que no mueve nada: se compararon las posiciones de los DIEZ controles con el panel cerrado y abierto — identicas, cero diferencias. Es la promesa que incumpli con el hover y que esta vez se midio. Todo compuesto del catalogo, sin reinventar el flotado: Popover con openOnHover (ya existia; solo se acorto la apertura a 120ms —esto es un control, no un tooltip— y se alargo el cierre a 400ms para poder viajar hasta el), MuteButton como trigger via child (sigue siendo el boton de verdad: conserva aria-pressed, el atajo m y el glifo por nivel) y VolumeSlider vertical, el eje que el morfo declaraba desde el principio. El gesto: hover abre, CLIC SILENCIA y cierra, que es el remate natural de esa accion. En tactil no hay hover, asi que el toque hace las dos cosas; es ademas la unica via por la que un usuario tactil llega al slider. TRAMPA ENCARADA: los tokens --_mp-* NO cruzan el portal (se declaran en [data-media-player] y el contenido sale fuera), asi que el tinte del slider lleva fallback al rol que envuelve. Sin el, el control sale sin color dentro del panel. Demo con chip volume inline|float para compararlos en la misma barra. check 0 errores propios · player 14/14 · eidos-lint invalid 0 · docs:check 0 · prettier limpio. Panel 48x131, slider 32x96 vertical, visto en Chrome. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
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 |
|
|
b906dd4077 |
fix(direction): el atributo `dir` se estampaba VACIO cuando nadie lo afirmaba
El contrato de `013ceac57` dice que `dir` se OMITE cuando nadie ha afirmado una
direccion. `6.5` habia visto `dir=""` en sidebar y tree-view sin averiguar de
donde salia. Es el shorthand `{dir}`: para `dir` Svelte emite una asignacion de
PROPIEDAD ademas del atributo, y con `undefined` el resultado es la cadena
vacia en vez de la omision. Atrapado con un trap sobre `setAttribute` + el
descriptor de la propiedad: dos escrituras, la segunda por propiedad.
No estaba en el SSR (`dir=""` no aparece ni una vez en el HTML servido) — lo
escribia el cliente al hidratar.
Un valor invalido HEREDA, asi que nada pintaba mal; pero contradice la letra
del contrato y hace que `[dir]` matchee.
Arreglado con spread condicional, que si omite:
{dir} -> {...dir ? { dir } : {}}
- `tree-view` (eidos): el wrapper LO NECESITA cuando el valor es concreto — el
recipe selecciona con `[data-tree-view-root]:dir(rtl)` y ese div es el
ANCESTRO del provider que estampa el attr. Con el valor ausente hereda, que
es lo correcto.
- 26 demos: el arnes estandar (`data-uix-stage-area` / `data-uix-canvas-inner`).
Verificado: en `auto` solo quedan `<html>` (la proyeccion de prefs) y el
provider con su valor resuelto — cero `dir=""`; en `rtl` los tres elementos lo
reciben concreto; al volver a `auto` desaparece limpiamente. Las 26 rutas
sirven 200 y ninguna trae `dir=""`.
METODO: mi primer regex tambien matcheaba dentro de `dir={dir}` y dejo
`dir={...}` — dos ficheros con parse error que `check` cazo (76 vs 74). Contar
ficheros modificados NO basta; hay que mirar el diff.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
07515eedf5 |
fix(motion,demos): dos desviaciones propias, encontradas auditando la sesion
1. La regla `:dir()` de `3c3d8e1ac` nacia SIN ACOTAR, o sea matcheaba cada elemento del documento para declarar una custom property que solo lee el nodo animado. Acotada a `[data-animation-style]:dir(...)`: `:dir()` se resuelve contra ese nodo, asi que el caso del subarbol anidado sigue cubierto y el coste deja de ser global. Medido igual que antes — origin 65.05px en RTL / 0px en LTR, con el borde de anclaje clavado en 0. 2. `releasePosition()` en la demo del float-panel (`1857c7854`) soltaba la posicion SIEMPRE, tambien con `useAnchor` apagado: cambiar `side` en modo libre re-centraba un panel colocado a mano. Condicionado al ancla, que es lo unico con lo que la posicion fijada es excluyente. Medido: con ancla off el panel se queda en (24,24); con ancla on sigue re-sembrando (start x=234 · end x=61). Y el hueco de metodo que las destapo: habia tocado `render-css.ts` —el generador de CSS de TODO eidos— corriendo solo los tests del ambito. La pasada COMPLETA da 8 fallos en 4 ficheros, y verifique que ninguno es mio cruzando lo que señalan contra `git diff --name-only 40db0b981..HEAD`: audio-player (sonido), aura + menubar (eje agentico), orca (hook timeout), soma-attr-audit (pasa en solitario: flaky bajo carga). Queda anotado en 9.7 un cabo suelto AJENO: 6.7 retiro la entrada de `data-ready` de `component-visual-attrs.test.ts` al migrar tabs, pero `contracts.test.ts` sigue marcando `data-ready` en radio-group y tabs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
b0f995a3bc |
refactor(metrics)!: placement 'right' pasa a 'end', que es lo que siempre hizo
El CSS de `metrics` es integramente logico (`inset-inline-end`, `padding-inline-end`), asi que `placement="right"` ya ponia el chart a la IZQUIERDA en RTL. Medido: fromRight 0 en LTR, fromLeft 0 en RTL. El comportamiento era el bueno; mentia el nombre. Y era una ISLA: `layout: 'icon-start'`, `align: 'start'` y el resto del vocabulario del componente son logicos — `right` era el unico termino fisico de toda su superficie. No es el caso legitimo del `toast`, que es fisico y coherente CONSIGO MISMO; aqui la incoherencia estaba dentro del componente. BREAKING: `MetricsChartPlacement` pasa de `'below' | 'right' | 'full'` a `'below' | 'end' | 'full'`. Sin shim (doctrina del proyecto): actualizados el tipo, los dos selectores del recipe, el README y la demo. Fuera de ellos no habia consumidores. No tocado y señalado: el token `--metrics-chart-right-width` conserva el nombre fisico. Describe un ANCHO, no un lado, y es superficie publica de theming — su renombrado es otra decision. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
1857c7854d |
fix(demos): drawer, slider y float-panel no dejaban ver su mitad logica en RTL
Los tres eran agujeros de OBSERVABILIDAD — el componente estaba bien, la demo
no permitia mirarlo. Y cada uno lo era por un motivo distinto:
drawer — faltaba el control: solo listaba top/right/bottom/left, y
start/end son justo los que ejercitan RTL. Medido: start da
left en LTR y right en RTL; left fisico NO voltea, correcto.
slider — faltaba el control: secondaryValue solo se veia en la demo del
waveform, que es horizontal, asi que vertical+RTL no era
observable. Cableado como un mando de porcentaje, con
SecondaryRange montado siempre (sin valor pinta a tamano cero).
Horizontal espeja; vertical sale IDENTICO en las dos
direcciones, que es lo correcto: el eje de bloque no gira.
float-panel — el control existia (useAnchor, con side y align) pero otro
estado lo anulaba: posA arranca en {x:24,y:24} y seedRect()
solo siembra con position vacia, asi que la semilla anclada
nunca corria y el align era inerte. releasePosition() la suelta
al tocar useAnchor / side / align, que son excluyentes con una
posicion fija por diseno.
Y aun asi seguia sin verse, por un CLAMP: el disparador nacia
pegado al borde del stage, asi que align=end pedia x=-173 y
volvia a 0. Centrada la fila de disparadores. Entonces si:
start->dLeft 0 / end->dRight 0 en LTR, espejado en RTL, y
center no gira.
Con esto el arreglo de
|
2 months ago |
|
|
ae756ee578 |
fix(scroll-area): el eje horizontal no se espejaba en RTL — el thumb salia del carril y aria-valuenow era negativo
En RTL el navegador DESCANSA scrollLeft en 0 con el contenido pegado a la derecha y lo lleva NEGATIVO hacia el final de lectura (modelo negativo del CSSOM). Nadie lo traducia, asi que resolvedDir era codigo muerto y toda la matematica del eje inline estaba rota: aria-valuenow -50 / -99 con aria-valuemin="0" -> 0 / 50 / 100 thumb left: -167px, FUERA del carril -> dentro y espejado data-at-left siempre puesto -> solo en el borde izq. data-at-right nunca -> en reposo click canalon al 10% desde la izquierda -> 0 -> -232, el 90% logico Dos traducciones privadas en el provider y cada consumidor toma la suya: lo que sigue el eje de LECTURA (offset del thumb, aria-valuenow, click en el canalon) y lo que se queda FISICO (data-at-left / data-at-right, cuyos nombres lo son y cuyo uso documentado —sombras de scroll— tambien). El ancla del thumb pasa a inset-inline-start porque el offset es progreso de lectura: es la decision opuesta a tabs / sliding-indicator, donde el JS media una coordenada fisica. La regla no cambia: mira de que lado esta la medida. El arrastre no necesito nada, y no por suerte: subir scrollLeft mueve el viewport a la derecha en los dos marcos. Verificado, no supuesto. Ambos extremos llevan ahora la misma holgura de 1px, porque solo uno cae en un cero exacto y cual de los dos depende de la direccion. De paso LTR gana el data-at-right del extremo, que tampoco se encendia. Medido en Chrome en las dos direcciones, con el toggle del topbar. El test nuevo sale ROJO con el provider anterior. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
8086f21d6e |
fix(chart): el formato del eje Y no llegaba al tooltip, asi que ambos etiquetaban distinto el mismo punto
Reportado por el usuario con una captura: el eje decia "55k" y el tooltip "55"
para el mismo punto de Marzo.
El chart NO calcula mal. Los datos de la demo son decenas (42, 48, 55...) que
representan miles, el trazado es correcto y el punto cae donde debe. Lo que
fallaba es que el formateador del valor Y llegaba SOLO al eje.
EN EL FRAMEWORK, y afecta a los cinco presets. line-chart, area-chart,
bar-chart, bubble-chart y scatter-chart aceptan `formatY` y lo pasan a
<YAxis format={formatY}> y a nadie mas. El tooltip lo activan con la prop
booleana `tooltip` del Chart, que deja tooltipConfig en null, asi que el valor
cae al formatNumber por defecto. Resultado: `<LineChart formatY={money} />` da
eje "51k" y tooltip "51", y el consumidor NO puede arreglarlo porque el preset
no expone el formato del tooltip. Ahora cada preset declara
`{#if tooltip}<Tooltip format={formatY} />{/if}`: los dos rotulan el MISMO valor
y deben decir lo mismo.
EN LA DEMO. El chart compuesto —el de la captura— pasaba `format={money}` al
YAxis y dejaba `<Chart.Tooltip />` sin formato. Corregido, y tambien el snippet
de ejemplo que la pagina muestra, para que no ensene el patron incompleto.
Medido en Chrome disparando el pointermove sobre los tres SVG a la vez. Antes:
compuesto "May revenue 67 expenses 44", LineChart "Apr revenue 51", AreaChart
"Apr revenue 51" — todos contra un eje 0k..100k. Despues: "67k / 44k",
"51k / 35k" y "51k", coherentes con su eje.
Verificado: check 74 = linea base exacta, 54 warnings tambien. No hay tests de
chart en el repo. Prettier: 19 avisos en esos .svelte ANTES de tocarlos, 15
despues — ninguno nuevo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
5469e05df9 |
feat(direction): las demos dejan de clavar ltr, y la prosa inglesa se declara inglesa
Cierra el eje de direccion abierto en
|
2 months ago |
|
|
013ceac574 |
fix(direction): getDir() no era reactivo, y 33 componentes estampaban su valor congelado
El slider no respondia al cambio rtl/ltr. Resultaron ser dos defectos
independientes, y el segundo era del ecosistema entero.
1) EL SLIDER. slider.css emparejaba un inset LOGICO con un transform
FISICO en las tres reglas verticales (inset-inline-start: 50% +
translateX(-50%)). Los transforms no se voltean, asi que en RTL el rail y
el relleno quedaban fuera de eje un ancho entero. Medido en Chrome:
raiz/pulgar/ticks en x=961, rail y relleno en x=955. El Tick se libraba
porque ya usaba margin-inline-start — ese era el idioma correcto del
propio fichero y es el que aplico. Mismo defecto en accordion.css:
text-align: left en el trigger, que era el UNICO text-align fisico de
todo eidos; ahora quedan cero.
2) LA RAIZ. makeDimension().get() leia engine.snapshot() esquivando la
celda $state, asi que prefs.<dim>.get() — y con ella el
soma.prefs.getDir() que la guia manda usar — era una lectura sin
tracking. Los ~64 componentes que resuelven su direccion desde prefs la
congelaban al montarse. La proyeccion DOM se salvaba porque usa
onChange: misma dimension, dos caminos de lectura, solo uno reactivo.
Arreglado con un contador de cambios POR dimension, bombeado desde el
effectiveDiff que el motor ya calcula, de modo que cubre tambien el
cambio por DERIVACION (direction siguiendo a language) y no solo el
intent. Un contador por clave y no una celda global: con una sola celda,
tocar motion despertaria las 64 derivaciones de direccion. El test
negativo lo clava.
3) VALOR NO ES AFIRMACION. 33 de 37 componentes estampaban dir en su raiz
con un valor siempre concreto, asi que una app que ponga html dir=rtl sin
registrar la preferencia se encontraba 33 islas volteadas del reves.
Investigadas las cinco librerias de referencia: MUI y react-aria no
estampan nunca, Radix estampa siempre (sus mantenedores arrastran
discussions/1405 por esto), y Zag estampa prop("dir") — presente si
alguien lo pidio, ausente si no. Adoptado el modelo de Zag pero uniforme:
el atributo NUNCA tiene defecto. La herencia la hace el navegador por
AUSENCIA de atributo, sin ninguna lectura del DOM, asi que la cadena
prop -> prefs -> 'ltr' se queda intacta.
4) HOMOGENEIDAD. Habia DIEZ formas distintas de resolver lo mismo en los
wrappers y CINCO de declarar el tipo, con un cuarto escalon semantico
—heredar del menu padre— enterrado en dos expresiones sueltas.
Normalizado a activeDir(getter, soma) en los 36, un solo tipo
Direction | undefined, y el escalon de los submenus declarado en la
llamada, donde se ve.
5) LA SHELL. El toggle de direccion pasa de 2 a 3 estados: auto hace
clearIntent, que es lo unico que deja ganar al derive. Y entra el arabe
en el catalogo de idiomas, porque sin un idioma RTL la derivacion no era
disparable. Con las dos cosas, elegir arabe voltea la direccion sin tocar
el control — la cadena documentada funcionando de punta a punta por
primera vez.
Verificado: check 74 = linea base exacta, cero nuevos. 602/602 tests en
101 ficheros. En Chrome real: el topbar mueve al slider y al accordion en
vivo, y el arabe voltea por derivacion.
QUEDA: 48 demos siguen clavando dir='ltr'; el interrogante desplazado del
accordion necesita dir="ltr" lang="en" en el contenedor de la demo (no es
un fallo nuestro, es bidi correcto sobre texto ingles en parrafo RTL);
html lang no sigue al idioma; smoke y perm:check sin ejecutar.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
3ceda5b593 |
fix(media-player): el boton de velocidad no decia su velocidad, y los sliders ignoraban la direccion
Dos defectos que solo aparecieron al MIRAR la pagina en un navegador real
(el panel embebido va a innerWidth 0 y sus medidas mienten: durante esta
misma sesion invento tres "overflow" inexistentes).
1) EL BOTON "x". El usuario pregunto que era ese boton que "no hace nada".
Si hacia: playbackRate llegaba a 1.5. Lo que no hacia era DECIRLO. El morfo
declaraba `data-rate` con propRef pero sin `emit: 'value'`, y sin enum el
compilador lo trata como bandera de presencia (compile.ts: hasEnum false y
emit !== 'value' -> 'html-presence'), asi que emitia data-rate="" para
siempre. El wrapper eidos pinta `{data-rate ?? 1}x` y `??` NO atrapa la
cadena vacia, de modo que el control mostraba un "x" pelado mientras la
velocidad cambiaba por debajo. Un boton que parece muerto. Anadido
`emit: 'value'` (el patron que chronos.ts ya practicaba). Verificado en
Chrome: "1x" -> click -> "1.25x" con playbackRate 1.25.
2) LA DIRECCION. Ni el TimeSlider ni el VolumeSlider recibian `dir`: cero
menciones en ambos ficheros. Caian al default del Slider, que resuelve de
la config de soma — que es GLOBAL de la app. Resultado medido: cromo del
player en ltr con sus DOS sliders en rtl, pulgares anclados al borde
equivocado. Es el mismo defecto que el Waveform tenia hoy ("dir no se
reenviaba al Slider embebido") y el mismo arreglo: el player gana `dir` y
lo reenvia a los dos. Verificado en Chrome: stage/player/ambos sliders
coinciden, tiempo en left:0% con valor 0 y volumen en left:100% al maximo.
La demo reenvia ahora su eje `dir` a los dos roots (AudioPlayer y
MediaPlayer), asi que el RTL del player es comprobable POR PRIMERA VEZ: el
arnes fijaba el stage y los roots no lo recibian.
Verificado: check 75 = baseline, 0 propios; 19 tests; component:audit
--only media-player PASS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
77f8a4e46f |
docs(waveform): la demo carga solo la pista real, y `sample` deja de fingir que es una prop
Fuera los earcons de sema del selector: eran material de INTERFAZ (blips de 80-220 ms; `pop` pintaba casi plano) en la demo de un componente de CONTENIDO, y estaban ahi solo porque eran el unico audio del repo. Con una pista real disponible, ensenaban lo que el componente no es. Y con una sola fuente, un radiogroup de una opcion es ruido: `sample` deja de ser un control y pasa a ser un dato en modo lectura. Eso hace visible en la pagina la frontera que el usuario pregunto explicitamente — debajo (`shape`, `value`, `step`, `secondaryValue`, `readonly`, `disabled`, `dir`, `size`) son PROPS del componente; `sample` y `buckets` NO lo son: son como la demo fabrica los `peaks`. El contrato del waveform es `peaks: number[]` y nada mas (D-WF.2): la app provee los datos, el componente nunca descarga ni decodifica. Verificado: check 75 = baseline, 0 propios; sonda en vivo — la onda sigue pintando los picos reales de la pista (path de 4031 chars), shape/bars vivo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
9050125076 |
fix(waveform): el buffered vuelve a ser banda recta, y la demo estrena material real
BUFFERED — segunda correccion por MIRADA, y la definitiva.
Las dos formas se construyeron y se miraron. Primero fue el SecondaryRange del
Slider con un velo neutro al 14%: rechazado ("el secundario no puede ser del
mismo color"), y la medicion le dio la razon — velo y onda eran alfas del MISMO
token (--color-content-primary al 14% vs 28%), o sea el mismo gris. Se paso
entonces a una tercera copia recortada del path, con la idea de que "el buffered
ES la onda un escalon mas alla". TAMBIEN rechazado, y por un motivo mas fuerte:
"se hace inapreciable y confunde visualmente".
La leccion, que queda escrita en los dos README: al compartir silueta con la
senal, el ojo lee la banda COMO la senal — es ilegible POR CONSTRUCCION y ningun
color la salva. La forma recta es legible precisamente porque NO es la onda. Asi
que el buffered vuelve al Slider.SecondaryRange (G-2), la parte canonica que ya
existia para esto, en vez de que el waveform pinte lo suyo.
El COLOR si sobrevive de la iteracion anterior: acento lavado
(color-mix del primary-solid con content-primary), que se separa del gris en
TONO y no solo en luminosidad. Tokens de paleta, cero literales. Los tres
colores de la onda son publicos y retunables por el disenador.
Nota de metodo: la distancia RGB avalo un gris que el ojo rechazo al instante
(esa metrica esta dominada por la luminosidad y es ciega al croma), y ademas un
bug propio en la alfa del color-mix dio numeros inflados que se corrigieron. Dos
recordatorios de que la medicion informa pero no decide.
DEMO — material real. El control se llamaba `signal`, que es una de las 8
FAMILIAS del canon, en un componente que declara CERO eventos
(expression: 'delegated'): ensenaba lo contrario de la verdad. Renombrado a
`sample`. Y el material eran los earcons de sema (pitidos de 80-220 ms, `pop`
salia casi plano) porque eran el unico audio del repo; ahora arranca en una
pista real de 2:45 (static/music/t2.wav, aportada por el usuario).
Y un defecto que 30 MB destaparon: el efecto de carga dependia de `source` Y de
`buckets`, asi que mover el slider de buckets volvia a DESCARGAR el fichero
entero. Separado en dos: decode una vez por fuente (caro), rebucketing puro y
barato. Medido: t2 (5 MB, 86 ms) frente al master de 48 kHz/16 bit (30 MB,
186 ms); sus envolventes NO son identicas — delta maximo 0.298 por cubo, medio
0.048, porque los 8 bits suben el suelo de ruido y aplanan los pasajes suaves.
Se elige t2 igualmente: 6x mas ligero por una envolvente marginalmente mas fiel.
Verificado: check 75 = baseline, 0 propios; 35 tests; eidos-lint 0 invalidos;
recipe-css-contract verde; component:audit --only waveform PASS; docs:check 0;
sonda en vivo — 2 paths en la onda, banda recta al 66% en rgb(130,95,161).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
df86a4e509 |
fix(slider,media-player): el anillo del commit no enmarca contenedores, y saltar al final no es completar
Cuatro defectos que solo aparecieron cuando el usuario MIRO la pagina.
1) EL BORDE DE LOS SLIDERS ("se viene arrastrando desde el principio").
No era del arquetipo ni del slider: es la firma GLOBAL de la familia
commit. BUILTIN_SIGNATURES.commit monta commit-settle, cuyo 30% pinta
box-shadow 0 0 0 3px. El commit-set del Slider apunta al PROVIDER, asi que
cada paso de flecha y cada release dibujaban un aro alrededor de la caja
entera (medido en vivo: 544x40). Un aro lee como feedback en un boton y
como borde parasito en un rail ancho y transparente. Exencion acotada en
slider.css — el patron que gradient-builder.css ya practicaba. Alcanza a
todo slider embebido (waveform, media-player, color-picker, time-picker)
porque todos llevan [data-slider]. `animation` es la palanca, no
`box-shadow`: las declaraciones de keyframe ganan a las normales, asi que
solo reiniciar el NOMBRE de la animacion detiene la pintura.
2) EL MISMO ANILLO EN EL PLAYER: commit-complete / commit-fail apuntan al
provider -> borde alrededor de los ~700px del reproductor. Misma exencion
en media-player.css. Los botones conservan su aro pequeno (20x36), que es
donde el feedback pertenece.
3) SALTAR AL FINAL ANUNCIABA UNA COMPLECION FALSA. Los botones de salto no
tienen evento propio: seek() delega a proposito (D-AP2.13, "el release del
scrubber ya ES el commit-set del Slider") — cierto para el scrubber y
VACIO para los botones, que no tienen Slider detras. Al saltar mas alla del
final el elemento dispara `ended` -> commit-complete en el provider ->
sonido (es audible por D-AP2.7) + anillo, mientras el boton de retroceso
quedaba mudo. Saltar al final no es completar: es abandonar la obra en el
final. seek()/scrubTo() marcan el salto que aterriza en el final y `ended`
no anuncia; un `play` posterior limpia la marca, asi que el final natural
se sigue anunciando. Los dos botones quedan simetricos y mudos, que es la
doctrina del transporte.
4) MEDIASESSION SIN OVERLAY DEL SO: el modo audio YA funcionaba (metadata
completa, medida en vivo); el roto era VIDEO, el modo por defecto de la
demo. <AudioPlayer> deriva metadata de title/artist/artwork; el
<MediaPlayer> compositivo no puede leer su hijo Title sin volverse
data-driven, asi que la declara el consumidor — y la demo no lo hacia.
El arreglo es de la demo; el art se comporta como D-SR.6/D-AP2.5 lo firmo.
Guard nuevo (no anunciar compleccion al saltar al final; si al final
natural) VISTO EN ROJO antes de darlo por bueno.
Nota de doctrina, tras leer el corpus a peticion del usuario: la exencion
acotada en la receta ES la puerta, no un olor. motion.md §4 fija que la
firma es generica por decision estructural ("un commit asienta igual en
todo el sistema"), eidos es el UNICO dueno del canal visual, los packs de
sema solo transportan sound/haptic, y el VisualChannel no consulta
activeChannels — no hay palanca declarativa para callar solo lo visual.
Queda anotado, menor: commit-settle escribe 3px a pelo donde sus hermanos
announce-pulse-* usan var(--focus-ring-width).
Verificado: check 75 = baseline, 0 propios; 11/11 media-player;
motion.test.ts verde (la firma global intacta); sonda en Chrome vivo —
slider real -> animation none, elemento no-slider con el mismo sello ->
sigue commit-settle, aro de foco intacto.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
4e4f1b612d |
feat(waveform): F4 eidos + F5 demo — y el buffered pasa a ser la onda
F4 (eidos): wrapper raiz que carga slider.css + waveform.css y resuelve
data-size por ActiveEidos; Wave passthrough; recipe con bars = stroke en
UNIDADES DE VIEWBOX (el svg estira con preserveAspectRatio="none", asi el
ciclo barra/hueco sobrevive) y wave = fill sin stroke; espejo RTL en CSS;
el Slider embebido en scope con track/range transparentes y el thumb
convertido en playhead de 2px a alto completo. Tokens waveform:{height-*,
bar-width, color, color-buffered, color-played, playhead-width} —
playhead-width y color-buffered van ANADIDOS sobre la lista firmada en el
gate, ambos declarados en el README.
F5 (demo + dossier): /uix/components/waveform con picos REALES (fetch de
/sounds/*.wav -> uix.sound.decode -> uix.sound.peaks), cada prop como
control vivo, READMEs de soma y eidos. Fix arrastrado de F3: `dir` no se
reenviaba al Slider embebido.
BUFFERED — cambio de forma (decision del usuario tras ver el render).
El primer intento re-tintaba el SecondaryRange del Slider; el usuario lo
rechazo ("el secundario no puede ser del mismo color") y la auditoria
encontro por que ningun color lo arreglaba del todo: ese part es una BARRA
de 6px con la geometria del track, y sobre una pintura de barras al ~58%
de ciclo el 42% de su area cae sobre fondo desnudo, ademas de tenir el
tramo ya reproducido. Ahora el buffered es una TERCERA COPIA del mismo
path, recortada a su fraccion — es la onda un escalon mas alla, la lectura
de SoundCloud/wavesurfer. El waveform ya no monta Slider.SecondaryRange.
Escalera: color 28% -> color-buffered 55% -> color-played acento.
Guard nuevo (bufferedFraction: ausente sin valor, clampado, dominio
degenerado) VISTO EN ROJO desconectando el cableado antes de darlo por
bueno.
Verificado: check 75 = baseline, 0 propios; 5/5 soma + 4/4 morfo;
component:audit --only waveform PASS; eidos-lint 0 invalidos;
recipe-css-contract y motion.test verdes; docs:check 0; prettier limpio.
Queda F6 (mirada del usuario): el panel del navegador no composita, asi
que la verificacion fue por sonda del DOM, sin captura.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
c4d64ba98a |
feat(media-player): modo audio de referencia — AudioLayout, <AudioPlayer> con 4 variantes y buffer real (F4-F7 parcial)
El modo audio deja de ser "video colapsado" (las 3 reglas CSS de v1):
- F4 sema: el docblock del pack escribe la doctrina D-AP2.7 — el silencio
mientras suena la obra lo posee el SERVICIO (duckUiWhileContent, receta
documentada); la resta-de-gain queda disenada sin cablear a proposito.
- F5 buffer real (G-2 consumido): el TimeSlider soma pasa bufferedTime como
secondaryValue del Slider compuesto y monta Slider.SecondaryRange; se
retiran el div .mp-buffered, la var --media-buffered (y su writeProperty),
--_mp-buffered y --_mp-rail. Look intacto: el 42% pasa a
--slider-secondary-bg en scope.
- F5 wrappers eidos de las 6 partes: Artwork compone AspectRatio+Image del
catalogo via child; RateButton compone Button ghost mostrando {data-rate}x;
Artist/Identity/Transport/LiveIndicator passthrough.
- F5 AudioLayout: espejo de DefaultControls para audio — identidad
(artwork+title+artist+live) + transporte + fila de scrubber + secundarios.
- F5 raiz competidora <AudioPlayer> (eidos/components/audio-player/, el
precedente Toast/Toaster): una etiqueta monta todo, y la metadata del SO se
DERIVA de la identidad visible (title/artist/artworkSrc) salvo override —
lo que ves es lo que muestra el lock screen (D-AP2.5).
- F5 recipe de 4 variantes (card/row/bar/inline) scoped a [data-variant]
(componer a mano sigue sin opinar) + tokens publicos --audio-player-*
(gap, artwork-radius, artwork-size-row/bar), regenerados.
- F7 parcial: el badge live usa el text localizado del morfo via soma (fuera
el "LIVE" hardcodeado) · demo con el modo audio real (<AudioPlayer> +
selector de variante + snippet con paridad) · README eidos con la seccion
del modo audio · component:audit --only media-player PASS.
Verificacion: player 18/18 · recipe-css-contract + component-api-contract
37/37 · eidos-lint media-player 0 invalidos · check 74 = baseline, 0 propios
(demo incluida). Declarado: audio-player.css es recipe de composicion sobre
el morfo del player (el lint per-component no la cubre — gap del tooling);
los internos --_mp-* los comparte la recipe hermana del MISMO morfo.
Quedan F6 (verificacion VISUAL de las 4 variantes — exige ojos) y el resto
de F7 (medicion Chrome del escenario mixto + teclas de medios + stamp sema).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
2de1718c3b |
refactor(langs): words se pliega en palabras — y el nombre sale del corpus
El pack words desaparece: sus strings de motor entran en palabrasLangs y las guardas (ACTIVE_DEV_TRACK, WIP_TRACKS) dejan de listar 'words'. Las menciones en comentarios de recetas, READMEs, SPECs, demos y docs de proceso pasan a Palabras. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
e09170f9da |
feat(uix): Menubar.Panel — una entrada puede abrir un panel (role=dialog)
Nueva parte Panel en el morfo (archetype content, role dialog, opcional) y aria-haspopup pasa a propRef: 'menu' cuando la entrada despliega un Content, 'dialog' cuando despliega un Panel. Wrapper eidos Menubar.Panel + receta, y la demo monta el mega-menú/form-panel anclado a la barra o al trigger. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
ae7e3462e5 |
feat(barcode): el ISBN es un PERFIL de entrada sobre EAN-13, no una simbología
Un código de barras de ISBN ES un EAN-13: ISO 2108 mete los libros en el mismo espacio GTIN-13 que todo lo demás bajo los prefijos Bookland 978/979 — por eso el mismo lector lee una novela y una lata de garbanzos. Así que `isbn` NO entra como noveno valor de `symbology`: declararlo haría que `data-symbology` afirmara un estándar que no existe. Entra como perfil de entrada sobre `ean13`, que es la forma que adoptan las implementaciones serias. - **Separadores tolerados** en las simbologías NUMÉRICAS: un ISBN viaja con guiones y un GTIN con espacios. Code 128 y Code 39 quedan fuera a propósito — ahí un guión es dato codificable y quitarlo cambiaría la cadena en silencio. - **ISBN-10 → Bookland**: un valor de 10 caracteres en `ean13` se lee como ISBN-10, se le valida SU dígito de control (pesos 10…1, mod 11, con `X` valiendo 10) y se convierte con prefijo 978 y mod-10 recalculado. La validación previa es la parte que importa: sin ella, un ISBN-10 mal tecleado se convierte en un EAN-13 perfectamente válido que apunta a OTRO libro — el modo de fallo más caro del dominio. Falla con `invalid-check-digit`. - **La hyphenación NO es nuestra** y queda documentada como tal: las posiciones de los guiones no se calculan desde el número, dependen de las tablas de rangos de la International ISBN Agency, que son dato versionado con fecha de caducidad. Meterlas aquí pondría un reloj de mantenimiento dentro de una librería de cero dependencias. Si algún día se pinta la línea `ISBN 978-…` sobre las barras, el string con guiones lo trae el consumidor. Y un defecto que el ISBN destapó, anterior a él: **el nombre accesible mentía**. `aria-label` anunciaba el valor CRUDO mientras el símbolo codificaba otro — pasaba ya con cualquier EAN al que se le añade el dígito de control, y con el ITF-14, y con Code 39 al mayusculizar. Ahora anuncia lo que el símbolo lleva de verdad. - Vectores publicados en los tests: `0-306-40615-2` → `978-0-306-40615-7`, y los dos casos de control `X` (`0-8044-2957-X`, `0-9752298-0-X`). - `D-1.2` estaba ROTO en el commit anterior y no lo vi: prettier parte la unión `type Tab` a 102 caracteres y el audit exige la forma de UNA línea que documenta la guía de demos. Restaurada con `prettier-ignore`; el audit vuelve a PASS con 0 errores. - `F-1.4` se rompió al escribir la sección ISBN: mencionaba `## Gaps` en línea y el regex del audit engancha esa primera aparición. Reescrito. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
3097cfcb69 |
revert: deshacer la auditoría entera — se hizo sin leer la doctrina
Revert de los 7 commits de la sesión del 2026-07-29/30: |
2 months ago |
|
|
c39170abbf |
fix(uix): la auditoría del sistema — lo que los guards no veían
Auditoría clean-room de todo ActiveUIX (excluido `web/`), componente a
componente. Lo que sale de aquí no es una lista de bugs: es un patrón.
El framework validaba que lo escrito fuese VÁLIDO, no que lo declarado
se CUMPLIESE — y sus guards fallaban ABIERTOS.
## El colapso de las uniones de props (95 → 0)
Un `Props` de eidos es `{ …props propias… } & <atributos nativos>`.
Cuando el elemento declara un atributo homónimo, la intersección funde
ambos y una unión estrecha contra el `string` nativo COLAPSA a `string`.
Causa: `Without<T, U> = Omit<T, keyof U>` invocado como `Without<T, {}>`
— `Omit<T, never>`, un no-op — 433 veces en soma; sólo 3 con argumento
real. Invisible para `svelte-check`: ensanchar un tipo no es un error,
es una garantía perdida.
Medido: 95 props en 72 componentes. `<Avatar color="nonsense">`
compilaba. `ComboboxInput.size` chocaba con el `<input size>` numérico
y era inusable. Migrado con codemod sobre AST (nunca regex) a
`Own & Omit<Nativos, keyof Own>`: 92 tipos en 73 ficheros + carousel a
mano. `check` no se movió.
Garantía nueva: `eidos/prop-surface.test.ts` (PROP-1) compara los
literales de la anotación del autor contra los de la propiedad pública.
Verificado que falla reintroduciendo el defecto.
## Los cuatro guards que fallaban abiertos
- `translations:check` crasheaba en CADA ejecución de su historia — un
stripper de comentarios borraba `//` dentro de strings. Sustituido por
import dinámico. Al arrancar destapó 8 slots `texts` sin traducción.
- `soma-attr-audit` agotaba el timeout de 5 s: sin veredicto, verde por
omisión.
- `component-audit` D-7.4 hacía `continue` mudo cuando el tipo no
resolvía. Ahora resuelve con el checker de TypeScript
(`scripts/prop-unions.ts`): puntos ciegos de 124 → 3.
- `component-audit` R-1.1: el regex casaba `[data-motion='reduce']` y
daba PASS por el motivo equivocado.
Regla adoptada: un guard que no puede evaluar TIENE que decirlo. El
informe lleva ahora bloque «Not verified» y recuento en el resumen.
## D-1 · tooltip y D-2 · card, cableados
`tooltip` declaraba 3 eventos `emerge` que nadie emitía. Ahora emiten;
`present` pasa a `sequence: 'post'` — con `'pre'` el hold de ~240 ms
gateaba el montaje del propio overlay.
`card` declaraba `commit-select` sin emisor posible (scope sin soma).
Puente headless en `soma/components/card/` con la forma ya establecida
por `menu-dial` / `onion-menu`: eidos posee estado y render, soma posee
sólo el `SomaRuntime` que emite.
## Documentación: 22 mentiras corregidas
`docs/` afirmaba guards inexistentes (`NO_MISSING_PROVIDER_TESTS`),
APIs con firma equivocada y un modelo de Motion que el código no
implementa. Corregido en CANON, arquitectura, glosario, theming/motion,
checklist y los README de `motion` / `callout` / `arts/motion`.
## Además
- CardGroup: la descripción se metía en la primera celda del grid.
- Motion: `data-state` siempre estampado, salida real en `leave()`,
token fantasma `--motion-stagger-each-default` eliminado.
- `engine-motion`: `handoffState` Map → WeakMap (fuga por nodo).
- `mockup`: primitivo crudo → token de rol (R-4.6).
- 5 catálogos de traducción que faltaban.
Handoff: `docs/process/CONTINUE-audit-2026-07-29.md`.
Batería: check 0 errores en src · vitest server 3701/3701 ·
docs:check 0/0 · component:audit 161 PASS / 2 NEEDS-WORK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
d4c49aedbf |
feat(eidos): `Barcode` — el código de barras 1D, con encoder propio y cero dependencias
Ocho simbologías desde su estándar ISO publicado (Code 128 con auto-switching A/B/C, EAN-13/8, UPC-A/E, Code 39, ITF/ITF-14) en `$libs/barcode`: tablas transcritas de la especificación o derivadas de su regla de construcción, sin una sola dependencia npm añadida. JsBarcode y bwip-js son referencia de corrección, jamás import — la misma doctrina con la que `$libs/qr` usó a Nayuki. Ningún headless del mercado trae barcode: el terreno lo ocupan encoders imperativos sin anatomía ni a11y. - **La física manda sobre la ergonomía visual.** La dimensión X (`moduleWidth`) es el ancla de escaneabilidad y la altura de barra un eje libre — un escáner lee una franja horizontal. Por eso la geometría es px absolutos en el viewBox (precedente: la familia `chart`) y no el token `size` cuadrado del QR: colapsar los dos ejes en uno haría que la X dependiera de la altura, que es justo lo que no puede flotar. La zona muda va en módulos, con el mínimo de cada estándar. - **Lo que hace reconocible a la familia.** Las guardas EAN/UPC bajan hasta la línea base y los dígitos se imprimen dígito-bajo-dígito sobre su celda de 7 módulos, en mono (la HRI del estándar es OCR-B, de ancho fijo). ITF-14 lleva su barra portadora por fuera de la zona muda, que no puede comerse. - **Un valor no codificable es un ESTADO, no una excepción.** `encode` lanza un `BarcodeError` tipado; el wrapper lo captura, marca `data-invalid`, avisa por `onInvalid` (deduplicado por razón: `encoded` es un objeto nuevo por pulsación) y deja un marco placeholder decorativo. Un EAN a medio teclear es un paso normal de edición; dejarlo reventar dentro de un `$derived` tumbaría la página. - **`'text'` entra en `MorfoElement`** — y también en el schema de sium, que tiene su propia lista literal. Añadir solo la unión de TypeScript compila y falla en runtime; lo cazó `morfo:check`, no `npm run check`. Verificación: 48 tests de encoder en tres capas (invariantes estructurales de cada tabla · vectores publicados transcritos aparte · decoders independientes que releen la retícula), la lectura real confirmada con escáner por el usuario, y la retícula decodificada desde el SVG ya pintado en navegador. `component:audit` PASS con 0 errores; los 2 warnings (D-1.5 / D-4.3) son los mismos que da `button`, la demo canaria: sus regex buscan la forma v1 inline del MutationObserver y del `emit`, que la v2 movió al harness compartido. Demo v2 canónica de 9 pestañas. El alcance diferido (Codabar/MSI/Pharmacode, GS1-128, add-ons EAN-2/5, y las 2D como componentes aparte) queda registrado en `next-features.md` §10 — el hueco de numeración se cierra cuando el track de blocks commitee sus §8/§9, hoy sin commitear. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
19e8dbfb63 |
feat(scene): `portal` — la presencia leída como esfera a contraluz (variante)
Encargo del usuario a partir de un componente de referencia de pago cuya fuente NO está disponible (la pestaña «Code» es de clientes). Así que implementación propia — que es la regla de la casa de todos modos: solo el vocabulario de parámetros se dejó cercano para poder compararlos. QUÉ ES Una esfera a contraluz: filo encendido, interior abierto, y capas de estelas que la envuelven y giran — cada una sobre su eje inclinado, con su ritmo, con motion blur a lo largo del barrido. El fragmento se mapea sobre la esfera unidad y las estelas viven en ese espacio 3D, que es lo que las hace curvarse con la superficie en vez de leerse como rayas planas. ES UNA VARIANTE, LA ELIGE EL DESARROLLADOR No sustituye al `orb`: conviven. `orb` está afinado para 32-48px (silueta simple, movimiento perceptible a ese tamaño); `portal` tiene mucho más detalle y luce en superficies grandes — a tamaño de componente pierde justo la irregularidad que lo define. La demo gana un switch **studio** (220px) para poder juzgar eso, porque un shader no se juzga a 40px y forzarlo desde la consola no deja nada para la sesión siguiente. EL INTERIOR ES TRANSPARENTE No se pinta nada dentro: lo que se ve a través es la página. Eso es lo que hace la variante agnóstica al tema SIN una sola rama claro/oscuro — un interior relleno tendría que elegir bando, y elegirlo fue lo que en el primer intento la convirtió en una bola de cristal. Los tres colores salen de los mismos slots de rol que el orb (`solid` en el filo, `solid-hover` en la cara cercana, `element` en la que se aleja): retintas el tema y cambia entera. TRES LECCIONES DEL FORMATO, no de este efecto — anotadas en el README porque las va a repetir quien toque el siguiente shader: - Las estelas no pueden salir de senos. Un `sin()` da anillos de latitud PERFECTAMENTE regulares que se leen como jaula de alambre por mucho que se afinen; la irregularidad (longitudes distintas, trazos sueltos) exige ruido anisótropo — aquí 1 : 9 en longitud/latitud, lento a favor del flujo y rápido a través. - `sin(q.y * freq)` sobre la esfera unidad da `freq/π` ciclos: ~9 son tres trazos finos por capa, 22 es malla, 2 es un borrón. El número sale de la cuenta, no del tanteo — tanteando me pasé de largo en ambos sentidos. - El motion blur PROMEDIA: 5 muestras × 0.16 rad son 46° de barrido, que borra las bandas en vez de estirarlas. Verificado en Chrome real, claro y oscuro, en `acting` y `escalated`, a 48px y en studio. Tests 370 (scene 9 + eidos 361) · eidos-lint aura 0 inválidos · docs:check sin errores nuevos · check en los 73 preexistentes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
2 months ago |
|
|
356ebf92af |
feat(button): el CTA que navega y parece botón, por composición (opción D)
El hueco que registró `site-header`: un «Empezar gratis» de cabecera tiene que
NAVEGAR y parecer botón. `Button` no crece un `href` —`Link` posee la
navegación, y un ancla se activa con Enter, no con espacio, lo cual es
correcto—: presta la pintura por composición. Lo que hacía de esa forma un
downgrade era que el `child` (asChild) descartaba la decoración; se completa el
slot.
- **eidos `<Button>`**: el `child` recibe ahora `content`, el cuerpo YA
decorado (icono · etiqueta · endIcon · spinner) en su propio snippet que
comparten las dos ramas de render. Así
`<a href {...props}>{@render content()}</a>` conserva TODOS los slots en vez
de sustituirlos (antes la flecha del sitio alpha estaba escrita a mano).
Nuevo tipo exportado `ButtonChildProps`.
- **soma / morfo**: en la forma `child` el elemento es del consumidor, así que
soma deja de estampar `type` (un `<a type="button">` es una pista de MIME
falsa). El componente pasa `type: undefined` cuando hay `child`; el provider
lo REENVÍA verbatim (antes lo re-defaulteaba a `'button'` y pisaba el drop —
el default vive en el destructure del componente); el morfo declara el attr
`type` condicional (`prop-truthy`).
- **docs**: ejemplo rancio de `index.ts` corregido (anunciaba un `asChild`/
`variant="link"` que no existen); sección «CTA que navega» en el README de
eidos con el patrón y el footgun documentado (un `<button>` en un `<form>` vía
`child` se pone su propio `type`); nota en el README de soma.
- **site-header**: el CTA de la demo usa ya la forma real (`<a>` sólido con
flecha), y el hueco pasa de «candidato a canon» a CERRADO por composición —
`Button` sigue sin `href` y `Link` sigue poseyendo la navegación, las dos
decisiones firmadas se mantienen. Actualizados PLAN/CONTINUE-blocks.
- **demo de Button**: control `child (asChild → <a>)` vivo, snippet del código
y fila de a11y explicando por qué el ancla activa solo con Enter.
Verificado en navegador (dev, restart para módulos frescos): asChild ON →
`<a href="#pricing">` sin `type`, pintura sólida completa (bg primary, tinta
blanca, 36px, padding 16px), y el slot de icono SOBREVIVE dentro del ancla
(`[data-button-icon]` + svg + body); asChild OFF → `<button type="button">`
intacto (sin regresión de submit implícito); el CTA real de `site-header` sale
`<a>` con la flecha final y 0 errores de consola. `blocks:check` verde ·
`vitest src/uix/morfo` 114/114 · `svelte-check` sin errores propios nuevos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
75e1fe32a1 |
uix(sidebar): review adversarial · 31 hallazgos confirmados, arreglados
Review de 6 dimensiones × 3 verificadores escépticos (135 agentes): 43 hallazgos brutos → 31 confirmados, 12 rechazados. Todos los confirmados arreglados y verificados en navegador real. Estado (los dos ejes) - `collapsible='none'` ya no deja el sidebar INALCANZABLE en móvil: la inercia del modo se limita al escritorio, que es donde «nunca colapsa» significa algo. En móvil el panel es un Drawer que arranca cerrado, el raíl está oculto y el drawer no tiene trigger propio: con el toggle inerte no había forma humana de abrir la navegación en un teléfono. - `'none'` PINTA el estado a expandido: antes `data-state`, `aria-expanded` y `useSidebar().open` podían decir «colapsado» sobre un panel plenamente visible, y ninguna regla del recipe ni el toggle podían reconciliarlo. - El estado del drawer móvil se DESCARTA al salir de la presentación móvil: abrirlo en un teléfono y rotar dos veces remontaba el Drawer ya abierto, con overlay, foco atrapado y scroll bloqueado sin tocar nada. Submenú flotante (era el nudo con más hallazgos) - Cierra también por PUNTERO: se abría en `pointerenter` de la fila, pero el único cierre por puntero vivía en el propio flyout — salir hacia la página sin cruzarlo lo dejaba pintado para siempre. - Escape funciona desde donde el foco ESTÁ (la fila), no solo dentro del sub. - Entrar/salir se resuelve en el `menu-item`, que contiene fila Y sub, así que viajar entre ambos no lo cierra. - El flag se limpia al cambiar de presentación: expandir y volver a colapsar reabría un flyout que nadie había tocado. - El motor flotante se engancha solo cuando el sub está ABIERTO, no por modo: antes `autoUpdate` (rAF + observers) corría por cada fila del raíl. - `hasSub` deja de latir a true: se limpia en el teardown del sub. A11y - Las filas del raíl recuperan NOMBRE: un tooltip solo describe (`aria-describedby`), así que con la etiqueta oculta el texto de `tooltip` pasa también a `aria-label` — incluidas las filas con submenú, que no llevan tooltip visible. - El panel off-canvas colapsado sale del tab order y del árbol de accesibilidad (`visibility: hidden` con la transición retrasada para que el deslizamiento siga animando). - El diálogo móvil recibe el mismo nombre que el landmark. - El raíl se llama «Contraer barra lateral» (lo que hace) en vez de «Redimensionar», que prometía un arrastre fuera de alcance; cursor de puntero en vez de `ew-resize`. Recipe - El signo del off-canvas se deriva de side × dirección: en RTL la fila flex se invierte, así que el negativo fijo barría el panel POR ENCIMA de la página en vez de sacarlo por su borde. - El flyout declara banda `z-index` (nuevo token `--sidebar-sub-z`); era la única superficie flotante del ecosistema sin ella. - La transición de `inline-size` (layout por frame) queda SOLO bajo el modo icono; la base anima únicamente `transform`. - El `Drawer` es el único dueño del ancho móvil (dos dueños dejaban una banda de cromo en dos tonos) y se compone con `dragToDismiss`. - Las reglas de modo icono ya no alcanzan el subárbol flotante: sus filas conservan badges y acciones. - Bloque `prefers-reduced-motion` — la traslación del panel es la mayor del sistema. Demo - Copias corregidas donde afirmaban de más: el tooltip ahora sí NOMBRA (y se explica por qué), y los anchos son rem fijos a propósito (no los alcanza el escalado; sí el interior). - Tabla de eventos y de teclado en la pestaña morfo; `onOpenChange` y una tabla de props por parte en API. - Los chips de estado se inhabilitan en `collapsible='none'`, que es lo que el componente hace; botón «show me» que pone icon + colapsado de una vez. - El trigger pasa a ser solo icono: el morfo le da `aria-label`, que pisaba cualquier texto visible. Verificado en navegador: nombres accesibles en el raíl · flyout que cierra por puntero, por foco y por Escape (con retorno de foco) y que no resucita al cambiar de modo · off-canvas fuera del tab order · RTL sacando el panel por su propio borde · z-index 80 en el flyout · drawer móvil nombrado, a ancho completo y con el panel a ras · `collapsible='none'` abrible en un teléfono. Gates: audit PASS 0E/0W · eidos-lint 47 morfo-backed / 0 invalid · svelte-check 0 errores propios · vitest src/uix/eidos 353/353 · contracts.test solo con los 3 fallos ajenos conocidos. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
e10eed98cc |
uix(aura): la superficie visible del agente — anillo de custodia, orbe-contorno y desenlaces expresados
Rediseño dirigido por el usuario sobre un banco de mockups con el orbe REAL
corriendo dentro. Seis frentes, todos con maquinaria existente:
ANILLO DE CUSTODIA (parte nueva `Aura.Ring`, morfo+soma+eidos)
El estado se lee como FORMA y no solo como tinte: raíl punteado en reposo,
arco que respira al ofrecerse, segmentos = pasos del plan (laten), dos arcos
enfrentados y QUIETOS esperando tu revisión (la quietud lo separa de acting),
arco que gira actuando, anillo cerrado + pulso saliente cuando te necesita.
Envuelve al Indicator (es su marco de referencia, no un hermano posicionado a
mano). El movimiento continuo son loops REGISTRADOS (data-animation-style),
nunca @keyframes de receta (R-4.5) — vienen con su puerta de reduced-motion,
donde el dash sigue distinguiendo el estado. El attr va en los hijos del SVG,
jamás en la parte: motionAttrs() estampa además data-state, del morfo ahí.
EL ORBE ES UN CONTORNO
`thickness` en el efecto orb; el hueco es real (alfa propia) sobre cualquier
fondo. Se va la rama claro/oscuro y el parámetro backgroundColor: el shader
mezclaba con el fondo y derivaba el alfa del canal máximo, o sea pintaba un
disco OPACO del color de la superficie justo en el centro (en claro un círculo
relleno; en oscuro el azul del seed). Y la receta deja de pintar su disco en
cuanto monta el canvas (:has(canvas)) — solo sobrevive donde no hay render
vivo: SSR, tests, effect={false}, sin WebGL.
COLOR = TOKENS DE ROL, PINTADO Y NO ROTADO
colorA/colorB/colorDeep (solid / solid-hover / element) resueltos con
eidos.resolveToken y pasados como hex, que es el seam que el propio arte
documenta. El hue-delta anterior se medía en OKLCH y se aplicaba en YIQ: risk
salía VERDE junto a su propio anillo naranja.
ONION EN EL IDIOMA DEL ORBE
Prop `look="solid" | "luminous"` (defecto solid: nadie más se entera). En
luminous el sector no tiene relleno — filo de luz (pasada borrosa + nítida) y
etiqueta en el color del sector; la cuña sigue siendo área táctil. El trigger
con snippet propio va BARE: sin disco, sin sombra FAB y sin el salto del hover
(el orbe ES el trigger, no su contenido). Medio anillo bottom-center y radios
44/40. El halo respira (loop pulse) y en commit-select da UNA pasada de ping
pivotando en el centro real del menú — la firma genérica de commit anima
box-shadow, que sobre un path de SVG no hace nada.
DESENLACES EXPRESADOS (el hueco real: un fallo era indistinguible de un éxito)
Tres eventos terminales con intent INTRÍNSECO — delegate-return (fulfill) /
-aborted (sin intent: un decline es ausencia, no pérdida) / -failed (loss) —
en vez de un intent dinámico, que no tiene dónde vivir: TriggerOptions no
lleva intent y el fromProp del morfo ata a un prop PÚBLICO, no a estado de
máquina. Puerto `runs?` opcional en AuraAgentSource porque activeRun ya es
null al cerrar (§0: sin él, anuncio genérico). Primeras firmas de la familia
delegate en el registro; la de loss es MOVIMIENTO (return-recoil: retirada +
descenso), no tinte — no estrena ningún hue.
MAPA SEMÁNTICO DE COLOR (demo)
Un hue = un significado en toda la superficie: gris=sin agencia ·
naranja=tu revisión · morado=él actúa · rojo=escalated (exclusivo) ·
teal=tu sí. El anillo de autoridad reusa los colores de la custodia con el
MISMO sentido (Review=risk porque vivirás en reviewing; Auto=primary porque
actúa) y los declines pasan a neutral — canon: el intent de un cancel es
`absent`. Además el anillo en reposo DELEGA (las tres autoridades abren el
run) y se cierra por transición, no siempre que no hay run.
Dos guardas del framework corrigieron el diseño por el camino:
- BK-FRAME-NO-INTENT rechazó dar intent a una familia de marco sin
justificación escrita (excepción A-1): queda el intentRationale — el motor
no emite nada y no hay evento evaluable que enmarcar, así que el retorno
consolidado es el ÚNICO sitio expresivo del desenlace.
- fallbackTarget gana sobre el target del morfo (contrato de TriggerOptions):
el sello caía en el provider. Los terminales pasan el Ring cuando existe.
Verificado en Chrome real (ventana delante: en segundo plano el navegador
congela rAF y ResizeObserver y las medidas mienten): seis estados en claro y
oscuro, y un error de transporte REAL apuntando a un puerto muerto →
delegate-return-failed · loss · return-recoil sobre el Ring · región
«Agente: falló — control devuelto».
Tests 478 (morfo 114 · soma/aura 11, +5 casos de desenlace · eidos 353),
eidos-lint 0 invalid / 0 class-hooks nuevos, morfo:vocabulary limpio,
check en los 73 errores ajenos preexistentes.
Pendiente (handoff actualizado): la jerarquía tipográfica del Status (D1
llevaba dos líneas y solo se hizo el anillo) · el pulso de commit-select se
corta si el consumidor cierra el anillo al elegir (necesita retención tipo
Presence) · D5 la superficie de composición del encargo, decisión abierta.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
245c22471b |
demo(sidebar): controles vivos de mobileBreakpoint y onOpenChange
Faltaban dos props públicos como control (regla de demo-authoring: cada prop público es un control vivo). `mobileBreakpoint` va con chips y el valor `xxl` (1536px) deja este viewport POR DEBAJO del corte, así que la presentación móvil —el `Drawer` compuesto— se puede ver sin tocar la ventana. `onOpenChange` se cablea a un contador que además enseña la doctrina: no se dispara con el estado del drawer, porque ese no es la preferencia de layout que el app persiste. Verificado en navegador: con `xxl` el shell estampa `data-mobile`, el drawer arranca CERRADO y el trigger de la página lo abre con el panel dentro de `[data-drawer-content]`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
21bbf9f506 |
uix(sidebar): F1.7 eidos + demo · raíl, modo icono y submenú flotante
Segunda tanda del sidebar: la capa visual, la demo canónica de 9 pestañas y la verificación en navegador real. Con esto el componente está vivo; queda el review adversarial. Eidos - Recipe con los dos ejes: off-canvas por `transform` (trabajo de compositor, el inset no reflowea) y `width` animado SOLO en modo icono, donde el inset sí tiene que seguir al raíl. El raíl NO lleva `will-change` (una franja fina tiembla a DPR fraccionario — memoria NavMenu). - Anchos como TOKENS: `--sidebar-width` 16rem · `--sidebar-width-icon` 3rem · `--sidebar-width-mobile` 18rem, más raíl, gaps, altura de fila y colores. - Tres composiciones que la capa headless no puede hacer: `Drawer` para la presentación móvil, `Tooltip` para nombrar filas en modo icono y `Badge` para los contadores. El trigger compone `Button` por el patrón `child`. - Convención de icono documentada: sobrevive el PRIMER elemento de la fila. Arreglos que destapó el navegador - **`mobile` en SSR**: sin viewport, `isAtLeast` responde «por debajo de todo» → el HTML salía con `data-mobile` y la hidratación NO lo corrige (no diffea atributos y el valor ya no cambia). Viewport desconocido = ESCRITORIO. - **Estado móvil propio** (arranca cerrado): en escritorio `open` es preferencia de layout persistida; en móvil sería un overlay tapando la página, que jamás puede ser el estado inicial. `onOpenChange` solo se dispara con el estado de escritorio. - **`open` controlado/no controlado**: se adopta el patrón de la casa (`$bindable` con default, el provider escribe siempre) — mi versión previa con estado interno no propagaba `bind:open`. - **El submenú flotante no se posicionaba**: `FloatingAnchor` resuelve su provider por CONTEXTO y el eidos envuelve la fila en un `Tooltip`, que publica el suyo → el ancla caía en el tooltip y el submenú se quedaba en `translate(0,-200%)`. Ahora el ancla se cablea directamente al provider flotante del item. Además el `<ul>` ya no se posiciona a sí mismo (colapsaba el wrapper a tamaño cero) y toma elevación del plano `overlay` por `data-depth`, como cualquier panel flotante del ecosistema. - **Tooltip vs submenú**: una fila con submenú no lleva tooltip en modo icono (dos superficies sobre el mismo ancla se pisan) — nuevo `useSidebarItemOr()`. - El item envuelve (`flex-wrap`) para que el submenú caiga en su propia línea en vez de ponerse al lado de la fila. Demo: shell de app real (grupos, badge, acción, submenú, fila deshabilitada), controles vivos de los tres props y las 9 pestañas del canon. Verificado en navegador real (Playwright): dos landmarks nombrados en el idioma activo · `aria-current="page"` en la fila activa · cada menú etiquetado por SU grupo · raíl enfocable que alterna con Enter · colapso 256→48px · off-canvas por transform · submenú flotante anclado a su fila y Escape que devuelve el foco · sema `emerge-expand/collapse` estampando en `panel` · móvil 600px = Drawer cerrado con el landmark dentro · RTL espeja el shell entero · oscuro · 0 errores de consola. Gates: `component:audit --only sidebar` PASS 0E/0W · eidos-lint 45 morfo-backed / 0 invalid / 0 class-hooks · `svelte-check` 0 errores propios · `vitest src/uix/eidos` 353/353 · `contracts.test` solo con fallos ajenos (menubar, radio-group y las claves camelCase de aura). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
ef6bd13be3 |
uix(nav-tree): `disabled` por nodo — destino no disponible
Decisión del usuario. Un nodo deshabilitado conserva su fila (sigue siendo parte del mapa) pero pierde el `href`: no hay nada que activar con clic, Enter ni «abrir en pestaña nueva». Un `pointer-events: none` de CSS solo habría tapado el ratón. - morfo: la parte `link` gana `data-disabled` (propRef) y `aria-disabled` (literal `true` bajo `prop-truthy: disabled`). - soma: `disabled` solo aplica con `href` (un grupo sin destino lo ignora); el toggle del grupo NUNCA se deshabilita — mostrar/ocultar es control de vista, no destino. - eidos: fila en `--color-content-disabled`, sin puntero y sin hover. - demo: «Agent» va deshabilitado con badge «Soon» + filas de API y a11y. Verificado en navegador real: `<a>` sin href, `aria-disabled="true"`, `data-disabled`, color 0.79 frente a 0.61 de una fila normal, clic sin efecto, fuera del orden de tabulación (44 focusables en vez de 45), badge dentro del control. `component:audit` PASS · eidos-lint 28 morfo-backed / 0 invalid · `vitest src/uix/eidos` 353/353 · svelte-check 0 errores míos. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
a7e629f9ed |
uix(nav-tree): F1.8 CERRADA · demo + navegador + review adversarial
Cierra el árbol de navegación data-driven (E-1) de F1: demo canónica de 9
pestañas con el mapa real de docs (43 nodos, 3 niveles), verificación en
navegador real y review adversarial (5 dimensiones × 3 verificadores
escépticos; 22 hallazgos brutos, 10 confirmados) con todos los confirmados
arreglados.
Arreglos del review
- sema: la parte `group` —target de los eventos emerge— se registraba SIN
`ref`, así que `runtime.trigger` lanzaba `SomaRuntimeTargetError` en
silencio y el pack no sonaba nunca (cero `data-event-*` en el grupo frente
a los de collapsible). El provider posee ahora el ref del `<ul>`.
- eidos: en una fila navegable el chevron resolvía `inline-size: 100%` como
flex-basis y ocupaba media fila (101 de 231 px en «Soma»), robándole clics
al enlace. Toggle compacto con suelo de diana de 24 px (WCAG 2.5.8).
- soma: una clave duplicada podía volver cíclico `parentByKey` y colgar la
pestaña dentro de `trailKeys` (deriva en render) → clave sufijada + aviso
del logger + guarda de ciclo en el paseo.
- soma: el colapso es CONTEXTUAL (recuerda el `activeKey` bajo el que se
hizo): cerrar la sección que lees se respeta, pero caduca al navegar
DENTRO del grupo, para que la página actual nunca quede sin fila visible.
Sigue siendo query pura, sin `$effect` que escriba estado.
- soma: `child` recibe también `children` (el árbol renderizado); antes
dejaba el landmark vacío, porque un árbol data-driven no lo puede
reautorar el consumidor.
- morfo + langs: el nombre accesible del chevron se declara en el contrato y
se localiza («Alternar sección {label}»); ya no duplica el del enlace.
- eidos: RTL completo — el glyph espeja solo (bordes lógicos), lo que no
espeja es el giro, así que bajo `[dir='rtl']` las dos rotaciones se
intercambian. El Gap «dirección del chevron en RTL» queda RESUELTO.
- demo: paridad de snippet con los controles vivos; fuera el token fantasma
`--nav-tree-rail-width` del docblock del recipe.
badge en v1 (decisión del usuario, delegada)
Está en el suelo de paridad del dossier §P5 (los 6 refs lo llevan). Se
resuelve con SNIPPET, no con recursión a nivel de eidos: el morfo declara la
parte `badge`, soma renderiza el snippet recibido (sin él, el valor crudo —
sigue siendo headless) y el wrapper de eidos pasa el `Badge` canónico. Va
DENTRO del control de la fila, así su texto entra en el nombre accesible
(«TSC, New, enlace»). `disabled` se descarta en v1 (fuera del suelo, y un
enlace de navegación deshabilitado es semánticamente dudoso); ambos quedan
registrados en la tabla de Gaps.
Verificado: `component:audit` PASS · eidos-lint 26 morfo-backed / 0 invalid ·
`svelte-check` 0 errores en estos archivos · `vitest src/uix/eidos` 353/353 ·
navegador real (Playwright): trail auto-expandido, sema estampando en el
grupo, teclado nativo, foco visible, claro y oscuro, RTL, 375 px sin
desbordes, 0 errores de consola.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
d008605fac |
uix(aura): variante orb por defecto en el indicator — presencia perceptible
El aurora (banda horizontal recortada al círculo) leía estático a 32-48px (señalado por el usuario). El render vivo pasa a ser el efecto `orb` de $scene — la presencia esférica nativa: anillo respirando con ruido + luz orbitando, movimiento de silueta perceptible en tamaños pequeños y deformable por el puntero. `aurora` queda como variante para superficies grandes (`<Aura.Indicator variant="aurora">`). Modulación §F6 del orb: el estado fija la energía (forceHoverState + hoverIntensity como gain + rotateOnHover en acting/escalated) y el rol el matiz como delta de hue OKLCH sobre la identidad del shader (primary; reviewing=risk, escalated=threat). Verificado en Chrome real, estado por estado: idle recogido → planned anillo violeta → acting denso rotando → escalated rojo agitado. Demo de aura: chips de variante; scene/README: acta D4 pendiente de F3. eidos 353/353 · check 0 propios. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
fe2d43b7ac |
uix(anchor-nav): F1.6 · scrollspy TOC (soma+eidos) + hardening
Nav landmark + native anchors; IntersectionObserver band detection (never a scroll listener + getBoundingClientRect — anti-reflow doctrine). aria-current="location", per-depth rail indent, data-active drives the eidos accent. Real-IO chromium test: band tracking + bottom dead-zone. Adversarial-review hardening: - ref-count register/unregister so duplicate hrefs / mount churn can't drop a still-referenced target (+ test) - tokenize the rail transition (--duration-fast/--ease-default) and the focus outline-offset (calc(var(--focus-ring-width) * -1)) - document two v1 limits (instant-jump dead-zone, link-order == section order) — both need a v2 observation-contract change Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
6be7195f0f |
feat(agent): eje agéntico — motor $agent (F1) + Aura + promoción uix.scene
Materializa el eje ortogonal agéntico del ecosistema: la 8ª familia semántica
`delegate` («¿quién actúa ahora?») deja de estar sin materializar. El agente es
OTRO ACTOR (LLM, macro, regla, workflow) que actúa por la MISMA API pública del
provider; la ruta de llamada no se bifurca, la concreción semántica depende del
actor.
F0 · Doctrina
- docs/architecture/agent.md — la doctrina permanente (capítulo comparativo de
referencias + bibliografía de seguridad, máquina estados=verbos, contrato de
participación, fila §0, a11y, amenazas).
- docs/process/{PLAN,TRIAGE,INFORME}-agent — plan de ejecución con todas las
decisiones firmadas (D-AG.1–11 + ⚖️1/2/3), triage de 4 revisores externos, e
informe autocontenido para revisión externa.
F1 · Motor ($libs/actor + arts/agent, 24º arte)
- EngineAgent puro (sin DOM/runes, portable a servidor): máquina D-AG.4
(suggest/review/auto · escalated con reason+timeout · returned outcome-tipado
· kill switch · autorización journaled aunque sea auto), tool-loop D-AG.5
(acts secuenciales, fallos→verbos, unknown-capability acotado, idempotencia
por callId, presupuestos acts/turns/wall-clock vía puerto de timers), techo de
autonomía por origen no confiable (F8b).
- ActiveAgent (sesión reactiva, contrato ActiveEngine) · protocolo v1 espejo
AG-UI (5 categorías + dirección tipada + reservas) · ScriptedAgentTransport
determinista (adapters/ fuera del barrel) · journal WAL + puerto de trazas
OTel · emisor sium→JSON Schema.
- Acuñación del actor (⚖️2/F6b): ActorToken opaco en $libs/actor (hoja bajo
orca/agent), registro privado WeakMap — los forjados resuelven a null;
costura `actor?` en TriggerOptions→SemanticSignal (runtime copia verbatim;
sema no resuelve). defineActiveAgent (service-factory app-level, timers del
core — cero setTimeout a pelo).
F3 · Aura — primer componente del eje (ruta 9 fases 0–6)
- El materializador canónico de `delegate` (reservado en scene §F6): morfo con
los eventos del ciclo (offer/escalate-untilAction/return + sustain-processing
stateBound) — PRIMER morfo del ecosistema que emite familia delegate; provider
soma que observa un puerto estructural (sin importar $agent — degradación
total); orb eidos = aurora $scene modulada por estado (§F6) con fallback CSS
= render de reduced-motion; live region única atribuida (WCAG 4.1.3); cancel
compone Button (asChild). Demo v2 sobre el MOTOR REAL con compuertas
deterministas. Orb-size derivado de la primitiva Avatar (32/40/48).
D4 · Promoción uix.scene (Aura llegó)
- defineEngineScene + superficie ActiveUix/ActiveEidos/contracts; el orb prefiere
el motor compartido (presupuesto de escenas global) con fallback por-superficie.
Gates: arts/agent 18/18 · aura 6/6 · arts:check 24 · eidos battery + recipe
30/30 + eidos-lint aura 0 inválidos · morfo:vocabulary + esquemas · smoke aura
PASS · verificado en navegador (ciclo completo + estampa delegate en DOM +
aurora WebGL pintando). Los tokens de recipe base.ts + CSS generado ya entraron
en HEAD vía una sesión concurrente.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
5b84749ee5 |
uix(prose): F1.5 · tipografía editorial con auto-exclusión de componentes embebidos
Quinto componente F1 del plan blocks (eidos-only; alcance E-2 = suelo del
dossier §P4). Estiliza HTML/markdown RENDERIZADO que el app pasa como children.
EL DIFERENCIADOR (que ninguna referencia puede): cada regla de elemento se
emite a especificidad CERO vía `:where([data-prose] EL)`. Como todo componente
del framework se estiliza por `[data-{component}]` (≥0,1,0), un <Button> /
<Callout> / <Card> embebido conserva su PROPIO estilo AUTOMÁTICAMENTE — sin
anotación `not-prose`. Verificado en navegador: un <Callout> dentro de prose
mantuvo su tinte affirm, borde-acento 3px y layout grid en claro Y oscuro; las
reglas :where de prose no lo tocaron. El donut `:not(:where([data-prose-ignore],
…))` es el opt-out explícito para subárboles HTML crudos.
- morfo: 2 partes (provider bare data-prose + ignore documenta el attr opt-out;
eidos-lint lo ve morfo-backed). Selectores de elemento = estructurales bajo la
parte (S1). 0 eventos, apg:none, passive
- recipe: cobertura de paridad (h1–h6 incl. h5/h6 que tailwind NO; p, a, strong/
em, blockquote borde-acento lógico, ul/ol + ::marker, dl, code/pre + reset kbd,
img/figure/figcaption, hr, table); ESCALA EM-relativa (un font-size raíz
re-deriva todo — no la escala 5× de tailwind); overflow de tabla estilo GitHub
sin wrapper (display:block+overflow); trim de bordes; SIN backticks en code
(doblarían sobre <Code>); dark GRATIS por roles (no el patrón -invert);
propiedades lógicas → RTL correcto
- decisión :where (no @scope): :where establecido en el codebase + soporte 2021+;
@scope diferido a Gaps (mitad de tamaño pero «Baseline newly available» sin
precedente aquí)
- demo v2 9 tabs con markdown real + <Callout>/<Button> embebidos (prueba de
auto-exclusión) + ejes size/measure; README (Comparativa 5 refs · Decisiones ·
Passive · Gaps con disposición)
Verificación: component:audit PASS 0E/0W (21 literales em con /* literal */
justificado + eidosSnippet) · eidos-lint 45 morfo-backed/0 invalid ·
recipe-css-contract 30/30 · svelte-check sin errores propios · navegador:
h1 36px/bold, h2 24px, medida 65ch (~692px), blockquote acento, tabla
display:block+overflow, code chip, AUTO-EXCLUSIÓN del Callout en claro y oscuro.
Nota: nav sidebar fuera del commit (flip CRLF ajeno); índice langs reconstruido
prose-only (entangle con aura sin commitear de sesión paralela).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
6cba9a0b19 |
uix(sticky): fix · pin con inset FÍSICO (top/bottom), no lógico — hallazgo de review
Adversarial review confirmó (minor): el recipe fijaba en el eje LÓGICO
(inset-block-start/end) mientras el rootMargin del observer es FÍSICO
(top/bottom). Coinciden en todos los modos horizontales (LTR y RTL: top/bottom
son eje de bloque, invariantes al flip inline), pero DIVERGEN en writing-modes
verticales, donde inset-block-start = izquierda/derecha física ≠ el borde que
el observer vigila → data-stuck se dispararía en la posición equivocada (o
nunca). La API `edge` es física ('top'|'bottom') y el observer es físico, así
que el CSS debe ser físico también (mi razón previa «lógico por RTL» era
incorrecta — RTL no afecta el eje de bloque).
- sticky.css: inset-block-start/end → top/bottom; margin-block-end/start del
centinela → margin-bottom/top (mismo eje de bloque, RTL-safe)
- demo: corregidas las 3 menciones «logical inset / writing-mode correct»
Verificado: component:audit PASS 0E/0W (físico top/bottom = eje bloque, no
dispara regla RTL) · test real-IntersectionObserver en chromium PASS (físico
top en horizontal = comportamiento idéntico). Nota: la lente teardown-ssr del
review falló por API stall mid-stream; esos caminos ya están cubiertos por los
tests (cleanup/disconnect aserted, disabled aserted, SSR no-op por contrato de
ActiveDom).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
998b11d9bc |
uix(sticky): F1.1 · position:sticky con detección de estado (soma+eidos) — el primer behavioral
Primer componente F1 con comportamiento real (soma), y el que desbloquea el block site-header (F2.1). Construido tras un fan-out de investigación (6 lectores) que fijó el mapa: template = FeedSentinelProvider, motor = IntersectionObserver por el port bendecido de $adom, escritura de attrs por syncAttrs (nunca dom.apply crudo). Alcance E-2 = suelo del dossier §P3. - morfo: 2 partes (provider=box → data-sticky/data-edge/data-stuck · sentinel aria-hidden → data-edge); 0 eventos con justificación de COMPLEX BEHAVIOR (apg: none); data-stuck presente/ausente + data-edge enum espejan @container scroll-state(stuck: top/bottom) — este componente es el polyfill cross-browser de ese contrato CSS futuro - soma: un StickyProvider registra AMBAS partes en un runtime; el observer vive en un $effect que RETORNA observeIntersection(...) como teardown (mirror de feed); el callback async voltea stuck=$state; rootMargin DERIVADO del offset (la línea de disparo del centinela ES la línea de pin — acoplamiento matemático, offset obligatorio); root=scroll-host (default viewport), Document coercionado a null; offset viaja como --_sticky-offset (dato A8, no CSS visual — precedente meter) - eidos: recipe fino que SOLO posiciona (position:sticky + inset lógico desde --_sticky-offset + token --sticky-z-index + centinela layout-neutral 2px con margen negativo); el tratamiento del estado pegado es del consumidor ([data-sticky][data-stuck]) — el recipe posiciona, no decora - centinela = HERMANO de flujo del box (dos nodos raíz), no hijo (un hijo se fija con el box y nunca detecta); orden según edge - demo v2 9 tabs con scroll container real + readout de stuck vivo; READMEs soma + eidos (Comparativa AntD/Mantine/Chrome-technique/headless/CSS · Decisiones · Passive justification · Gaps con disposición) Verificación: component:audit PASS 0E/0W · eidos-lint 6 morfo-backed/0 invalid ([data-stuck] sin regla = intencional, consumidor decora) · morfo:check limpio · svelte-check sin regresión propia · 5 tests: 4 unit (rootMargin por borde, root null-coerce, flip de stuck, disabled, cleanup) + 1 REAL-IntersectionObserver en chromium (foreground: no-stuck→scroll→ stuck→scroll-back→no-stuck — prueba end-to-end de la geometría offset↔ rootMargin, lo que el pane suspendido no puede). Navegador: box pinea a los 12px del offset, recipe correcto. Decisión declarada (test harness): sticky-provider.svelte.test.ts usa el installSomaHarness compartido (translator identidad) como los otros 86 tests de provider — TÉCNICAMENTE en tensión con la regla CLAUDE.md «never fake translators; use createActiveUix». Son unit tests del provider; la integración real la cubren contracts.test.ts + la verificación en navegador. Seguido el precedente universal; flaggeado para tu ratificación. Nota: nav sidebar de sticky (web/routes/uix/+layout@.svelte) fuera del commit — el archivo arrastra un flip CRLF de sesión paralela (2125 líneas de ruido); la entrada viaja en el árbol de trabajo. Registros langs/soma de sticky reconstruidos sticky-only (los índices tenían entangle con aura sin commitear). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
9124570e69 |
uix(callout): F1.4 · admonicion inline (morfo+eidos) al suelo del dossier
Tercera pieza F1 del plan blocks (alcance E-2, dossier §P3). Banner = tira de anuncio de pagina; Callout = el aside del documento (split deliberado). - morfo: 4 partes (provider role=note / icon / title / content) + texts con TITULOS DEFAULT LOCALIZADOS por intent (note/tip/warning/caution — patron GitHub de label visible: la semantica nunca viaja solo en color/icono); 0 eventos justificados (dismissible DIFERIDO a pasada soma+sema por la regla de admision — dismiss ES un evento real) - el hueco IMPORTANT resuelto con el modelo Radix, sin 5o enum: `intent` (neutral|affirm|risk|threat — mapea NOTE/TIP/WARNING/CAUTION) + `color` override SOLO bajo neutral (doctrina §4: intent evaluativo gana); IMPORTANT = intent neutral + color + titulo propio (preset en la demo) - pintura sobre la maquinaria C6/THM-2 (patron badge): forwarders por color + slots `_palette-track/text/solid` → el generador emite la cascada de 8 roles Y el forward presence-guarded al shared layer — 33 escalas y colores custom con CERO CSS extra - a11y: role=note NOMBRADO via aria-labelledby → Title SOLO mientras esta montado (registro reactivo por context local); escalacion tipada role=status|alert|none (el role=alert estatico de shadcn = bug de referencia que NO copiamos); Title NO es heading (protege el outline que escaneara anchor-nav); icono decorativo = mapa doctrinal IntentIcon - fix cazado en navegador: el `+=` del registro leia el estado dentro del tracking del $effect del hijo → effect_update_depth silencioso (contador a -997, attr nunca estampado) — untrack() en register/cleanup; leccion registrada en memoria (incidente 2 de la clase) - recipe: grid con acento logico border-inline-start (RTL-correcto), nesting tolerado (Docusaurus); demo v2 9 tabs con preset IMPORTANT + PalettePicker showIntent=false; README completo Verificacion: component:audit PASS 0E/0W · eidos-lint 8 morfo-backed/0 invalid · guards 37/37 · svelte-check 76E/51W = baseline exacto · navegador: «Nota»/«Atencion» localizados · risk=ambar hue 45-60 · IMPORTANT=plum hue 326 resuelto por shared layer · labelledby=titleId · cero errores de consola. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
bf940c9a4a |
uix(result): F1.3 · estado terminal de flujo/pagina (morfo+eidos) al suelo del dossier
Segunda pieza F1 del plan blocks (alcance E-2 = suelo de paridad, dossier
§P3). Comparte el esqueleto de empty-state a proposito: EmptyState describe
AUSENCIA de datos, Result reporta un RESULTADO — un lenguaje de layout, dos
contratos (duplicacion consciente registrada en el README; revisable al 3er
consumidor).
- morfo: 6 partes display (provider/media/title/description/actions/extra;
extra SIN archetype — parte genuinamente propia), scope ['eidos'],
0 eventos justificados (el resultado ya OCURRIO antes de renderizar)
- status: enum de 7 = paridad AntD CON `warning` (decision firme del
dossier) y HTTP renombrados semanticos (forbidden/not-found/server-error,
nunca '404' stringly); default 'info'; data-status = attr eidos-only
- media default: compone el mapa doctrinal IntentIcon (success→fulfill ·
error→threat · warning→risk) + Info de catalogo; HTTP = codigo mono
grande NEUTRO aria-hidden (situaciones, no fallos — AntD jamas pinta 404
de rojo); children reemplazan el default entero (context local eidos-only
con getter reactivo)
- Title default h2 (vs h3 de empty-state — Result suele SER la pagina);
Actions con label→role=group; Extra alineado a inicio (detalle que lee)
- recipe: glifo display 3× xl bundle; tokens publicos
gap/actions-gap/measures + {status}-color como forwarders de rol
retintables; sin eje size (paridad AntD, gap diferido)
- demo v2 9 tabs con copy por status + entrada nav (Status) + README
(Comparativa · Decisiones · Passive justification · Gaps con disposicion)
Verificacion: component:audit PASS 0E/0W · eidos-lint 6 morfo-backed + 5
eidos-only sancionados · recipe/api/visual-attrs 37/37 · morfo:check verde
(los 7 fallos listados son deuda foranea preexistente) · svelte-check
76E/51W = baseline exacto · navegador: success=fulfill verde 84px ·
error=threat rojo · not-found=«404» mono neutro, copy conmutando, cero
errores de consola.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |