Add design tokens and self-hosted fonts
Independent review Codex gpt-6-sol high: CHANGES_REQUIRED (1 high)
CI / ci (pull_request) Successful in 1m29s
CI / ci (push) Successful in 1m30s

Copy the handoff tokens.css into web/src/styles/tokens.css verbatim except
the Google Fonts @import, replaced by @font-face rules for self-hosted
Bagel Fat One (400) and Figtree (400/600/700) woff2 files, committed with
their OFL licence texts. Base styles set the page background, text colour,
Figtree body and the accent focus outline.

npm test now runs lint:colors, which fails on any hex colour literal in
web/src outside tokens.css. npm run build ends with check:dist-fonts, which
fails if dist references fonts.googleapis.com or fonts.gstatic.com.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 15:52:41 -04:00
co-authored by Claude Opus 5.5
parent 7cc96121e4
commit 41a1272358
13 changed files with 390 additions and 2 deletions
+32
View File
@@ -0,0 +1,32 @@
// Fails when the production build references Google Fonts: fonts are
// self-hosted, so dist must never point at fonts.googleapis.com or
// fonts.gstatic.com.
import { readFileSync, readdirSync } from 'node:fs'
import { join, relative } from 'node:path'
import { fileURLToPath } from 'node:url'
const distDir = fileURLToPath(new URL('../dist', import.meta.url))
const hosts = ['fonts.googleapis.com', 'fonts.gstatic.com']
function* files(dir) {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const path = join(dir, entry.name)
if (entry.isDirectory()) yield* files(path)
else yield path
}
}
const violations = []
for (const path of files(distDir)) {
const content = readFileSync(path, 'latin1')
for (const host of hosts) {
if (content.includes(host)) violations.push(`${relative(distDir, path)}: ${host}`)
}
}
if (violations.length > 0) {
console.error('Production build references Google Fonts; fonts must be self-hosted:')
for (const v of violations) console.error(` ${v}`)
process.exit(1)
}
console.log('check:dist-fonts: dist makes no Google Fonts requests')
+39
View File
@@ -0,0 +1,39 @@
// Fails when a hex colour literal appears in web/src outside the design
// tokens: every colour must come from a custom property in tokens.css.
import { readFileSync, readdirSync } from 'node:fs'
import { extname, join, relative } from 'node:path'
import { fileURLToPath } from 'node:url'
const webRoot = fileURLToPath(new URL('..', import.meta.url))
const srcDir = join(webRoot, 'src')
const allowed = new Set(['src/styles/tokens.css'])
const extensions = new Set(['.css', '.ts', '.tsx'])
const hexColour = /#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4})\b/g
function* sourceFiles(dir) {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const path = join(dir, entry.name)
if (entry.isDirectory()) yield* sourceFiles(path)
else if (extensions.has(extname(entry.name))) yield path
}
}
const violations = []
for (const path of sourceFiles(srcDir)) {
const name = relative(webRoot, path).split('\\').join('/')
if (allowed.has(name)) continue
readFileSync(path, 'utf8')
.split('\n')
.forEach((line, i) => {
for (const match of line.matchAll(hexColour)) {
violations.push(`${name}:${i + 1}: ${match[0]}`)
}
})
}
if (violations.length > 0) {
console.error('Hex colour literals found; use a token from src/styles/tokens.css:')
for (const v of violations) console.error(` ${v}`)
process.exit(1)
}
console.log('lint:colors: no hex colour literals outside src/styles/tokens.css')