/** * Análisis del hallazgo sistémico (firma 3): las declaraciones `background:` * (atajo) sobre nodos velados, clasificadas por la CLASE de regla — porque la * clase decide el trabajo: * * BASE la regla sin estado: el fix es mecánico (→ background-color) y * NO mueve píxel en reposo; destapa el velo en hover. * HOVER un hover propio: es la migración de la firma 3 (retirar). * ACENTO selected/checked/active/current con valor de rol: §38 manda * background-color para que el velo COMPONGA encima. * OTRA dragging, disabled, variantes…: caso a caso. * * Y por el VALOR: un token que resuelva a un GRADIENTE no puede pasar a * background-color — romperlo sería peor que el velo ausente. */ import { readFileSync, existsSync, readdirSync } from 'node:fs'; import { join, resolve } from 'node:path'; import { compileMorfo } from '../src/uix/morfo/compile'; import type { Morfo } from '../src/uix/morfo/types'; const ROOT = resolve('src/uix/eidos/components'); const VEILED = new Set(['trigger', 'item', 'option']); const CONTRACT = readFileSync(resolve('src/uix/eidos/lib/recipes/base.ts'), 'utf8').replace(/\r\n/g, '\n'); // ¿el valor (o el token que referencia, un salto) contiene un gradiente? function hasGradient(value: string): boolean { if (/gradient\(/.test(value)) return true; for (const m of value.matchAll(/var\((--[a-z0-9-]+)/g)) { const name = m[1]; // buscar la declaración del token en el contrato (clave → valor) const key = name.replace(/^--[a-z0-9]+(-[a-z0-9]+)*?-/, ''); // aproximación const re = new RegExp("'" + name.slice(2).split('-').slice(1).join('-') + "'\s*:\s*'([^']*)'"); const hit = CONTRACT.match(re); if (hit && /gradient\(/.test(hit[1])) return true; } return false; } type Klass = 'BASE' | 'HOVER' | 'ACENTO' | 'OTRA'; const stats = new Map(); const perComp = new Map>(); const gradients: string[] = []; const accentSamples: string[] = []; const otherSamples: string[] = []; let total = 0; let baseNoHover = 0; // nodos con base-atajo y SIN hover propio: el velo aparecería como hover NUEVO for (const dir of readdirSync(ROOT)) { const css = join(ROOT, dir, `${dir}.css`); if (!existsSync(css)) continue; if (!existsSync(resolve(`src/uix/morfo/components/${dir}.ts`))) continue; let compiled; try { const mod = (await import(`../src/uix/morfo/components/${dir}`)) as Record; const key = `${dir.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())}Morfo`; if (!mod[key]) continue; compiled = compileMorfo(mod[key] as Morfo); } catch { continue; } const veiledAttrs: string[] = []; for (const kebab of compiled.parts.order) { const part = compiled.parts.byKebab.get(kebab); if (part && VEILED.has(String(part.archetype))) veiledAttrs.push(compiled.parts.attrs[kebab]); } if (!veiledAttrs.length) continue; const lines = readFileSync(css, 'utf8').replace(/\r\n/g, '\n').split('\n'); const seenBase = new Set(); const seenHover = new Set(); for (let i = 0; i < lines.length; i++) { if (!/^\s*background\s*:/.test(lines[i])) continue; let sel = ''; for (let j = i; j >= 0; j--) if (lines[j].includes('{')) { sel = lines[j].replace('{', '').trim(); break; } const last = sel.split(/\s+(?![^[]*\])/).pop() ?? ''; const attr = veiledAttrs.find((a) => last.includes(`[${a}]`)); if (!attr || /::/.test(last)) continue; total++; const value = lines[i].replace(/^\s*background\s*:\s*/, '').replace(/;.*/, ''); let k: Klass; if (/:hover/.test(last) || /data-state='hover'/.test(last)) { k = 'HOVER'; seenHover.add(attr); } else if (/data-(selected|state='(checked|on|open|active|indeterminate)'|current|highlighted|in-range|range-(start|end))|aria-selected/.test(last)) { k = 'ACENTO'; if (accentSamples.length < 8) accentSamples.push(`${dir} :: ${last.slice(0, 55)} → ${value.slice(0, 38)}`); } else if (/:active|:focus|data-(disabled|dragging|readonly|invalid|variant|mode|outside-month)/.test(last)) { k = 'OTRA'; if (otherSamples.length < 8) otherSamples.push(`${dir} :: ${last.slice(0, 55)} → ${value.slice(0, 38)}`); } else { k = 'BASE'; seenBase.add(attr); } stats.set(k, (stats.get(k) ?? 0) + 1); const pc = perComp.get(dir) ?? new Map(); pc.set(k, (pc.get(k) ?? 0) + 1); perComp.set(dir, pc); if (hasGradient(value)) gradients.push(`${dir} :: ${last.slice(0, 50)} → ${value.slice(0, 45)}`); } for (const a of seenBase) if (!seenHover.has(a)) baseNoHover++; } console.log('declaraciones-atajo sobre nodo velado:', total, 'en', perComp.size, 'componentes\n'); console.log('por clase de regla:'); for (const [k, n] of [...stats].sort((a, b) => b[1] - a[1])) console.log(` ${k.padEnd(7)} ${n}`); console.log('\nnodos con base-atajo y SIN hover propio (el velo sería un hover NUEVO):', baseNoHover); console.log('\nvalores con GRADIENTE (no pueden pasar a background-color):', gradients.length); for (const g of gradients) console.log(' ' + g); console.log('\nmuestras ACENTO:'); for (const s of accentSamples) console.log(' ' + s); console.log('\nmuestras OTRA:'); for (const s of otherSamples) console.log(' ' + s); console.log('\npor componente (BASE/HOVER/ACENTO/OTRA):'); const rows = [...perComp].map(([c, m]) => [c, (m.get('BASE')??0)+(m.get('HOVER')??0)+(m.get('ACENTO')??0)+(m.get('OTRA')??0), m] as const).sort((a,b)=>b[1]-a[1]); for (const [c, n, m] of rows) console.log(` ${c.padEnd(20)} ${String(n).padStart(3)} ${m.get('BASE')??0}/${m.get('HOVER')??0}/${m.get('ACENTO')??0}/${m.get('OTRA')??0}`);