Guard persisted Eidos config shapes

active-uix
dev 5 months ago
parent b2c832b47a
commit eba7b4522d

@ -42,10 +42,12 @@ Actualizacion Eidos ecosystem 2026-05-16:
canonicos y pasos `1..12`.
- Valores CSS del `EidosConfig` comparten la misma defensa que los overrides
runtime: no pueden contener `;`, `{` ni `}`.
- Documentos persistidos con grupos opcionales malformados (`primitives.*`,
`themes.*`, `recipes.*`) fallan temprano como `EidosConfigValidationError`.
- Validado:
- `npx vitest run src/uix/eidos/active-eidos-config.test.ts` -> 34 tests OK.
- `npx vitest run src/uix/eidos/active-eidos-config.test.ts` -> 35 tests OK.
- `npx vitest run src/uix/eidos/active-eidos.test.ts` -> 19 tests OK.
- `npx vitest run src/uix/eidos` -> 7 archivos, 74 tests OK.
- `npx vitest run src/uix/eidos` -> 7 archivos, 75 tests OK.
- `npm run check` -> 0 errores, 0 warnings.
Actualizacion permutation runner 2026-05-16:

@ -338,6 +338,28 @@ describe('ActiveEidos config', () => {
)
})
it('reports malformed optional config groups before deep validation', () => {
const malformed = structuredClone(createThemeBaseEidos().toDocument())
;(malformed.options.primitives as unknown as Record<string, unknown>).typography = 'bad'
;(malformed.options.themes!['base-light'] as unknown as Record<string, unknown>).color = 'bad'
;(malformed.options.recipes as unknown as Record<string, unknown>).tooltip = 'bad'
try {
createEidosFromDocument(malformed)
throw new Error('expected malformed optional groups to fail')
} catch (error) {
expect(error).toBeInstanceOf(EidosConfigValidationError)
expect((error as EidosConfigValidationError).issues.map((issue) => issue.path)).toEqual(
expect.arrayContaining([
'primitives.typography',
'themes.base-light.color',
'recipes.tooltip'
])
)
}
})
it('ships typography families separately from typography styles', () => {
const typography = createThemeBaseEidos().snapshot().primitives.typography!

@ -175,6 +175,27 @@ function validateEidosConfigShape(
message: 'Eidos config must define a primitives object'
})
ok = false
} else {
const primitiveKeys = [
'color',
'size',
'space',
'controlHeight',
'radius',
'border',
'focusRing',
'layout',
'density',
'typography',
'motion',
'icon',
'opacity',
'zIndex'
] as const
for (const key of primitiveKeys) {
ok = validateOptionalPlainRecord(input.primitives[key], `primitives.${key}`, issues) && ok
}
}
const semantics = input.semantics
@ -189,25 +210,61 @@ function validateEidosConfigShape(
ok = false
}
if (input.themes !== undefined && !isPlainRecord(input.themes)) {
issues.push({
path: 'themes',
message: 'Eidos config themes must be a plain object'
})
ok = false
if (input.themes !== undefined) {
if (!isPlainRecord(input.themes)) {
issues.push({
path: 'themes',
message: 'Eidos config themes must be a plain object'
})
ok = false
} else {
for (const [themeId, theme] of Object.entries(input.themes)) {
if (!isPlainRecord(theme)) {
issues.push({
path: `themes.${themeId}`,
message: 'Eidos config theme entries must be plain objects'
})
ok = false
continue
}
ok = validateOptionalPlainRecord(theme.color, `themes.${themeId}.color`, issues) && ok
ok = validateOptionalPlainRecord(theme.shadow, `themes.${themeId}.shadow`, issues) && ok
}
}
}
if (input.recipes !== undefined && !isPlainRecord(input.recipes)) {
issues.push({
path: 'recipes',
message: 'Eidos config recipes must be a plain object'
})
ok = false
if (input.recipes !== undefined) {
if (!isPlainRecord(input.recipes)) {
issues.push({
path: 'recipes',
message: 'Eidos config recipes must be a plain object'
})
ok = false
} else {
for (const [component, tokens] of Object.entries(input.recipes)) {
ok = validateOptionalPlainRecord(tokens, `recipes.${component}`, issues) && ok
}
}
}
return ok
}
function validateOptionalPlainRecord(
value: unknown,
path: string,
issues: EidosValidationIssue[]
): boolean {
if (value === undefined) return true
if (isPlainRecord(value)) return true
issues.push({
path,
message: 'Eidos config entries must be plain objects'
})
return false
}
export function listEidosColorScales(options: EidosConfig): readonly string[] {
return Object.keys(options.primitives.color?.scales ?? {})
}

Loading…
Cancel
Save

Powered by TurnKey Linux.