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 }
197 Commits (6f42eebfe80bc9ac5656be2bbf621c6d62e768fb)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
6f42eebfe8 |
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve
El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho
componentes y `emerge-open` en tres. No era estetica — un preset de movimiento
engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna
firma y simplemente no animaba, sin romper una sola prueba.
Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos,
256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran
40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y
`handle-drop` ya existian en 10 y 4 componentes.
`validateMorfo` cierra la puerta: un `events[].name` que no empiece por su
familia ahora lanza. Visto fallar antes con un nombre pelado inyectado.
Lo que el renombrado destapo, y va aqui tambien:
- La receta del splitter enganchaba `commit-resize`, muerto desde
`bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como
slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el
catalogo de morfos — el guard que lo habria cazado en su dia.
- La familia `shift` era muda en el canal visual, contra su propia doctrina
(c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya
describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora
tiene firma direccional: sexto atributo del sello (`data-event-direction`,
`forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por
`:dir()`. Medido: LTR -30px/+30px, RTL los invierte.
- El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro
calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues
—media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze`
moria sin pintar un fotograma. Una superficie, una ranura (A-36).
- 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de
decision, el componente vivo). Las docs desfasadas, corregidas; los nueve
DEFECTOS de codigo obsoleto quedan abiertos y sin tocar.
- `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y
habia tres cosas distintas deletreadas «direction».
check en su linea base con 0 errores nuevos por diferencia de conjuntos ·
docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador
medidas con raton real y rAF vivo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
866089a407 |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar
La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
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 |
|
|
3c3d8e1ac8 |
fix(motion): las barras horizontales crecian desde su punta en RTL
`grow-x` clavaba `transform-origin: 0% 50%` — el borde fisico izquierdo. En RTL la barra se dispone contra el borde DERECHO, asi que la entrada la hacia crecer desde su punta de vuelta a su base, despegandose de su ancla. Medido en los dos consumidores, con la direccion movida por el toggle del topbar: bar-segment borde izq. clavado en 296, el derecho avanza 108 -> 0 bar-list borde izq. clavado en 239, y el de ANCLAJE se aleja 65 -> 3 `transform-origin` no tiene forma logica en CSS, asi que el preset lee ahora `--motion-origin-inline-start`, que `render-css.ts` emite por `:dir()` — el mismo patron que `scale-fade` ya usa con `--floating-transform-origin`. La var HEREDA (al reves que el indice de stagger, que es `inherits: false`): es lo que permite que un `:dir()` de un ancestro alcance al nodo animado. Se declaran las DOS direcciones para que un subarbol que redeclare la suya tambien acierte — la misma razon por la que `[dir='rtl']` esta prohibido. Tras el arreglo el borde de anclaje queda CLAVADO en 0 y la punta avanza, en ambas direcciones. LTR intacto (`origin: 0px`, ancla en 0). Verificado a ojo congelando la animacion al 45%: las cinco barras del bar-list nacen pegadas a su ancla —derecha en RTL, izquierda en LTR— con el stagger escalonado. RTL-1 no podia verlo: es `transform-origin` en un preset de TypeScript, ni siquiera CSS estatico. `metrics-progress` NO usa `grow-x`; los afectados eran solo esos dos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
4356020e37 |
feat(audio-player): la caratula de `card` sale a token, como sus dos hermanas
`row` y `bar` tenian tope publico (--audio-player-artwork-size-{row,bar}) y
`card` lo llevaba clavado en la receta como `calc(var(--space-8) * 8)`: un
tema podia retocar dos de las tres caratulas y la tercera no. Ahora son tres.
El valor se preserva EXACTO — 256px a densidad 1x / zoom 1x, verificado
resolviendo la custom prop en el navegador (el viewport del panel es 0, asi
que se mide la cascada y no el valor usado). Cero cambio visual.
Se conserva tambien la asimetria de FORMA, que no es un descuido y queda
escrita: `card` va sobre la escala de espacios (--space-8 ya trae densidad x
zoom compuestos) para que la caratula heroica respire con la fundacion,
mientras `row` y `bar` son rem planos a proposito — una fila de lista y una
barra de app no deben crecer con la densidad o dejan de casar con las filas
y barras de alrededor.
Verificado: recipe-css-contract 31 verdes (el guard de huerfanos exige que un
token declarado se consuma), eidos-lint media-player 0 invalidos,
component:audit --only media-player PASS, docs:check 0, check 75 = baseline.
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 |
|
|
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 |
|
|
2f872ef864 |
feat(sound): $sound orquestador (buses+voces+media) y player v2 F1-F3 sobre el servicio
Rediseno clean-room del sonido (gate D-SR.1-12 firmado; la doctrina "la voz
es del art" de PLAN-sound-engine s16.1 queda revocada):
- Mezcla: master -> buses ui/content (gain/mute/duck refcount, gana la mas
fuerte; politica pre-contexto). prefs de UI jamas tocan la obra.
- Voces registradas: la calibracion es DATO del consumidor (sema registra
'sema'; el default conserva la calibracion historica - cero cambio audible,
A/B del usuario: "suena igual").
- Ciudadania de media: sound.media(el) PROVEE el transporte (la forma exacta
del puerto MediaProvider) + foco mixed/exclusive/duck PERSISTENTE (estado
derivado; el ducker mas reciente retiene la palabra) + duckUiWhileContent +
MediaSession de la fuente activa (play/pause/seekto + posicion, liberacion
y promocion) + visibilidad content-aware + attach() opt-in (irreversible,
CORS declarado). Re-registro idempotente por elemento.
- diagnostics.ts con catalogo tipado (contrato de arts) y guards post-dispose
(ningun camino abre un contexto huerfano). Auditoria AU-1..9 resuelta;
bundle medido: 16,2 KB min / 5,6 KB gz.
Player v2 (gate D-AP2.1-13 firmado) F1-F3:
- F1 gaps de framework: aria-valuetext en Slider (valueText por thumb) +
parte SecondaryRange con token --slider-secondary-bg (buffer/clip/capitulos)
+ formatDuration en $libs/days + fix del selector del pack (H-1: la regla
de play/pause construia sobre provider y el stamp aterriza en play-button).
- F2 morfo: 6 partes audio-only (artwork/artist/identity/transport/
rate-button/live-indicator), commit-set-rate, commit-set-time RETIRADO
(D-AP2.13: el scrubber delega en el commit-set del Slider compuesto),
sustain-loading cableado (H-9), teclado j/l, </> y 0-9 (con guard de
modificadores para no pisar atajos del navegador).
- F3 la migracion: el transporte default es uix.sound.media(el, {metadata})
(pineado con el motor real); nativeMediaProvider RETIRADO (media-provider.ts
queda como contrato puro del puerto, sin shim); G-5: los commits de
play/mute/rate cabalgan el evento de RESULTADO (play/pause saltando el
pause de ended, volumechange con transicion de muted, ratechange);
formatTime -> formatDuration; sub-providers + wrappers + exports de las 6
partes; prop metadata publica (captura al registrar, documentado).
Verificacion: 45 suites / 458 tests verdes en el alcance (sound, sema,
active-uix, active-app, morfo, slider, media-player, days) - check 74 =
baseline de la rama, 0 propios - docs:check 0 - invariante de UN AudioContext
intacta (e2e sin tocar). Planes de proceso y handoff al dia
(PLAN-sound-redesign, PLAN-audio-player-v2, CONTINUE-sound-engine).
Co-Authored-By: Claude Fable 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 |
|
|
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 |
|
|
59f20e2b1f |
uix(aura): el Status en dos líneas — cierra D1
La otra mitad de la dirección firmada: el anillo dice el estado como FORMA, pero la respuesta a «¿quién actúa?» es texto, y una palabra gris de 13px junto a un orbe vivo no la daba. Ahora el Status son dos renglones: Esperando tu revisión ← el estado, content-primary, peso medium la custodia es tuya ← qué significa para ti, secondary, un escalón menos Sin parte nueva: el Status ya existía y añadir un `Detail` habría sido abstracción prematura (y una pelea de layout — dos hermanos no apilan sin envoltorio). El soma renderiza las dos líneas por defecto y expone `detailText` en los snippet props, así que un consumidor que traiga sus children sigue mandando. La copia del detalle es FIJA por estado y localizada como la etiqueta (es/en): los contadores reales del run (turnos / actos) NO están en el puerto estructural, y ensancharlo para un subtítulo más bonito es una decisión, no un defecto. Queda dicho en el README por si algún día se firma. Tokens: `--aura-status-color` pasa a `content-primary` (era secondary: el peso informativo estaba en el sitio equivocado), + `-weight`, `-gap` y `-detail-color`. Verificado en Chrome real, claro y oscuro, con un run en `reviewing`: peso 500, primary sobre secondary, y las dos líneas escalando con `data-size`. Tests 478, eidos-lint 0 invalid / 0 class-hooks, morfo:vocabulary 0 errores, check en los 73 ajenos preexistentes. Con esto D1 queda cerrada entera (anillo + jerarquía). Handoff al día. 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 |
|
|
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 |
|
|
e835163103 |
uix(nav-tree): F1.8 WIP · data-driven docs nav tree (soma+sema+eidos)
Complete component, green on all static gates (audit PASS, eidos-lint 20/0, svelte-check 0, contracts clean for its parts). NOT tree-view: APG Disclosure Navigation (<nav> landmark + native links + disclosure groups), data-driven (E-1) — the app passes `nodes` + `activeHref`, the provider resolves the active node and auto-expands its ancestor trail. - morfo: nav/list/item/trigger/link/group; emerge-expand/collapse events - soma: NavTreeProvider (shared state, pure isExpanded query) + per-node NavTreeItemProvider (menubar pattern, recursive render) - sema pack: soft emerge on group disclosure (mirrors collapsible) - eidos: rail + per-depth indent recipe; own bespoke rows (not composed Link), Badge deferred to a Gap; aria-current="page" from data-active Remaining before F1.8 closes: demo + browser verify + adversarial review. Handoff: docs/process/CONTINUE-nav-tree.md Co-Authored-By: Claude Opus 4.8 <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 |
|
|
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 |
|
|
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 |
|
|
f19e91b985 |
uix(empty-state): F1.2 · componente base display (morfo+eidos) al suelo del dossier
Primera pieza F1 del plan blocks (PLAN-blocks.md; alcance E-2 = suelo de paridad, dossier §P3). Ruta de 9 fases completa: - morfo: 5 partes display (provider/media/title/description/actions), scope ['eidos'], 0 eventos con justificacion pasiva (patron renderEmptyState de las refs headless), Title role:'heading', texts.label - langs: components.empty-state.label (es/en) — el indice tambien recoge la retirada foranea del import de words (inseparable por staging de archivo; coherente con la migracion palabras ya enviada; words.ts sigue en su arbol) - eidos: compound EmptyState + Media(kind icon|media, placa 2x glifo) + Title(level 2-6, default h3 — modelo Atlaskit, tamano visual desacoplado) + Description(measure 45ch) + Actions(label -> role=group +aria-label, buttonGroupLabel); recipe sobre el bundle --size-* (titulo un paso discreto arriba; sm=in-collection, lg=hero); tokens publicos minimos (gap/actions-gap/media-bg/media-fg/media-radius/description-measure) - demo v2 9 tabs (harness: SystemAxes/MotionPanel/SemaPanel; snippet con paridad; escena in-collection via Card) + entrada nav (grupo Status) - README: Baseline · Comparativa (shadcn/Chakra/Atlaskit/AntD/Polaris) · Decisiones · Passive justification · Gaps con disposicion Verificacion: component:audit PASS · eidos-lint 5 morfo-backed + 4 eidos-only sancionados · morfo:check + morfo:vocabulary verdes · recipe-css-contract/api-contract/visual-attrs 37/37 · svelte-check 76E/51W = baseline exacto (cero regresion) · navegador claro Y oscuro por estilos computados (titulo 18->24px, placa 40->64px, chips vivos, cero errores de consola). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
9a7a438a96 |
fix(emoji-picker): panel encaja en el Popover + ancho tokenizado, sin overflow
El contenido (331px) desbordaba el Popover (~284px, --popover-min-width-md) y el viewport lo recortaba con scroll horizontal — la ultima columna de emojis salia cortada, evidente en oscuro. Fixes: - El Popover recibe su ancho via prop `width` = TOKEN `--emoji-picker-panel-width` (23rem, token de componente al estilo color-picker content-width — NO hardcode). El contenido pasa a inline-size:100%; nada desborda el popover. - PopoverContent size="sm" (padding mas ajustado) + categorias en ToggleGroup size="xs" → las 9 caben sin scroll (antes 6/9). Tonos size="sm". - scrollbar-width:thin en el grid (el unico scroll, vertical, esperado). Tamaños: iconos/glifos canonicos (Icon size, --size-lg/xl-font-size, ToggleGroup size); grid-max-block-size min(50vh,17rem) replica el patron de command.css. Verificado en claro Y oscuro (capturas): content Hscroll 0, strip Hscroll 0, 9 categorias visibles, celda 36px, un solo panel; component:audit PASS · eidos-lint invalid 0 · recipe-contract 30/30 · check baseline 76. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
d59b8af9aa |
uix(chat): v2 · emoji-picker (picker completo) + escaparate rico del bloque
Nuevo componente emoji-picker y rediseño del escaparate de mensajeria. emoji-picker (ruta 9 fases): - Compone Popover (open/close/foco/portal) + Command (busqueda + grid + nav de teclado + a11y combobox/listbox, su "grid mode" documentado) + dos ToggleGroup (categorias + tono de piel). Delegated: sin eventos propios (D.1); el grid y las celdas doble-registran Command.List/Item, conservando la a11y pero con contrato visual propio (gana especificidad con selectores de 2 niveles, sin tocar attrs [data-command-*] ajenos — eidos-lint limpio). - Datos: $libs/emoji, dataset vendorizado de emojibase (MIT) horneado por scripts/generate-emoji-data.ts (precedente lucide: JSON crudo en static/ + script generador). ~1906 emoji con nombre CLDR + keywords, 316 con tono de piel single-person. Grid acotado (categoria activa o resultados de busqueda), sin virtualizacion (refinamiento v2). Helpers puros + test (12/12). - morfo delegated (7 partes + header/footer de layout); soma (root + trigger + content + grid + option + preview, provider-test 4/4); eidos (panel empaquetado, receta con tokens --emoji-picker-*); langs es/en; demo 5 tabs (excepcion delegated documentada); READMEs dobles. Escaparate del bloque (web/routes/uix/demos/chat) — rehecho rico y correcto: - Avatares por-autor con color solido (Avatar del ecosistema): antes gris neutro indefinido, ahora teal/ambar/verde/indigo/primary bien contrastados. - Dos columnas: chat-list (sidebar con badges + typing-en-lista) + sala. - Conversacion rica: separador de dia, run agrupado, reaccion, reply citado, recibos de lectura, typing con avatar, y el emoji-picker integrado en el composer (inserta en el draft). - Solape de burbujas (transitorio, estimateSize=48 con mensajes ~90-126px) mitigado subiendo el estimate a 76. Auditoria de tamanos/espacios: recipes del bloque limpios (todo con tokens --space-*/--size-*/--color-*); unico literal (punto tono-capable) tokenizado. Verificacion: check baseline 76 (0 nuevos) · component:audit emoji-picker PASS · eidos-lint invalid=0 · recipe-contract 30/30 · morfo:vocabulary 0 · emoji lib 12/12 · provider-test 4/4 · Playwright PASS (busqueda 'rocket'->rocket+ astronautas, click->onSelect+cierra, teclado, tono oscuro 👋->👋🏿, picker abre en el composer). Capturas claro miradas. next-features §7 marca emoji-picker SHIPPED. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
f9889b0b18 |
uix(chat): v2 · chat-list — panel de conversaciones (compone Listbox)
Componente nuevo del bloque chat v2: el panel de lista de salas/DMs. Compone el Listbox del ecosistema (doble registro id/ref compartido): selección única + roving-tabindex + typeahead + role=option vienen de Listbox; chat-list aporta la disposición de fila (avatar · título/preview · meta) y el estado visual data-unread (negrita + Badge compuesto). - morfo: expression 'delegated', sin eventos propios (D.1 — el actor es el Listbox compuesto); parts provider/item(data-unread)/avatar/title/preview/meta. - soma: ChatListProvider crea ListboxProvider dentro (value string <-> string[]); ChatListItemProvider per-instance (unread = boolean crudo); 4 clases de contenido explicitas. - eidos: rejilla avatar | (titulo/preview) | meta, avatar y meta abarcan las dos filas; truncado; unread -> titulo en negrita. No neutraliza el arquetipo item (la conversacion ES una opcion seleccionable). Sin @keyframes; ejes logicos. - demo: 5 salas, seleccion limpia unread, typing-en-lista (compone ChatTyping), Badge de no-leidos; tabs live/system/api/morfo/a11y. - READMEs dobles (eidos con Passive justification, delegated); next-features 7 marca chat-list SHIPPED. Verificacion: check baseline 76 (0 nuevos) · component:audit PASS · eidos-lint invalid=0 · recipe-contract 30/30 · morfo:vocabulary 0 · Playwright PASS (role=listbox, 5 salas, aria-selected, unread=2, badges=2, typing-en-lista, click + teclado seleccionan). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
8fe9d1faaa |
uix(chat): v2 · jump-to-message highlight en chat-message
Tercer y último remate de v2 (batch elegido por el usuario). chat-message gana el prop `emphasized` → attr `data-emphasized`: al saltar a una cita (reply.onJump → scrollToIndex) la app enciende `emphasized` ~1.4s y la fila objetivo hace un flash suave que se desvanece. VISUAL PURO — sin evento de sema (desvía a propósito del signal-emphasize -target del plan): las apps de referencia hacen un flash SILENCIOSO; un signal que pitara por defecto sería molesto. Cumple 2-de-3 (soma escribe el attr, eidos lo lee). El pulso = toggle de dos estados + transition (cero @keyframes, R-4.5); reduced-motion = instantáneo. Morfo attr + soma prop/provider (devuelve el booleano al resolver, no '') + eidos recipe (wash color-mix 14% de toda la fila) + demo componente (botón flash) + escaparate (reply → jump → emphasize). next-features §7 marca los 3 remates shipped. Verificado (Playwright): flash on → bg tintado → auto-clear a 1.4s; escaparate intacto. audit PASS, eidos-lint invalid=0, recipe-contract 30/30, check en baseline (76). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
e838e7107b |
uix(chat): v2 · read-by list — parte ReadBy en chat-message
Segundo remate de v2. chat-message gana la parte opcional `ReadBy`: una fila de avatares "visto por N" que la app compone (AvatarGroup), típica de mensajes propios; el emisor la alimenta con los acuses recolectados vía el onMessageSeen (v1). DECORATIVA (aria-hidden): el indicador Status ya transmite "read" a AT; las caras son realce visual. Morfo part + soma provider/wrapper + eidos wrapper/recipe (fila de avatares muted, ejes lógicos in/out) + demo con toggle. next-features §7 marca read-by y typing-avatars como shipped. Verificado (Playwright): la fila aparece con 3 avatares + "Visto por 3", aria-hidden, en la columna del cuerpo. component:audit PASS, eidos-lint invalid=0, recipe-contract 30/30, check en baseline (76). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
4c4878f139 |
uix(chat): bloque de mensajería chat-* v1 (log · message · composer · typing)
Familia chat-* completa, componiendo el ecosistema (Feed + VirtualList anclado, Textarea, FileUpload, Avatar, Popover, Icon). Transporte-agnóstica: el modelo de datos es de la app, los componentes reciben props + snippets. - chat-log: Feed + VirtualList(anchor:'end') en triple registro; day separators, unread divider, filas de sistema (Feed.Article), pill "N nuevos" + notifyIncoming (announce a live-region), to-latest distance-aware. - chat-message: raíz Feed.Article; runs ($libs/chat), ticks de entrega, reply embebido, reacciones (chip pressed + tapback panel), acciones hover. - chat-composer: Textarea (doble registro) + FileUpload; teclado Enter/Shift+Enter/Esc/ArrowUp; typing saliente debounced (uix.timers); barra reply/edit. - chat-typing: sustain-streaming (visual-only, loop del sistema); aggregateTypers 1/2/3+ con plantillas plurales. - $libs/chat: groupIntoRuns (ventana 8 min estilo Discord) + aggregateTypers. - VirtualList: extensión anchor:'end' (followOutput, prepend por clave estable, sticky monótono, onAtEndChange) — sin scroller paralelo. Diseño de referencia (WhatsApp/Telegram, pase lado-a-lado): burbujas suaves (out = primary-element + texto normal; in = surface card + sombra) sobre lienzo muted del log, meta dentro de la burbuja, tapback bar horizontal, iconos lucide del ecosistema (nada de SVG a mano). Contrato de tamaños: size="md" = 16px canónico. Fixes de framework (afectaban más allá de chat): - EngineLangs.t() ahora pela el prefijo '#?': los idlangrefs imperativos (pagination/file-upload/chronos/chat) caían al fallback EN en locale es. Fix parseLangRef ?? parsePathFallback + test de regresión (132/132). - virtual-list commit-set-resize con channels:[]: es bookkeeping de ResizeObserver, no gesto → dejaba de emitir beep/vibrate pre-interacción (interventions de navigator.vibrate / AudioContext). Escaparate messenger en /uix/demos/chat (bot de eco con uix.timers; variante $connection señalada). Gaps v2 registrados en next-features §7. Pase: component:audit 4×PASS · eidos-lint invalid=0 · recipe-contract 29/29 · Playwright escaparate PASS end-to-end · consola limpia · npm run check en baseline (76, cero errores nuevos). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
b93cc6c5c0 |
uix(color): arregla el custom-color roto de Card y Avatar (fill)
BUG (descubierto en la verificación adversarial de la iniciativa): un color
CSS crudo (p.ej. `color="#3b82f6"`) NO pintaba el FILL de Card ni Avatar —
caía a neutral. Card/Avatar (Fase 2a) usaban un split bespoke pre-helper
(`isCanonicalColor` + seed `--{c}-color-custom` + bloque derivador
`[data-{c}][data-color-custom]{--_{c}-palette-*: …}` en su .css). Ese bloque
quedó ENSOMBRECIDO por el forward compartido de THM-2 en `generated/base.css`
(misma especificidad (0,2,0), carga después → gana); para un valor crudo
`--palette-*` está sin definir (el shared layer lo deriva de `--color-custom`,
NO de `--{c}-color-custom`) → host neutral.
Fix (los alinea con TODO el resto de la iniciativa):
- card.svelte / avatar.svelte: el split bespoke → `resolveComponentColor`
(genérico: canónico→`data-color`; crudo→`data-color-custom` + seed
`--color-custom`, que el forward YA consume). Ambos ahora COMPONEN el seed
con el `style` del consumidor (`composeInlineStyle`) para que `...rest` no lo
pise — Card lo pisaba (style antes de rest), por eso el custom se perdía
incluso con el seed correcto.
- card.css / avatar.css: borrados los bloques bespoke `[data-{c}][data-color-
custom]{…}` (muertos + ensombrecidos). Las variant slices leen
`--_{c}-palette-*`, que el forward llena para rol/escala/custom uniformemente.
- Avatar RING y BADGE son canales de color SEPARADOS (`--avatar-ring-color-
custom` / `--avatar-badge-color-custom`): NO se tocan — usan su propio
namespace de tokens, el forward no los ensombrece y funcionan (verificado).
- base.ts: quitados los 4 tokens de recipe muertos (`card`/`avatar` ·
`color-custom` + `color-custom-contrast`); ring/badge se conservan.
Verificado en Chrome: Card real (picker del demo) + Avatar sintético pintan el
custom en soft/solid/outline/ghost, componiendo el style del consumidor;
roles/escalas siguen exactos (behavior-preserving); ring + badge intactos.
Orphan test resuelto (los `-contrast` ya no huérfanos). Los 2 fallos residuales
del contract son WIP de chat-composer (otro track, sin commitear), ajenos.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
6cdba0182f |
uix(color): cierra 2 fugas transitivas (card-group-item · s-text) + guard runtime
Verificacion adversarial multi-agente (17 componentes de la sesion de cierre,
23 agentes) cazo 2 fugas que la verificacion individual en Chrome no cubrio —
componentes abiertos TRANSITIVAMENTE (heredan un alias abierto) y nunca
tocados ni verificados:
- **card-group-item**: estampaba `data-color={color}` CRUDO en ambas ramas
(button + div) sin `data-color-custom` ni seed → un valor CSS crudo (que el
tipo abierto `CardColor=ComponentColorProp` permite) caia a neutral y se
descartaba en silencio. Ahora pasa por `resolveComponentColor` (ruta
GENERICA `--color-custom`, que el forward compartido `[data-card][data-color-
custom]` consume). NOTA: NO usa el split bespoke de Card (`--card-color-
custom`) a proposito — esa ruta esta ENSOMBRECIDA por el forward de THM-2 y
no pinta (bug pre-existente de Card/Avatar, flageado aparte). Verificado en
Chrome: teal=escala exacta, #3b82f6=palette-solid exacto, neutral=sin estampa.
- **s-text**: `STextColor = TextColor` se abrio gratis al ensanchar TextColor
en Fase 4, pero su .svelte seguia en el puente pre-Fase-4
(`var(--color-content-${colorKey})` para TODO valor) → escalas/intents/crudo
producian vars inexistentes. Migrado a la rama eje-primero de Fase 4 (espejo
de Text): ink axis inline sin estampa; resto por el forward. Recipe
`_palette-text` + regla colored-only + stamp. Verificado en Chrome
(risk/teal exactos, custom aplicado, default sin estampa).
- **split-button**: revisado, SIN cambios — delega `{color}` crudo en
ButtonGroup→Button (ya verificado); no re-estampa.
Guard runtime NUEVO en recipe-css-contract.test.ts — "pairs a dynamic
`data-color` stamp with `data-color-custom`": el guard de tipos es ciego al
runtime (solo lee types.ts), asi que un stamp dinamico sin el par custom pasa
inadvertido (justo la clase de card-group-item). Este falla si algun .svelte
estampa `data-color={…}` dinamico sin `data-color-custom` en el mismo fichero;
literal estatico (`data-color="neutral"` del badge +N de avatar-group) exento;
WIP excluido. 30/30 verde.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
67d7dc89cc |
uix(color): Fase 4 — tinta de contenido en text · heading · display · code · label
Los 5 primitivos tipograficos tenian OTRO eje de color: la tinta de contenido
(primary|secondary|muted|disabled|on-solid, donde primary = --color-content-
primary, NO el rol), interpolada inline por el wrapper (--_x-color) sin
data-color ni cascada CSS. Abrir el tipo a pelo rompia: color='teal' →
var(--color-content-teal) inexistente; 'muted' via resolveComponentColor →
--color-custom: muted invalido.
Apertura con RAMA EJE-PRIMERO (identica en los 5):
- tipo: `ComponentColorProp | 'muted' | 'disabled' | 'on-solid'` (label sin
on-solid — conserva su union exacta de 4). primary/secondary ya estan en
ComponentColorProp pero la rama los captura ANTES y les conserva el
significado de TINTA ("conserva su eje", handoff §Fase 4).
- wrapper: valor ∈ eje → var inline como siempre, SIN estampa; resto →
resolveComponentColor (canonico → data-color; raw → data-color-custom +
seed). Los dos caminos son mutuamente excluyentes.
- css: regla colored-only `--_x-color: var(--_x-palette-text)` — no pelea con
el eje porque el eje nunca estampa.
- recipe: `_palette-text` nueva por componente (host = fallback-only).
Text NO estaba en la lista del handoff (code/display/heading/label) pero tiene
la MISMA union cerrada y es el primitivo modelo de la familia — incluido y
declarado (dejarlo fuera era una incoherencia de la propia fase).
Verificado en Chrome: heading con las DOS ramas y prop real (chip muted →
inline var sin estampa; risk/teal/custom = probe exacto); label idem (la demo
monta color='primary' real → inline, y risk exacto sin el inline); text/
display risk/custom exactos; code el token fluye exacto — su pisoton visible
es una colision PRE-EXISTENTE del chrome de docs ([data-uix-docs] code, ya
pisaba el eje viejo; señalada como tarea aparte). Contract test 28/28 —
primera vez todo verde.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
04659558d4 |
uix(color): abre `color` en proof-of-human + exencion fixed-tone formal
Apertura (era el ultimo standalone con cascada): - El accent del reto (2 slots: solid + element-soft) pasa de 7 bloques per-rol al forward compartido en la parte `root` (display:contents → cascada a todos los retos, verificado que el skin hereda). Recipe NUEVA (su comentario "needs no recipes/base.ts entry" quedaba obsoleto). - `ProofOfHumanColor = ComponentColorProp` (era ColorRole, README decia "8 roles-only"). Root + los 3 retos (clock/path-trace/rotate-align) estampan via resolveComponentColor — sus stamps son CSS-muertos hoy, pero con el tipo abierto habrian estampado raw strings como data-color. Exencion fixed-tone (los "2 fallos pre-existentes" del contract): - FIXED_TONE_COMPONENTS se hoista y comparte entre el test de color Y el de font-size, añadiendo proof-of-human: la paloma mensajera + carta son paleta identitaria del objeto dibujado (anotada linea a linea) y los font-size 13px/11px son unidades de usuario SVG del viewBox, no escala tipografica (anotacion añadida al .ra-card que no la tenia). Nota: esto tambien exime a natural-time-picker/color-picker del test de font-size (hoy limpios; su doctrina fixed-tone ya esta documentada). card-group: revisado, SIN cambios — card-group-item ya usa `CardColor` (= ComponentColorProp desde Fase 1): abierto por delegacion. Verificado en Chrome: default primary = probe exacto (2 slots), risk/jade exactos, skin hereda por display:contents, custom → seed + mix element. Contract: mis 2 pre-existentes ELIMINADOS; unico fallo restante = chat-message `--radius-xs` (WIP vivo de otra sesion, ajeno). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
18026dfd84 |
uix(color): abre `color` en float-panel
El accent (regla del header, focus ring, grip de resize) pasa de 8 bloques per-rol al forward compartido en la parte `content` (el panel portala y estampa su propio `data-color` via ctx). El default real NO era el "fantasma" que temia el handoff: la recipe SI tiene `accent: neutral-border` y el root siempre estampa `color='neutral'` por defecto → estampa-siempre + regla generica es fiel (host del forward = neutral-border, el valor del default). El fallback sin-ctx (`var(--float-panel-accent)`) queda intacto. Tipos: `FloatPanelColor = ComponentColorProp` (era ColorRole); el Content resuelve via resolveComponentColor con seed `--color-custom` para raw values. Verificado en Chrome: default neutral = probe exacto; threat/iris = token compartido; sin estampa → fallback neutral-border; custom → color-mix del seed. Contract test sin fallos nuevos. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
c9db0a8d26 |
uix(color): abre `color` en color-picker (cross-portal resuelto)
La trampa: el accent vivia en DOS scopes — cascada per-rol en el root y una re-declaracion fija `primary-border` en el contenido portalado. El forward del root nunca alcanzaba el portal: el prop `color` solo tintaba el ring del trigger; los rings del portal (area-thumb, slider-thumb, eye-dropper) eran SIEMPRE primary. Solucion (espejo de select): el forward apunta a las DOS partes que estampan — recipe `_palette-border` con `parts: ['trigger', 'content']` (fusionado en la recipe existente). El trigger consume el token directo en su ring; el content re-deriva su `--_color-picker-accent` del token EN SU scope (sus descendientes lo heredan como antes). Root: fuera la decl base + la cascada de 7 roles; el provider solo estampa el marcador canonico y el color RAW viaja por contexto (Trigger/Content/ChannelInput lo resuelven cada uno via resolveComponentColor, con seed `--color-custom` para valores raw). El marker div del ChannelInput enruta ademas el forward de color-field, como un ColorField standalone. Tipos: `ColorPickerColor = ComponentColorProp` (era ColorRole). Verificado en Chrome con el popover abierto: content portalado estampa su propio data-color; default primary = probe exacto en trigger y content; risk/teal en el scope del portal = token compartido; custom → color-mix del seed; el area-thumb hereda el accent. Contract test: mis cambios no añaden fallos (los 2 de chat-composer son WIP de otra sesion con recipe fuera del tree; 2 de proof-of-human pre-existentes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
6b37beed69 |
uix(color): abre `color` en field-langs · radio-cards · image
Tres aperturas de bajo peso: - field-langs: delegante puro (patron E) — pasa `color` al `<Field>` compuesto (abierto en Fase 3). Solo tipo: `FieldLangsColor = ComponentColorProp`. - radio-cards: comparte elemento provider con radio-group, cuyo forward ya enruta la paleta que las reglas de card consumen. Tipo abierto (era AffirmativeColorRole) + ruta custom en el wrapper (seed `--color-custom`). - image: migra el mecanismo bespoke pre-helper (`data-placeholder-color` + seed propio) al forward compartido, paridad con avatar (su gemelo declarado). Arregla de paso las escalas: `placeholderColor='teal'` caia a la rama custom y pintaba el NAMED COLOR css `teal` (hue equivocado) y `'iris'` era invalido; ahora resuelven a la escala canonica. `'neutral'` (default) no estampa y conserva el fallback neutral-track. Fuera el token de recipe `placeholder-color-custom` (huerfano tras la migracion). Verificado en Chrome (probe): radio-cards risk/mint/custom en el forward y el item checked consume borde+fondo (el gris inicial era la transition congelada del pane suspendido, caso conocido, verificado con transition:none); image default=neutral-track exacto, risk/teal=escala, custom=color-mix; field-langs estampa via Field. check: 0 errores en tocados; total 77 = 76 baseline + 1 de web/routes/uix/demos/chat (WIP de otra sesion). Contract test 26/28. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
838b01a005 |
uix(color): abre `color` en metrics (featured icon) + form (submit/reset)
Dos aperturas por partes (`parts:` en recipe, forma `declarations:`): - metrics-icon: los 5 slots del featured icon (track/solid/text/contrast/ border) pasan de 8 bloques per-rol al forward compartido en la parte `icon`. Fuera el guard roles-only del wrapper (caia a neutral con cualquier valor no-rol); `plain` sigue sin estampar. Default primary estampado = identico. El flash de valor NO se toca: gradua por `data-event-intent` (sema), no por el prop `color`. `MetricsChartProps.color` queda cerrado: delega en la familia chart, que NO esta en la cola del handoff (hueco señalado aparte). - form submit/reset: 4 slots (solid/solid-hover/border/contrast) en las DOS partes via el mismo forward. `'neutral'` (default de reset) NO estampa: la base action look (element bg + text ink) ES el neutral — estamparlo habria cambiado reset a solid grey. Submit default primary = identico. Verificado en Chrome (probe getComputedStyle): metrics-icon risk 5/5 slots + crimson + custom (contrast blanco derivado); form threat 4/4 + iris + custom; reset sin estampa con look base intacto. check: 0 errores en tocados (total 76 = baseline); contract test 26/28 (2 pre-existentes proof-of-human). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
60a30c2e60 |
uix(color): abre `color` en skeleton · spinner · textarea
Tres standalones al forward de paleta compartido (`resolveComponentColor` +
recipe `_palette-*`):
- skeleton: tinta el wash (slot track, 50% alpha) + el highlight del shimmer.
Colored-only: el default `'neutral'` NO estampa data-color y conserva su
mezcla base cross-mode (oklab + 16% solid). Delta declarado: affirm/fulfill/
risk/threat/loss ganan highlight tintado en el shimmer (antes solo primary/
secondary lo definian; la regla generica uniformiza la cobertura).
- spinner: patron C puro — la base consume `--_spinner-palette-{text,track}`
(host = primary, behavior-preserving); fuera los 7 bloques per-rol.
`'inherit'` se conserva como union aditiva con su regla literal
(currentColor); no puede viajar por el seed custom (trampa `--x: inherit`).
- textarea: tinta el borde de focus (slot solid). Colored-only: el default
`'primary'` no estampa y conserva `--focus-ring-color` (que NO es
primary-solid: es un mix al 52% — estampar habria cambiado el default).
Verificado en Chrome (probe getComputedStyle): default intacto en los tres;
risk/teal/amber/jade = token compartido exacto; custom hex → color-mix del
seed. check: 0 errores en los componentes tocados; contract test 26/28 (2
pre-existentes de proof-of-human).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
1efa034397 |
uix(color): abre `color` en link + mark (tinta de contenido)
Migra los dos content-ink al forward de paleta compartido:
- link: `color` tinta el texto. Base pasa a `color: var(--_link-palette-text)`
(host = primary-text); se quita la cascada per-rol de 8 bloques. hover/active
siguen en primary (comportamiento pre-existente). Recipe nuevo `_palette-text`.
- mark (resaltado): `element` tinta el fondo (default = fulfill, el resalte
clásico), `text` tinta la tinta SOLO cuando hay color (la base sigue
`color: inherit`; no se puede poner `inherit` como host de custom-prop — trampa
del `--x: inherit`). Regla añadida `[data-mark][data-color],[…custom]{color:…}`.
Ambos wrappers pasan por `resolveComponentColor` (custom hex → data-color-custom
+ seed). Verificado en Chrome: link risk→texto risk, custom→color-mix; mark
fulfill por defecto, risk→fondo risk, custom→color-mix.
Nota: `check` marca 94 (no 76) por la caché incremental de svelte-check aflorando
errores `any` pre-existentes en demos ajenos (heroscrolling/animations/palabras/
alpha/temas); CERO errores en componentes tocados (link/mark limpios).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
528ddaa7f9 |
uix(color): abre `color` en listbox · timeline · combobox
Migra los 3 al forward de paleta compartido, cada uno con su matiz: - listbox: 2 slots (solid = indicador activo, element = fila seleccionada), raíz bare `[data-listbox]`. El `accent-text` era código muerto (sin consumidor) → eliminado. Recipe nuevo. - timeline: 5 slots (solid/track/border/contrast/text) que alimentan `--_timeline-hue-*` del item; data-intent por item sigue mandando sobre el acento del root. Tipo ProgressiveColorRole → ComponentColorProp. Recipe existente ampliado. - combobox: la cascada vivía en CUATRO partes (control/input/trigger/content), así que el forward las cubre vía `parts: [...]` (forma declarations[]). 3 slots (track/border/text). Los 5 wrappers (root + 4 partes) pasan por `resolveComponentColor` para que el custom hex no se estampe crudo. Recipe existente (content-z) ampliado. Verificado en navegador: combobox data-color=risk → palette-border del control = token risk compartido; custom #00b894 → color-mix. table (lote previo) idem. check: 76 ajenos, 0 propios. Contract 26/28 (2 = proof-of-human pre-existente). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
c5699bd019 |
uix(color): abre `color` en la familia lista/data-grid (10 componentes)
Migra table · tree-grid · tree-view · virtual-list · virtual-grid · feed ·
drag-drop · grid-list · clipboard · carousel al forward de paleta compartido.
Patrón 2-slots: `--_{c}-accent` (indicador sort/activo) -> `--_{c}-palette-solid`,
`--_{c}-accent-soft` (tinte de fila seleccionada) -> `--_{c}-palette-element`.
Recipe nuevo con host = primary; el generador emite base default + forward. Se
quita la cascada per-rol de cada css; tipo -> ComponentColorProp; wrapper vía
`resolveComponentColor`.
Detalles no uniformes resueltos:
- Los 7 que estampan `data-color` en `[data-{c}-root]` usan `parts: ['root']`
(forma declarations[], la única que el generador lee para parts) -> forward
`[data-{c}-root][data-color]`. Los 3 bare (grid-list/clipboard/carousel) van
al selector base.
- drag-drop ya tenía recipe (preview-z) -> fusionado, sin duplicar clave.
- tree-grid/tree-view solo tintan la fila (element); su `accent` (solid) no
tenía consumidor -> declaran solo `_palette-element`.
- virtual-list/virtual-grid tenían la cascada accent MUERTA (sin consumidor):
contenedores virtualizados sin visual por color. Tipo abierto (guard) +
wrapper inerte, pero SIN recipe (evita token de paleta huérfano).
Verificado en navegador (demo table): data-color=risk -> palette-solid/element
= tokens risk compartidos exactos; custom #00b894 -> solid=#00b894, element=
color-mix(...). check: 76 ajenos, 0 propios. Contract 26/28 (2 = proof-of-human).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
889b03dae1 |
uix(color): abre `color` en year-grid + range-calendar (completa la familia calendar)
Mismo patrón mini-recipe que month-grid: recipe con 5 tokens `_palette-*`
(host = primary) -> el generador emite base default + forward
`[data-{c}][data-color], [data-{c}][data-color-custom]`; se quita la cascada
per-rol de la css y se renombran los consumidores accent -> palette; tipo ->
ComponentColorProp; wrapper vía `resolveComponentColor`.
- year-grid: espejo exacto de month-grid (5 slots, sin cross-refs).
- range-calendar: el END endpoint + `--calendar-range-end-*` bind a
`--_range-calendar-palette-*`; el swap de START a secondary para affirm/fulfill
(distinción de extremos) se conserva como regla per-rol (no es paleta).
Verificado en navegador (demo range-calendar): data-color=risk -> palette-solid
= range-end-solid = token risk compartido; affirm -> start = secondary; custom
#00b894 -> range-end = #00b894. check: 76 ajenos, 0 propios. Contract 26/28 (2 =
proof-of-human pre-existente).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
861427fe12 |
uix(color): Fase 3 (parcial) — abre `color` en field-family · calendar · month-grid · time-picker/range
Continúa la apertura de la jaula del color hacia el sistema completo
(role · intent · 33 escalas · valor custom), tipando a `ComponentColorProp`
y enrutando por el forward `--palette-*` compartido:
- Field family: field base gana el forward `_palette-text`; search-field
y 8 hojas (password/mask/number/css/color/date/date-range/time-field)
adoptan `data-field` y heredan el forward. Wrappers vía
`resolveComponentColor`; demos con picker de roles/intents/escalas/custom.
- Calendar base: recipe `_palette-{solid,solid-hover,track,border,text}`;
calendar.css migra su cascada per-rol al forward compartido.
- month-grid: patrón mini-recipe (5 tokens `_palette-*`, sin recipe previo);
el generador emite base default + forward `[data-month-grid][data-color]`.
- time-picker / time-range-picker: tipo + wrapper.
Fix de regresión de runtime: la migración de la field-family dejó huérfana
la cascada `--_time-field-accent-*`, y time-range-picker re-exportaba el
Input crudo de soma, así que sus endpoints no recibían el color del picker.
Nuevo `time-range-picker-input.svelte` enhebra `data-color`/`data-color-custom`
a cada endpoint (verificado en navegador: color del picker -> ambos extremos).
time-picker (single) ya se salvaba por el overlay `data-field` en su raíz.
Fix de 9 errores de tipo: los factories de opts de los tests de provider
de switch/toggle no pasaban `colorCustom` (lo hicimos required en Fase 2b).
Regenera generated/base.css. check: 76 errores ajenos, 0 propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
149c0fef6b |
uix(color): abre `color` al sistema completo (role·intent·paleta·valor) en la familia interactiva + rail segmentado de radio-group
Motor (Fase 0): tipo abierto `ComponentColorProp`, helper `resolveComponentColor`,
derivación custom compartida `[data-color-custom]` (color-mix desde `--color-custom`)
+ forward extendido a `[data-{c}][data-color-custom]`; guard recipe-css-contract
actualizado.
Eidos-wrapper (14): radio-group, checkbox, stepper, toggle-group, select, badge,
editable, file-upload, tag-group, tags-input, surface, avatar, card. Patrón:
tipo -> ComponentColorProp, wrapper -> resolveComponentColor (estampa
data-color/data-color-custom/style), demo con picker + input custom. Incluye el
fix del bug de escalas de card (una escala caía al path custom y pintaba el CSS
named color en vez de la escala de paleta).
Soma-routed (button/switch/toggle): patrón morfo+soma limpio — el provider omite
data-color y emite data-color-custom para el valor crudo, con el intent evaluativo
ganando y suprimiendo el custom. Ensancha los tipos de color de la capa soma.
Arreglados 2 bugs del runtime del framework:
- html-presence con `v.literal('')` (valor vacío = falsy) nunca emitía el attr ->
`v.propRef('colorCustom')` (como data-disabled).
- el custom pisaba al intent evaluativo -> el prop `colorCustom` se anula con
intent no-neutral.
Bundle: incluye el rail segmentado de radio-group (variant=segmented, indicador
deslizante compartido soma-mide/eidos-pinta: measured-indicator + sliding-indicator)
de la misma sesión, intercalado en los ficheros de radio-group.
Handoff para continuar: docs/process/open-color-cage-2026-07.md (Fase 3/4/5 pendientes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
d990fe42f9 |
eidos(gradient-finish): auditoria final - tinta named determinista via --_{c}-finish-ink (D7)
Re-auditoria completa del ciclo (batteria entera de guards + bordes
discriminantes medidos en navegador). Dos hallazgos, dos cierres:
1. El override directo de --_{c}-fg del named finish EMPATABA (0,3,0) con el
slice solid de Surface y dejaba el ganador al orden de hojas de estilo -
no es un contrato (el caso discriminante aurora-sobre-amber lo destapo:
la primera verificacion usaba primary, cuya contrast blanca coincidia con
la tinta de aurora y enmascaraba el empate). Fix por la doctrina D7: el
generador emite la var --_{c}-finish-ink y el slice solid de cada recipe
la consume con su contrast de fallback - gana por existir, nunca por
especificidad; las variantes no-solid quedan inertes por no leerla.
Verificado por valores: aurora-amber blanco (autorada) en Button/Badge/
Surface; flat/rampa amber oscuros (heredada); outline inerte.
2. Nombre no declarado (gradient="foo") degrada a la rampa - medido y
documentado en D11 como fallback gracioso.
Atribucion corregida de paso: el crash de morfo:vocabulary/morfo:check es el
morfo de text-blur (deuda preexistente ajena, chip de tarea creado) - NO
palabras como se asumio antes; surface/box/palabras validan OK.
Bateria final: eidos entero 331/333 (2 = proof-of-human preexistentes),
config 72/72, guard 5/5, audit 145/145, eidos-lint invalid:0 en los tres
consumidores, typecheck limpio.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
77ef3b0b3d |
eidos(gradient-finish): v2.2 named finishes + on/data-on - EL PLAN COMPLETO
D11 - Named finishes (el diseno A rescatado como opt-in explicito):
- gradientFinish.named opta gradientes del open cage como acabado, cada uno
con su tinta AUTORADA obligatoria (--gradient-{name}-ink, override de la
convencion --_{c}-fg a especificidad (0,3,0) sobre el slice de variante).
- La base sigue siendo el solid de la identidad (fill por capas, D2) -> el
aurora shipped son blobs de rol con alfa SIN color base final:
background-image-valido POR ARQUITECTURA (el caveat del mesh disuelto, no
exceptuado), y re-tine por tema y modo via referencias de rol.
- Honestidad documentada: la validacion numerica al peor stop de un string
CSS arbitrario no es implementable (stops desconocidos); posible solo para
gradientes de modelo (buildGradient) - diferida. El guard clava la sanidad
del config (named subconjunto de gradients + ink presente).
D12 - on/data-on minimo (Surface on="light|dark"):
- La foundation re-vincula --color-content-*/--color-border-default para el
subarbol (dark -> tinta on-solid; light -> on-solid-contrast; mixes oklch
82/64/32%). Verificado EN VIVO: un parrafo muted dentro del aurora computa
la tinta del contexto al 64%.
- Limites POR CONSTRUCCION y documentados en cada consumidor: componentes
anidados con tokens propios y contenido portaleado NO se re-entintan (la
inversion completa sigue siendo iniciativa independiente). forced-colors:
el contexto resuelve a CanvasText/GrayText (bloque extendido). Sin
color-scheme a proposito (solo chrome UA).
Ademas: prop gradient ampliada a (string & {}) para named en Button/Badge/
Surface; poda del orphan-guard (border/text de Surface: 3 slots x 8 colores,
lo que las variantes consumen); guard 5/5; audit 145/145; lab CASO 07 con el
aurora REAL (<Surface gradient="aurora" on="dark">) verificado por valores.
Docs: capitulo D11+D12 + registro D1-D12 + roadmap COMPLETO; reference p39;
changelog p42; plan cerrado (quedan declaradas: inversion completa, demo
propia de Surface, validacion de modelo para tintas named).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
58076cadfe |
eidos(surface): primitiva Surface - el lienzo temable (Box + tratamiento), v2.1
Box es layout-only por doctrina y rechaza background/color; Surface es la primitiva que posee el lado del tratamiento: el caso hero / banda decorativa / bgGradient como primitiva de primera clase, no como escape-hatch style=. - Composicion sobre <Box> (patron Section): hereda TODA la API de caja y estampa data-surface + color/variant/gradient/rounded (attrs eidos-only de wrapper, doctrina D8). - Recipe palette-tint espejo de Card SIN chrome: _palette-* 5 slots x 8 colores -> el forward THM-2 ruta roles + 33 escalas donantes gratis (verificado: color="teal" resuelve sin cableado extra). - Variantes soft (tinte track, tinta global) y solid (lienzo saturado, texto plano hereda contrast); gradient en el gate del acabado (rampa anclada + spread; sin solid-hover -> extremo profundo cae a solid). - Morfo declarativo patron Box (scope eidos, 1 parte, 0 eventos justificados); eidos-lint invalid:0; audit 145/145 PASS (ficha completa: Baseline/Decisiones/Gaps con disposiciones/Passive justification). - Lab temas/gradientes CASO 07: heroes REALES con Surface (rampa primary, spread plum, banda soft teal) verificados por valores computados; el aurora nombrado queda como preview del siguiente paso. Cola v2 restante: named finishes con tinta autorada + on/data-on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
63f6209e5f |
eidos(gradient-finish): v1.5 kind spread - rotacion de matiz anclada, medida antes de implementar
Esta vez en el orden de la doctrina D6b: las dos puertas se midieron ANTES
de escribir la implementacion.
- Puerta 1 (sonda, 84 combos): la rotacion pura a L constante rompia grass
(+-4 grados) y gold (+-27) - L de OKLCH no es luminancia relativa. Rescate =
la propia doctrina aplicada suave: ambos stops rotados toman la mezcla
DEBIL del ancla (lift/3) -> 0 regresiones hasta +-45 grados.
- Puerta 2 (RCS en Chromium, por valores computados): el canal h de
relative color es <number> - calc(h +- 30deg) computa none -> el token
--gradient-finish-spread es SIN unidad ('30').
Shipped: gradient="spread" en Button+Badge (boolean | 'ramp' | 'spread';
data-gradient='spread' overridea la var del acabado - cero cambios de CSS de
recipe); token + override por tema (gradientFinish.spread); guard 4/4 con el
invariante del spread anclado; lab con especimenes reales (threat cruza el
360, gris C=0 queda ~plano - documentado, no caso especial); docs D10 en
theming/gradient-finish.md + reference p39 + changelog p42 + plan v1.5
completa.
Una doctrina, dos kinds: la rampa huye de la tinta con fuerza; el spread
huye suave mientras juega con el matiz.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
322a1939a8 |
eidos(gradient-finish): acabado anclado a la sombra de la tinta - v1 Button+Badge + dial temable + guard
El gradiente entra al sistema como ACABADO (material) del fill, jamas como
valor del eje de color: prop `gradient` -> attr eidos-only `data-gradient`
(familia data-variant; NO morfo - el runtime lo resolveria desde props de
soma y mergeProps clobberea el stamp del wrapper).
- Fill por capas: background-color = base solida (degrada sola en
forced-colors) + background-image = rampa derivada de los slots de LA
instancia (roles + 33 escalas + custom gratis, tinta heredada).
- Dial unico de tema: primitives.gradientFinish.lift (26%) ->
--gradient-finish-lift (0% = apagado; override por instancia via cascada).
- RAMPA ANCLADA a la sombra de la tinta ("la rampa huye de la tinta"):
tinta blanca -> #000 fuerte abajo (CTA sombreado); tinta oscura -> #fff
fuerte arriba (glossy). Ancla+angulo por color x modo con el MISMO flip
del slot contrast. Rectificacion MEDIDA: el lift global hacia blanco
rompia la tinta heredada en 52/84 combos a 26% (techo global 0%) - el
contraste ahora solo puede mejorar: dial sin topes.
- Guard ejecutable gradient-finish-guard.test.ts (3/3): no-regresion <=40%
sobre 84 combos + set flat-fail clavado (cyan/orange, deuda on-solid
preexistente).
- Generador emite la VAR (--_{c}-fill-finish); el recipe pinta (solid +
re-assert en hover: su shorthand background resetea el longhand).
- Lab temas/gradientes: 9 casos con componentes reales sobre el token real
(dial en vivo, polaridad observable, evidencia de pasos 7/11, forced-colors).
- Docs: capitulo theming/gradient-finish.md (registro de decisiones D1-D9,
alternativas rechazadas con evidencia, leccion de proceso: medir ANTES de
fijar defaults) + reference.md paragrafo 39 + changelog paragrafo 42 + plan
docs/process/gradient-finish-plan-2026-07.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
326a25ec4d |
palabras: retirar el chrome words deprecado (FW)
Decision de usuario 2026-07-14: words (chrome eidos + demo) quedo deprecado en pos de palabras; existia como referencia historica. - Migradas las 4 piezas que palabras importaba (git mv, historia conservada, sin shims): words-activate -> palabras-activate, words-block-drag -> palabras-block-drag, words-block-drag-type -> palabras-block-drag-type, words-image-file -> palabras-image-file; imports actualizados a rutas locales. - Borrados src/uix/eidos/components/words/** (wrapper, menubar, bubble, inspector, gutter, rail, words.css, langs-menu/inspector, AUDIT visual) y la demo web/routes/uix/components/words/. - Sidebar: fuera el link Words, entra Palabras (la demo era inalcanzable desde la nav). - Podado el bloque `words:` de lib/recipes/base.ts + regeneracion (generated/base.css: -84 lineas, solo bajas --words-*). - Docs del track: referencias al AUDIT visual de words apuntan a git history. NO muere: soma/components/words (motor + provider + tests), morfo y langs de words — el fundamento vivo de palabras. Verificacion: engine 493/493 - guards eidos (recipe-css-contract + api-contract + visual-attrs) 35/35 - grep eidos/components/words en codigo = 0 - navegador (server propio): demo palabras monta sin errores de consola, activate fija bloque, grip abre panel (tabs + pills H1-H6/HC + texto/enlace). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
efc3f4c301 |
feat(eidos): anti-CLS metric-override fallbacks for the default fonts
Ship a metric-overridden local() fallback face per default family (Fontaine /
next-font model): before the webfont loads, the browser uses a system font
(Arial / Georgia / Courier New) resized via size-adjust + ascent/descent/
line-gap-override to match the webfont's geometry, so text doesn't reflow when
the variable woff2 swaps in — zero CLS.
Values precomputed from @capsizecss/metrics (each webfont vs its system fallback)
and set in the config's `fallback` field (theme data — the sanctioned home, not
magic numbers in CSS). The engine emits the '{family} Fallback' @font-face and
injects it into the --font-family-* stack after the real family. Display repeats
Lora's fallback so its own token (hero/h1/h2, where CLS is most visible) is
covered without emitting a duplicate face.
Verified: the 3 fallback faces register in the browser; the stacks inject
'{family} Fallback'; tests 99/99; npm run check src/ = 9-error baseline, 0 new.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
7d4f95005d |
feat(eidos): default fonts -> variable woff2 + full variable-font API
Ship the base theme's default families (Instrument Sans / Lora / Azeret Mono --
identity unchanged) as self-hosted VARIABLE woff2 (SIL OFL, latin subset, wght
range, preload on primary), so the engine's variable path is exercised by the
default theme, not merely capable. The hand-written themes/fonts.css is retired
(@font-face is config-driven from typography.families[].faces); the static TTF
set under static/fonts/air is removed.
Also close the theming-API gaps so it supports ANY variable font, not just
wght+opsz:
- FontAxes is now a 4-letter-tag map. renderFontFace emits a descriptor per
registered axis: wdth -> font-stretch: min% max%, slnt -> font-style: oblique
(OpenType slnt negated to the CSS oblique sign), alongside wght/opsz.
- TypographyStyle.variationSettings -> --style-{name}-font-variation-settings,
applied by the Text recipe (fallback normal), engaging non-standard axes
(GRAD/MONO/custom) and fixed axis defaults.
Verified: variable faces load with weight ranges + mid-axis rendering; new axis
+ variation-settings emission covered by active-eidos-config.test.ts (99 pass);
npm run check src/ = 9-error baseline (0 new); no default-font regression.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |