docs(01-design-tokens-rams-foundation): create phase plan (4 plans, 3 waves)

This commit is contained in:
araharah-planner
2026-10-22 02:33:01 +08:00
commit 16eb089cc3
13 changed files with 1825 additions and 0 deletions
@@ -0,0 +1,147 @@
---
phase: 01-design-tokens-rams-foundation
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- package.json
- .gitignore
- DESIGN.md
- theme/package.json
- theme/src/tokens.css
- theme/src/theme.css
- theme/fonts/geist/Geist-Regular.woff2
- theme/fonts/geist/Geist-Medium.woff2
- theme/fonts/geist/Geist-SemiBold.woff2
- theme/fonts/geist/Geist-Bold.woff2
- theme/fonts/space-grotesk/SpaceGrotesk-Bold.woff2
- theme/fonts/space-grotesk/SpaceGrotesk-SemiBold.woff2
- theme/fonts/space-grotesk/SpaceGrotesk-Regular.woff2
autonomous: true
requirements: [DSGN-01]
user_setup: []
must_haves:
decisions: [D-01, D-02, D-04, D-05, D-06, D-07, D-15, D-16, D-17]
truths:
- "Tokens exist as CSS custom properties importable by any later phase (npm script or plain CSS import)"
- "Building the theme produces a compiled dist CSS containing Tailwind v4 utilities wired via @theme"
- "Zero Google Fonts CDN references in the package self-hosted fonts serve both typefaces"
- "Root DESIGN.md declares itself superseded for specific values and points to the token package"
artifacts:
- path: "theme/src/tokens.css"
provides: "All color/spacing/typography tokens as CSS variables + @theme (D-01, D-02)"
contains: "signal-orange"
- path: "theme/src/theme.css"
provides: "Fonts @font-face declarations + @import of tokens (D-16, D-10)"
contains: "@font-face"
- path: "theme/package.json"
provides: "tailwindcss v4 CLI deps + build/audit/gallery scripts (D-15, D-17)"
contains: "@tailwindcss/cli"
- path: ".gitignore"
provides: "OS junk, node_modules, secrets exclusions"
contains: "node_modules"
key_links:
- from: "theme/src/theme.css"
to: "theme/src/tokens.css"
via: "CSS @import"
pattern: "@import.*tokens\\.css"
- from: "theme/package.json"
to: "theme/src/theme.css"
via: "build script"
pattern: "@tailwindcss/cli"
---
<objective>
Create the centralized Telemetry Rams token package (Tailwind v4 CSS-first), self-host the two typefaces, and resolve the repo's design-source conflict. This is the single source of truth every later phase imports.
Purpose: DSGN-01 requires all screens render from one token set; today tokens are hand-duplicated into 4 mockup `tailwind.config` blocks and two conflicting DESIGN.md typography claims (per CONTEXT.md D-05, D-06).
Output: `theme/` package with tokens + fonts + build scripts, repo hygiene (.gitignore, Zone.Identifier scrub), superseded-designation banner on root DESIGN.md.
</objective>
<read_first>
@planning/phases/01-design-tokens-rams-foundation/01-CONTEXT.md
@planning/codebase/STACK.md
@planning/codebase/CONCERNS.md
@stitch_minimalist_route_navigator/telemetry_rams/DESIGN.md
@DESIGN.md
</read_first>
<interfaces>
Token vocabulary (Copy VERBATIM from the frontmatter of `stitch_minimalist_route_navigator/telemetry_rams/DESIGN.md` — D-02 requires 1:1 mockup mapping):
Core brand colors: `signal-orange: #EB8008`, `matte-black: #1A1A1A`, `soft-beige: #D9D2C6`, `functional-red: #ED3F1C`, `olive-green: #736B1E`, `chassis-subtle: #CCC4B5`, `surface-dark: #121212`, `pure-white: #FFFFFF`
Material neutrals: surface #fff8f0 / on-surface #1e1b14 / on-surface-variant #564335 / outline #897363 / outline-variant #dcc1af / transparent variants per frontmatter (copy all keys from lines 3–49)
Spacing: `space-xs 0.25rem` … `space-2xl 3rem` + `gutter` / `gutter-desktop` / `margin` / `margin-desktop` (lines 138–148)
Typography scale: `display-lg`, `display-lg-mobile`, `headline-lg`, `headline-lg-mobile`, `headline-md`, `headline-sm`, `body-lg`, `body-md`, `body-sm`, `label-lg`, `label-md`, `label-sm`, `telemetry-num` with exact px/weight/tracking values (lines 59–137)
No-radius rule: all `--radius-*: 0px` (Shapes section)
</interfaces>
<tasks>
<task type="auto">
<name>task 1: Repo hygiene + package scaffold</name>
<files>.gitignore, package.json, theme/package.json</files>
<action>
1. Scrub Zone.Identifier artifacts (CONCERNS.md debt): `find . -name "*:Zone.Identifier" -type f -delete` (run from repo root). Confirm count 0 afterward.
2. Create root `.gitignore` containing (exact entries): `node_modules/`, `dist/`, `theme/dist/`, `.env`, `.env.*`, `!*.example`, `*.local`, `theme/screenshots/`, `OS junk`: `.DS_Store`, `Thumbs.db`, `desktop.ini`.
3. Create root `package.json`: name `araharah`, private true, type `module`, workspaces `["theme"]`, scripts delegating to theme (`"build": "npm run build --workspace theme"`, `"sheet": "npm run sheet --workspace theme"`, `"gallery": "npm run gallery --workspace theme"`, `"audit": "npm run audit --workspace theme"`, `"capture": "npm run capture --workspace theme"`).
4. Create `theme/package.json`: name `@araharah/theme`, type `module`, devDependencies `tailwindcss@^4` and `@tailwindcss/cli@^4`, dependencies `playwright` (needed by plan 03), plus scripts:
- `"build": "tailwindcss -i ./src/theme.css -o ./dist/rams.css --minify"` (D-17 local-only)
- `"send": "mkdir -p dist/fonts && cp -r fonts/geist fonts/space-grotesk dist/"` (fonts copied alongside build so sheet.css/font paths resolve)
- `"gallery", "audit", "capture", "sheet" script stubs are created by later plans 02–04 — do NOT add them here except as documented below in tasks of those plans.` (Correction: create only `build` and `send` in this plan; later plans append their scripts.)
</action>
<verify><automated>test ! -f .gitignore || grep -q "node_modules" .gitignore; test -f theme/package.json && grep -q "@tailwindcss/cli" theme/package.json && find . -name "*:Zone.Identifier" | grep -c . || echo "zone-files=0"</automated></verify>
<done>.gitignore exists with node_modules/.env entries; theme/package.json declares tailwindcss v4 CLI; zero `*:Zone.Identifier` files remain in the repo.</done>
</task>
<task type="auto">
<name>task 2: Token package (tokens.css + theme.css + fonts)</name>
<files>theme/src/tokens.css, theme/src/theme.css, theme/fonts/**</files>
<read_first>@stitch_minimalist_route_navigator/telemetry_rams/DESIGN.md</read_first>
<action>
1. Download Geist and Space Grotesk woff2 (weights: Geist 400/500/600 + Space Grotesk 700 needed by D-10/labels; Geist Regular, Medium, SemiBold, Bold + Space Grotesk Regular, SemiBold, Bold) from the `fontsource` npm CDN unpkg distribution (`npm i @fontsource-variable/geist @fontsource-variable/space-grotesk` to fetch then extract woff2 files, OR direct unpkg `.woff2` fetches). Place files under `theme/fonts/geist/` and `theme/fonts/space-grotesk/` with names recorded in frontmatter.
2. Write `theme/src/tokens.css` (per D-01, D-02, D-04, D-07): one `@theme` block declaring EVERY token from telemetry_rams/DESIGN.md frontmatter, names verbatim: colors as `--color-*`, spacing as `--spacing-*`, typography via `--font-sans/mono` aliasing (Geist/Space Grotesk), radius `--radius-DEFAULT: 0px` (zero roundedness per design doc Shapes), and label type scale preserved as Tailwind v4 text custom classes via `@utility` or as plain CSS custom properties under `:root` — pick `@theme` for colors/spacing/fonts, `:root`-level custom properties for the typography scale entries (each as a compound var, e.g. `--type-display-lg` composed of font/size/weight/line-height/tracking).
3. Enforce the 8px-base rule: spacing values in the frontmatter are already grid-conformant; copy verbatim (D-07 forbids recomputation).
4. Write `theme/src/theme.css`:
- `@import "tailwindcss";`
- `@import "tokens.css";`
- seven `@font-face` blocks (font-family Geist / Space Grotesk, font-weight per file, `src: url(../fonts/…/*.woff2) format('woff2')`, `font-display: block;` non-flash per Rams aesthetics).
- Dual Telegram light/dark slot pre-slotting (D-04): add `:root { --shell-bg: var(--color-surface); }` and `@media (prefers-color-scheme: dark) { :root { --shell-bg: var(--color-surface-dark); } }` — only shell-level slots (background, on-surface text pair) are themeable, map cartography stays Rams-constant.
5. Run `npm run build --workspace theme` and verify `theme/dist/rams.css` exists and contains `signal-orange` and `.font-geist` font stack.
</action>
<verify><automated>npm run build --workspace theme && test -f theme/dist/rams.css && grep -c "signal-orange" theme/dist/rams.css | awk '$1>=3{exit 0}{exit 1}' && grep -q "@font-face" theme/dist/rams.css</automated></verify>
<tdd>false</tdd>
<done>tokens.css declares ~40-token vocabulary verbatim from mockup config (D-02), theme.css @font-faces self-hosted Geist + Space Grotesk (D-16), shell theme slots pre-arranged (D-04), and `npm run build --workspace theme` produces dist/rams.css containing all token names with no console errors.</done>
</task>
<task type="auto">
<name>task 3: DESIGN.md superseding banner</name>
<files>DESIGN.md, README.md</files>
<read_first>@DESIGN.md</read_first>
<action>
1. Insert at the very top of root `DESIGN.md` a 3-line banner (before any existing heading, no H1 style change so prose stays visible):
`> ⚠ SUPERSEDED FOR SPECIFICS — the centralized token set is the single source of truth for all numeric values (colors, sizes, spacing). This document is kept for Rams design philosophy only. Consult the token package (see README.md) for specific value definitions. Conflicting typography claims in this document (Helvetica/Inter, 5-color palette) are superseded by the token package.` (per D-05)
2. Create or append to `README.md` a one-paragraph "Design system" section: live in `theme/`, consume via `@import 'theme/src/tokens.css'`, build via `npm run build`. Do NOT restate hex codes or px sizes in the README (D-07: docs never restate numeric values — write "palette" and "the token set" descriptively, with no hex/px numbers).
</action>
<verify><automated>head -5 DESIGN.md | grep -q "SUPERSEDED" && ! grep -qE '#[0-9A-Fa-f]{6}|(\d+px)' README.md</automated></verify>
<done>DESIGN.md first line declares supersession; README references the package without numerically restating any design value (D-07).</done>
</task>
</tasks>
<verification>
- `npm run build --workspace theme` exits 0 and emits `theme/dist/rams.css`
- `grep -c "signal-orange" theme/dist/rams.css` ≥ 3
- `find . -name "*:Zone.Identifier" | wc -l` = 0
- `head -3 DESIGN.md` mentions SUPERSEDED
- `grep -r "fonts.googleapis" theme/` returns no matches (D-16)
</verification>
<success_criteria>
Token package exists, builds, and is importable by any later phase; fonts self-hosted; design-source conflict resolved via superseding banner + non-numeric README; repo git hygiene established. Unblocks component sheet (02), basemap (03).
</success_criteria>
<output>
After completion, create `.planning/phases/01-design-tokens-rams-foundation/01-01-SUMMARY.md`
</output>
@@ -0,0 +1,126 @@
---
phase: 01-design-tokens-rams-foundation
plan: 02
type: execute
wave: 2
depends_on: ["01-01"]
files_modified:
- theme/src/sheet.html
- theme/src/components.css
- theme/package.json
- theme/dist/rams.css
autonomous: true
requirements: [DSGN-01]
user_setup: []
must_haves:
decisions: [D-03, D-11]
truths:
- "A rendered component sheet displays every component type the 4 mockup screens show, styled exclusively by the token package"
- "The sheet includes the Legibility specimen (typography scale) per D-11"
- "Building the sheet via Tailwind v4 CLI emits a dist CSS that contains zero off-token values (numbers only from tokens.css)"
artifacts:
- path: "theme/src/sheet.html"
provides: "Component sheet demo page consuming tokens (D-03, D-11)"
contains: "signal-orange"
- path: "theme/src/components.css"
provides: "Component classes defined against tokens, not raw hex/px values"
contains: "var(--color-signal-orange)"
- path: "theme/package.json"
provides: "Appends `sheet` build script"
contains: "\"sheet\""
key_links:
- from: "theme/src/sheet.html"
to: "theme/dist/rams.css"
via: "<link rel=stylesheet>"
pattern: "rams\\.css"
- from: "theme/src/components.css"
to: "theme/src/tokens.css"
via: "var(--color-*) / var(--spacing-*) references only"
pattern: "var\\(--"
---
<objective>
Create the Rams component sheet — buttons, cards, chips/badges, the maneuver banner, inputs, checkboxes/toggles, and the typography specimen — all styled exclusively from the token package. This is the artifact DSGN-01's audit sweep (plan 04) measures and the gallery (plan 04) screenshots.
Purpose: D-03 — "the sheet itself proves token completeness." Scope is strictly mockup-derived (D-11: no invented components).
Output: `theme/src/sheet.html` + `theme/src/components.css` + a `sheet` npm script.
</objective>
<read_first>
@planning/phases/01-design-tokens-rams-foundation/01-CONTEXT.md
@planning/phases/01-design-tokens-rams-foundation/01-01-SUMMARY.md
@stitch_minimalist_route_navigator/telemetry_rams/DESIGN.md
@stitch_minimalist_route_navigator/active_route_guidance/code.html
@stitch_minimalist_route_navigator/navigate_map_overview/code.html
@stitch_minimalist_route_navigator/community_incident_reporter/code.html
@stitch_minimalist_route_navigator/telegram_live_traffic_feed/code.html
</read_first>
<interfaces>
From plan 01-01:
- `theme/src/tokens.css` — `@theme` colors (`--color-signal-orange`, `--color-matte-black`, `--color-soft-beige`, `--color-functional-red`, `--color-olive-green`, `--color-chassis-subtle`, `--color-surface-dark`, …), spacing (`--spacing-xs` … `--spacing-2xl`), fonts (`--font-sans` = Geist stack, `--font-display` = Space Grotesk stack).
- `theme/src/theme.css` — imports tokens + `@font-face` declarations; build target is `theme/dist/rams.css`.
- Typography scale exists as `:root` custom properties under `--type-display-lg` … `--type-telemetry-num` (compound font/size/weight/line-height/tracking).
Rendered mockup components to mirror (D-11 scope, observed across the four code.html screens):
- Primary action button: bg `signal-orange`, label `matte-black` uppercase label-lg, `2px solid` matte-black border, 0px radius; hover/focus inverts to matte-black bg / signal-orange text (per telemetry_rams/DESIGN.md Components).
- Secondary action: bg soft-beige, matte-black label, 2px border; hover bg chassis-subtle.
- Telemetry card: 1px solid matte-black perimeter, soft-beige bg, sharp corners; header plate with label-sm uppercase category + reference badge (`SEC-04 // SPEED`); content in telemetry-num typography.
- Chips: rectangular blocks — traffic = signal-orange bg / matte-black text; hazard = functional-red bg / white text; safe = olive-green bg / white text; all label-sm.
- Maneuver banner strip: inverted matte-black container, 32x32 signal-orange arrow (3px stroke), headline-md soft-beige street, Space Grotesk bold distance.
- Input: soft-beige bg, 2px solid matte-black border; focus = 3px signal-orange border, no glow/blur/animation.
- Checkbox 18×18 2px matte-black border with 8×8 signal-orange fill when checked; two-way horizontal toggle [ OFF | ON ] with inverted active box.
- Typography specimen: every scale entry (`display-lg` → `label-sm` + `telemetry-num`).
</interfaces>
<tasks>
<task type="auto">
<name>task 1: components.css — component layer built only from tokens</name>
<files>theme/src/components.css</files>
<action>
1. Create `theme/src/components.css`. Use Tailwind v4 `@layer components { … }` with utility classes constructed from `var(--color-*)` / `var(--spacing-*)` / `--type-*` references ONLY. NO raw hex values, NO raw px paddings/margins/font-sizes, NO border-radius other than 0 (D-11 fidelity hard requirement; plan 04's audit script flags any violation).
2. Component classes to define (names kebab-case): `.btn-primary`, `.btn-secondary`, `.input-field`, `.card`, `.card-header`, `.chip-traffic`, `.chip-hazard`, `.chip-safe`, `.maneuver-banner`, `.maneuver-arrow`, `.checkbox`, `.toggle`, plus typography helpers `.text-display-lg` through `.text-label-sm` and `.text-telemetry-num` consuming the `--type-*` custom properties.
3. Import it from `theme/src/theme.css` after tokens: add `@import "components.css";` as the last import so build picks it up.
4. Border geometry: 1px and 2px solid borders use `var(--color-matte-black)`; focus rings replace border color with `var(--color-signal-orange)` at 3px — zero blur/glow/transition.
</action>
<verify><automated>grep -cE "var\\(--" theme/src/components.css | awk '$1>=15{exit 0}{exit 1}'; ! grep -E "#[0-9A-Fa-f]{3,6}\b" theme/src/components.css || echo FAIL-off-token-hex; ! grep -E "border-radius:\s*(?!0)" theme/src/components.css -P || echo FAIL-radius</automated></verify>
<done>components.css defines all 14 component/typography classes using only token references; zero hex literals; zero non-zero border-radius (grep-verifiable).</done>
</task>
<task type="auto">
<name>task 2: sheet.html — rendered component sheet page</name>
<files>theme/src/sheet.html, theme/package.json</files>
<action>
1. Create `theme/src/sheet.html`: a minimal static demo page. `<link rel="stylesheet" href="./dist/rams.css">`. Sections in order, one per mockup-derived component family (D-11): Typography specimen → Buttons → Inputs → Chips/Badges → Telemetry card → Maneuver banner → Checkboxes & Toggle. Each section gets a label-sm uppercase section header with a technical reference badge (matching card-header pattern).
2. Use plain semantic HTML + the component classes (no Tailwind utility soup in the markup — the classes are the API later phases copy).
3. Representative content mirrors the mockups without inventing new components: e.g., chip texts "CONGESTION", "CRASH / BLOCK", "POLICE RADAR" exactly as seen in `telegram_live_traffic_feed/code.html`; card reference badge "SEC-04 // SPEED".
4. Add `"sheet": "tailwindcss -i ./src/sheet.css -o ./dist/sheet.css --minify || true && open ./src/sheet.html"` is WRONG shape — instead append to `theme/package.json` scripts: `"sheet": "npm run build && echo 'open theme/src/sheet.html in browser ( Consumes theme/dist/rams.css )'"`. Simplest correct wiring: sheet.html links the built dist/rams.css directly, so `npm run build` then opening sheet.html works with no extra CLI step.
</action>
<verify><automated>npm run build --workspace theme >/dev/null && test -f theme/src/sheet.html && grep -q "dist/rams.css" theme/src/sheet.html && grep -c "chip-\\|btn-\\|card\\|maneuver\\|checkbox\\|toggle" theme/src/sheet.html | awk '$1>=7{exit 0}{exit 1}'</automated></verify>
<done>sheet.html renders all 7 component families using only components.css classes; page loads dist/rams.css; no Google Fonts CDN link present in sheet.html (D-16); npm run build passes after components.css import.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| none cross-user | Static sheet, dev-only artifact; no network input |
</threat_model>
<verification>
- `npm run build --workspace theme` → 0 exit
- `grep -rn "fonts.googleapis" theme/src/` → empty
- `grep -cE "var\\(--" theme/src/components.css` ≥ 15
- visual: open `theme/src/sheet.html` in a browser — components visibly mirror mockup styling (font, colors, sharp corners)
</verification>
<success_criteria>
Component sheet renders every mockup-derived component family with style sourced 100% from token variables. It is the demonstrable artifact for DSGN-01's "every rendered component derives from tokens" and the input target for the audit sweep (plan 04).
</success_criteria>
<output>
After completion, create `.planning/phases/01-design-tokens-rams-foundation/01-02-SUMMARY.md`
</output>
@@ -0,0 +1,119 @@
---
phase: 01-design-tokens-rams-foundation
plan: 03
type: execute
wave: 2
depends_on: ["01-01"]
files_modified:
- theme/basemap/liberty-rams.json
- theme/basemap/build.mjs
- theme/basemap/index.html
- theme/package.json
autonomous: true
requirements: [DSGN-01]
user_setup: []
must_haves:
decisions: [D-08, D-09, D-10]
truths:
- "A Rams-styled MapLibre basemap renders in a browser: grayscale/muted land + water, desaturated roads, muted labels, zero signal-orange in the basemap"
- "Basemap values are derived from the token package, not hand-tuned hex in the style JSON (D-09)"
- "Map labels use the self-hosted Space Grotesk / Geist assets (D-10)"
artifacts:
- path: "theme/basemap/build.mjs"
provides: "Generates liberty-rams.json by reading token values from theme/src/tokens.css"
contains: "tokens.css"
- path: "theme/basemap/liberty-rams.json"
provides: "Forked OpenFreeMap liberty style with Rams cartography"
contains: "glyphs"
- path: "theme/basemap/index.html"
provides: "MapLibre GL browser view proving the basemap renders (success criterion 3)"
contains: "maplibre-gl"
key_links:
- from: "theme/basemap/build.mjs"
to: "theme/src/tokens.css"
via: "parse token custom properties"
pattern: "tokens\\.css"
- from: "theme/basemap/index.html"
to: "theme/basemap/liberty-rams.json"
via: "MapLibre style URL"
pattern: "liberty-rams\\.json"
---
<objective>
Fork the OpenFreeMap `liberty` basemap style into a Rams-constant cartography output: grayscale/muted grounding, desaturated roads, muted labels in Rams type, `signal-orange` reserved exclusively for the future route line (not used in the basemap itself). Prove it renders in a browser with MapLibre GL.
Purpose: D-08 (full Rams pass on basemap), D-09 (derived from tokens, not hand-tuned), D-10 (Rams font assets for labels), roadmap success criterion 3.
Output: `theme/basemap/` — build script, generated style JSON, browser proof page.
</objective>
<read_first>
@planning/phases/01-design-tokens-rams-foundation/01-CONTEXT.md
@planning/phases/01-design-tokens-rams-foundation/01-01-SUMMARY.md
@stitch_minimalist_route_navigator/telemetry_rams/DESIGN.md
</read_first>
<interfaces>
Derivation targets (map token → cartography role per D-08 / telemetry_rams/DESIGN.md "Grayscale Map Grounding"):
- Land/background ground tone: between `soft-beige` (#D9D2C6) and `chassis-subtle` (#CCC4B5)
- Water: desaturated mid-gray derived from `matte-black` mixed toward low-saturation (rgba derived, NOT brand hues)
- Roads: desaturated, hierarchy by weight only (major roads slightly darker than minor)
- Buildings: light gray within the `#D9D2C6 → #1A1A1A` calibrated gray band
- Labels: `matte-black` at reduced opacity; fonts Space Grotesk (major) / Geist (minor) — same assets as theme/fonts (D-10)
- `signal-orange` (#EB8008), `functional-red` (#ED3F1C), `olive-green` (#736B1E) must NOT appear in the basemap — accent is reserved exclusively for the route line rendered in Phase 5 (D-08).
Style input: `https://tiles.openfreemap.org/styles/liberty` style JSON (fetch at build time and fork; do not vendor the full original JSON — build.mjs fetches, transforms, writes).
</interfaces>
<tasks>
<task type="auto">
<name>task 1: basemap build script deriving style from tokens</name>
<files>theme/basemap/build.mjs, theme/basemap/liberty-rams.json</files>
<action>
1. Create `theme/basemap/build.mjs` (Node ESM, no external deps beyond node builtins): fetches `https://tiles.openfreemap.org/styles/liberty` style JSON, transforms it, writes `theme/basemap/liberty-rams.json`.
2. Derivation step: parse `theme/src/tokens.css` extracting the `--color-*` custom property values (deterministic regex `-\-color-([a-z0-9-]+):\s*(#[0-9A-Fa-f]{6})`). Compute the Rams cartography palette IN SCRIPT (D-09: derived, not hand-tuned): desaturate all land/road/water/building colors to the calibrated gray band via a small `desaturate(hex, satMultiplier)` helper (pure function: convert hex→HSL, multiply saturation toward ~0.06–0.12, adjust lightness per role per Interfaces table). Map transformed values onto the liberty style's `layers[].paint` color properties (`background`, `water`, `road-*`, `building-*`, `landcover-*`, `bridge-*`).
3. FONT STEP: replace liberty's glyph/name font stacks. Point the style's `glyphs` to the OpenFreeMap hosted glyphs endpoint (keep `https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf` to avoid self-hosting pbf glyphs in Phase 1), and rewrite every `layout["text-font"]` value to `"Space Grotesk Medium"` for major label layers (place-city, place-subtown and above) and `"Noto Sans Regular"` fallback chain ending `"Space Grotesk Regular"`… — IMPORTANT constraint: OpenFreeMap glyphs server only serves font stacks it hosts. If `Space Grotesk` is not available on the glyphs endpoint, detect a 404 at build time and fall back to the closest hosted stack, recording the substitution in a `"metadata"` block (`"rams-font-substitutions": {...}`) — this keeps D-10 satisfied in spirit (Rams typography for labels) with honest fallback provenance.
4. Common concerns: signal-orange/functional-red/olive-green token names are excluded from the derivation set (guard: throw if any of those 3 hex values end up anywhere in the output layers' paint properties).
5. Add npm script to `theme/package.json`: `"basemap": "node basemap/build.mjs"`.
6. Run it; verify `theme/basemap/liberty-rams.json` exists, is valid JSON, has `version: 8`, `glyphs`, `sources`, `layers[0].paint` uses a transformed neutral.
</action>
<verify><automated>npm run basemap --workspace theme && node -e "const s=require('./theme/basemap/liberty-rams.json'); if(!s.layers||!s.glyphs) process.exit(1); const rgx=/EB8008|ED3F1C|736B1E/i; const bad=JSON.stringify(s.layers).match(rgx); if(bad){process.exit(2)} console.log('OK layers='+s.layers.length)"</automated></verify>
<done>build.mjs derives all basemap colors from tokens.css (no hardcoded cartography hex in the script beyond token parsing), output JSON is a valid MapLibre style, and none of the 3 accent colors appear anywhere in the layer paint properties.</done>
</task>
<task type="auto">
<name>task 2: browser proof page (dummy route + rendered basemap)</name>
<files>theme/basemap/index.html, theme/package.json</files>
<action>
1. Create `theme/basemap/index.html`: static MapLibre GL viewer (CDN unpkg `maplibre-gl@^4`), `<style>` sets page chrome to Rams tokens (`var(--color-surface)` background etc. — import `../dist/rams.css`), init map centered ~3.1513, 101.6958 (KLCC / KL metro, per project region), zoom ~13, style = `./liberty-rams.json`.
2. Draw a placeholder route polyline in `signal-orange` (raw tolerance: the ROUTE line is exactly where signal-orange is allowed per D-08) with a thick 6px stroke so the visual "grayscale guiderail + orange vector" intent is previewable, labeled in a README comment as consumed by Phase 5.
3. Add `"basemap:dev": "npx serve theme/basemap || true"`? No extra server dep — just use `python3 -m http.server` invocation pattern for the verify command instead; document the open command in index.html's comment header. Add npm script `"basemap:preview": "cd theme/basemap && python3 -m http.server 8123"`.
4. Layer order: route layer added LAST in the layer stack so it sits above mapcar/roads but below any label overlap is not required here — keep simple.
</action>
<verify><automated>test -f theme/basemap/index.html && grep -q "maplibre" theme/basemap/index.html && grep -q "liberty-rams.json" theme/basemap/index.html && grep -qE "3\.15|101\.69" theme/basemap/index.html</automated></verify>
<done>index.html loads liberty-rams.json, centers KLCC, draws a signal-orange route placeholder, and is checkable via `npm run basemap:preview --workspace theme` → http://localhost:8123 (human eyes verify the grayscale+muted+Rams feel, success criterion 3).</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| basemap build → OpenFreeMap API | build.mjs fetches external style JSON across network |
</threat_model>
<verification>
- `npm run basemap --workspace theme` → 0 exit, liberty-rams.json regenerated
- No accent hex (#EB8008 / #ED3F1C / #736B1E) anywhere in layers' paint
- index.html renders the basemap via the preview script
- `grep -c "tokens.css" theme/basemap/build.mjs` ≥ 1 (derivation provenance)
</verification>
<success_criteria>
Rams basemap derived from (not decorated with) the token package renders in a browser at KL metro with muted grayscale grounding, Rams label fonts, and zero accent-color leaks into cartography. Route accent visually confirmed against the grayscale base (success criterion 3 of the roadmap).
</success_criteria>
<output>
After completion, create `.planning/phases/01-design-tokens-rams-foundation/01-03-SUMMARY.md`
</output>
@@ -0,0 +1,172 @@
---
phase: 01-design-tokens-rams-foundation
plan: 04
type: execute
wave: 3
depends_on: ["01-02", "01-03"]
files_modified:
- theme/scripts/audit.mjs
- theme/scripts/screenshot.mjs
- theme/gallery.html
- theme/package.json
- package.json
autonomous: false
requirements: [DSGN-02]
user_setup: []
must_haves:
decisions: [D-12, D-13, D-14]
truths:
- "The automated audit sweep scans the built CSS and flags every color/spacing/typography/radius value not in the token set — zero off-token values is evidence, not an eyeball claim (D-13)"
- "A static side-by-side gallery page renders implementation components against all four mockup screens' rendered output (D-12)"
- "Gallery captures are produced by a committed Playwright real-browser screenshot script (D-14)"
- "User has viewed the gallery and basemap preview and explicitly approved (DSGN-02 approval artifact)"
artifacts:
- path: "theme/scripts/audit.mjs"
provides: "Automated off-token audit (D-13): parses tokens.css allowlist, scans built dist CSS"
- path: "theme/scripts/screenshot.mjs"
provides: "Playwright capture script committed for reuse by later UI phases (D-14)"
- path: "theme/gallery.html"
provides: "DSGN-02 mockup-vs-implementation gallery — same pattern every later UI phase extends (D-12)"
min_lines: 80
- path: "theme/package.json"
provides: "Wires `audit` and `capture` scripts"
contains: "audit"
key_links:
- from: "theme/scripts/audit.mjs"
to: "theme/src/tokens.css"
via: "parse @theme/`:root` token values as the allowlist"
pattern: "tokens\\.css"
- from: "theme/scripts/audit.mjs"
to: "theme/dist/rams.css"
via: "scan built CSS for off-token literals; exit 1 on any"
pattern: "rams\\.css"
- from: "theme/scripts/screenshot.mjs"
to: "theme/screenshots/"
via: "Playwright chromium captures of sheet.html, 4 mockups, basemap preview"
pattern: "screenshots"
- from: "theme/gallery.html"
to: "theme/screenshots/*"
via: "<img> side-by-side comparison rows"
pattern: "screenshots/"
---
<objective>
Prove token fidelity by script and by the user's eyes: wire the automated off-token audit (D-13 — "zero off-token values" becomes evidence, not an assertion), build the Playwright capture toolchain (D-14), assemble the side-by-side mockup-vs-implementation gallery (D-12), and run the DSGN-02 approval checkpoint — the fidelity-bar gate every later UI phase reuses.
Purpose: DSGN-02's approval-artifact mechanism is born here; no product screen (Phase 2+) is built before the user approves this gallery.
Output: audit script + capture script + gallery page + approved DSGN-02 record.
</objective>
<execution_context>
@$HOME/.config/opencode/get-shit-done/workflows/execute-plan.md
@$HOME/.config/opencode/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/ROADMAP.md
@.planning/phases/01-design-tokens-rams-foundation/01-CONTEXT.md
<interfaces>
From plan 01-01: `theme/src/tokens.css` (@theme colors/spacing/fonts + `:root` `--type-*` entries), `theme/dist/rams.css` via `npm run build --workspace theme`. Root package.json already defines workspace delegates `build`, `sheet`, `gallery`, `audit`, `capture` (created in 01-01).
From plan 01-02: `theme/src/sheet.html` renders all 7 mockup-derived component families; `theme/src/components.css` uses only `var(--*)` references.
From plan 01-03: `theme/basemap/index.html` renders the Rams Liberty fork over KL (basemap at `theme/basemap/liberty-rams.json`).
Mockups: `stitch_minimalist_route_navigator/<screen>/code.html` (CDN Tailwind — needs network) with committed `screen.png` fallbacks per screen; read-only per D-06 — screenshot them via `file://`, never edit.
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>task 1: Off-token audit script (D-13)</name>
<files>theme/scripts/audit.mjs, theme/package.json</files>
<behavior>
- Test: a scratch stylesheet using ONLY token values → audit exits 0
- Test: a scratch stylesheet containing `color: #00ff00; border-radius: 12px; font-family: Arial; box-shadow: ...` → audit exits 1 and lists each offending declaration file:line
- Test: audit of `theme/dist/rams.css` (real build from the sheet) → exit 0
</behavior>
<action>
1. `theme/scripts/audit.mjs` — Node ESM, zero runtime deps:
a. Parse `theme/src/tokens.css` to extract the allowlist: `--color-*` hex values, `--spacing-*` values, `--type-*` names/families, `--radius-*` (must all be 0). Fail loud (exit 2) with a clear message if tokens.css parses to < 40 color tokens (drift guard).
b. Scan inputs (default `theme/dist/rams.css`; accept extra file args): flag any `#hex` literal not in the allowlist, any `font-family:` not in {Geist, Space Grotesk, ui-monospace/system fallbacks from tokens}, any `border-radius` other than 0, any `box-shadow:` or `filter: blur` / `backdrop-filter` presence (Rams Shapes: no shadows/blur/glass).
c. Print each violation as `file:line: declaration`. Exit 1 if any, 0 if none.
2. Wire in `theme/package.json`: `"audit": "node ./scripts/audit.mjs ./dist/rams.css"`. Root `audit` delegate already exists — verify it resolves.
Grep-gate hygiene: use exit-code based verification, never `grep -c X == 0` over unfiltered files.
</action>
<verify>
<automated>npm run build --workspace theme && npm run audit && echo AUDIT-CLEAN</automated>
</verify>
<done>Audit REJECTS an injected off-token stylesheet (proven during TDD RED) and passes clean against the real sheet build (DSGN-01 evidence, automated per D-13)</done>
</task>
<task type="auto">
<name>task 2: Playwright capture script + gallery page (D-14, D-12)</name>
<files>theme/scripts/screenshot.mjs, theme/gallery.html, theme/package.json, .gitignore</files>
<action>
1. `npm i playwright --workspace theme` + `npx playwright install chromium` (playwright dep already declared in 01-01's theme/package.json).
2. `theme/scripts/screenshot.mjs` (wired as root+theme `capture` script):
- Serve `theme/` via its own tiny static server (node http) so `./dist/rams.css` resolves; capture `theme/src/sheet.html` → `theme/screenshots/sheet-full.png` (390 viewport, fullPage).
- Capture each of the 4 mockups via `file://` with network enabled, viewport 390×844, wait networkidle + 800ms settle → `theme/screenshots/mockup-<screen>.png`. If a mockup shot fails (CDN hiccup), fall back to tracking that screen's committed `screen.png` in the gallery instead — never edit mockups (D-06).
- Capture `theme/basemap/index.html` → `theme/screenshots/basemap-kl.png` (1280×800, wait for `map.on('idle')` or first render + 2s).
3. `.gitignore`: ensure `theme/screenshots/` is excluded (regenerated artifacts). Wire `"capture": "node ./scripts/screenshot.mjs"`; root delegate `capture` already exists.
4. `theme/gallery.html` — static page, CDN-free, built from tokens only. Record in a leading HTML comment that this page is the DSGN-02 template mechanism every later UI phase extends:
- Header: "DSGN-02 Approval Gallery — Rams implementation vs mockups"
- Comparison rows, one per mockup screen: left = mockup capture, right = `sheet-full.png` (or per-section crops); final row = `basemap-kl.png`.
- Token ledger: table listing the typography scale + hero color token NAMES with rendered swatches/specimens only — no numeric restatement (D-07: docs never restate values).
- Approval strip stating scope: fidelity bar + basemap direction; interactive flows out of scope (per CONTEXT deferred).
</action>
<verify>
<automated>npm run capture && for f in theme/screenshots/*.png; do [ "$(stat -c%s "$f")" -lt 10240 ] && exit 1; done && test -f theme/gallery.html && grep -q "screenshots/" theme/gallery.html && npm run audit && echo GALLERY-TOOLS-OK</automated>
</verify>
<done>≥6 non-blank captures (sheet, 4 mockups, basemap); gallery references them; audit still clean</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>task 3: DSGN-02 approval checkpoint</name>
<what-built>
- Centralized Tailwind v4 token package (D-01/D-02), rendered component sheet (D-03/D-11), automated off-token audit — clean (D-13), Rams Liberty basemap over KL (D-08/D-09/D-10), Playwright captures (D-14), and the side-by-side approval gallery at `theme/gallery.html` (D-12).
</what-built>
<action>Present the completed gallery and basemap preview to the user per the how-to-verify steps below; record the decision and wait for the resume signal before any later-phase work relies on DSGN-02.</action>
<how-to-verify>
1. In your shell: `npx http-server theme -p 4444` (from repo root; gallery needs a static server for fonts + relative CSS).
2. Browse `http://localhost:4444/gallery.html` — side-by-side comparisons; judge fidelity of sheet vs mockups token-by-token.
3. Browse `http://localhost:4444/basemap/index.html` — Rams Liberty over KL; check grayscale/muted land + water, desaturated roads, no orange anywhere (route accent reserved per D-08, rendered in Phase 5).
4. Optionally compare against originals: `stitch_minimalist_route_navigator/<screen>/screen.png`.
5. Confirm the automated ground for "zero off-token values": `npm run audit` (executed during this plan; output shown in the summary).
</how-to-verify>
<resume-signal>Type "approved" (fidelity bar met, basemap direction good) — or list specific screens/components/basemap aspects to fix and iterate before DSGN-02 is recorded.</resume-signal>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| workstation browser → CDN Tailwind (mockup pages only) | Mockup screenshots fetch CDN scripts; all new design artifacts are CDN-free |
| audit.mjs → local files | Dev-only script reads repo files only |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-1-05 | I/T | screenshot.mjs CDN fetches | accept | Only the captured mockup pages (read-only per D-06) use CDN; captures are local gitignored artifacts. Gallery itself is CDN-free per D-16. |
| T-1-06 | D | approval artifact provenance | mitigate | theme/gallery.html committed as the durable DSGN-02 mechanism — later UI phases extend the same file; approval decision recorded in phase summary |
| T-1-07 | T | audit.mjs tampering | mitigate | Audit fails loud (exit 2) if token allowlist shrinks below 40 colors; violations exit 1 — no silent passes |
</threat_model>
<verification>
- `npm run audit` clean from a clean checkout after `npm run build --workspace theme`
- `npm run capture` produces ≥6 non-blank screenshots
- `theme/gallery.html` renders side-by-side with ≥4 captures referenced
- User approval recorded (checkpoint passed)
</verification>
<success_criteria>
- DSGN-01 audit sweep is automated and passing (D-13)
- DSGN-02 approval artifact exists; mechanism template documented for reuse (D-12)
- Playwright capture script committed and working (D-14)
- Design source of truth singular: package owns every numeric value (D-07)
</success_criteria>
<output>
After completion, create `.planning/phases/01-design-tokens-rams-foundation/01-04-SUMMARY.md`
</output>