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/audit/theming-audit.md

144 lines
20 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.

# Auditoría del sistema de theming — Fase 1 (clean-room)
- **Estado**: CERRADA (2026-07-05 → 2026-07-07). Todos los ítems del checkpoint ejecutados bajo veredicto de usuario, con guard y verificación por ítem.
- **Reemplaza**: el informe de Fase 1 anterior, invalidado por contaminación metodológica (ancló en auditorías previas, invirtió la jerarquía doc↔código, decidió valores unilateralmente). Este informe se derivó de cero.
- **Método**: clean-room — entradas prohibidas `docs/old-deprecated/**`, informes previos de `docs/audit/**`, `tmp/component-audit.md`; sin agentes; evidencia primaria (lectura directa + greps reproducibles + sondas de navegador). **La norma es la decisión de diseño documentada y fechada** (changelog/RFC/canon); el código discrepante es *deriva*, y las fechas arbitran. Ningún valor se decidió sin veredicto explícito del usuario.
- **Desviación sancionada del plan**: el plan original era solo-informe. En el checkpoint (F) el usuario redirigió a **resolver ítem a ítem** — análisis (implicaciones + referencias de frameworks + coherencia del ecosistema) → veredicto → ejecución con nota fechada + guard + verificación. Este informe documenta hallazgos **y** su resolución.
- **Detalle canónico**: cada ejecución dejó su crónica en [`docs/theming/changelog.md`](../theming/changelog.md) §39–§41 y notas fechadas en secciones/RFCs previos. Este informe **enlaza, no copia**.
---
## 1. Resumen ejecutivo
El sistema quedó, tras la ejecución, en el estado que su propia doctrina promete: **una fuente por concepto, todo eje como dato del config, contrato ≡ emisión, y cada arreglo sistémico con su guard**. Los cinco hallazgos de mayor calado, todos ejecutados:
1. **El contrato CSS era un segundo censo a mano y había derivado 211 tokens** (familias enteras — depth, named styles, scaling, breakpoints, banda overlay-z — invisibles para `setCssVariables` estricto y para el esqueleto de temas CSS-only). Hoy el contrato **se deriva de la propia emisión** (census-as-data) con muro bidireccional en suite. §2-A.9.
2. **Dieciséis alias token-level** (puentes de la migración air→eidos, 2026-05-14) mantenían una segunda gramática — incluida una cadena de TRES nombres para la tipografía de UI y un par duplicado de focus con ambos nombres adoptados (36 vs 13 refs). Doctrina de usuario: *"no quiero alias"* → migración value-preserving (~207 refs) y muerte de todos. §2-A.9.
3. **El pick de contraste on-solid vivía dos veces**: computado en el bucle de roles y como **lista curada a mano** en la cascada per-instance — y la lista había derivado: `color="orange"` embarcaba tinta blanca a **2.97:1 (sub-AA)**. Hoy hay **un criterio único computado** (`pickOnSolid`, blanco-preferente salvo fallo de AMBOS suelos APCA≥60 y WCAG≥3) consumido por ambos caminos + test de paridad. §2-A.7.
4. **Knobs canónicos cocidos fuera del sistema de datos** (state-layer en `archetypes.css`; radius-factor/default, inset-ring, floating-gaps como constantes de emisor). Doctrina de usuario: *"todo tiene que ser tematizable"* → promovidos a `primitives.{state,floating,radiusFactor,radiusDefault}` + `border.insetRingWidth`, valores verbatim. §2-A.10.
5. **La clase eager-freeze**: tokens de `:root` referenciando privados `--_*` de CSS de componente se congelaban *guaranteed-invalid* — las marcas de festivo/evento del calendario **nunca pintaron**, las alturas de segmento computaron `auto` desde su nacimiento, y un slot inexistente (`--color-content-tertiary`) heredó color un mes. Resueltos con veredictos + **dos guards** (G1 scope-para-privados, G2 referencias-fantasma contra el contrato derivado). §2-B.
Métricas finales: contrato **4.540 nombres** (3.383 static + 1.157 theme), **emisión ≡ contrato en ambas direcciones** (por construcción + guard), solo **31 paths `(derived)`** (el resto con procedencia real de config). Suites eidos **299/299** · `check` 59 (baseline intocada) · `component:audit` 86/47/1 (baseline) · `docs:check` 0 errores.
---
## 2. Hallazgos y resolución (checkpoint F, con veredictos)
Formato: clasificación · severidad · evidencia · norma · veredicto → ejecución → guard. Severidades: **RC** rompe contrato prometido · **SE** salida errónea silenciosa · **DR** deriva doc↔código · **OL** olor/carencia.
### A.1 — El eje scaling no estaba definido como dato (carencia · OL→RC)
- **Evidencia**: la participación por familia (space/controlHeight/fontSize/iconSize/blur escalan; radius/borderWidth/shadow/motionDistance nítidos) vivía implícita en los emisores; `rfc-scaling` además contenía la falsedad "as in Radix" (Radix SÍ escala su radius).
- **Veredicto**: el modelo es canónico y **se define por elemento como dato** (`ScalingParticipationMap`), default general nitidez-del-chrome.
- **Ejecución**: `STATIC_SCALING_PARTICIPATION` + `PrimitiveSet.scaling` (mapa parcial mergeable); emisores consumen el mapa; RFC corregido con nota. Verificado en vivo: radius 6px→6px bajo zoom 110 mientras font 16→17.6.
- **Guard**: test de flip (radius:true opta al zoom) + asserts del default. Changelog §23 nota fechada.
### A.2 — Container widths: colisión de namespace + fantasma (SE)
- **Evidencia**: `--container-width-xl` re-declarado con valor fantasma (`--layout-container-width-xl, 80rem` — la clave sombreada no existía); breakpoints y container desconectados.
- **Veredicto**: interrelación canónica — `--container-width-{k}` **referencia** `var(--breakpoint-{k})` (480/768/1024/1280/1536); ancho de página canónico = xl 1280.
- **Ejecución + guard**: STATIC_LAYOUT re-anclado; fantasma muerto; test de referencia; verificado en vivo los 5 valores. Changelog §35 nota.
### A.3 — El tracking óptico no llegaba a los named styles (DR)
- **Evidencia**: 12 `letterSpacing: '0'` redundantes en styles pisaban el tracking por-tamaño; 3 lineHeights duplicados.
- **Veredicto**: los styles **heredan** la óptica del tamaño salvo divergencia deliberada anotada.
- **Ejecución + guard**: 12+3 eliminados, 9 divergencias anotadas `/* diverges … on purpose */`; guard styles-never-redeclare; verificado hero −0.02em→−1.6px\@80px. rfc-typography pauta + changelog §35.
### A.4/A.5 — Canon temporal de sema: cita falsa + doble tabla de holds (DR·SE)
- **Evidencia**: `holds.ts` citaba "cap. 24 §6.2 *verbatim*" — esa sección es otra cosa; **el libro da regiones cualitativas, no ms** (c4 §13 · c12 §4-9 · TABLA 32.1/32.2). Dos tablas de holds (holds.ts y sema-map.ts) podían discrepar. El hold actuaba de tijera sobre la expresión visual.
- **Veredicto**: **el libro es el canon**; los ms son materialización del framework; peldaño intermedio `settled: 400`; *"el hold es suelo, no tijera"*.
- **Ejecución**: citas reales; `SEMA_HOLDS_BY_INTENT` = única tabla (resolver + canal visual la consumen; la columna de sema-map murió); `commit.fulfill → settled`, `signal.loss → brief`; `awaitExpression` espera el fin de la animación tras el hold con techo absoluto `MAX_EXPRESSION_WAIT_MS = 1500` (nunca derivado del hold — el "2×hold" propuesto se rechazó por re-acoplar presupuestos); firmas announce retuneadas a regiones del libro.
- **Guard**: triple (granularidad fulfill 400 · suelo-no-tijera · cap con timers por fases) + **design-lint**: toda firma ≤ cap. Sema 178/178. `book-deviations.md` D.12 (candidata editorial ApD).
### A.6 — Depth Decisión 8 (ratificación, docs-only)
El plano pinta el **bundle de APARIENCIA** (surface·border·shadow+halo·tipografía on-surface); **z JAMÁS se pinta** — *"el plano pinta, el posicionador posiciona"*. rfc-depth §5 + changelog §29 Adopción-v2. (La cobertura Tier-A de estampado `data-depth` → auditoría de componentes.)
### A.7 — On-solid: lista curada vs cómputo (SE — accesibilidad)
- **Evidencia** (medición mecánica): set computado {cyan, yellow, amber, orange, sky, mint, lime, gold} vs lista `LIGHT_SOLID_SCALES` de 6 — **orange blanco a 2.97:1 sub-AA** y cyan Lc 59.5 embarcados; 0 desacuerdos light↔dark en las 33 escalas.
- **Veredicto**: P1 — cómputo en generación + **criterio único** canonizado como el shipped: blanco-preferente salvo fallo de AMBOS suelos (APCA |Lc|≥60 Y WCAG≥3). El "elige |Lc| mayor" del borrador del RFC quedó **rechazado** (volcaría media paleta: teal 60.5, grass 60.2, blue 62.6…).
- **Ejecución + guard**: [`lib/on-solid.ts`](../../src/uix/eidos/lib/on-solid.ts) (la `pickOnSolid` que rfc-ce §6.1 nombraba) consumida por AMBOS caminos; test de paridad rol↔instancia + pin del set; instancias orange/cyan → tinta oscura (verificado + captura). rfc-ce §8 corregido; changelog §24.1 actualización.
### A.8 — Dos idiomas dimensionales: px/py vs logical properties (RC)
- **Evidencia**: 198 claves `p[xy]` (+3 `my`) evadían R-4.4; 20 shorthands físicos.
- **Veredicto** (literal): *"normalizar el ecosistema de una puta vez… aunque haya que reescribir todo"*; exclusión words/palabras/chronos LEVANTADA para el codemod.
- **Ejecución + guard**: codemod atómico 57 ficheros (~570 nombres) + longhands lógicos; R-4.4 ampliado a segmentos abreviados (error, sin allowlist); **muro de tipos** `defineRecipes` (clave física no compila). Before/after idéntico en navegador. recipe-contract §1 nota.
### A.9 — Alias token-level + contrato-censo derivado (RC·DR) — el mayor
- **Evidencia**: (a) 211 tokens emitidos fuera de `getCssContract()` (sonda reproducible: parse de declaraciones `--x:` de la emisión vs el census a mano) — `setCssVariables` estricto LANZABA sobre vocabulario legítimo; (b) 16 alias (función literal `appendTransitionAliasDeclarations`, nacidos `6e8ced2e` 2026-05-14): `--font-ui` (cadena de 3 nombres, 94 refs), `--font-mono` (64), `--color-focus-ring` vs `--focus-ring-color` (13 vs 36 — ambos vivos), `--text-{1..6}-*` (que además **evadía la regla §5 del bundle**, como px/py evadía R-4.4), `--radius-xs` (step inexistente), etc.
- **Veredictos**: P1 directa (contrato derivado); *"no quiero alias"*; font-ui muere → `--style-label-font-family` + **validación**: `styles.label.family` es requisito; los knobs `(derived)` se promueven en lote con A.10.
- **Ejecución**: codemod ~207 refs (con reversión selectiva del **vocabulario local** del dev-site — `layout.css` usaba `--font-sans/--font-mono` como hook propio: renombrarlos habría re-tipografiado los componentes del site); [`lib/contract.ts`](../../src/uix/eidos/lib/contract.ts) reescrito — **parsea la emisión real** (static + temas `scales:'all'`; tema sintético para configs CSS-only), metadatos por tabla de reglas (una familia sin regla entra igual: degrada metadatos, nunca cobertura), WeakMap cache; 4 tokens `field-*` compartidos: par adoptado → recipe `field`; par hover **superseded por el state-layer** con 0 consumidores → eliminado con veredicto explícito.
- **Guards**: muro bidireccional (emisión-pública ≡ contrato, ambas direcciones) + pins de familias nuevas + pins de los 16 alias muertos. Changelog **§39** (crónica completa).
### A.10 — Knobs cocidos en emisor → config (RC)
- **Evidencia**: `--state-{hover,press,selected}` hardcoded en `archetypes.css :root` (8%/12%/12% — los números de M3, sobre `currentColor`); `--radius-factor: '1'`, `--radius-default: md`, `--ring-inset-width: medium`, `--floating-gap-{menu,panel}` como constantes de render-css. Ninguno expresable en config, todos fuera de validación/serialización.
- **Veredicto** (literal): *"todo tiene que ser tematizable"* — a la capa primitives, defaults verbatim.
- **Ejecución + guard**: `primitives.state` + `radiusFactor` + `radiusDefault` + `border.insetRingWidth` + `primitives.floating`; el bloque de archetypes.css **murió** (las reglas quedan); validación (porcentajes, membresías); test de **retune** (hover 4%, factor 1.25…); +5 pins al muro. Cero cambio visual (verificado). Changelog **§40**.
### B — Fantasmas value-changing: la clase eager-freeze (SE ×3)
Mecánica común: custom property de `:root` referenciando `--_*` de CSS de componente → congela *guaranteed-invalid* → herencia congelada (punto ciego del TSC: su inferencia solo ve deps públicas).
| Fantasma | Realidad shipped | Veredicto → ejecución |
| --- | --- | --- |
| `--calendar-{day-holiday,event}-shadow` | **nunca pintaron** | *"las features deben existir"* → `scope:'host'` (dato TSC): **pintan por primera vez** (verificado + captura). Préstamos cross-component (range-calendar 79 tokens de calendar, month/year-grid 65, drp 38) → veredicto de diseño: **la familia calendar se formaliza como capa compartida/arquetipo** (chronos beberá de ella) — registrado. |
| `--{date,time,color}-field-segment-height` | `auto` desde su nacimiento | Veredicto de diseño: la altura sale del **eje size a nivel familia field** (`--field-control-height-{k}`, hoy re-duplicada por cada x-field) — **mandatado**: los componentes deben incorporar el wrapper Field (sondado: los segmentos NO viven bajo `[data-field]`). Mientras: 3 tokens rotos + 4 consumos eliminados (cero cambio visual, verificado 20px→20px). |
| `image-adjustments` `value-color: var(--color-content-tertiary)` | slot **inexistente** — heredó color desde `7da7285d` (2026-06-11) | Corrección de usuario: *"tertiary era un color de ACENTO"* (mezcló namespace content con nombre de ROL) → `var(--color-tertiary-text)`; el read-out pinta el acento (verificado). |
- **Guards**: **G1** — referencia a `--_*` en valor de recipe exige scope que la cubra (nunca `:root`); **G2** — toda referencia pública sin fallback debe existir en el contrato derivado (`--color-content-tertiary` habría roto el build el día que se escribió). Changelog **§41**.
### C — Lote doc-sync: 18 contradicciones doc↔doc (DR ×18)
Arbitradas por fechas/código y corregidas al doc con nota fechada. Las de más peso: la tabla de gramática §6 de reference ganó **16 familias ausentes**; "6 sizes"→7; "two builders"→**seis + capstone `applyTheme`** (existía; el "pendiente" del changelog llevaba semanas stale); escala tipográfica corregida (xxl 32→48 · xxxl 40→80); stubs §32/§36 al día; los "13 slots" anotados como crónica (12 vigentes, `border-hover` retirado); arch-eidos ya no enseña como ejemplo el anti-patrón R-2.3; smoothing = **exponente** (el "factor 0..1" del RFC nunca aterrizó). El detalle ítem a ítem quedó en las notas de cada doc (todas fechadas 2026-07-07). El nº 17 no era prosa: es la **decisión D-1** de §5.2.
---
## 3. Censo de tokens — estado final
| Clase | Antes | Después |
| --- | --- | --- |
| **Duplicados** (dos nombres, un concepto) | 16 alias + par focus duplicado + doble censo (contrato) + doble tabla de holds | **0** — un nombre por concepto; contrato y holds con fuente única |
| **Repetitivos** (patrón que la gramática debería factorizar) | `height-{xs..xl}` re-declarada por cada x-field; alturas de segmento ×3; vocabulario calendar prestado ×5 componentes (~250 refs) | Registrados como **diseño de familia** para la auditoría de componentes (field-family + calendar-surface/arquetipo) |
| **Contradictorios** | on-solid lista vs cómputo; holds ×2; §36 gap 0 vs space-1; 13 vs 12 slots; … | **1 vivo** → decisión D-1 (0.96 vs 0.97) |
| **Fuera de gramática** | `--state-*`/`--floating-gap*` sin casa en config; `p[xy]` ×198; `text-N` numérica paralela | **0 sistémicos** |
## 4. Matriz de ejes ortogonales — post-ejecución
Todos los ejes cumplen ahora las cinco columnas — definido como **dato de config** · emitido · **contratado** (derivación) · con **guard** · con pauta documentada: color (escalas/roles/slots + on-solid computado) · tipografía (escala + styles + óptica heredada) · espacio/density · size-bundle (7 tamaños, coordenadas completas incl. letter-spacing) · radius (+factor/default) · border (+inset-ring) · sombra/depth (planos+cues) · motion (duraciones 9 + métricas + loops) · **state-layer** · **scaling (mapa de participación)** · shape · blur · gradient · **floating-gaps** · focus (dos anillos, inner-width) · z (banda overlay) · opacity · breakpoints (fuente de container).
**Carencias restantes conocidas** (registradas, no defectos silenciosos): per-theme-id (`themes.{id}`) solo retunea color+shadow — los demás ejes son config-level (suficiente hoy; evolución registrada si un tema-id real lo pide); la **adopción por componentes** de varios ejes es la deuda que cobra la auditoría de componentes (§5.3).
## 5. Plan de normalización
### 5.1 Ejecutado durante la auditoría (nada que aprobar)
Todo §2, con crónica en changelog §23–§41 (notas fechadas), RFCs corregidos (scaling · typography · depth · shape · structure · color-model · color-engine §6.1/§8) y `docs/CANON.md`/`book-deviations.md` D.12. Verificación por ítem: suites (eidos 299/299 · sema 178/178) + `check` 59 + `component:audit` 86/47/1 + `docs:check` 0 + navegador con sondas y capturas (before/after donde hubo riesgo visual).
### 5.2 Decisiones de diseño requeridas (NO ejecutadas — tu aprobación)
- **D-1 · press 0.96 vs 0.97**: el token `--motion-scale-press: 0.97` no tiene consumidor; la firma press-squeeze usa el literal `0.96` (y `channels.md` documenta 0.96). Opciones: (a) la firma consume el token y el token pasa a **0.96** (canoniza el shipped); (b) firma a 0.97 (canoniza el token; cambio perceptible mínimo); (c) anotar el literal como deliberado y podar el token. Referencia: M3 usa un único pressed-scale de sistema.
- **D-2 · huérfanos deliberados con decisión pendiente documentada**: `--focus-ring`/`--focus-ring-error` compuestos (0 consumidores CSS — changelog §32 los deja "podar es decisión de API aparte") y la poda de los `--{x}-bg-hover` huérfanos post-state-layer (changelog §38 ◇). Podar/mantener = decisión de API.
- **D-3 · Meter y CVD**: hueco G3 documentado (zona solo-color, WCAG 1.4.1) — ¿cue no-cromático obligatorio o dev-warning?
- **D-4 · capa 4 (component-color)**: la FAQ la deja "candidata a deprecación si en ~6 meses no hay consumidor" (vence ~2026-11) — confirmar o colapsar entonces.
### 5.3 Iniciativas registradas ([`docs/next-features.md`](../next-features.md))
1. **Paridad de rampa tonal con M3** — etapa 1: tabla de pares slot↔slot con suelos + medición en generación (generaliza `on-solid.ts`); etapa 2: garantía por construcción en `generateScale`, gated en la migración base→seeds (rfc-ce §9).
2. **Emisión per-theme de la cascada palette-contrast** — trigger documentado: un tema real que discrepe de polaridad.
3. **Auditoría de componentes** (la deuda de adopción, con mandatos): **Field composition** (segmentos bajo `[data-field][data-size]` + token de familia + des-duplicación de alturas — MANDATADO); **calendar-surface/arquetipo** (formalizar la familia; chronos); adopción del bundle letter-spacing (~34 recipes) + ~7 literales; named-styles en controles; cobertura Tier-A `data-depth`; los borrowers de B.2; los duplicados de matemática de los pickers.
4. **sema.md hold-sync** (bloqueado: fichero WIP del autor) y **nota editorial del libro** (D.12, "el hold es suelo, no tijera").
### 5.4 Guardas añadidas (para que nada reaparezca)
Participación de scaling (flip test) · container↔breakpoint refs · styles-never-redeclare óptica · triple guard temporal sema + design-lint firmas≤cap · paridad on-solid rol↔instancia + pin del set computado · R-4.4 ampliado (segmentos físicos, error) + muro de tipos `defineRecipes` · **muro bidireccional emisión≡contrato** + pins (familias nuevas, alias muertos, state/floating) · validación `styles.label.family` + porcentajes state + membresías radiusDefault/insetRingWidth · test de retune de knobs · **G1** (privados exigen scope) · **G2** (referencias fantasma vs contrato). Doctrina cumplida: *cada fix sistémico se empareja con un guard* — y tres de ellos pararon errores míos durante esta misma auditoría.
---
*Auditoría Fase 1 cerrada 2026-07-07. Siguiente fase natural: la auditoría de componentes (§5.3-3), que cobra la adopción de los ejes que este pass dejó canónicos, contratados y guardados.*

Powered by TurnKey Linux.