You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/process/CONTINUE-affix.md

13 KiB

CONTINUE — Affix, la capa de colocación, y el eje que abrió: los knobs visuales

Kickoff: «Lee docs/process/CONTINUE-affix.md y sigue por §3.» Fecha: 2026-08-15 (cierre de jornada) · Rama alpha-0.1-dir-prefs, pusheada en 65536ae6c — limpia contra el remoto, 0 por delante. ⚠️ RAMA COMPARTIDA, y hoy se notó: otra sesión trabaja en blocks y su commit 948f7cf7e es la punta actual. En el árbol quedan 23 ficheros suyos sin commitear (src/uix/blocks/*, docs/process/*blocks*, scripts/blocks-check.ts). Stagear por lista explícita, nunca git add de un directorio: hoy un git add docs/ se llevó cinco ficheros ajenos. Doctrina viva: architecture/morfo.md §What morfo does NOT contain + §Backlog undeclaredState · architecture/eidos.md · theming/reference.md §39 · canon/vocabularies.md §Placement grids (2 × 9). El relato completo del eje Affix está en PLAN-affix.md §6 y §7 — este documento no lo repite.


0 · Dónde estás

Catorce commits publicados en la jornada; trece son de este eje (el catorceavo, 948f7cf7e, es de la sesión de blocks — se publicó con el push porque la rama es una sola).

Commit Qué
92c8f6466 Affix — la pieza que Sticky no puede ser, y la capa que Fab y MenuDial ya habían copiado dos veces
1fc0a7a96 Fab deja su copia privada de la colocación y lee la capa
c945be23b breaking — un eje de capa = un token público + una ranura; --fab-offset / --fab-z retirados sin shim
acb7f0b41 la capa deja de no tener guard: uno de texto y uno de valor computado
4b39b718c el aviso al pie se lee donde se ve
c53efc602 MenuDial lee la colocación de la capa — la última copia privada, borrada
ee41b691d los 20px del MenuDial son una decisión, no una deriva
7ea913ab1 la rejilla lógica se canoniza — cinco uniones a mano pasan a una; cierra EID-3
a11805d43 la doctrina alcanza a la jornada
de0615caf affix deja de declarar lo que sólo lee una capa; el gancho pasa a nombre de CAPA
b72a56491 la capa sale de components/ — tenerla ahí acoplaba Fab y MenuDial a Affix
998d72191 los 25 knobs visuales salen del contrato — comentados, no borrados
65536ae6c el enum que salió del morfo vuelve, leído de la unión de TypeScript (§2), y este handoff

1 · El eje Affix: CERRADO

Está entero en PLAN-affix.md §6 (lo construido vs lo planeado) y §7 (lo que vino después: el peldaño --z-index-affix: 150, el patrón de dos tokens por eje, la canonización de la rejilla, los dos guards, y las dos correcciones del autor). Lo único que sigue abierto ahí:

  • Test de geometría de la capa — diferido al tercer consumidor. El tercero ya existe (Affix, Fab, MenuDial), así que la condición se cumplió. El hueco y por qué ninguna aserción a nivel de propiedad lo cubre está escrito en la cabecera de scripts/layer-check.ts: getComputedStyle da el valor USADO, y un inset: auto se lee como píxeles (medido: -1976.7px en una sonda, 324px en el fallo real del ciclo). Discriminarlo exige geometría — el borde contra su bloque contenedor —, y eso exige recorrer los ancestros buscando transform / filter / contain / will-change.

2 · El eje que abrió: los knobs visuales fuera del contrato

No estaba previsto. Nació de la corrección de de0615caf (§7 del plan): affix declaraba dos attrs que lee una sola capa. Al aplicarle la misma vara al resto del árbol salieron 25 declaraciones más, en seis morfos.

La sentencia del autor, literal

«la doctrina prevalece, porque no tiene sentido declarar atributos que sólo se usan en una capa, morfo funciona como una capa declarativa entre capas, eso es el diseño.»

Y el criterio de retirada:

«no los retires, déjalos comentados y con la explicación del porqué.»

Lo aplicado (998d72191)

25 entradas comentadas en sitio — avatar 9 · s-text 6 · image 5 · avatar-group 2 · skeleton 2 · qr-code 1 —, cada bloque citando tres fuentes de doctrina (morfo.md §What morfo does NOT contain, morfo.md §Backlog undeclaredState, theming/reference.md §39). Comentadas y no borradas porque son el registro de lo que el morfo prometía y de los enums que un guard podría volver a leer.

De todo lo que declaraban esos seis morfos, sobrevive sólo data-status (avatar ×3, image ×4): lo mueve ImageProvider desde $soma/layers, así que sí cruza la frontera de capa. Ese es el criterio, y se sostiene solo.

Lo que se perdió al comentarlos, y se ha devuelto hoy

Un values: [...] en el morfo le daba a eidos-lint con qué rechazar [data-size='huge']. Al salir, esa validación se quedó sin fuente. Se ha devuelto leyendo la unión de TypeScript, que es donde el enum vive de verdad.

No en eidos-lint: en recipe-css-contract.test.ts, donde el mecanismo ya existía para variant desde antes (línea ~790, «Variant canon enforcement»). Lo hecho es extenderlo, no inventarlo:

  • el colector de valores CSS pasa a tomar el attr por parámetro;
  • un extractor nuevo entiende Extract<Base, 'a' | 'b'> — donde viven cinco de los ejes (AvatarSize, ImageSize, SkeletonSize, QrCodeSize, AvatarBadgePosition) — y el miembro boolean de AvatarRing;
  • 11 ejes en la tabla KNOB_AXES, inspeccionando 44 valores CSS reales.

Dos cosas que importan más que el conteo:

  • qr-code.cell-shape quedó FUERA, con motivo escrito. Estampa data-cell-shape en el <svg> raíz pero ninguna regla CSS lo lee: la forma se dibuja en la geometría del <path>. Con él dentro, el test pasaba inspeccionando el conjunto vacío. De ahí la aserción used.size > 0: un eje ciego es fallo, no verde. Su prop la sigue guardando tsc en el call site.
  • Probado por mutación, 4/4: typo en un valor CSS · unión estrechada · unión reescrita a un alias ilegible · eje cegado por renombrado de reglas. Los cuatro detectados; el árbol restaurado byte a byte después.

El hallazgo, y la decisión que espera

Buscando si los ejes con alias estaban cubiertos en otro sitio apareció src/uix/eidos/component-visual-attrs.test.ts. Es la otra mitad del mismo problema, y lleva ahí desde antes de esta jornada: verifica que el wrapper siga estampando el attr, para 60+ componentes, y guarda data-size / data-variant / data-color enteramente fuera de morfo. Es la prueba, en el propio árbol, de que la sentencia del autor no inaugura nada: describe cómo estaba construido el sistema ya.

Las dos mitades, ahora explícitas:

Mitad Pregunta Dónde
Presencia ¿el wrapper sigue estampando el attr? component-visual-attrs.test.ts
Enum ¿el valor es uno que el tipo permite? recipe-css-contract.test.ts (§2)

Y el hueco en la mitad de presencia, medido: image, skeleton, qr-code y s-text no tienen ninguna fila (0 de 4); avatar tiene 3, pero no lista data-ring, data-position (badge) ni data-stacking. Añadir filas ahí es una decisión del autor, no una consecuencia mecánica: está presentada y parada.


3 · Qué queda, en orden

  1. DECISIÓN PENDIENTE — cerrar el hueco de la mitad de presencia. Cuatro componentes sin fila y tres ejes de avatar sin cubrir (arriba). Es añadir filas a VISUAL_ATTRS; lo que no es mecánico es decidir si los 7 knobs booleanos (data-dot, data-truncate, data-clamp, data-canvas, data-italic, data-underline, data-animated) entran ahí o no se guardan.
  2. Los dos ejes con alias, sin cubrir por el guard de enum: data-color (→ ComponentColorProp, en eidos/lib/types.ts) y data-style de s-text (→ TextStyle, en components/text/types.ts). El parser no sigue imports a propósito. Resolverlos es leer el módulo destino; decidir si vale la pena está abierto.
  3. Test de geometría de la capa (§1) — el tercer consumidor ya existe.
  4. Ajenos a este eje, preexistentes y NO tocados: morfo:check falla en fab (data-fab-size sin declarar) y menu-dial (data-state ausente en el trigger). ⚠️ Sobre el primero: data-fab-size no está en ningún morfo — grep -rn "data-fab-size" src/uix/morfo/ no devuelve nada, y git log -S confirma que nunca lo estuvo. Lo mismo data-picker-size (×7) y data-marker-size. No son deuda: son el modelo. Lo que chirría es morfo:check, que clasifica por PREFIJO mientras la doctrina clasifica por NATURALEZA. La salida limpia sería renombrar a data-_* (prefijo reservado, «intentionally outside morfo») — sin firmar.

4 · Cómo medirlo (estado al cerrar, verificado hoy)

npx vitest run src/uix/eidos
Guard Estado
npx vitest run src/uix/eidos 421/421 en 32 ficheros (recipe-css-contract aporta 42)
suite completa (npm run test) 4960/4969; los 9 fallos = 6 reales + 3 falsos (abajo)
contracts.test.ts 6 fallos PREEXISTENTES: waveform, media-player, audio-player, menubar, aura, radio-group, tabs — ninguno tocado por este eje
npm run morfo:check 6/159 fallan: color-field, combobox, fab, gradient-builder, menu-dial, palabras (preexistentes)
npm run docs:check 0 errores / 627 docs
npm run rtl:check 0 errores / 178 ficheros
npm run layer:check requiere dev server; 0 violaciones sobre 3 consumidores en la última medida
npm run check 69 errores / 6899 ficheros — la línea base heredada, sin mover en toda la jornada

5 · Tres trampas de la jornada, para no repetirlas

  • Un test en verde no prueba nada hasta que falla sobre lo que dice atrapar. layer-check pasó en su primera ejecución habiendo inspeccionado cero elementos (la demo de fab venía con placement="static", que no estampa gancho). El guard de enum estuvo a punto de repetirlo con qr-code. De ahí que ambos traten el conjunto vacío como violación, y que los dos estén probados por mutación (8 defectos inyectados en los de la capa, 4 en el de enum; 12/12 detectados).
  • prettier --write sobre un fichero preexistente sucio reformatea el fichero entero. Pasó tres veces hoy (66 líneas por 9 mías; 227 por 1). 7 de los 14 tests de src/uix/eidos/ están sucios en HEAD — todos sin punto y coma. La regla de CLAUDE.md manda: match existing style. No formatear al pasar.
  • Correr guards pesados en paralelo con la suite produce fallos fantasma. npm run test dio 9 fallos con cuatro scripts tsx corriendo a la vez; los 3 de soma-attr-audit / orca / storage eran timeouts por inanición de CPU — 161/161 en aislamiento. Medir en serio es medir solo.

Powered by TurnKey Linux.