From cfee72bfc8328372aaea7ecc7c224225127685af Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 16 May 2026 23:16:04 +0200 Subject: [PATCH] Validate Eidos static primitives --- continue.md | 7 ++-- src/uix/eidos/active-eidos-config.test.ts | 19 +++++++++++ src/uix/eidos/index.ts | 1 + src/uix/eidos/lib/config-types.ts | 3 +- src/uix/eidos/lib/config.ts | 41 +++++++++++++++++++++++ 5 files changed, 68 insertions(+), 3 deletions(-) diff --git a/continue.md b/continue.md index 6b529f775..f8559f00d 100644 --- a/continue.md +++ b/continue.md @@ -32,10 +32,13 @@ Actualizacion Eidos ecosystem 2026-05-16: (`DURATION_KEYS`, `EASE_KEYS`, `MOTION_DISTANCE_KEYS`, `MOTION_SCALE_KEYS`, `ICON_STROKE_WIDTH_KEYS`) se exportan y validan en runtime antes de generar CSS. +- Primitivas estaticas base reforzadas: `space` valida sufijos CSS abiertos, + `controlHeight` y `radius` usan key sets canonicos, y `focusRing` valida + valores no vacios. - Validado: - - `npx vitest run src/uix/eidos/active-eidos-config.test.ts` -> 30 tests OK. + - `npx vitest run src/uix/eidos/active-eidos-config.test.ts` -> 31 tests OK. - `npx vitest run src/uix/eidos/active-eidos.test.ts` -> 19 tests OK. - - `npx vitest run src/uix/eidos` -> 7 archivos, 70 tests OK. + - `npx vitest run src/uix/eidos` -> 7 archivos, 71 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 6c1bd0e3c..522d1e461 100644 --- a/src/uix/eidos/active-eidos-config.test.ts +++ b/src/uix/eidos/active-eidos-config.test.ts @@ -451,6 +451,25 @@ describe('ActiveEidos config', () => { expect(report.issues.some((issue) => issue.path === 'themes.base-light.shadow.6')).toBe(true) }) + it('validates static primitive keys and focus ring values', () => { + const cfg = structuredClone(THEME_BASE_OPTIONS) + + ;(cfg.primitives.space as Record)['bad space'] = '4px' + delete (cfg.primitives.controlHeight as Record)['md'] + ;(cfg.primitives.radius as Record)['huge'] = '32px' + ;(cfg.primitives.focusRing as { width: string }).width = '' + + const report = createEidos(cfg).validate() + + expect(report.ok).toBe(false) + expect(report.issues.some((issue) => issue.path === 'primitives.space.bad space')).toBe(true) + expect(report.issues.some((issue) => issue.path === 'primitives.controlHeight.md')).toBe( + true + ) + expect(report.issues.some((issue) => issue.path === 'primitives.radius.huge')).toBe(true) + expect(report.issues.some((issue) => issue.path === 'primitives.focusRing.width')).toBe(true) + }) + it('validates layout primitive keys', () => { const cfg = structuredClone(THEME_BASE_OPTIONS) diff --git a/src/uix/eidos/index.ts b/src/uix/eidos/index.ts index 508471207..8592f16d4 100644 --- a/src/uix/eidos/index.ts +++ b/src/uix/eidos/index.ts @@ -185,6 +185,7 @@ export { MOTION_DISTANCE_KEYS, MOTION_SCALE_KEYS, OPACITY_KEYS, + RADIUS_KEYS, SHADOW_KEYS, SHADOW_PHYSICAL_KEYS, SHADOW_SEMANTIC_KEYS, diff --git a/src/uix/eidos/lib/config-types.ts b/src/uix/eidos/lib/config-types.ts index 2fa04b92c..19dd62ac4 100644 --- a/src/uix/eidos/lib/config-types.ts +++ b/src/uix/eidos/lib/config-types.ts @@ -96,7 +96,8 @@ export interface ColorSemanticSet { export type ScaleMap = Record; export type ControlHeightKey = Exclude; -export type RadiusKey = 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'full'; +export const RADIUS_KEYS = ['none', 'sm', 'md', 'lg', 'xl', 'xxl', 'full'] as const; +export type RadiusKey = (typeof RADIUS_KEYS)[number]; export const CONTAINER_WIDTH_KEYS = ['sm', 'md', 'lg', 'xl', 'xxl', 'full'] as const; export const CONTENT_WIDTH_KEYS = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl', 'prose'] as const; diff --git a/src/uix/eidos/lib/config.ts b/src/uix/eidos/lib/config.ts index c0a434a45..2f70d6776 100644 --- a/src/uix/eidos/lib/config.ts +++ b/src/uix/eidos/lib/config.ts @@ -13,6 +13,7 @@ import { MOTION_DISTANCE_KEYS, MOTION_SCALE_KEYS, OPACITY_KEYS, + RADIUS_KEYS, SHADOW_KEYS, SIZE_PRIMITIVE_KEYS, TEXT_SIZES, @@ -56,6 +57,7 @@ const ICON_STROKE_WIDTH_KEY_SET = new Set(ICON_STROKE_WIDTH_KEYS) const MOTION_DISTANCE_KEY_SET = new Set(MOTION_DISTANCE_KEYS) const MOTION_SCALE_KEY_SET = new Set(MOTION_SCALE_KEYS) const OPACITY_KEY_SET = new Set(OPACITY_KEYS) +const RADIUS_KEY_SET = new Set(RADIUS_KEYS) const SHADOW_KEY_SET = new Set(SHADOW_KEYS) const SIZE_PRIMITIVE_KEY_SET = new Set(SIZE_PRIMITIVE_KEYS) const TEXT_SIZE_SET = new Set(TEXT_SIZES) @@ -103,6 +105,7 @@ export function validateEidosConfig(options: EidosConfig): EidosValidationReport validateColorRoles(options, issues) validateTypography(options, issues) validateSizePrimitives(options, issues) + validateStaticPrimitives(options, issues) validateBorderPrimitives(options, issues) validateLayoutPrimitives(options, issues) validateDensityPrimitives(options, issues) @@ -526,6 +529,30 @@ function validateSizePrimitive( validateNonEmptyCssValue(`${path}.gap`, size.gap, issues) } +function validateStaticPrimitives(options: EidosConfig, issues: EidosValidationIssue[]): void { + validateOpenScalePrimitive('primitives.space', options.primitives.space, 'space', issues) + validateRecordPrimitive( + 'primitives.controlHeight', + options.primitives.controlHeight, + SIZE_PRIMITIVE_KEYS, + SIZE_PRIMITIVE_KEY_SET, + issues + ) + validateRecordPrimitive( + 'primitives.radius', + options.primitives.radius, + RADIUS_KEYS, + RADIUS_KEY_SET, + issues + ) + + const focusRing = options.primitives.focusRing + if (!focusRing) return + + validateNonEmptyCssValue('primitives.focusRing.offset', focusRing.offset, issues) + validateNonEmptyCssValue('primitives.focusRing.width', focusRing.width, issues) +} + function validateBorderPrimitives(options: EidosConfig, issues: EidosValidationIssue[]): void { const border = options.primitives.border if (!border) return @@ -755,6 +782,20 @@ function validateRecordPrimitive( } } +function validateOpenScalePrimitive( + path: string, + record: Record | undefined, + label: string, + issues: EidosValidationIssue[] +): void { + if (!record) return + + for (const [key, value] of Object.entries(record)) { + validateCssTokenSuffix(`${path}.${key}`, key, label, issues) + validateNonEmptyCssValue(`${path}.${key}`, String(value), issues) + } +} + function validateTypographySizeValue( path: string, value: unknown,