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

204 lines
13 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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`](../architecture/morfo.md)
> §_What morfo does NOT contain_ + §Backlog `undeclaredState` ·
> [`architecture/eidos.md`](../architecture/eidos.md) ·
> [`theming/reference.md`](../theming/reference.md) §39 ·
> [`canon/vocabularies.md`](../canon/vocabularies.md) §_Placement grids (2 × 9)_.
> El relato completo del eje `Affix` está en
> [`PLAN-affix.md`](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`](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`](../../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`](../../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)
```bash
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.