Template
Design tokens + agent iteration hardening
- tokens.css: full token layer (type scale rem-based, palette, spacing, radii) - App.css rebuilt token-driven; zero hardcoded colors/px font sizes - readability preset: --root-font-size 112%; button matrix scrolls when narrow - App.tsx split into real components (Hero, CodeSample, RightRail + reflow) - light theme via [data-theme=light] overrides; topbar sun/moon toggle persisted - scripts/check-tokens.mjs + npm run check (lint, token guard, tsc, build) - AGENTS.md: token contract, check gate, preview workflow
This commit is contained in:
1 parent
b1049d5ded
commit
47127db84b
20 files changed
+874
-57
No files matched your search
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env node
|
||||
/* Token contract guard — runs as part of `npm run check`.
|
||||
Bans hardcoded values in application CSS so every visual change
|
||||
goes through src/tokens.css. */
|
||||
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const CSS_ROOTS = ['src']
|
||||
const EXEMPT = new Set(['tokens.css'])
|
||||
// Neutral shine/shadow literals stay allowed (they are surfaces, not palette).
|
||||
const RGB_ALLOW = [/rgba\(\s*0\s*,\s*0\s*,\s*0/, /rgba\(\s*255\s*,\s*255\s*,\s*255/]
|
||||
|
||||
const violations = []
|
||||
|
||||
function walk(dir) {
|
||||
for (const entry of readdirSync(dir)) {
|
||||
const full = join(dir, entry)
|
||||
if (statSync(full).isDirectory()) {
|
||||
walk(full)
|
||||
} else if (full.endsWith('.css') && !EXEMPT.has(entry)) {
|
||||
checkFile(full, readFileSync(full, 'utf8'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkFile(file, css) {
|
||||
css.split('\n').forEach((line, i) => {
|
||||
const n = `${file}:${i + 1}`
|
||||
const hex = line.match(/#[0-9a-fA-F]{3,8}\b/)
|
||||
if (hex) violations.push(`${n} hex color ${hex[0]} — add a token in tokens.css instead`)
|
||||
if (/font(-size)?\s*:[^;}]*\d+px/.test(line)) violations.push(`${n} px font size — use var(--text-*)`)
|
||||
const rgb = line.match(/rgba?\(/)
|
||||
if (rgb && !RGB_ALLOW.some(re => re.test(line))) {
|
||||
violations.push(`${n} raw rgba() — use color-mix(in srgb, var(--token) N%, transparent)`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
CSS_ROOTS.forEach(walk)
|
||||
|
||||
if (violations.length) {
|
||||
console.error(`\n✗ token contract violations:\n\n${violations.join('\n')}\n`)
|
||||
process.exit(1)
|
||||
} else {
|
||||
console.log('✓ token contract OK — no hardcoded colors or font sizes found')
|
||||
}
|
||||
Reference in new issue
Block a user