diff --git a/continue.md b/continue.md index 02c453b6d..d17a42491 100644 --- a/continue.md +++ b/continue.md @@ -24,10 +24,12 @@ Actualizacion Eidos ecosystem 2026-05-16: - Validacion de config reforzada: documentos persistidos con `options` malformadas fallan como `EidosConfigValidationError` con issues de contrato, no como `TypeError` de runtime. +- IDs de theme y nombres de escalas de color quedan validados como segmentos + CSS kebab-case antes de generar selectores o custom properties desde config. - Validado: - - `npx vitest run src/uix/eidos/active-eidos-config.test.ts` -> 27 tests OK. + - `npx vitest run src/uix/eidos/active-eidos-config.test.ts` -> 28 tests OK. - `npx vitest run src/uix/eidos/active-eidos.test.ts` -> 19 tests OK. - - `npx vitest run src/uix/eidos` -> 7 archivos, 67 tests OK. + - `npx vitest run src/uix/eidos` -> 7 archivos, 68 tests OK. - `npm run check` -> 0 errores, 0 warnings. Actualizacion permutation runner 2026-05-16: diff --git a/src/uix/eidos/active-eidos-config.test.ts b/src/uix/eidos/active-eidos-config.test.ts index 3c2df0349..b76424532 100644 --- a/src/uix/eidos/active-eidos-config.test.ts +++ b/src/uix/eidos/active-eidos-config.test.ts @@ -505,6 +505,30 @@ describe('ActiveEidos config', () => { ) }) + it('validates CSS-facing theme and color scale keys', () => { + const cfg = structuredClone(THEME_BASE_OPTIONS) + + ;(cfg.primitives as { color: { scales: Record } }).color = { + scales: { + 'bad scale': colorScale('bad-scale') + } + } + ;(cfg.themes as Record)['bad theme'] = cfg.themes!['base-light'] + ;(cfg.themes!['base-light'].color!.scales as Record)['BadScale'] = + colorScale('bad-scale') + + const report = createEidos(cfg).validate() + + expect(report.ok).toBe(false) + expect( + report.issues.some((issue) => issue.path === 'primitives.color.scales.bad scale') + ).toBe(true) + expect(report.issues.some((issue) => issue.path === 'themes.bad theme')).toBe(true) + expect( + report.issues.some((issue) => issue.path === 'themes.base-light.color.scales.BadScale') + ).toBe(true) + }) + it('validates explicit alpha color scales against their physical scale', () => { const cfg = structuredClone(THEME_BASE_OPTIONS) const color = cfg.themes!['base-light'].color as { diff --git a/src/uix/eidos/lib/config.ts b/src/uix/eidos/lib/config.ts index 8e7286d45..264d3b597 100644 --- a/src/uix/eidos/lib/config.ts +++ b/src/uix/eidos/lib/config.ts @@ -88,6 +88,7 @@ export function validateEidosConfig(options: EidosConfig): EidosValidationReport } } + validateThemeKeys(options, issues) validateColorScales(options, issues) validateColorRoles(options, issues) validateTypography(options, issues) @@ -239,14 +240,22 @@ function validateColorScales(options: EidosConfig, issues: EidosValidationIssue[ const scales = options.primitives.color?.scales ?? {} for (const [name, scale] of Object.entries(scales)) { + validateCssTokenKey(`primitives.color.scales.${name}`, name, 'color scale', issues) validateColorScale(`primitives.color.scales.${name}`, name, scale, issues) } for (const [themeId, theme] of Object.entries(options.themes ?? {})) { for (const [name, scale] of Object.entries(theme.color?.scales ?? {})) { + validateCssTokenKey(`themes.${themeId}.color.scales.${name}`, name, 'color scale', issues) validateColorScale(`themes.${themeId}.color.scales.${name}`, name, scale, issues) } for (const [name, scale] of Object.entries(theme.color?.alphaScales ?? {})) { + validateCssTokenKey( + `themes.${themeId}.color.alphaScales.${name}`, + name, + 'alpha color scale', + issues + ) validateColorScale(`themes.${themeId}.color.alphaScales.${name}`, name, scale, issues) if ( !hasOwn(options.primitives.color?.scales ?? {}, name) && @@ -261,6 +270,12 @@ function validateColorScales(options: EidosConfig, issues: EidosValidationIssue[ } } +function validateThemeKeys(options: EidosConfig, issues: EidosValidationIssue[]): void { + for (const themeId of Object.keys(options.themes ?? {})) { + validateCssTokenKey(`themes.${themeId}`, themeId, 'theme id', issues) + } +} + function validateColorRoles(options: EidosConfig, issues: EidosValidationIssue[]): void { const roles = options.semantics.color.roles const globalScales = options.primitives.color?.scales ?? {} @@ -741,6 +756,19 @@ function isTextMetric(value: unknown): value is TextMetric { ) } +function validateCssTokenKey( + path: string, + value: string, + label: string, + issues: EidosValidationIssue[] +): void { + if (isCssTokenSegment(value)) return + issues.push({ + path, + message: `${label} must be a kebab-case CSS token segment` + }) +} + function resolveRoleScaleName( roleDef: string | ColorRoleDefinition | undefined ): string | undefined {