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 }
25 Commits (b7474e499dd1a844059adfc34b883c057e7bdc28)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
b7474e499d |
blocks(logo-cloud): la banda de marcas vuelve, y su parte difícil no era la disposición
Se difirió en F2 sobre una premisa que las referencias desmienten —que su versión honesta pedía un marquee o quedaba en un wrap trivial—: Tailwind Plus, Untitled y Flowbite lo shippean estático. Una decisión cuya premisa cae se corrige. Pero el argumento para construirlo no es la paridad. Lo difícil de un muro de logos es la tinta: veinte marcas en veinte paletas gritan sobre la página y entre ellas. Ese tratamiento acaba de entrar en la fundación como contexto, así que el bloque queda fino y es exactamente la forma correcta — cuando lo difícil es un tratamiento, el tratamiento va a la fundación y el bloque sólo lo estampa. Aquí no hay una línea de CSS, que es además lo que el contrato exige. Dos decisiones medidas, no elegidas. Las marcas van en un wrap y no en una rejilla, porque llegan con proporciones dispares y unas pistas iguales dejan huecos irregulares alrededor de las estrechas. Y la parte del ítem se gana su sitio defaulteando el eje de bloque, que es lo único que un logo nunca trae consigo: un vectorial carga su viewBox, un raster sus píxeles, y uno al lado del otro aterrizan a cinco alturas distintas. Verificado con ocho marcas de anchos y colores deliberadamente dispares: las ocho a una sola altura conservando sesenta y cuatro píxeles de diferencia de ancho, que es cada una guardando su proporción. El marquee se queda como hueco con disparador y con su forma decidida: si algún día entra será un preset de movimiento sobre la fila, no un bloque nuevo, y con la preferencia de movimiento reducido parándolo. Y el hueco de la utilidad para nombres sólo audibles sale aquí por tercera vez en la ola. Tres consumidores reales lo piden ya. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
e7677d6d87 |
blocks(pricing): la tabla de comparación, y las palabras que una marca no dice
La brecha recurrente del dossier en pricing, y la última fila del primer tramo de la ola. Es una PARTE y no un hermano porque tiene que obedecer al periodo de la sección, y sólo una parte puede leer ese contexto: B-10 le prohíbe a un hermano importar el block que lo posee. Vivir dentro de la sección es además lo que permite que la app meta el precio en una celda y cambie con el conmutador, sin que esta página cablee nada. La app construye la tabla y el block la compone. B-5 admite datos justo donde el canon compuesto ya es data-driven, y la tabla del canon lo es: exige la instancia que devuelve createTable. Así que la parte no inventa un formato de matriz propio ni traduce nada de ida y vuelta, que es el acoplamiento que esa regla existe para evitar. Las tarjetas y la tabla conviven, como en las referencias, alimentadas por el mismo array de planes. Dos cosas que el plan había esbozado y que construirlas demostró equivocadas, y quedan escritas en vez de calladas. No hay un Record de ejes visuales por plan: acentuar una columna es componer canon, que es justo lo que ya permite el snippet de cabecera, y ese Record habría sido una segunda fuente de lo que la app declara en su plan. Y el block no fija la columna: el motor es del app, y meter mano ahí sería el block decidiendo cómo se comporta la tabla de otro. Lo que sí posee es lo que la app no puede: el nombre accesible de la tabla y las palabras que una marca de visto o de guion no dice en voz alta. Ese glifo lleva todo el significado de la celda y es silencio para un lector de pantalla, así que va oculto y la frase viaja en su etiqueta, desde un mapa exhaustivo con respaldo en inglés y su test en las dos direcciones. Y absorbe el cast del canon siendo genérico: la tabla tipa su ranura sin parámetro mientras el motor sí lo devuelve, así que todo consumidor castea, empezando por la demo del propio componente. Aquí se castea una vez a la entrada y se deshace a la salida, para que el snippet de celda devuelva la fila con la forma del app. Medido a 1280 y 375: ocho filas por cuatro columnas, tabla nombrada, marcas que se anuncian, y en móvil la tabla desplaza con la columna de características quieta y la última columna alcanzable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
0646d5ebde |
blocks(pricing): el plan dice dónde estás, y el que no puedes elegir dice por qué
Firma del usuario: faltaba la decoración del elegido, y faltaba poder marcar un plan como no disponible — si estoy cambiando de plan, el que ya tengo no debería poder seleccionarse. Lo segundo cambia la posición doctrinal del block. Su README decía desde julio que nada en una tabla de precios puede bloquearse y que por tanto no hay nada que explicar. Eso vale mientras la sección es sólo una comparación; se cae en cuanto el visitante ya está en un plan. Y ahí manda lo que costó contact: una acción bloqueada debe su frase, o es un agujero — algo que no se puede hacer y nada en pantalla diciendo por qué. El plan gana un estado: disponible, actual o elegido. Un enum y no tres booleanos, porque tres booleanos admiten lo que no existe, como elegido y no disponible a la vez. Y ortogonal a featured, que es la recomendación de quien vende: el plan recomendado puede ser justamente el que ya tienes. Se deriva una sola vez en el plan y viaja por contexto. La acción se lo entrega al botón del app por parámetro de su snippet, así que nadie vuelve a deducir en el punto de uso si se puede pulsar, y las palabras del botón siguen siendo del app. La frase, en cambio, es del block: idlangref con respaldo en inglés dentro de un Record exhaustivo, el mismo mapa que bloquea la acción es el que escribe el motivo, así que un estado que bloquea en silencio no se puede ni expresar. Su test lo afirma en las dos direcciones. Y una parte nueva la dice y posee el id al que apunta el aria-describedby del botón deshabilitado, sin renderizar nada cuando no hay nada que decir. La decoración la pone el canon: el elegido reenvía el anillo de Card, el actual su tratamiento deshabilitado. Nada de un data-pricing-selected propio: sería un atributo que nadie lee, porque un block no pinta CSS. Medido de punta a punta en el escenario de cambio de plan: el actual sale con cursor de no permitido, botón deshabilitado y aria-describedby que casa con el id de la frase, traducida por el arnés; al elegir otro, su tarjeta gana el anillo de dos píxeles con el primary de su paleta y las demás no. Queda anotado un gap del canon que salió al medir esto y que no toco: Card disabled nunca atenúa. La regla existe y su cursor sí aplica, pero la animación de montaje fija la opacidad en uno con prioridad de animación y se come la del estado; aislado con no-emerge, atenúa. Es el patrón que la doctrina de motion llama frágil-conocido —dos dueños peleándose por una propiedad en un nodo— y alcanza a toda Card deshabilitada del ecosistema. 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 |
|
|
973d2c886d |
feat(direction): la proyeccion prefs->DOM es del boot, y el DOM la siembra
La proyeccion cross-modal (dir · lang · data-motion/sound/haptic sobre <html>)
era cableado manual de cada app: solo 4 de ~13 boots de la demo la tenian, y el
estampado per-componente del valor de prefs actuaba de SUSTITUTO de la
proyeccion que faltaba. Para que el flip de P3 pueda soltar prefs del atributo,
el ambiente tiene que llegar al DOM una vez y siempre:
- createActiveUix la crea por defecto (opt-out `projectPrefs: false` cuando la
app posee <html>: i18n por routing, proyeccion propia). En attach es opt-IN —
el inverso — porque el host puede gobernar el documento.
- Se dispone la PRIMERA en dispose(), antes de que prefs/dom mueran debajo.
- Los 3 cableados manuales commiteables se borran EN ESTE MISMO commit (uix,
blocks, BootUix); el de web/routes/alpha se edita en arbol pero no se
commitea nunca (regla de la rama). Borrarlos junto a la automatica evita el
agujero medido del doble montaje: dispose() BORRA los attrs gestionados sin
restaurar.
Y LA SEMILLA, que es lo que hace la automatica segura: sin ella, el boot
REESCRIBIRIA un <html dir="rtl"> puesto a mano (el escenario
|
2 months ago |
|
|
6036560ff0 |
fix(blocks): la galeria emite — packs de sema y canales perceptivos en el arnes
El motor SI emitia: pulsar un boton de un block estampa `data-event=contact-activate` con `family=contact` y `phase=active`, porque el morfo del `Button` declara ese evento (verbo `activate`, secuencia `pre`, sin intent a proposito — una pulsacion no carga intencion evaluativa). Lo que faltaba era el otro lado: la seccion arrancaba UIX **sin `events` y con cero packs de sema**, frente a los 69 de `/uix`. El evento salia a una sala sin altavoces. Viene de F0, cuando el arranque se monto como «espejo minimo» de `/uix` y sema se quedo fuera. Un framework semantico cuya propia galeria no suena no esta demostrando lo que existe para demostrar, asi que los canales van ENCENDIDOS aqui: `sound` y `haptic` a true en las dos cadenas de arranque (galeria y previews). La lista de packs es DERIVADA del barrel (`Object.values(packs)`), no escrita a mano, y eso es deliberado: la lista a mano del otro arnes se quedo atras dos veces — `69c108d2a` tuvo que anadir «los 25 packs que faltaban en la composicion de demos». Una lista derivada no puede quedarse atras. El barrel recomienda importar solo los que usas por tree-shaking; aqui no aplica, porque esto no es un producto sino la galeria del framework y un block compone lo que quiera del canon. Verificado espiando la Web Audio API en un clic real dentro del block: al cargar 1 AudioContext y 0 fuentes; tras pulsar, 2 fuentes y 3 nodos de ganancia mas. Y las 15 demos cargan limpias con los 69 packs, cero errores de pagina. Gates: blocks:check verde (15) · svelte-check sin errores en lo tocado · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
378457f46c |
feat(blocks): content-section cierra F2 — la rejilla de escape, y las escalas tipograficas salen de Text
El ultimo block de sitio, y el que mas cerca estuvo de ser un envoltorio:
`Section` + `Container` + `Prose` no habria anadido nada, porque `Prose` ya trae
su medida de lectura. Lo que falta en el ecosistema es el ESCAPE — dentro de un
`Container` nada puede ser mas ancho que el, asi que una figura a sangre hay que
sacarla del articulo y ponerla de hermana, y el orden de lectura se rompe.
La seccion es una rejilla de 5 pistas (canalon · flanco · MEDIDA · flanco ·
canalon) y cada parte dice hasta donde llega: `measure` col 3, `wide` col 2/5,
`full` col 1/-1. Todas las longitudes son tokens del sistema (`--measure-*`,
`--container-width-*`, `--container-padding-inline`). `.Body` y `.Media` son
compound porque SE REPITEN; la cabecera son slots de snippet. `.Body` apaga la
medida de `Prose`: dos duenos del mismo ancho se pelean y ganaria el mas
estrecho en silencio.
Los tres ejes son RESPONSIVE (`measure`, `wide`, y el `width` de cada parte),
resueltos con `eidos.resolve()` — el mismo camino que usan los primitivos de
eidos, asi que los unicos breakpoints en juego son los canonicos. Es lo que un
valor unico no puede decir: `{ base: 'full', md: 'wide' }` = foto a sangre en
movil y contenida de `md` arriba.
CANON: un componente no es libreria de otro. `Heading` importaba CINCO escalas
tipograficas de `Text` (`TextTracking`, `TextLeading`, `TextWrap`,
`TextNumeric`, `TextMeasure`) y al tipar este block repeti la violacion. Las
cinco se mueven a `eidos/lib/types.ts`, que es donde su propio encabezado dice
que van y donde ya esta documentado el precedente del mismo fallo (`ColorRole`
re-escrito a mano que derivo a 8 miembros contra 9). Sin shim de compatibilidad:
`text` y `heading` las importan de la libreria.
Cuatro defectos que solo dijo el navegador:
1. `gap` separa tambien las COLUMNAS y una parte que las cruza se lleva esos
huecos encima — `wide` media 1088 en vez de los 1024 del contenedor con el
que debe alinearse, y a 420px `full` llegaba a 532 dentro de una rejilla de
404 y hacia scrollear la pagina. Van `rowGap` + `columnGap={0}`.
2. Una pista `min(medida, 100%)` ignora sus propios canalones: a 420px la
central se quedaba los 404 enteros y la rejilla se iba a 436.
3. Un token de container es un ancho EXTERIOR: con `--container-width-lg` a
pelo la figura `wide` sobresalia 16px por cada lado respecto al texto de la
seccion hermana de debajo, a 1280/1440/1920. La pista ancha resta ahora
`2 * --container-padding-inline` (992 en `lg`) y el desfase es 0. Lo cazo el
usuario mirando la pantalla: yo habia medido el block contra si mismo, no
contra la pagina.
4. `100%` significa algo distinto dentro de cada parte — en `measure`/`wide` ya
es una pista, en `full` es la rejilla entera con canalones —, asi que capar
el pie igual en los dos casos lo dejaba a 340 contra una columna de 372 en
uno y a 404 en el otro.
Verificado en navegador: barrido 375→1920 sin desbordamiento; alineacion
pie↔prosa identica en 3 viewports × 3 medidas × 3 anchuras (18/18); figura
`wide` a ras del texto de la seccion hermana en 1280/1440/1920; y el valor
responsive cambia exactamente en el `md` canonico (a sangre a 767, contenida a
768). Claro, oscuro, 420px y 1920 mirados, y la geometria confirmada tambien en
el Chrome del usuario.
Semantica propia `figure`/`figcaption` (no interactivos → estructura de
documento, B-8) con el hueco senalado: el canon no tiene primitiva `Figure`.
Contexto de CONFIGURACION, no de estado — es un block de layout y no coordina
nada.
Gates: blocks:check verde (15 blocks) · svelte-check sin errores en lo tocado ·
eidos 28/29 suites (la que falla es `audio-player` sin morfo, del hilo de audio,
ajena) · docs:check 0 · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
b1b568e32f |
feat(blocks): contact CERRADO — la coordinacion entra en el contrato B
El estado se le pregunta al ESQUEMA, no al Form. `form.isValid` con `validationBehaviour: 'progressive'` significa «aun no se ha encontrado nada mal»: un formulario vacio e intacto no tiene errores, asi que se declaraba valido y `incomplete` era INALCANZABLE al cargar — la seccion pedia resolver la verificacion con los tres campos vacios, justo el hueco que la doctrina existe para cerrar. Ahora la raiz valida los valores con `validateSync` de SIUM: sincrono y sin escribir errores (`form.validate()` habria encendido los tres campos en rojo en el primer frame). `state.test.ts` fija la maquina con 9 casos — primer test unitario del tier, porque `state.ts` es su primera logica pura: el orden de prioridad, que solo `ready` deja enviar, y que todo estado bloqueado tiene frase. Contrato B (architecture/blocks.md): seccion «Coordination» con las tres cosas que posee un block que coordina (estado, forma de sus datos, palabras), B-5 admite el esquema por defecto, B-7 enmendado (posee las palabras de SUS estados como idlangref con fallback ingles) y la convencion de servicios acotada: el traductor es el UNICO servicio sancionado. Dice tambien a quien NO aplica — los blocks de layout no coordinan nada. i18n por la via real: el arnes registra el namespace `blocks` en las DOS cadenas de arranque (galeria y previews, que duplican el boot) y la demo se lee en castellano en vez de caer al fallback ingles. Ademas: README y pestanas de doc rehechos al reparto nuevo, ficha F2.13 y bitacora en PLAN-blocks.md, y el control vivo que faltaba para un prop publico — `verification` con reto / sin reto, porque omitir el prop no es lo mismo que pasar un estado que nunca verifica. Verificado en navegador, arco completo y las dos ramas: sin reto vacio → un campo → tres campos (se desbloquea) → «Enviando…» → «Enviado»; con reto, con los tres campos llenos el boton sigue bloqueado y la razon pasa a «Resuelve la verificacion de arriba». `verifying` y `rejected` quedan cubiertos por test unitario, no por el reto en vivo: el provider de ProofOfHuman escribe `status` el mismo y el veredicto del app no se sostiene (hilo de canon abierto). Gates: blocks:check verde (14 blocks) · vitest 9/9 · docs:check 0 · svelte-check sin errores propios · prettier limpio en los ficheros propios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
39d3790803 |
feat(blocks): contact (F2.13) — el bloque de contacto completo
Compound contact.* (header/reason/form/fields/submit/details) con schema y contexto propios, README y ruta de preview; marcado shipped en el catálogo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
75017e5515 |
feat(blocks): `team` — las personas, y un contexto que sirve para una sola cosa
F2.12. Compound porque `Member` SE REPITE (el app mapea sobre N), como
`feature-grid`. Pero es el primero del tier cuyo **contexto existe para una sola
cosa**: el `align` de la sección viaja a cada `Member` y a su fila de enlaces, así
que la foto, el nombre, el cargo y los enlaces comparten eje sin que el app repita
la alineación en cada tarjeta. Un `align` explícito en una parte siempre gana.
Esa es la diferencia con `feature-grid`, cuyos ítems solo se repiten y por eso no
llevan contexto: **la regla de forma se aplica parte por parte, no block por
block**.
## Dos decisiones de forma
- **Sin `.MemberAvatar`.** El plan la dibujaba; no tendría nada que añadir sobre
`<Avatar size radius>` y escondería su API (`Image`, `Fallback`, los estados de
carga). El app compone el `Avatar` del canon directamente dentro del `Member`.
Se envuelve lo que el block DEFAULTEA —tipografía, retícula, eje—, no lo que
solo reenvía.
- **El nombre es un `Heading level={3}`** apagado con `size="sm"`: una persona en
una retícula tiene nombre, y las referencias lo marcan igual. El nivel es
estructura del documento; el tamaño, tipografía. Así un lector de pantalla puede
saltar de persona a persona.
Y un detalle que separa cumplir de sobresalir: la fila de enlaces va pegada al
fondo de la tarjeta (`marginTop: auto`). Con biografías de distinto largo las filas
quedaban a alturas distintas y la retícula se leía descuadrada; sin biografías las
tarjetas ya miden lo mismo y la regla no cambia nada.
## Encontrado al verificar
**Un `columns` fijo no colapsa.** A 420px, cuatro columnas dejan celdas de ~90px
con el nombre partido en tres líneas y el avatar desbordando. El default del block
es fluido (`minChildWidth`), así que el fallo era de la demo: ahora pasa
`columns={{ base: 2, md: N }}` — para eso están los props responsivos. Anotado en
el README.
Verificado en navegador, esperando el atributo que pone la runtime del morfo y no
el reloj: 2/3/4 columnas, `align` center/start propagándose por contexto al eje de
cada miembro, biografía sí/no, claro/oscuro/RTL y 420px, seis avatares, doce
botones de icono **con nombre propio por persona** («Escribir a Ada Lovelace», no
«correo»), escalonado estructural con índices 0,1,2…, cero desbordamiento
horizontal y cero errores de página. Gates: `blocks:check` verde (13 blocks) ·
`svelte-check` sin errores propios · `docs:check` 0 · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
c57b66dc92 |
feat(blocks): `banner` — el aviso de arriba, y tres defectos del canon que no existían
F2.11. El block más FINO del tier a propósito: cuando el canon ya tiene la pieza,
el block es colocación y nada más. Reenvía la superficie entera del `Banner` con
`Omit<BannerProps, 'children'>` —sin re-declarar `intent`/`variant`/`size` ni
estrecharlos—, lo mete en columna con `Container` (`width="100%"` +
`paddingX={0}`: a sangre por fuera, en columna por dentro), envuelve la fila con
`Wrap` en vez de aplastarla, y cablea `Banner.Close` solo si llega `onDismiss`.
La visibilidad NO es suya: es la decisión que ya tomó el componente
(«composición, no un booleano `dismissible`»), así que el app envuelve en su
`{#if}`. Un block que guardara ese estado sería una segunda fuente de verdad.
Tampoco emite sema: el canon declara cero eventos para `Banner` a propósito, y un
aviso persistente sin descarte es tan legítimo como uno descartable.
La demo lo enseña **encima del `site-header` de verdad**, con página para
desplazarse. Un aviso dentro de un recuadro no se parece a un aviso.
## El hallazgo que la fase 0 anticipó
`Banner` estampa `role="banner"` DESPUÉS de sus rest props, así que no se puede
relajar a una región normal. Con el `site-header` en la misma página —que es la
única colocación que shipean las referencias— quedan **dos landmarks `banner`**.
Medido en la vista previa: dos. Lo único que puede hacer un app hoy es nombrarlos
con `aria-label`, y eso hace la demo.
## Y una lección de método que costó una sesión
Reporté tres «defectos del canon» —`aria-label` ausente en `Banner.Close`, `type`
desaparecido de todo `Button`, `IconButton` tragándose el `onclick`— y **los tres
eran falsos**. La causa era la misma: medir demasiado pronto.
Los `data-variant` / `data-size` los escribe el componente de eidos al renderizar,
así que están desde el primer frame; `type`, `aria-label` y los handlers los
aplica la **runtime del morfo en un efecto posterior**. A ~1s: `type` ausente en
3 de 3 botones y ningún clic disparando. A ~6s: `type="button"`,
`aria-label="Descartar"` y el descarte funcionando.
La espera válida es un atributo que solo pueda haber puesto la runtime
—`waitForFunction(() => boton.hasAttribute('type'))`—, no que el nodo exista ni
que se vea. La regla queda afinada en `CONTINUE-blocks.md`, que ya avisaba de
esperar la condición y no el reloj: fallé al elegir la condición.
De paso, un aviso de soma que sí era real y era mío: `Tabs.List` sin nombre
accesible en `BlockDemo.svelte`. Afectaba a las 12 demos del tier.
Verificado en navegador: los 4 intents, los 3 tratamientos, `descartable` sí/no
(con `no` el botón deja de renderizarse, no se oculta), claro/oscuro/RTL y 420px,
la tira siempre por encima de la cabecera, cero desbordamiento horizontal, el
descarte con la página recolocándose, y los controles de la demo moviendo la
vista previa en línea. Gates: `blocks:check` verde (12 blocks) · `svelte-check`
sin errores propios · `docs:check` sin errores míos · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
eb13fff409 |
feat(blocks): `site-footer` — el pie de la página, con el selector de idioma vivo
F2.10. Es el block donde la regla de forma del tier se ve mejor: `Column` SE
REPITE (el app mapea sobre N grupos), así que es parte compound; la marca, la
banda de alta, lo social, lo legal y el `extra` no se repiten ni coordinan, así
que son slots. El landmark es un `<footer>` de verdad: `contentinfo` sin pedir
nada.
Las columnas van en `AutoGrid minChildWidth`, **sin un solo breakpoint**: el mismo
código sirve para 2 grupos o para 5, y en móvil caen a dos. La entrada es un
`Motion trigger="viewport"` solo en la región superior y **sin escalonado** — una
línea de copyright que aparece con fundido es teatro, y nadie lee un pie columna
por columna.
## Los dos huecos que nombraba el dossier, cerrados
- **La banda de alta al boletín EN el pie** (3 de 7 en Tailwind Plus) como slot
`signup`, con su propia fila a lo ancho: en la columna de la marca (~230px) la
fila de correo tendría que apilarse. El formulario es del app; este block NO
importa el block `newsletter` (B-10) ni inventa validación.
- **El selector de idioma** como `extra` (el slot libre de D-BLK.6), y está VIVO:
escribe `uix.prefs.setIntent('language', …)`, el eje real del ecosistema.
Verificado — al elegir «English», `document.documentElement.lang` pasa a `en`.
## Tres números que salieron de medir, no de suponer
- El `container` bajó de `xl` a **`lg`**: con `xl` el pie no se alineaba con
ninguna sección de la página compuesta encima.
- La rejilla superior pasó de `1fr 2fr` a **`1fr 3fr`**: con `2fr`, un pie de
cuatro grupos se partía en 3+1.
- El gap entre columnas es **6, no 8**: con 8, cinco grupos no comparten fila al
ancho `lg` (728px justos). Horizontal más apretado que vertical, porque el gap
horizontal es el que decide cuántos grupos caben.
## Dos hallazgos de canon (F21/F22 en `PLAN-blocks-quality.md` §6)
- **Los primitivos de layout no pueden cambiar de elemento.** `Text` y `Heading`
aceptan `as`; `Box` —y por tanto `Stack`, `Flex`, `Grid`, `Group`, `Wrap`,
`Container`, `Section`— renderiza un `<div>` fijo. Consecuencia: una columna de
enlaces no puede ser `<ul>/<li>`, que es como la marcan las referencias. Un
block solo puede elegir entre divs o escribir markup que luego no puede estilar.
- **Un `Select` controlado muestra el VALOR crudo hasta que se abre una vez.**
`getDisplayText()` resuelve contra un registro de etiquetas que llenan los
`Select.Item` al montarse, y con el `Content` en un portal cerrado no hay ninguno
montado: `value=['es']` pintaba «es» en vez de «Español». Rodeado con el `child`
de `Select.Value`.
Verificado en navegador: 2/3/4/5 columnas, banda de alta sí/no, claro/oscuro/RTL
y 420px (2×2 columnas, fila de correo apilada), cero desbordamiento horizontal,
separador `aria-hidden`, los tres `IconButton` con nombre obligatorio, y el
idioma cambiando de verdad. Cero errores de página. Gates: `blocks:check` verde
(11 blocks) · `svelte-check` sin errores propios · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
f69cf76cfd |
feat(blocks): `newsletter` — el alta al boletín, y el reset que el framework daba por hecho
F2.9. Slots de snippet (eyebrow · title · description · field · submit · note ·
children), `center` y `justified`, y `panel` como interruptor real: el panel de
marca es la cara de las referencias, pero el control, la ayuda y el error están
calibrados para la superficie de página, así que apagarlo es un estado de primera
y no un fallback.
La fila es `Grid templateColumns={{ base: '1fr', sm: '1fr auto' }}`: el campo se
queda la pista libre y la acción abraza su contenido —eso es lo que hace que un
alta se lea como UN gesto— y bajo `sm` pasa a una columna, porque un botón al lado
de un campo de correo deja inservibles a los dos.
El block NO valida y NO emite sema. `Form` posee el runtime, el esquema, dirty/
touched, la agregación de errores y el foco al primer error; `Field` posee el
cableado ARIA; el morfo del `Form` ya declara `commit-submit` y `signal-invalid`.
El app posee esquema, valores y handler. No hay ni un `if` sobre un correo aquí.
## Superación del dossier: la etiqueta
El único hueco que el dossier nombraba era la nota de privacidad, y está. Pero la
diferencia de verdad es la etiqueta: las referencias shipean la fila escondiéndola
con `sr-only` o dejando solo un placeholder. El canon tiene `Field floatingLabel`
—arranca dentro del control y sube al borde al enfocar o rellenar—, así que la fila
queda alineada CON etiqueta real y asociada. Verificado con pulsaciones de teclado
de verdad: 10px dentro en reposo → −11px sobre el borde al enfocar y al rellenar.
## Tres hallazgos de canon más, medidos
- **La fundación de eidos no trae reset de modelo de caja y lo asume del app.**
`[data-field-control]` declara `inline-size: 100%` + padding, así que bajo
`content-box` el control mide 30px más que su contenedor: el campo se metía por
debajo del botón de envío. Campo 480 / control 510 en la galería frente a
502 / 502 en los docs de componentes, que sí resetean (igual que
`web/routes/active/styles.css`). Arreglado en app-land con
`web/routes/blocks/_lib/reset.css`, con A/B sobre los 10 previews y 5 páginas de
shell: cambia el newsletter y NADA más. Hay que importarlo dos veces porque la
galería arranca UIX en línea en vez de pasar por `BootUix` — deuda del arnés,
anotada en el handoff.
- **`onValidSubmit` es un no-op silencioso** cuando se pasa un `form` ya
construido: el componente solo lo reenvía al `createForm` que hace él mismo. El
envío validaba, limpiaba el error y no anunciaba nada. Por eso el block no
expone el prop: el handler va en el `createForm` del app.
- **Los mensajes de SIUM son idlangref.** La vía correcta es
`uix.langs.t(issue.message, issue.params)` —verificado, sale «Debe ser una
dirección de correo válida»—, pero la demo de docs del propio `Form` parte la
cadena a mano tras el `|`, así que el único ejemplo del repo enseña el patrón
equivocado y siempre muestra inglés.
Los tres quedan en los gaps del README y en `PLAN-blocks-quality.md` §6
(F18/F19/F20), sin tocar nada fuera del tier.
Verificado en navegador el arco completo: correo inválido → error traducido con
`role="alert"`, `aria-invalid`, `aria-describedby` y foco al primer error; correo
válido → confirmación del app (`Callout` afirmativo con la dirección) y error
limpio. Claro/oscuro/RTL, tres colores, `panel` sí/no, 420px. Cero errores de
página. Gates: `blocks:check` verde (10 blocks) · `svelte-check` sin errores
propios · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
03d6796582 |
feat(blocks): `cta` — el panel que pide el siguiente paso, y tres mentiras que destapó al medirlo
F2.8. Slots de snippet (eyebrow · title · description · actions · children),
misma forma que `hero`: las partes de un CTA no repiten ni coordinan, así que un
compound no se gana nada. Dos disposiciones: `center` para cerrar una página y
`justified` —copia al inicio, acciones al final— que es el hueco que nombraba el
dossier. Un solo `Motion trigger="viewport"`: el panel llega ENTERO, porque un
CTA es una sola afirmación y repartir sus tres partes se leería como duda.
Lo que salió al verificarlo en navegador, medido y no a ojo:
- `variant='soft'` **fuera de la API**. Su track queda a `oklch(0.9932)` contra
un `--color-surface-default` de `oklch(0.9911)`: 0.002 de luminancia, o sea
ningún panel en claro. Y `Surface` no tiene borde al que caer. Cortar el prop
es más barato que shipear un estado que se esfuma.
- La ranura `contrast` de la paleta es blanco en TODO escalón sólido, así que un
lienzo de luminancia media deja el cuerpo por debajo de AA: `primary` 5.18 ·
`indigo` 5.21 · `plum` 4.75 pasan en ambos modos; `neutral` 3.32 · `teal` 3.07
fallan en claro. El block reenvía cualquier `color`; la demo solo ofrece los
que pasan.
- `Text align` es inerte por defecto: renderiza un `span`, y `text-align` no hace
nada sobre una caja inline. `align="center"` dejaba la copia a la izquierda
dentro del layout centrado, sin avisar. Rodeado con `as="p"`.
Y `Group` no apila: a 420px la etiqueta de la acción secundaria se parte contra
el botón primario, así que las acciones van en `Flex direction={{ base:
'column', sm: 'row' }}`. `hero` compone las suyas con `Group` — anotado.
Los tres hallazgos de canon quedan en los gaps del README del block y en
`PLAN-blocks-quality.md` §6 (F15/F16/F17), sin tocar nada fuera del tier.
Verificado: `center` y `justified` en claro/oscuro/RTL y a 420px, tres colores,
entrada disparada, descripción en `<p>` centrada, cero errores de página.
Gates: `blocks:check` verde (9 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
4e785d32bf |
feat(blocks): F2.7 `stats-band` — la cifra que se cuenta sola
La fila de números que respalda lo que la página acaba de afirmar. Compound
(`.Stat` repite, sin contexto): `<StatsBand>` + `.Stat` + `.Value` + `.Label`.
- **Compone, no reinventa**: la semántica de KPI es el `Metrics` del canon, leído
de su API y pasado tal cual (precedente `faq`/`Accordion`). `Metrics` es
surfaceless, que es exactamente lo que una banda necesita: no son tarjetas de
panel. Un delta, un icono o un sparkline son `Metrics.Delta`/`.Icon`/`.Chart`
que la app compone dentro — el block no los re-expone.
- **La superación literal del dossier**: `<StatsBand.Value count={12500} />`
compone `CountUp`, así que la cifra se cuenta sola al entrar en pantalla.
NINGUNA referencia puede shipearlo: todas entregan markup estático. El formato
locale-aware y el salto directo bajo reduced-motion vienen del `CountUp`, no
de aquí.
- **`count` es opt-in, nunca default**: una cifra que se anima sin que el lector
lo pida es ruido, y algunas no son contables («99,98 %»). Sin `count`, la
cifra la pone la app por children.
- **Ni una palabra ni un separador salen del block** (B-7): las palabras por
children, el formato por `uix.format` dentro del `CountUp`.
- Escalonado estructural: `data-stagger` en la banda y cada `.Stat` es un
`Motion trigger="viewport"`, así que las cifras aterrizan una tras otra sin un
solo milisegundo escrito a mano.
Verificado en navegador (con la banda bajo el pliegue, para cazar el conteo desde
el primer fotograma): 4 cifras con índices estructurales 0,1,2,3 contando
—9377→11.678 · 255→318 · 36→45—, el porcentaje quieto por no ser contable, y el
separador de millares por locale (`11.678`). Cero errores de página.
`blocks:check` verde (8 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
2 months ago |
|
|
54b8b838e7 |
feat(blocks): F2.6 `faq` — proxy fino del Accordion del canon
La sección de preguntas: una cabecera sobre un acordeón de P/R en columna estrecha. Compound (`.Item` repite) y —la regla al envolver un componente interactivo— un PROXY FINO del `Accordion` del canon: el block lee su API y la pasa tal cual, no reinventa el disclosure. `<Faq>` + `.Header` + `.List` + `.Item`. - `.List` **ES** el `Accordion`: toda su API pasa sin gate — `type` (single/multiple), `bind:value`, `collapsible`, `variant`, `size`. Defaults de FAQ: single, collapsible (el abierto se puede cerrar), outline. - `.Item` proxya `Accordion.Item > Header > Trigger`(snippet `question`) / `Content`(children) y autogenera el `value` (clave de estado) con `$props.id()` si no se pasa — la única conveniencia sobre el andamiaje. - El teclado (flechas, Home/End, Enter/Espacio), `aria-expanded` y `aria-controls` salen del `Accordion`; el block no toca la a11y del disclosure. `Container` estrecho (`md`) para una columna legible. Demo (`web/routes/blocks/faq/`): full-bleed + ruta `preview`, cinco preguntas en una columna, con la cola «¿aún tienes dudas?» que la app pone tras `.List`. Huecos a decisión del usuario (Gaps): la **lista estática 2/3 columnas** (6 de 7 en TW NO son acordeón, sino P/R siempre abiertas) — otra disposición, prop `layout` o hermano. Verificado en navegador: el acordeón abre/cierra, claro/oscuro × LTR/RTL. `blocks:check` verde (7 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
b59332132f |
feat(blocks): F2.5 `testimonials` — rejilla de citas con autor
Prueba social: una cabecera sobre una rejilla responsive de tarjetas de cita, cada una con su autor (avatar · nombre · cargo). Compound —la `.Item` se repite, como feature-grid— y SIN contexto (las partes no coordinan). `<Testimonials>` + `.Header` + `.Items` + `.Item` + `.Quote` + `.Author`(slot `avatar` + `.AuthorName`/`.AuthorRole`). - `AutoGrid` de tarjetas de igual alto; `.Author` fijada al borde inferior de la tarjeta (`margin-block-start: auto`) para que una fila de citas de distinto largo alinee las caras. - La cara es del app: un `<Avatar>` con imagen o un `Avatar.Fallback` de iniciales va en el slot `avatar` (B-7). - La tarjeta del quote usa `variant="outline"` — el `soft neutral` es casi invisible en claro. Encontrado al componer (en el README del block): el tema activa solo un SUBCONJUNTO de escalas donor (`green/indigo/orange/plum/teal`); una escala no activada (`cyan/ruby/amber/jade`) en `color` cae en SILENCIO a `primary` (la regla `[data-color]` hace `var(--scale-…, primary)`). Config del tema, no bug de componente; la demo usa escalas activadas. Hueco a decisión del usuario (Gaps): la **cita única en spotlight** (grande, centrada, logo+avatar+autor) — la variante modal del dossier, el grid es minoría. Otra disposición → candidato a hermano `testimonial-spotlight`. Demo (`web/routes/blocks/testimonials/`): full-bleed + ruta `preview`, cinco citas con avatares de iniciales en cinco colores distintos. Verificado en claro/oscuro × LTR/RTL, caras alineadas al fondo. `blocks:check` verde (6 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
594e5a5db4 |
feat(blocks): F2.4 `pricing` — el primer compound CON CONTEXTO del tier
La sección de planes: un toggle de periodo sobre una fila de planes, uno destacado. Es el primer block cuyas partes se COORDINAN de verdad (no solo se repiten como en feature-grid/split): el `Switch` escribe el periodo de facturación y cada `PlanPrice` lo lee. Esa coordinación es lo que gana un contexto compartido — la forma más fuerte de compound. `<Pricing bind:period>` + `.Header` + `.Switch` + `.Plans` + `.Plan`(featured, badge) + `.PlanName`/`.PlanDescription`/`.PlanPrice`/`.PlanFeatures`/ `.PlanFeature`/`.PlanAction`. - **Contexto reactivo** (`context.ts`): la raíz provee el periodo como getter sobre un `$bindable`; el `Switch` (un `ToggleGroup`) lo escribe, el `PlanPrice` lo lee y muestra el snippet `monthly` o `annual`. Mismo patrón que `CardGroup`. `period` es bindable por si la app quiere observarlo. - **El block NUNCA formatea moneda**: la app compone `FormatNumber` dentro de los snippets de precio (B-7). El block posee el switch, no el dinero. - `.PlanAction` fija el CTA al borde inferior de la tarjeta (`margin-block-start: auto`) para que una fila de planes alinee sus botones aunque tengan distinto nº de features; `.Plan` con `align="start"` deja los checks en columna limpia; `featured` da acento (borde primary) + elevación. Demo (`web/routes/blocks/pricing/`): full-bleed + ruta `preview`, tres planes (Pro destacado en el centro) con el toggle mensual/anual vivo. Hueco a decisión del usuario (Gaps del block): la **tabla de comparación** (features × planes) — la brecha recurrente del dossier en pricing. Es una tabla, no una fila de tarjetas: candidato a hermano `pricing-table`. Presentado, no resuelto. Verificado en navegador: el toggle cambia los TRES precios a la vez (0/29/99 → 0/23/79), tarjetas de igual alto con CTAs alineados, featured con acento, en claro/oscuro × LTR/RTL. `blocks:check` verde (5 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
08ea82d128 |
feat(blocks): F2.3b `feature-split` — la brecha nº1 del dossier, con Mockup
La sección que las 5 refs shippean y que va justo tras el hero: una afirmación de producto junto a un screenshot, alternando lados fila a fila. Otra disposición que la rejilla de iconos de `feature-grid`, así que block hermano (decisión del usuario), no una variante turbia dentro de aquél. Compound —la `.Row` se repite—: `<FeatureSplit>` + `.Row` (`reversed`, slot `media`) + `.Eyebrow` + `.Title` + `.Text` + `.Features`/`.Feature` (checklist) + `.Actions`. El block coloca; la app pone la copy y la media. - `reversed` mueve la media al lado de inicio vía `grid-column` (Box expone `gridColumn`/`order`), dejando la copy SIEMPRE primera en el DOM — el orden de lectura y el foco no cambian aunque el screenshot salte de lado. - El check de cada `.Feature` es decorativo (`aria-hidden`): la palabra lleva el significado. - La media se COMPONE con el primitivo `Mockup`: la demo enseña cromo de navegador (dashboard) y de teléfono (app), cerrando el hueco de «tratamiento de media» del dossier de raíz en vez de falsearlo. `.Row` usa un tipo limpio (no `HTMLAttributes`) porque su slot `media` colisiona con el atributo HTML homónimo; el resto de sub-partes extienden los props del componente canon que envuelven (lección de feature-grid). Demo (`web/routes/blocks/feature-split/`): full-bleed + ruta `preview`, con control del nº de filas y del lado inicial; 3 filas con Mockup navegador/teléfono. Verificado en navegador (Playwright, módulos frescos): filas alternas en claro/oscuro × LTR/RTL, `reversed`, el orden de lectura copy-primero, y los dos cromos de Mockup. `blocks:check` verde (4 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
d01b3e6af5 |
feat(blocks): F2.3 `feature-grid` — el primer block compound del tier
`<FeatureGrid>` + `.Header` + `.Items` + `.Item` + `.ItemIcon`/`.ItemTitle`/ `.ItemText`: una cabecera sobre una rejilla responsive de features (icono · título · texto). Es la sección que responde «qué hace», la nº1 tras el hero. Primer block COMPOUND del tier, y aplica la regla de forma que fijamos: el `.Item` se REPITE (el app mapea sobre N features) → gana sub-componentes, donde `hero`/`site-header` usan slots de snippet (partes fijas de layout). Las partes no coordinan —sin contexto ni estado entre ellas—: la rejilla es del padre, las celdas del app. `.Items` es el envoltorio honesto de la rejilla (`AutoGrid`), que deja la cabecera fuera sin un `grid-column: 1/-1` a pelo. - El block coloca (Section · Container · AutoGrid · Surface · Heading · Text); el app pone todo el contenido por children (B-7). - `.Items` fluido por `minChildWidth` (tantas columnas como quepan) o `columns` fijas; `.Item` `align` start/center; `.ItemIcon` chip `Surface`; `.ItemTitle` `Heading` h3; `.ItemText` `Text` apagado. - `align` de sección (center/start) coloca la cabecera coherente con las columnas. Dos cosas encontradas al construir, resueltas: - **Los sub-componentes de bloque extienden los props del componente canon que envuelven** (`BoxProps`, `StackProps`, `HeadingProps`…), **no `HTMLAttributes`**: el `style: string|null` del atributo HTML crudo choca con el `style: string` del canon al hacer spread (+ "union type too complex"). - **`.ItemIcon` por defecto `solid`, no `soft`**: el soft-primary en claro es casi blanco (oklch 0.99) → el chip era invisible; solid da el chip con glifo on-solid (la tinta de contraste la pone `Surface`). Demo (`web/routes/blocks/feature-grid/`): full-bleed + ruta `preview`, con control de columnas (fluido/2/3/4), align, nº de items y dir; 6 features con iconos del canon. Hueco a decisión del usuario (en los Gaps del block): el **feature-split/ alternante** (texto junto a un screenshot, lados alternos) — la brecha nº1 del dossier — es otra disposición (filas de 2 columnas, no rejilla de iconos): probablemente un block hermano `feature-split`. Presentado, no resuelto. Verificado en navegador (Playwright, módulos frescos): center/start × claro/ oscuro × LTR/RTL, columnas fluidas y fijas, 3/4/6 items, chips visibles. `blocks:check` verde (3 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
5125502486 |
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se razonó con el usuario y con el paisaje de referencia. Compound se reserva para partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un hero son cinco slots de layout que no se hablan entre sí y que el root arregla, así que snippets. Además el campo entero shippea marketing como copy-paste plano —nadie aplica compound a una sección—, y los slots por zona son la historia de personalización que distingue al tier del copy-paste. `<Hero layout="center|split|background" level container size>` + `eyebrow/title/description/actions/media/background/children`. - El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el `id` que nombra el `<section aria-labelledby>` y el nivel del encabezado, mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son contenido libre (ahí los componentes del canon SON la API). - `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de dos columnas (una sola sin media), apila en estrecho. - **`background` (cover)** — añadido por scope-approval del usuario: la media a sangre detrás de la copy, con velo de contraste (`--color-overlay` a `--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>` justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin `z-index`. Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`). Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow` no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el README del block y en el handoff para revisar el cableado de `--box-flex`. Verificado en navegador (Playwright headless, módulos frescos): center/split/ background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado (región con `aria-labelledby` que resuelve al título). `blocks:check` verde (2 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
ae7b4f8c3b |
demo(blocks): el block se ve en una página de verdad, no dentro de una caja con scroll
«¿Dónde has visto que el sticky esté por debajo del scroll?» — en ningún sitio, y la respuesta correcta era esa. Un `position: sticky` metido en un div de 460px con scroll propio no es el que va a vivir nadie: se pega al borde de una caja, no al del viewport. Estaba enseñando un comportamiento que no existe fuera de mi página. Ahora la demo presenta el block A SANGRE en la propia página: el header se pega al viewport de verdad, con el scroll de verdad. Verificado: al bajar queda en top 0, con `data-stuck`, visible y por encima de todo (hit-test), y con `offset: 48` se queda exactamente a 48. Los anchos de dispositivo (375 / 768) sí necesitan un documento propio, así que ahí —y solo ahí— se monta un iframe de la ruta `preview`. Es opt-in a propósito: en dev, dos documentos sin empaquetar a la vez agotan las conexiones del navegador (`ERR_INSUFFICIENT_RESOURCES` se llevaba por delante las DOS páginas; lo medí). Con `loading="lazy"` y bajo demanda, no molesta. Para no duplicar contenido, el mini-sitio vive en `SiteHeaderSite.svelte` y lo consumen las dos superficies: la demo lo renderiza inline con sus props y la ruta `preview` lo sirve como documento leyendo la URL. «Abrir ↗» lleva ahí. De paso: la topbar de la sección deja de ser pegajosa. Una página cuya estrella es un block que se pega no puede tener su propio cromo peleándole el borde superior — el header del block se metía debajo y el `offset` que elegías se leía como otro número. El raíl sí sigue pegado, que no compite. Verificado: `/blocks`, `/blocks/site-header` y la preview con `?dir=rtl&mode=dark` responden 200 sin desbordes ni errores de consola; `blocks:check` verde; `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
d574982ab9 |
demo(blocks): el escenario va a ras — `offset: 0` volvía a ser 0
Tenías razón: con `offset` a 0 la barra quedaba a 21px del borde del marco, y
16 y 48 sumaban sobre esa deuda (37 y 69). El offset del block estaba bien;
el marco era el que mentía.
Causa: usaba un `Card` como escenario y le pasaba `padding={0}`, pero el
recipe del Card pinta con su propia variable (`--card-padding-*`), que la capa
Box no alcanza — así que se quedaban sus 20px de relleno más 1px de borde.
Un header pegado se pegaba al borde INTERIOR de ese relleno.
El escenario pasa a ser un marco desnudo (`[data-blocks-stage]` en
`blocks.css`): borde, radio, altura y su propio scroll, sin relleno. Un
escenario tiene que ir a ras o miente sobre lo que enseña.
Verificado en navegador: offset 0 → 0px del borde, 16 → 16, 48 → 48, y al
volver a 0 → 0. Cero errores de consola.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
33328df7ae |
demo(blocks): la sección tiene cromo de verdad — tema, idioma, dirección, densidad y catálogo
La crítica era exacta: no se podía cambiar a oscuro, ni de idioma, ni había forma de moverse entre blocks. La sección arrancaba UIX y renderizaba la página desnuda, con un comentario que decía que el modo seguía al sistema operativo «para verificar oscuro emulando prefers-color-scheme» — cómodo para mí, inútil para quien mira la página. Ahora hay shell: topbar pegajosa con los ejes que de verdad mueven el framework (idioma ES/EN/FR/DE y dirección como INTENTS de prefs, así que llegan a langs, formatos y al AGENTS.md debug.log routes tsconfig.json CLAUDE.md docs scripts tsconfig.tsbuildinfo README.md node_modules src vite.config.ts STUMBLES.md package-lock.json static web audit package.json svelte.config.js worker.js build plans tmp workers del documento por el camino del sistema; densidad proyectada en :root para que la hereden también los overlays porteados; y el conmutador de tema, que alimenta el del ActiveEidos) y un raíl con el catálogo entero de los 27 blocks planificados, marcando el actual y atenuando los que aún no existen. Todo persiste en localStorage, así que una recarga te deja donde estabas. El catálogo pasa a : raíl y galería leen la MISMA fuente, y un block que aterriza no hay que recordarlo en dos sitios. Las tres regiones las coloca — cromo de app-land, el papel que hace en las docs de componentes; la regla de «sin CSS» del contrato B es para el tier (), no para el sitio que lo enseña. Dentro de esas regiones todo son componentes del canon. Verificado en navegador: el botón conmuta a oscuro de verdad (fondo oklch(0.1776 0 0)), el idioma llega a , el raíl lista los 27 y desaparece por debajo de 64rem sin desbordes, cero errores de consola. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
50574806d6 |
demo(blocks): harness compartido de demos + site-header con contenido real
La página de demo del primer block era mala y con razón: sin cabecera, sin contexto del block, controles sueltos flotando, backticks de markdown en crudo dentro de <Text> y relleno del tipo «filler section so the page scrolls». Harness (`web/routes/blocks/_lib/BlockDemo.svelte`), escrito una vez para las 14: identidad arriba (chip, ruta de import, vuelta a la galería, título, una frase de función y chips de datos: qué compone, landmark, props, cuántas cadenas propias) → el block VIVO dentro de un marco con su propio scroll, que es una página en miniatura → panel de controles agrupado → pestañas Composición · API · A11y · Gaps · Notas, que solo aparecen si la demo las pasa. Todo con componentes del canon (Card, Tabs, Badge, Stack…): esta sección no tiene cromo de docs propio y sigue haciendo dogfooding. `DocRow` acompaña al harness porque el `Table` del canon es una tabla de DATOS (quiere un `createTable`): la documentación en prosa usa una fila de dos columnas, igual en las 14. site-header: reescrita sobre el harness con contenido creíble de producto —nav con destinos reales y descripciones, hero y secciones que dicen algo— en vez de relleno evidente, y con control vivo de los cinco props (sticky, container, breakpoint, offset, dir) más el eje de dirección. Sin backticks literales: lo que es código va en <Code>. Verificado en navegador tras el arreglo de Box: cabecera compacta, escenario con la barra a 950px que se pega al hacer scroll dentro del marco, controles en una fila y las cuatro pestañas con contenido. Cero errores de consola. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |