|
|
# 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.*
|