/** * Muta-prueba del clasificador `--names` (gramática D-TH.6, firmada 2026-08-20). * * Un guard que no se ve rojo sobre casos conocidos es un guard que pasa sobre * el vacío. Éste asserta contra el catálogo VIVO, así que se actualiza cuando * el catálogo se mueve — y esa es su gracia: si alguien reintroduce el idioma * viejo, el caso correspondiente cae. */ import { execSync } from 'node:child_process'; const out = execSync('node --import tsx/esm scripts/theming-census.ts --names --json', { encoding: 'utf8', maxBuffer: 1 << 28 }); type F = { component: string; key: string; klass: string; to?: string }; const all: F[] = JSON.parse(out); const find = (c: string, k: string) => all.find((f) => f.component === c && f.key === k); const CASES: Array<[string, string, string, string | undefined, string]> = [ // [componente, clave, clase esperada, destino, por qué existe el caso] // ── La gramática, ya aplicada ──────────────────────────────────────────── ['accordion', 'trigger-fg', 'ok', undefined, 'tinta = fg: el caso central de la firma'], ['tabs', 'active-trigger-fg', 'ok', undefined, 'tinta + modificador delante'], ['tag-group', 'hover-primary-solid-bg', 'ok', undefined, 'modificador delante a nivel receta'], ['toggle', 'solid-hover-bg', 'ok', undefined, 'ya lo hablaba antes del codemod'], ['gradient-builder', 'checker-fg', 'ok', undefined, 'la corrección de los 7 ya revisados'], // ── Lo que NO se toca: rol/paleta lleva el modificador DETRÁS por canon ── ['button', 'primary-solid-hover', 'role-slot', undefined, 'COLOR_ROLE_SLOTS, no deriva'], ['button', 'palette-solid-hover', 'role-slot', undefined, '24 consumos: romperla habría roto button'], ['toggle', 'palette-hover', 'role-slot', undefined, 'palette-{slot}'], // ── Exentas firmadas ──────────────────────────────────────────────────── ['accordion', 'trigger-focus-ring-color', 'exempt', undefined, 'espeja la familia del SISTEMA'], ['aura', 'orb-color-idle', 'exempt', undefined, 'color como sustantivo'], ['gradient-builder', 'stop-color-title-fg', 'exempt', undefined, 'stop-color ES la parte'], // ── Contextual DETRÁS — la otra mitad de la firma ──────────────────────── // Sin estos casos el clasificador promovía cualquier valor del morfo y // reescribía --sidebar-width-icon (el raíl colapsado) como // --sidebar-icon-width (la anchura de un icono). ['sidebar', 'width-icon', 'ok', undefined, 'modo colapsado: contextual'], ['radio-group', 'gap-vertical', 'ok', undefined, 'orientación: contextual'], ['meter', 'indicator-bg-below', 'ok', undefined, 'zona semántica: contextual'], ['progress', 'indicator-bg-loaded', 'ok', undefined, 'contextual, no interactivo'], ['drawer', 'footer-margin-top', 'ok', undefined, 'eje dimensional'], // ── Capa de estado: lo decide el VALOR **y** la PROPIEDAD ──────────────── // El velo de §38 es background-image. Un hover de borde o de tinta no es la // capa de estado por muy neutro que sea su valor: es deuda de nombre. ['splitter', 'handle-bg-hover', 'state-layer', undefined, 'neutro Y pinta fondo'], ['scroll-area', 'thumb-bg-hover', 'state-layer', undefined, 'neutro Y pinta fondo'], ['switch', 'track-bg-off-hover', 'state-layer', undefined, 'fondo vía privado, un salto'], ['field', 'control-border-hover', 'modifier', 'hover-control-border', 'BORDE: no es la capa de estado'], ['tabs', 'trigger-color-hover', 'both', 'hover-trigger-fg', 'TINTA: no es la capa de estado'], ['dropdown-menu', 'hover-item-bg', 'ok', undefined, 'acento (--color-primary-element): palette swap legítimo'], ['pagination', 'hover-selected-bg', 'ok', undefined, 'acento: se renombró, no migra'] ]; let bad = 0; for (const [c, k, klass, to, why] of CASES) { const f = find(c, k); if (!f) { console.log(` ? ${c}.${k} — NO EXISTE en el catálogo (${why})`); bad++; continue; } const okKlass = f.klass === klass; const okTo = to === undefined ? !f.to : f.to === to; if (okKlass && okTo) console.log(` ok ${c}.${k} → ${f.klass}${f.to ? ' / ' + f.to : ''}`); else { console.log( ` XX ${c}.${k} → ${f.klass}${f.to ? ' / ' + f.to : ''} ESPERADO ${klass}${to ? ' / ' + to : ''} (${why})` ); bad++; } } console.log(bad === 0 ? '\nMUTA-PRUEBA VERDE' : `\nMUTA-PRUEBA ROJA: ${bad} caso(s)`); process.exit(bad === 0 ? 0 : 1);