Validate Eidos semantic color groups

active-uix
dev 5 months ago
parent cfee72bfc8
commit b48a6c48bf

@ -35,10 +35,13 @@ Actualizacion Eidos ecosystem 2026-05-16:
- Primitivas estaticas base reforzadas: `space` valida sufijos CSS abiertos,
`controlHeight` y `radius` usan key sets canonicos, y `focusRing` valida
valores no vacios.
- Grupos semanticos de color (`surface`, `content`, `border`, `focus`) quedan
cerrados con key sets canonicos y valores no vacios antes de generar tokens
de theme.
- Validado:
- `npx vitest run src/uix/eidos/active-eidos-config.test.ts` -> 31 tests OK.
- `npx vitest run src/uix/eidos/active-eidos-config.test.ts` -> 32 tests OK.
- `npx vitest run src/uix/eidos/active-eidos.test.ts` -> 19 tests OK.
- `npx vitest run src/uix/eidos` -> 7 archivos, 71 tests OK.
- `npx vitest run src/uix/eidos` -> 7 archivos, 72 tests OK.
- `npm run check` -> 0 errores, 0 warnings.
Actualizacion permutation runner 2026-05-16:

@ -605,6 +605,31 @@ describe('ActiveEidos config', () => {
).toBe(true)
})
it('validates semantic color groups before rendering theme CSS', () => {
const cfg = structuredClone(THEME_BASE_OPTIONS)
;(cfg.semantics.color as unknown as Record<string, unknown>).surface = {
default: 'Canvas'
}
;(cfg.themes!['base-light'].color!.content as unknown as Record<string, string>).disabled =
''
;(cfg.themes!['base-light'].color!.border as unknown as Record<string, string>).ghost =
'transparent'
const report = createEidos(cfg).validate()
expect(report.ok).toBe(false)
expect(
report.issues.some((issue) => issue.path === 'semantics.color.surface.raised')
).toBe(true)
expect(
report.issues.some((issue) => issue.path === 'themes.base-light.color.content.disabled')
).toBe(true)
expect(
report.issues.some((issue) => issue.path === 'themes.base-light.color.border.ghost')
).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 {

@ -171,15 +171,18 @@ export {
BORDER_STYLE_KEYS,
BORDER_WIDTH_KEYS,
COLOR_ALPHA_STEPS,
BORDER_COLOR_KEYS,
COLOR_ROLE_SLOTS,
COLOR_ROLES,
COLOR_SCALE_STEPS,
CONTAINER_WIDTH_KEYS,
CONTENT_COLOR_KEYS,
CONTENT_WIDTH_KEYS,
DENSITIES,
DENSITY_KEYS,
DURATION_KEYS,
EASE_KEYS,
FOCUS_COLOR_KEYS,
HIERARCHY_COLOR_ROLES,
ICON_STROKE_WIDTH_KEYS,
MOTION_DISTANCE_KEYS,
@ -191,6 +194,7 @@ export {
SHADOW_SEMANTIC_KEYS,
SIZE_PRIMITIVE_KEYS,
SIZES,
SURFACE_COLOR_KEYS,
TEXT_SIZES,
Z_INDEX_KEYS
} from './lib/config-types';

@ -67,6 +67,7 @@ export interface SurfaceColorRoles {
readonly overlay: string;
readonly backdrop: string;
}
export const SURFACE_COLOR_KEYS = ['default', 'raised', 'overlay', 'backdrop'] as const;
export interface ContentColorRoles {
readonly primary: string;
@ -74,17 +75,20 @@ export interface ContentColorRoles {
readonly disabled: string;
readonly onSolid: string;
}
export const CONTENT_COLOR_KEYS = ['primary', 'secondary', 'disabled', 'onSolid'] as const;
export interface BorderColorRoles {
readonly subtle: string;
readonly default: string;
readonly strong: string;
}
export const BORDER_COLOR_KEYS = ['subtle', 'default', 'strong'] as const;
export interface FocusColorRoles {
readonly ring: string;
readonly ringError: string;
}
export const FOCUS_COLOR_KEYS = ['ring', 'ringError'] as const;
export interface ColorSemanticSet {
readonly roles: ColorRoleMap;

@ -1,14 +1,17 @@
import {
ASPECT_RATIO_KEYS,
BORDER_COLOR_KEYS,
BORDER_STYLE_KEYS,
BORDER_WIDTH_KEYS,
COLOR_ROLES,
COLOR_SCALE_STEPS,
CONTAINER_WIDTH_KEYS,
CONTENT_COLOR_KEYS,
CONTENT_WIDTH_KEYS,
DENSITY_KEYS,
DURATION_KEYS,
EASE_KEYS,
FOCUS_COLOR_KEYS,
ICON_STROKE_WIDTH_KEYS,
MOTION_DISTANCE_KEYS,
MOTION_SCALE_KEYS,
@ -16,9 +19,11 @@ import {
RADIUS_KEYS,
SHADOW_KEYS,
SIZE_PRIMITIVE_KEYS,
SURFACE_COLOR_KEYS,
TEXT_SIZES,
Z_INDEX_KEYS,
type BorderPrimitiveSet,
type ColorSemanticSet,
type ColorRole,
type ColorRoleDefinition,
type ColorScale,
@ -48,11 +53,14 @@ const COLOR_ROLE_SET = new Set<string>(COLOR_ROLES)
const ASPECT_RATIO_KEY_SET = new Set<string>(ASPECT_RATIO_KEYS)
const BORDER_WIDTH_KEY_SET = new Set<string>(BORDER_WIDTH_KEYS)
const BORDER_STYLE_KEY_SET = new Set<string>(BORDER_STYLE_KEYS)
const BORDER_COLOR_KEY_SET = new Set<string>(BORDER_COLOR_KEYS)
const CONTENT_COLOR_KEY_SET = new Set<string>(CONTENT_COLOR_KEYS)
const CONTAINER_WIDTH_KEY_SET = new Set<string>(CONTAINER_WIDTH_KEYS)
const CONTENT_WIDTH_KEY_SET = new Set<string>(CONTENT_WIDTH_KEYS)
const DENSITY_KEY_SET = new Set<string>(DENSITY_KEYS)
const DURATION_KEY_SET = new Set<string>(DURATION_KEYS)
const EASE_KEY_SET = new Set<string>(EASE_KEYS)
const FOCUS_COLOR_KEY_SET = new Set<string>(FOCUS_COLOR_KEYS)
const ICON_STROKE_WIDTH_KEY_SET = new Set<string>(ICON_STROKE_WIDTH_KEYS)
const MOTION_DISTANCE_KEY_SET = new Set<string>(MOTION_DISTANCE_KEYS)
const MOTION_SCALE_KEY_SET = new Set<string>(MOTION_SCALE_KEYS)
@ -60,6 +68,7 @@ const OPACITY_KEY_SET = new Set<string>(OPACITY_KEYS)
const RADIUS_KEY_SET = new Set<string>(RADIUS_KEYS)
const SHADOW_KEY_SET = new Set<string>(SHADOW_KEYS)
const SIZE_PRIMITIVE_KEY_SET = new Set<string>(SIZE_PRIMITIVE_KEYS)
const SURFACE_COLOR_KEY_SET = new Set<string>(SURFACE_COLOR_KEYS)
const TEXT_SIZE_SET = new Set<string>(TEXT_SIZES)
const Z_INDEX_KEY_SET = new Set<string>(Z_INDEX_KEYS)
const RESPONSIVE_KEY_SET = new Set(['base', 'sm', 'md', 'lg', 'xl', 'xxl'])
@ -103,6 +112,7 @@ export function validateEidosConfig(options: EidosConfig): EidosValidationReport
validateThemeKeys(options, issues)
validateColorScales(options, issues)
validateColorRoles(options, issues)
validateColorSemanticGroups(options, issues)
validateTypography(options, issues)
validateSizePrimitives(options, issues)
validateStaticPrimitives(options, issues)
@ -323,6 +333,95 @@ function validateColorRoles(options: EidosConfig, issues: EidosValidationIssue[]
}
}
function validateColorSemanticGroups(options: EidosConfig, issues: EidosValidationIssue[]): void {
validateColorSemanticSet('semantics.color', options.semantics.color, issues)
for (const [themeId, theme] of Object.entries(options.themes ?? {})) {
if (!theme.color) continue
validateColorSemanticSet(`themes.${themeId}.color`, theme.color, issues)
}
}
function validateColorSemanticSet(
path: string,
color: Partial<
Pick<ColorSemanticSet, 'surface' | 'content' | 'border' | 'focus'>
>,
issues: EidosValidationIssue[]
): void {
validateColorSemanticGroup(
`${path}.surface`,
color.surface,
SURFACE_COLOR_KEYS,
SURFACE_COLOR_KEY_SET,
issues
)
validateColorSemanticGroup(
`${path}.content`,
color.content,
CONTENT_COLOR_KEYS,
CONTENT_COLOR_KEY_SET,
issues
)
validateColorSemanticGroup(
`${path}.border`,
color.border,
BORDER_COLOR_KEYS,
BORDER_COLOR_KEY_SET,
issues
)
validateColorSemanticGroup(
`${path}.focus`,
color.focus,
FOCUS_COLOR_KEYS,
FOCUS_COLOR_KEY_SET,
issues
)
}
function validateColorSemanticGroup(
path: string,
group: unknown,
keys: readonly string[],
keySet: Set<string>,
issues: EidosValidationIssue[]
): void {
if (group === undefined) return
if (!isPlainRecord(group)) {
issues.push({
path,
message: 'color semantic group must be a plain object'
})
return
}
for (const key of keys) {
if (!hasOwn(group, key)) {
issues.push({
path: `${path}.${key}`,
message: 'color semantic groups must define every canonical key'
})
continue
}
const value = group[key]
if (typeof value !== 'string' || value.trim() === '') {
issues.push({
path: `${path}.${key}`,
message: 'color semantic CSS value cannot be empty'
})
}
}
for (const key of Object.keys(group)) {
if (keySet.has(key)) continue
issues.push({
path: `${path}.${key}`,
message: 'unknown color semantic key'
})
}
}
function validateColorScale(
path: string,
name: string,

Loading…
Cancel
Save

Powered by TurnKey Linux.