docs(theming): mañana lo primero — el vocabulario de tokens: D-TH.6 (codemod) y R-5.3 (guard de gramática)

La conversación de hoy destapó que el backfill está escribiendo canon sobre un
catálogo que habla al revés: el vocabulario de nombres existe (theming §6.7 +
recipe-contract §1) pero no tiene guard, y la medición fundacional del
2026-07-01 ya dijo lo que le pasa a un canon defendido por prosa. Medido ahora
con `scripts/__names-inventory.ts` (semilla del `--names` del censo): sobre
3.333 claves públicas hay 370 desviadas en 68 componentes — 125 con el
modificador detrás, 215 con `color` como slot o segmento, 30 con ambas. Y dos
falsos amigos que NO se renombran de oficio: `*-focus-ring-color` espeja la
familia del sistema, y los `orb-color-*` de aura usan color como sustantivo.

El handoff pone el bloque como primera tarea, con el orden que evita el error
clásico: primero la FIRMA (D-TH.6, una decisión con el inventario delante),
luego el codemod value-preserving con el triple muro del precedente px/py
(renombres 1:1 en generated, censo idéntico, computed intacto en tres sondas),
y sólo entonces el guard — R-5.3 como gramática y no como lista, porque el
vocabulario de tokens es compositivo donde el de eventos era cerrado: la talla
contra las siete canónicas, el modificador cerrado y delante, el slot contra
sus tres fuentes, y la parte contra el morfo compilado como ya hace eidos-lint.
En `error` directo: tras el codemod no hay deuda que tolerar, y la rampa warn
es como estos guards mueren. Con muta-prueba antes de darlo por bueno.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent 6e501f4fdf
commit 51122efa32

@ -11,11 +11,69 @@ SIGUEN SIN FIRMAR** — lo hecho hasta ahora es lo que NO depende de ellas.
su análisis, su propuesta y, en §5, el **veredicto** escrito a mano, que
sobrevive a la regeneración.
## Por dónde entrar mañana
## MAÑANA, LO PRIMERO — vocabulario de tokens: D-TH.6 (codemod) + R-5.3 (guard)
**Por qué antes que nada** (conversación 2026-08-20): el canon de nombres
existe (theming §6.7 slots + recipe-contract §1 dimensionales) pero **no tiene
guard**, y la medición del 2026-07-01 ya demostró que canon sin guard deriva
30-85 %. Está derivando: el backfill de ayer escribió la forma DOCUMENTADA
(modificador delante, 36 claves) mientras el catálogo habla al revés — cada
componente que se corrija sin firmar esto ensancha la brecha. El guard de
eventos (`eidos-event-vocabulary.ts`) es el precedente: nació de un
`commit-resize` muerto tres meses en una receta; R-4.4 es el otro (una regla de
nombres ya guardada, deuda muerta «en el mismo pass, sin allowlist»).
**Inventario medido 2026-08-20** (semilla: `scripts/__names-inventory.ts`;
el exacto lo produce el censo con `--names` en el paso 1): sobre **3.333 claves
públicas**, **370 desviadas en 68 componentes** — 125 con el modificador DETRÁS
(`-hover`/`-active`/`-selected`/`-disabled`…), 215 con `color` como slot o
segmento (`trigger-color` → `fg`; `accordion.trigger-color-open` las dos cosas),
30 con ambas. Peores: tag-group 22 · file-upload 18 · toast 16 · calendar 15 ·
radio-group 15. **Falsos amigos a adjudicar con el autor, NO renombrar de
oficio**: `*-focus-ring-color` (espeja la familia del SISTEMA `--focus-ring-*`,
que usa `-color` legítimamente) y `aura.orb-color-*` (color como sustantivo del
orbe, no como slot).
Los pasos, en orden y con su gate:
0. **Presentar D-TH.6 al autor** — UNA decisión, cinco preguntas, con el
inventario delante. Decide el idioma (la propuesta del plan: el documentado,
modificador delante) y las adjudicaciones de los falsos amigos. **Sin firma
no hay codemod.**
1. **`--names` en el censo**: el inventario exacto por clase de desviación,
regenerable, con los falsos amigos ya excluidos por la firma.
2. **Codemod value-preserving** (molde: px/py del 2026-07-06 y su triple muro):
renombra la clave en `recipes/base.ts` Y todos los consumidores
(`var(--{c}-{clave})` en css/svelte/ts/md — con `git ls-files`, no de
memoria), regenera, y verifica: diff de `generated/` = **renombres 1:1**,
censo global IDÉNTICO (un rename no cambia alcance), sonda de computed en 3
componentes renombrados = **0 diffs**. Un commit, script commiteado como
registro.
3. **Guard R-5.3 en `component-audit`** — de **GRAMÁTICA**, no de lista (a
diferencia del de eventos, que valida pertenencia a un vocabulario cerrado):
`[variante-]?[modificador-]?[parte-]?[slot][-talla]?` donde talla ∈ las 7
canónicas, modificador ∈ vocabulario cerrado y DELANTE, slot ∈ (§6.7
recipe-level ∪ `COLOR_ROLE_SLOTS` para `{role}-*`/`palette-*` ∪ ejes
dimensionales de recipe-contract §1), y la PARTE contra
`compileMorfo(morfo).parts` + la allowlist eidos-only (el mismo criterio que
`eidos-lint`). Severidad **`error` directo**: tras el codemod no hay deuda
que tolerar, y la rampa `warn` es como estos guards mueren. **Muta-prueba
obligatoria** antes de darlo por bueno: meter una clave `-bg-hover` y ver el
rojo (memoria `a-guard-that-inspects-nothing-passes`).
4. **Docs en el mismo pass**: recipe-contract §1 gana la fila del modificador
(hoy sólo la insinúa §6.7) y §4 la fila R-5.3; theming §6.7 nota fechada;
T-TH.2 del plan se cierra.
Gates del bloque entero: censo global idéntico antes/después del codemod ·
suite eidos sin rojos nuevos · `component:audit` sin fallos nuevos fuera de
R-5.3 · `docs:check` 0 · `rtl:check` 0.
## Después — por dónde seguir
1. `node --import tsx/esm scripts/theming-census.ts` — debe dar **162 recetas ·
5.203 knobs · 1.841 públicos (37 %) · 56 sin contrato**. Si no cuadra, alguien
tocó recetas: regenerar el informe (`--report`) antes de nada.
5.203 knobs · 1.841 públicos (37 %) · 56 sin contrato** (antes del codemod;
tras él, el MISMO alcance con nombres nuevos). Si no cuadra, alguien tocó
recetas: regenerar el informe (`--report`) antes de nada.
2. Abrir la ficha del componente que toque y leer su **§5 Veredicto**: dice qué
de la propuesta es correcto, qué está mal y qué espera firma. Está verificado
contra el CSS; la §4 generada NO se sigue a ciegas.

@ -0,0 +1,52 @@
// One-shot: classify every top-level recipe key in base.ts against the naming canon.
import { readFileSync } from 'node:fs';
const src = readFileSync('src/uix/eidos/lib/recipes/base.ts', 'utf8').replace(/\r\n/g, '\n');
const blocks = [...src.matchAll(/^\t'?([a-z0-9-]+)'?: \{/gm)].map((m) => ({
name: m[1],
at: m.index!
}));
const MODS = ['hover', 'active', 'selected', 'disabled', 'checked', 'open', 'focus', 'invalid'];
const out: Record<string, Set<string>> = {
trailingMod: new Set(),
colorSegment: new Set(),
both: new Set()
};
const perComp = new Map<string, number>();
let total = 0;
for (let i = 0; i < blocks.length; i++) {
const end = i + 1 < blocks.length ? blocks[i + 1].at : src.length;
const body = src.slice(blocks[i].at, end);
for (const k of body.matchAll(/^\t\t'?(_?[a-z0-9-]+)'?\s*:/gm)) {
const key = k[1];
if (key.startsWith('_')) continue;
total++;
const trailing = MODS.some(
(m) => key.endsWith('-' + m) || MODS.some((m2) => key.endsWith('-' + m + '-' + m2))
);
const colorSeg = /(^|-)color(-|$)/.test(key) && !/^color-scheme/.test(key);
const tag = blocks[i].name + '.' + key;
if (trailing && colorSeg) out.both.add(tag);
else if (trailing) out.trailingMod.add(tag);
else if (colorSeg) out.colorSegment.add(tag);
if (trailing || colorSeg) perComp.set(blocks[i].name, (perComp.get(blocks[i].name) ?? 0) + 1);
}
}
console.log('total public keys:', total);
console.log(
'trailing modifier:',
out.trailingMod.size,
'| color segment:',
out.colorSegment.size,
'| both:',
out.both.size
);
console.log('components affected:', perComp.size);
console.log(
[...perComp.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, 15)
.map(([c, n]) => `${c}:${n}`)
.join(' ')
);
console.log('sample colorSegment:', [...out.colorSegment].slice(0, 8).join(', '));
console.log('sample both:', [...out.both].slice(0, 6).join(', '));
Loading…
Cancel
Save

Powered by TurnKey Linux.