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
parent
6e501f4fdf
commit
51122efa32
@ -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…
Reference in new issue