Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion web/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
--surface-2: #f6f1f8; /* subtle inset (toolbars, meter tracks) */
--lavender: #f9f0ff;
--hairline: #e6e6e6;
/* Ruled lists (#864 direction): --rule between rows, --rule-strong (28% ink)
under headers, above totals and under fields. */
--rule: var(--hairline);
--rule-strong: rgba(29, 29, 29, 0.28);
--row-hover: #faf5fc;
--scroll-cue: rgba(29, 21, 33, 0.22); /* edge fade for horizontally-scrolling tables (#150) */

Expand Down Expand Up @@ -204,6 +208,8 @@
--surface-2: #2b2231;
--lavender: #241c2a;
--hairline: #3a2f40;
--rule: var(--hairline);
--rule-strong: rgba(242, 236, 244, 0.28);
--row-hover: #2b2231;
--scroll-cue: rgba(255, 255, 255, 0.16); /* edge cue for scrolling tables (#150) — a light glow, since a dark shadow vanishes on the dark surface */

Expand Down Expand Up @@ -3123,7 +3129,7 @@ input.locked {
}

.named-picker-trigger:focus-visible {
outline: 2px solid var(--accent, #4a90d9);
outline: 2px solid var(--focus);
outline-offset: 1px;
}

Expand Down
45 changes: 45 additions & 0 deletions web/src/styles.declared-tokens.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { readFileSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";

// Every custom property the app reads must be declared in styles.css. An
// undeclared `var(--x)` fails silently: the whole declaration becomes invalid
// at computed-value time, so a border, colour or outline disappears with no
// error anywhere. #883 shipped the Dashboard's ruled list reading `--rule`
// and `--rule-strong` that nothing declared, and `.named-picker-trigger`'s
// focus ring read `--accent` for even longer, resolving to its literal
// fallback instead of the farm's focus token. Test helpers under src/test
// name fake properties on purpose and are excluded.

const SRC = join(__dirname);
const css = readFileSync(join(SRC, "styles.css"), "utf8");
const declared = new Set([...css.matchAll(/^\s*(--[a-z0-9-]+)\s*:/gm)].map((m) => m[1]));

function walk(dir: string, out: string[] = []): string[] {
for (const entry of readdirSync(dir)) {
const p = join(dir, entry);
if (statSync(p).isDirectory()) {
if (entry !== "test") walk(p, out);
} else if (/\.(tsx?|css)$/.test(entry) && !/\.test\.tsx?$/.test(entry)) {
out.push(p);
}
}
return out;
}

describe("custom properties the app reads are declared in styles.css", () => {
it("references only declared tokens", () => {
expect(declared.size).toBeGreaterThan(40);
const undeclared = new Map<string, Set<string>>();
for (const file of walk(SRC)) {
const text = readFileSync(file, "utf8");
for (const m of text.matchAll(/var\((--[a-z0-9-]+)/g)) {
if (!declared.has(m[1])) {
undeclared.set(m[1], (undeclared.get(m[1]) ?? new Set()).add(file.slice(SRC.length + 1)));
}
}
}
const report = [...undeclared].map(([name, files]) => `${name} in ${[...files].join(", ")}`).join("\n");
expect(report, `undeclared custom properties:\n${report}`).toBe("");
});
});
Loading