diff --git a/continue.md b/continue.md index d17a42491..e100b517e 100644 --- a/continue.md +++ b/continue.md @@ -26,10 +26,12 @@ Actualizacion Eidos ecosystem 2026-05-16: 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. +- Claves authorables de tipografia (`families`, `weights`, `styles`) deben + generar sufijos CSS validos; `typography.sizes` permanece canonico. - Validado: - - `npx vitest run src/uix/eidos/active-eidos-config.test.ts` -> 28 tests OK. + - `npx vitest run src/uix/eidos/active-eidos-config.test.ts` -> 29 tests OK. - `npx vitest run src/uix/eidos/active-eidos.test.ts` -> 19 tests OK. - - `npx vitest run src/uix/eidos` -> 7 archivos, 68 tests OK. + - `npx vitest run src/uix/eidos` -> 7 archivos, 69 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 b76424532..fb5eb592a 100644 --- a/src/uix/eidos/active-eidos-config.test.ts +++ b/src/uix/eidos/active-eidos-config.test.ts @@ -376,6 +376,38 @@ describe('ActiveEidos config', () => { ).toBe(true) }) + it('validates typography keys that become CSS custom properties', () => { + const cfg = structuredClone(THEME_BASE_OPTIONS) + const typography = cfg.primitives.typography! + + ;(typography.families as Record)['Bad Family'] = { family: 'Bad' } + ;(typography.sizes as Record)['mega'] = { + size: '96px', + lineHeight: '1', + letterSpacing: '0' + } + ;(typography.weights as Record)['semi bold'] = 600 + ;(typography.styles as Record)['Hero Display'] = { + size: 'xxl' + } + + const report = createEidos(cfg).validate() + + expect(report.ok).toBe(false) + expect( + report.issues.some((issue) => issue.path === 'primitives.typography.families.Bad Family') + ).toBe(true) + expect(report.issues.some((issue) => issue.path === 'primitives.typography.sizes.mega')).toBe( + true + ) + expect( + report.issues.some((issue) => issue.path === 'primitives.typography.weights.semi bold') + ).toBe(true) + expect( + report.issues.some((issue) => issue.path === 'primitives.typography.styles.Hero Display') + ).toBe(true) + }) + it('validates size primitive references', () => { const cfg = structuredClone(THEME_BASE_OPTIONS) const size = cfg.primitives.size! diff --git a/src/uix/eidos/lib/config.ts b/src/uix/eidos/lib/config.ts index 264d3b597..1a7067f47 100644 --- a/src/uix/eidos/lib/config.ts +++ b/src/uix/eidos/lib/config.ts @@ -386,7 +386,23 @@ function validateTypography(options: EidosConfig, issues: EidosValidationIssue[] validateTextMetric(`primitives.typography.sizes.${size}`, metric, issues) } + for (const name of Object.keys(typography.sizes)) { + validateCssTokenSuffix(`primitives.typography.sizes.${name}`, name, 'typography size', issues) + if (!TEXT_SIZE_SET.has(name)) { + issues.push({ + path: `primitives.typography.sizes.${name}`, + message: 'unknown typography size key' + }) + } + } + for (const [name, family] of Object.entries(typography.families)) { + validateCssTokenSuffix( + `primitives.typography.families.${name}`, + name, + 'typography family', + issues + ) if (family.family.trim() === '') { issues.push({ path: `primitives.typography.families.${name}.family`, @@ -395,8 +411,18 @@ function validateTypography(options: EidosConfig, issues: EidosValidationIssue[] } } + for (const name of Object.keys(typography.weights)) { + validateCssTokenSuffix( + `primitives.typography.weights.${name}`, + name, + 'typography weight', + issues + ) + } + for (const [name, style] of Object.entries(typography.styles)) { const path = `primitives.typography.styles.${name}` + validateCssTokenSuffix(path, name, 'typography style', issues) if (style.family !== undefined && !hasOwn(typography.families, style.family)) { issues.push({ @@ -769,6 +795,19 @@ function validateCssTokenKey( }) } +function validateCssTokenSuffix( + path: string, + value: string, + label: string, + issues: EidosValidationIssue[] +): void { + if (isCssTokenSuffix(value)) return + issues.push({ + path, + message: `${label} must be a kebab-case CSS token suffix` + }) +} + function resolveRoleScaleName( roleDef: string | ColorRoleDefinition | undefined ): string | undefined { @@ -779,3 +818,7 @@ function resolveRoleScaleName( function isCssTokenSegment(value: string): boolean { return /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value) } + +function isCssTokenSuffix(value: string): boolean { + return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value) +}