diff --git a/static/css/components/menu.css b/static/css/components/menu.css index 1fee64cd..8f846a33 100644 --- a/static/css/components/menu.css +++ b/static/css/components/menu.css @@ -46,8 +46,20 @@ pointer-events: none; } +/* Destructive entries. The token used to be the nonexistent `--danger-color`, + which made the declaration invalid at computed-value time: the colour then + fell back to the menu's inherited text colour, so every "Delete …" entry in + the folder and model-card context menus rendered plain. */ .context-menu-item.delete-item { - color: var(--danger-color); + color: var(--lora-error); +} + +/* The shared .context-menu-item:hover paints the accent background, which the + red label does not read against — destructive entries get their own wash. */ +.context-menu-item.delete-item:hover, +.context-menu-item.delete-item:focus-visible { + background-color: var(--lora-error-bg); + color: var(--lora-error); } .context-menu-item i { diff --git a/static/css/components/modal/settings-modal.css b/static/css/components/modal/settings-modal.css index 52e63304..82bc6f3f 100644 --- a/static/css/components/modal/settings-modal.css +++ b/static/css/components/modal/settings-modal.css @@ -747,13 +747,13 @@ } .priority-tags-input.settings-input-error { - border-color: var(--danger-color, #dc2626); - box-shadow: 0 0 0 2px rgba(220, 38, 38, 0.12); + border-color: var(--lora-error); + box-shadow: 0 0 0 2px rgba(from var(--lora-error) r g b / 0.12); } .settings-input-error-message { font-size: 0.8em; - color: var(--danger-color, #dc2626); + color: var(--lora-error); display: none; } diff --git a/tests/frontend/regression/contextMenuTokens.test.js b/tests/frontend/regression/contextMenuTokens.test.js new file mode 100644 index 00000000..81421c2c --- /dev/null +++ b/tests/frontend/regression/contextMenuTokens.test.js @@ -0,0 +1,58 @@ +import { describe, it, expect } from 'vitest'; +import { readFileSync, readdirSync } from 'fs'; +import path from 'path'; + +// Regression guard for the destructive context-menu entries. +// +// They were styled with `var(--danger-color)`, a token defined nowhere in the +// stylesheet tree. A var() reference to an undefined property makes the +// declaration invalid at computed-value time, so the colour silently fell back +// to the menu's inherited text colour: every "Delete …" entry in the folder +// sidebar menu and the model-card menus rendered plain. The first check below +// keeps menu.css wired only to tokens that actually resolve. +describe('Context menu design tokens', () => { + const repoRoot = path.resolve(__dirname, '../../..'); + const cssDir = path.join(repoRoot, 'static/css'); + const menuCss = readFileSync(path.join(cssDir, 'components/menu.css'), 'utf-8'); + + const collectCss = (dir, files = []) => { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) collectCss(full, files); + else if (entry.name.endsWith('.css')) files.push(readFileSync(full, 'utf-8')); + } + return files; + }; + + const allCss = collectCss(cssDir).join('\n'); + const defined = new Set([...allCss.matchAll(/(--[\w-]+)\s*:/g)].map((match) => match[1])); + + // var(--x) with no fallback: an undefined name is a silent no-op. + const usedWithoutFallback = [...menuCss.matchAll(/var\(\s*(--[\w-]+)\s*\)/g)].map( + (match) => match[1] + ); + + it('resolves every custom property used by the context menu', () => { + const unresolved = [...new Set(usedWithoutFallback)].filter((name) => !defined.has(name)); + expect(unresolved).toEqual([]); + }); + + it('paints destructive entries with the themed error colour', () => { + const rule = menuCss.match(/\.context-menu-item\.delete-item\s*\{([^}]*)\}/); + expect(rule).not.toBeNull(); + expect(rule[1]).toContain('var(--lora-error)'); + expect(defined.has('--lora-error')).toBe(true); + }); + + it('gives destructive entries their own hover treatment', () => { + // The shared hover paints the accent background, which a red label does + // not read against. + expect(menuCss).toMatch(/\.context-menu-item\.delete-item:hover[\s\S]*?\{/); + expect(menuCss).toMatch(/\.context-menu-item\.delete-item:hover[\s\S]*?var\(--lora-error-bg\)/); + }); + + it('never references the dead --danger-color token', () => { + // Comments may name it; a var() argument may not. + expect(allCss).not.toMatch(/var\(\s*--danger-color/); + }); +});