Learning Track
Filter content by your role to see the most relevant sections
Automation & CI/CD
Automate token distribution, theme switching, and documentation updates: staged hooks for speed, pre-push gates for certainty, deployment pipelines that build from source, and the honest design for growing local automation into CI.
Why This Matters
A check that only runs when someone remembers is a habit, not a system. The foundations pages have made claims throughout—references flow one way, contracts stay traceable, pairs pass per mode—and every one of those claims is only as real as the automation that re-verifies it on every change, including the rushed ones. Especially the rushed ones.
The second reason is distribution. A token change that requires manual steps—rebuild here, copy a file there, ping the design team—will eventually skip one, and the skip is always discovered in production. Automation makes the correct path the only path: the build regenerates the artifacts, the deploy consumes them, the docs rebuild from the same sources.
This page covers the automation as it exists in this repository—hooks, gates, and the deploy pipeline—and the concrete design for growing it, because the honest state is: strong local automation, continuous integration as the next step rather than a present fact.
Core Concepts: Speed Layers, Certainty Layers, Distribution
Layer 1: Commit-Time (Fast, Touched-Files Only)
On every commit, lint-staged runs only against the files in the commit:
// .lintstagedrc.json — the staged-file contract
"*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix --max-warnings 0"]
"*.{css,scss}": ["prettier --write", "stylelint --fix --allow-empty-input"]
"*.{json,md,yml,yaml}": ["prettier --write"]The design principle is latency budgeting: commit-time checks must be fast enough that nobody is tempted to --no-verify, so they are scoped to touched files and limited to fixing (formatting) and fast judging (lint). Anything slower moves up a layer.
Layer 2: Push-Time (Slow, Whole-System)
The pre-push hook is where certainty lives—whole-repo checks that are too slow for every commit but cheap enough for every push:
// .husky/pre-push (shape)
node scripts/validate-contracts.mjs # contract well-formedness
npm run lint # whole-repo lint
# …and the heavy gates from the validate chain, run where
# failure blocks the push, not the commitThis is the layer that makes the foundations claims gates: a layer-boundary violation cannot leave the machine quietly. The tradeoff is deliberate—a push that takes minutes is a tax everyone pays gladly exactly until they bypass it, which is why the fast layer exists to keep the bypass culture from starting.
Layer 3: Build-and-Deploy (The Distribution)
Deployment is where automation stops being a gate and becomes a producer. This repository's build composes the full chain: next build runs the token build first, so the deployed CSS is always regenerated from the JSON sources—the distribution of tokens, themes, and documentation updates is a property of the pipeline, never a manual step:
"build": "npm run tokens:build:full && next build"
// Deploy (Vercel): push to main → build → the artifacts ship:
// app/designTokens.scss (regenerated, never hand-edited)
// component token CSS (generated from contracts)
// documentation (built from the same sources)Theme switching needs nothing from this layer—brands and density are runtime data attributes—but the arrival of a new brand is a build event, which is precisely the point: the eleventh brand of the theming page costs its three fields plus one green pipeline.
The Honest Gap: Continuous Integration
What this repository does not have yet is hosted CI—no GitHub Actions workflows run the chain on pull requests and main. The local gates are strong, but local gates share a failure mode: they run on the machines of the willing. The growth design is mechanical because the chain already exists as commands:
# .github/workflows/validate.yml (the shape to grow into)
on: [pull_request, push: { branches: [main] }]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run tokens:build:full # regeneration is green
- run: npm run validate # the whole claim chain
- run: npm run test # unit + rendered axe
- run: npm run test:e2e # the browser contractThe reason this is a small step rather than a project: every check is already a command with a clean exit code. Automation that must be invented for CI is a rewrite; automation that must be wired is an afternoon. The discipline that got the chain to command-shaped is the actual work, and it is done.
System Roles: Who Owns Which Layer
Engineering Impact
Engineers own the hooks' contents and the pipeline definitions—and the latency budget that keeps each layer tolerable. A gate that everyone bypasses is worse than no gate: it trains the bypass.
Governance Impact
Governance owns placement policy: which checks block commits, pushes, merges, or deploys. The rule of thumb this repo follows—fix-fast at commit, judge-slow at push, verify-everything at merge—is policy, revisable, and worth revisiting when check runtimes change.
Team Impact
The team owns the culture the automation encodes:--no-verify is always available, and its rare, justified use (with a follow-up issue) is the healthy pattern. Zero bypasses with resentment means the gates are miscalibrated, not that the team is virtuous.
Design & Code Interplay
To the design side, automation shows up as delivery reliability: a token change merged on Tuesday is in every consumer—CSS, mirrored variables on the next library sync, documentation—without a coordination message. The automation replaces the sync meeting with a green check, which is the only meeting replacement that actually works.
The wiring, from the developer's point of view:
npm run tokens:build # author-time: fast regeneration
git add … && git commit # lint-staged: touched files, fix + fast lint
git push # pre-push: contracts, whole-repo gates
# main updated → deploy builds from source, artifacts ship
# The invariant the wiring enforces:
# no token JSON reaches main whose artifacts were not regenerated
# no contract violation leaves a machine without a named failureApplied Example: Wiring the Missing CI Layer
Turn the honest gap into an afternoon, in four moves:
- Inventory the chain as commands:
validate,test,test:e2e,tokens:build:full— each already exits cleanly. Anything not command-shaped (a manual step, a "remember to") becomes a script first; that is the only real work. - Pick the blocking set per event: PRs block on the full chain; pushes to main additionally run e2e. Keep deploy-preview environments for visual review—automation verifies claims, humans verify taste.
- Budget the runtime: if the chain takes twenty minutes, split jobs (tokens → lint/type → test → e2e) so failures report fast and parallels buy minutes. A slow CI is a bypassed CI.
- Retire the duplicated local gates gradually: keep pre-push fast checks for feedback, move the certainty set to CI once it reliably runs there—two enforcement points for the same check is maintenance debt in the other direction.
Constraints & Trade-offs
- Local gates vs hosted CI: local is instant and private; hosted is unbypassable and auditable. The mature answer is both, with the split by latency, and this repo is mid-journey by design.
- Blocking vs advisory checks: every blocking check is a bet that the class of error it catches is cheaper than the friction it adds. Review the bet when check runtimes or team size changes.
- Pipeline as sole distributor vs published packages: rebuilding everything from source at deploy (this repo) guarantees coherence and costs rebuild time; publishing versioned token packages optimizes consumers' installs and adds a versioning surface to govern.
Common Pitfalls & Failure Modes
1. Gates slow enough to bypass
# Bad: Whole-repo e2e on every commit — bypass culture incoming
# Good: Touched-file fixes at commit, chain at push, e2e at merge2. Manual steps with signposts
A README saying "remember to rebuild tokens" is a step that will be skipped under deadline. If it matters, it is a hook or a pipeline stage; if it is a README, it is optional.
3. Checks nobody can fail
A gate that has never been red is untested—feed it a broken fixture deliberately (in a sandbox branch) and confirm it blocks. Trust in automation is earned by watching it refuse.
4. Distributed truth
Artifacts built anywhere other than the pipeline (a laptop's output committed by hand) reintroduce the dual-source problem the pipeline exists to kill. Generated files merge only from generated runs.
Verification Checklist
Additional Resources
- Code Tooling — the checks these layers run (
/blueprints/foundations/tooling/code) - Component standards — the contract validation the gates enforce (
/blueprints/component-standards) - GitHub Actions documentation — the hosting layer this page grows into (
https://docs.github.com/actions) - The sources —
.husky/pre-push,.lintstagedrc.json, thevalidatescript inpackage.json
Related Concepts
Reflection Questions
Apply This Concept
List the checks that run on your current project per commit, push, and merge. Which foundations claims have no check at all — and what would the command for one of them look like?
Reflect
A teammate argues pre-push hooks are enough and CI is bureaucracy. Steelman the position, then name the two failure modes only hosted CI prevents.
