Guard Eidos namespace assignments

active-uix
dev 5 months ago
parent bddfff9e39
commit 3c115ce093

@ -50,6 +50,17 @@ Actualizacion permutation axes 2026-05-16:
http://127.0.0.1:5192` -> 12 demos instrumentadas OK, 54 pasos, 28 variantes
URL.
Actualizacion Eidos component API guard 2026-05-16:
- `src/uix/eidos/component-api-contract.test.ts` ahora tambien valida que los
miembros declarados en cada `XxxNamespace` coincidan con las asignaciones
explicitas del barrel (`Tabs.Trigger = Trigger`, etc.).
- Esto cierra el hueco que dejaba el cast `const Tabs = TabsRoot as
TabsNamespace`: TypeScript no detecta una asignacion olvidada despues del
cast, pero la guardia estatica si.
- Validacion: `npx vitest run src/uix/eidos/component-api-contract.test.ts`
-> 3 tests OK; `npm run check` -> 0 errores, 0 warnings.
Arranque rapido para mañana:
- Rama `active-uix` queda por delante de `gita/active-uix` con los commits de

@ -5,6 +5,12 @@ import { describe, expect, it } from 'vitest'
const COMPONENTS_DIR = 'src/uix/eidos/components'
const ROOT_FILE_EXCEPTIONS = new Set(['svg'])
interface NamespaceContract {
component: string
properties: readonly string[]
assignments: readonly string[]
}
function stripComments(source: string): string {
return source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1')
}
@ -20,6 +26,22 @@ function readIndexSource(component: string): string {
return stripComments(readFileSync(join(COMPONENTS_DIR, component, 'index.ts'), 'utf8'))
}
function readNamespaceContract(source: string): NamespaceContract | undefined {
const match = /type\s+([A-Z][A-Za-z0-9]+)Namespace\s*=\s*typeof\s+\w+Root\s*&\s*\{([\s\S]*?)\n\}/.exec(
source
)
if (!match) return undefined
const component = match[1]
const body = match[2]
const properties = [...body.matchAll(/^\s*([A-Z][A-Za-z0-9]+)\s*:/gm)].map((m) => m[1])
const assignments = [
...source.matchAll(new RegExp(`\\b${component}\\.([A-Z][A-Za-z0-9]+)\\s*=`, 'g'))
].map((m) => m[1])
return { component, properties, assignments }
}
describe('Eidos component API contract', () => {
it('keeps component barrels on the disciplined option C surface', () => {
const violations: string[] = []
@ -48,4 +70,28 @@ describe('Eidos component API contract', () => {
expect(missingRoots).toEqual([])
})
it('keeps namespace type members and explicit assignments in sync', () => {
const violations: string[] = []
for (const component of readComponentDirs()) {
const contract = readNamespaceContract(readIndexSource(component))
if (!contract) continue
const declared = new Set(contract.properties)
const assigned = new Set(contract.assignments)
for (const property of declared) {
if (!assigned.has(property)) {
violations.push(`${component}: ${contract.component}.${property} declared but not assigned`)
}
}
for (const property of assigned) {
if (!declared.has(property)) {
violations.push(`${component}: ${contract.component}.${property} assigned but not declared`)
}
}
}
expect(violations).toEqual([])
})
})

@ -221,6 +221,11 @@ estos attrs/parts visuales. El lint base sigue siendo estricto con valores
inválidos de enums morfo; la allowlist solo evita que el reporte de drift se
llene de partes visuales intencionales.
`src/uix/eidos/component-api-contract.test.ts` protege la forma pública del
barrel: sin `Object.assign`, sin `Provider` público, root en
`{component}.svelte` y miembros del `XxxNamespace` sincronizados con sus
asignaciones explícitas (`Drawer.Trigger = Trigger`, etc.).
La guardia `src/uix/eidos/component-visual-attrs.test.ts` fija el cableado
mínimo entre wrapper y recipe: si un wrapper visual declara una prop que se
consume como `data-*` (`size`, `variant`, `color`, `position`, `columns`,

Loading…
Cancel
Save

Powered by TurnKey Linux.