Skip to content
Open
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
46 changes: 46 additions & 0 deletions apps/web/src/no-external-fonts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@ import { readFileSync } from "node:fs";
import document from "../index.html?raw";

const hearthTokens = readFileSync(new URL("./hearth-tokens.css", import.meta.url), "utf8");
const styles = readFileSync(new URL("./styles.css", import.meta.url), "utf8");

function cssBlock(selector: string) {
const start = styles.indexOf(`${selector} {`);
if (start < 0) throw new Error(`Missing CSS rule for ${selector}`);
const end = styles.indexOf("}", start);
if (end < 0) throw new Error(`Unclosed CSS rule for ${selector}`);
return styles.slice(start, end + 1);
}

function customProperty(block: string, name: string) {
const match = block.match(new RegExp(`\\s${name}:\\s*([^;]+);`));
if (!match) throw new Error(`Missing ${name}`);
return match[1].trim();
}

describe("production typography boundary", () => {
it("does not require an external font CDN", () => {
Expand All @@ -15,4 +30,35 @@ describe("production typography boundary", () => {
expect(hearthTokens).toContain("--hearth-azure");
expect(hearthTokens).not.toMatch(/url\(|https?:\/\/|@import/i);
});

it("keeps neutral metadata capsules on the public Hearth scale", () => {
const root = cssBlock(":root");
const tag = cssBlock(".tag");
const tagWrap = cssBlock(".tag-wrap");
const reference = cssBlock(".ref-chip");

for (const rule of [tag, reference]) {
expect(rule).toContain("gap: var(--s1)");
expect(rule).toContain("font-size: var(--type-xs)");
}
expect(tag).toContain("padding: 2px var(--s2)");
expect(tag).toContain("border-radius: var(--r-sm)");
expect(tagWrap).toContain("gap: var(--s1)");
expect(reference).toContain("padding: var(--s1) var(--s2)");
expect(reference).toContain("border-radius: 999px");
expect(customProperty(root, "--s1")).toBe("4px");
expect(customProperty(root, "--s2")).toBe("8px");
expect(customProperty(root, "--r-sm")).toBe("7px");
expect(customProperty(root, "--type-xs")).toBe("11px");
});

it("keeps state, topology selection, and AI suggestion capsules distinct", () => {
const state = cssBlock(".state-pill");
const filter = cssBlock(".filter-chip");
const suggestion = cssBlock(".suggest-chip");

expect(state).toContain("color: var(--c, var(--s-unknown))");
expect(filter).toContain("text-transform: capitalize");
expect(suggestion).toContain("border-radius: 999px");
});
});
22 changes: 14 additions & 8 deletions apps/web/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
--s5: 24px;
--s6: 32px;

/* Hearth's compact metadata type role. Geometry continues through the
established --s* and --r-* aliases until #222's wider export lands. */
--type-xs: 11px;

--shadow: var(--hearth-shadow);
--t: 140ms cubic-bezier(0.22, 0.61, 0.36, 1);

Expand Down Expand Up @@ -592,10 +596,12 @@ kbd {
.tag {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 2px 8px;
border-radius: 6px;
font-size: 11.5px;
/* The 2px block padding is a DockerMap density exception; the shared
type, horizontal rhythm, and corner geometry come from Hearth. */
gap: var(--s1);
padding: 2px var(--s2);
border-radius: var(--r-sm);
font-size: var(--type-xs);
color: var(--muted);
background: var(--surface-2);
border: 1px solid var(--border);
Expand All @@ -622,7 +628,7 @@ kbd {
.tag-wrap {
display: flex;
flex-wrap: wrap;
gap: 6px;
gap: var(--s1);
}

.kv {
Expand Down Expand Up @@ -1953,12 +1959,12 @@ kbd {
.ref-chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
gap: var(--s1);
padding: var(--s1) var(--s2);
border-radius: 999px;
background: var(--surface-2);
border: 1px solid var(--border);
font-size: 12px;
font-size: var(--type-xs);
font-weight: 600;
}

Expand Down
Loading