diff --git a/package.json b/package.json index cbfb2f301b..079592b3b9 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "test:e2e:accessibility": "node scripts/run-playwright.mjs tests/ui-accessibility.spec.ts --project=chromium", "test:e2e:chromium": "node scripts/run-playwright.mjs --project=chromium", "test:e2e:visual": "node scripts/run-playwright.mjs --config=playwright.visual.config.ts", - "verify:cheap": "npm run check:runtime && npm run lint && npm run typecheck && npm run test", + "verify:cheap": "npm run check:runtime && npm run lint && npm run typecheck && npm run test && npm run check:type-scale", "verify:ui": "npm run check:runtime && npm run test:e2e:chromium", "verify:release": "npm run check:runtime && npm run lint && npm run typecheck && npm run test && npm run build && npm run test:e2e && npm run check:production-readiness && npm run governance:release && npm run eval:quality:release", "ci:env-check": "node scripts/check-ci-env.mjs", @@ -53,6 +53,7 @@ "backfill:text-normalization": "tsx scripts/backfill-text-normalization.ts", "check:supabase-project": "tsx scripts/check-supabase-project.ts", "check:indexing": "tsx scripts/check-indexing.ts", + "check:type-scale": "node scripts/check-type-scale.mjs", "recover:ingestion": "tsx scripts/recover-ingestion-queue.ts", "registry:seed": "tsx scripts/seed-registry-records.ts", "reindex": "tsx scripts/reindex.ts", diff --git a/scripts/check-type-scale.mjs b/scripts/check-type-scale.mjs new file mode 100644 index 0000000000..9276d487fe --- /dev/null +++ b/scripts/check-type-scale.mjs @@ -0,0 +1,62 @@ +#!/usr/bin/env node +// Guardrail/diagnostic: flags arbitrary Tailwind font-size utilities +// (e.g. text-[12px], text-[1.45rem]) that bypass the design type scale. +// +// The scale lives in the @theme block of src/app/globals.css: named steps +// text-4xs … text-2xl-minus, on top of Tailwind's default xs/sm/base/lg/xl/2xl. +// Arbitrary text-[] values re-introduce off-scale sizes; this check +// tracks that drift. Colour utilities (text-[color:var(--…)]) are the sanctioned +// token-access form and are intentionally NOT flagged. +// +// Usage: +// node scripts/check-type-scale.mjs report only, exit 0 (default) +// node scripts/check-type-scale.mjs --strict exit 1 if any found (promote to a +// CI gate once the backlog is cleared) + +import { readFileSync } from "node:fs"; +import { execSync } from "node:child_process"; + +const strict = process.argv.includes("--strict"); +const ARBITRARY = /\btext-\[(\d*\.?\d+)(px|rem|em)\]/g; + +// Tracked source files under src (fast; respects .gitignore). +const files = execSync("git ls-files src", { encoding: "utf8" }) + .split("\n") + .filter((f) => /\.(tsx?|jsx?|css)$/.test(f)); + +const hits = []; +for (const file of files) { + let text; + try { + text = readFileSync(file, "utf8"); + } catch { + continue; + } + text.split("\n").forEach((line, i) => { + for (const m of line.matchAll(ARBITRARY)) { + hits.push({ file, line: i + 1, match: m[0] }); + } + }); +} + +if (hits.length === 0) { + console.log("✓ type-scale: no arbitrary text-[px|rem|em] font sizes in src."); + process.exit(0); +} + +const bySize = new Map(); +for (const h of hits) bySize.set(h.match, (bySize.get(h.match) ?? 0) + 1); +const fileCount = new Set(hits.map((h) => h.file)).size; + +console.log(`type-scale: ${hits.length} arbitrary font-size utilities bypass the scale (across ${fileCount} files):\n`); +for (const [size, n] of [...bySize.entries()].sort((a, b) => b[1] - a[1])) { + console.log(` ${String(n).padStart(4)} ${size}`); +} +console.log("\nPrefer a named @theme step from src/app/globals.css (text-3xs, text-sm-minus, text-2xl-minus, …)."); +console.log("Colour utilities like text-[color:var(--…)] are fine and not counted."); + +if (strict) { + console.error("\n✗ --strict: arbitrary font sizes present."); + process.exit(1); +} +process.exit(0); diff --git a/src/app/globals.css b/src/app/globals.css index 7fc0f91903..c2e1c148ae 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -38,12 +38,17 @@ 2xs 11px small chips / secondary labels (most common small size) (xs 12 · sm 14 · base 16 come from Tailwind's default scale) sm-minus 13px one notch under sm — dense body/labels bigger than xs - base-minus 15px one notch under base — comfortable body just under 16 */ + base-minus 15px one notch under base — comfortable body just under 16 + (lg 18 · xl 20 · 2xl 24 come from Tailwind's default scale) + lg-minus 17px one notch under lg — display titles a shade under 18 + 2xl-minus 22px one notch under 2xl — wide-screen nowrap headings */ --text-4xs: 0.5rem; --text-3xs: 0.625rem; --text-2xs: 0.6875rem; --text-sm-minus: 0.8125rem; --text-base-minus: 0.9375rem; + --text-lg-minus: 1.0625rem; + --text-2xl-minus: 1.375rem; /* Font families: bind Tailwind's font-sans / font-mono to the loaded Geist faces (variables set on by next/font). font-mono is used for