From 753e27487fbdcb609980161d152ca07ae0e3e0be Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 17 May 2026 01:31:37 +0200 Subject: [PATCH] Guard Eidos recipe value leaks --- continue.md | 7 ++++++- src/uix/eidos/components/popover/popover.css | 4 ++-- src/uix/eidos/generated/base.css | 1 + src/uix/eidos/lib/recipes/base.ts | 1 + src/uix/eidos/recipe-css-contract.test.ts | 18 ++++++++++++++++++ 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/continue.md b/continue.md index a3b985aa8..c5a06d53e 100644 --- a/continue.md +++ b/continue.md @@ -46,11 +46,16 @@ Actualizacion Eidos recipe typography 2026-05-17: `--dialog-trigger-font-size`. - `popover.css` ya no usa `font-size: 0.9rem` ni `0.85rem`; consume `--popover-trigger-font-size` y `--popover-close-font-size`. + - `popover.css` ya no usa fallback cromatico literal para overlay; consume + `--popover-overlay-bg`. - Los nuevos tokens viven en `THEME_BASE_RECIPE_TOKENS` y se regenero `src/uix/eidos/generated/base.css` con `npm run generate:eidos-css`. + - `recipe-css-contract.test.ts` ahora falla si un CSS de componente + reintroduce colores raw (`#...`, `rgb(...)`, `hsl(...)`) o `font-size` + literal en `px/rem`. - Validado: - `npx vitest run src/uix/eidos/recipe-css-contract.test.ts src/uix/eidos/generated-css.test.ts` - -> 2 archivos, 4 tests OK. + -> 2 archivos, 5 tests OK. - `npx vitest run src/uix/eidos` -> 7 archivos, 79 tests OK. - `npm run check` -> 0 errores, 0 warnings. diff --git a/src/uix/eidos/components/popover/popover.css b/src/uix/eidos/components/popover/popover.css index 8982604b1..a1920c373 100644 --- a/src/uix/eidos/components/popover/popover.css +++ b/src/uix/eidos/components/popover/popover.css @@ -40,8 +40,8 @@ [data-popover-overlay] { position: fixed; inset: 0; - background: var(--color-overlay, rgb(0 0 0 / 0.3)); - z-index: var(--popover-overlay-z, 60); + background: var(--popover-overlay-bg); + z-index: var(--popover-overlay-z); } /* The popover's floating wrapper is `position: fixed` but has no explicit diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index 679931260..0ddd634d6 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -592,6 +592,7 @@ --icon-stroke-width: var(--icon-stroke-width-md); --popover-content-z: 75; --popover-overlay-z: 60; + --popover-overlay-bg: var(--color-overlay); --popover-trigger-font-size: var(--font-size-sm); --popover-gap: var(--space-3); --popover-max-width: 32rem; diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index 16574a0be..4887185c8 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -336,6 +336,7 @@ export const THEME_BASE_RECIPE_TOKENS = { popover: { 'content-z': '75', 'overlay-z': '60', + 'overlay-bg': 'var(--color-overlay)', 'trigger-font-size': 'var(--font-size-sm)', gap: 'var(--space-3)', 'max-width': '32rem', diff --git a/src/uix/eidos/recipe-css-contract.test.ts b/src/uix/eidos/recipe-css-contract.test.ts index 356a6ae41..996a97557 100644 --- a/src/uix/eidos/recipe-css-contract.test.ts +++ b/src/uix/eidos/recipe-css-contract.test.ts @@ -7,6 +7,8 @@ import { THEME_BASE_RECIPE_TOKENS } from './lib/recipes/base' const COMPONENTS_DIR = 'src/uix/eidos/components' const CSS_CUSTOM_PROPERTY = /--([a-z][a-z0-9]*-[a-z0-9-]+)/g const COMPONENT_SOURCE_FILE = /\.(css|svelte|ts)$/ +const RAW_COLOR_LITERAL = /#[0-9a-f]{3,8}\b|\b(?:rgb|rgba|hsl|hsla)\(/i +const RAW_FONT_SIZE_LITERAL = /font-size\s*:\s*[0-9.]+(?:px|rem)\b/i const RECIPE_TOKENS = THEME_BASE_RECIPE_TOKENS as Readonly< Record>> > @@ -95,6 +97,22 @@ describe('Eidos recipe CSS contract', () => { expect(extra).toEqual([]) }) + it('keeps raw color values out of component CSS', () => { + const violations = readComponentCssFiles() + .filter(({ css }) => RAW_COLOR_LITERAL.test(css)) + .map(({ component }) => component) + + expect(violations).toEqual([]) + }) + + it('keeps raw font-size literals out of component CSS', () => { + const violations = readComponentCssFiles() + .filter(({ css }) => RAW_FONT_SIZE_LITERAL.test(css)) + .map(({ component }) => component) + + expect(violations).toEqual([]) + }) + it('does not leave declared public recipe variables orphaned', () => { const recipeValueReferences = readRecipeValueReferences() const orphaned: string[] = []