- 2/2 tasks: build script + token-derived style JSON, browser proof page with signal-orange route placeholder - real-browser render proof 8/8: grayscale grounding pixel-verified, zero accent leaks, route accent on top - D-08/D-09/D-10 satisfied; 3 Rule-1 deviations documented (rail regex, line-join spec, preview docroot)
16 KiB
phase: 01-design-tokens-rams-foundation plan: 03 subsystem: ui tags: [rams, maplibre, basemap, openfreemap, design-tokens, cartography, d-08, d-09, d-10]
Dependency graph
requires:
- phase: 01-design-tokens-rams-foundation plan 01 (wave 1) provides: "theme/src/tokens.css (55 verbatim --color-* tokens incl. soft-beige #D9D2C6, chassis-subtle #CCC4B5, matte-black #1A1A1A, pure-white, surface-container-low) — the D-09 derivation source; npm workspace + theme/package.json scripts"
- phase: 01-design-tokens-rams-foundation plan 02 (wave 2) provides: "Playwright chromium-1248 installed (render-proof tooling unblocked); dist build pipeline (npm run build → dist/rams.css with @theme static vars on :root)" provides:
- "Rams cartography basemap: theme/basemap/liberty-rams.json — OpenFreeMap liberty fork (110 layers), every cartographic color derived in-script from tokens.css via desaturate(tokenHex, satMultiplier, lightnessPerRole) with saturation clamped to [0.06, 0.12]; accent guard fails the build on #EB8008/#ED3F1C/#736B1E leaks"
- "Reproducible build: theme/basemap/build.mjs +
npm run basemap --workspace theme(fetch → derive → transform → guard → write, with derivation provenance embedded in style.metadata rams-cartography)" - "Browser proof: theme/basemap/index.html — MapLibre GL v4 at KLCC (101.6958, 3.1513, z13) with the signal-orange route placeholder +
npm run basemap:preview --workspace theme→ http://localhost:8123/basemap/" - "Real-browser render evidence: 8/8 pixel-level checks (grayscale grounding + accent-on-top confirmed numerically) — roadmap success criterion 3 satisfied" affects: [03-mini-app-shell + phase 5 map views (load liberty-rams.json as the MapLibre style), phase 5 route/guidance rendering (consumes the placeholder route slot + reserved signal-orange accent), plan 04 gallery (basemap proof page as gallery input)]
Tech tracking
tech-stack: added: [] (no new deps — build.mjs is node-builtins-only ESM; MapLibre GL v4 via CDN per plan, product copy wired in Phase 3) patterns: - "Token-derived cartography: hex→HSL, saturation multiplied then clamped to the calibrated [0.06, 0.12] band, lightness adjusted per role — hue genes from tokens keep the whole palette one warm-neutral instrument family" - "Wholesale role assignment onto paint color props (zoom-interpolated color ramps flatten to role constants; width/opacity ramps preserved — hierarchy by weight only)" - "Build-time font probing with recorded substitution (metadata.rams-font-substitutions) — honest fallback provenance instead of runtime 404 noise" - "Dual accent guard: token names AND values excluded from derivation, output scanned for hex + rgb() triple forms"
key-files: created: - theme/basemap/build.mjs - theme/basemap/liberty-rams.json - theme/basemap/index.html modified: - theme/package.json (basemap + basemap:preview scripts)
key-decisions:
- "Role palette (all in-script from tokens): ground = channel-mix(soft-beige, chassis-subtle) sat×0.4 (#CDCAC4); groundSubtle = chassis-subtle sat×0.45 (#C6C2BB); water = matte-black L0.71 (#B9B1B1 mid-gray); roadFillMinor = pure-white L0.96 (#F5F4F4); roadFillMajor = surface-container-low sat×0.12 L0.92 (#ECEBE9 — slightly darker than minor per Interfaces); one uniform machined casing = matte-black L0.34 (#5C5151) — hierarchy by weight, not by tone"
- "Labels: matte-black rgba at reduced opacity (major 0.82 / minor 0.6), halo = ground-tone rgba 0.9; text-halo-blur zeroed (Rams rejects blur)"
- "building-3d layer REMOVED — fill-extrusion is faux 3D, rejected by Rams Physical Planarity; removal recorded in metadata.rams-removed-layers. natural_earth raster relief desaturated via raster-saturation -1"
- "D-10 font fallback executed as planned: Space Grotesk Medium / Geist Regular / Space Grotesk Regular all probed 404 on the OpenFreeMap glyphs endpoint → Noto Sans Bold (major — closest hosted stack to the headline-weight role, liberty precedent for capitals/countries) / Noto Sans Regular (minor); substitutions recorded in metadata.rams-font-substitutions"
- "Major labels = place-city and above (country/state/capital/city); liberty has no subtown layer — interpretation noted in build.mjs"
- "Route placeholder color read at runtime from the token sheet (getComputedStyle(--color-signal-orange)) with literal fallback only if the sheet failed — keeps D-09 spirit in the proof page; route + 1px matte-black dashed centerline mirror the mockup route ribbon, added LAST (accent above grayscale, never inside it)"
patterns-established:
- "Basemap style is a BUILD ARTIFACT committed for review (regenerated deterministically by npm run basemap); derivation provenance machine-readable in style.metadata"
- "The accent guard pattern: guards live in the generator (fail the build), not just in review greps"
requirements-completed: [DSGN-01]
Metrics
duration: 15min completed: 2026-10-22
Phase 01 Plan 03: Rams Cartography Basemap Summary
OpenFreeMap liberty fork into Rams-constant cartography — every land/water/road/building color derived in-script from the token package (D-09) via a desaturate helper with a [0.06–0.12] saturation band, labels muted matte-black with recorded Space Grotesk/Geist→Noto font substitutions (D-10), zero accent leaks (D-08) — proven rendering in a real browser at KLCC with 8/8 pixel-level checks: 100% warm-gray map area, 296 sampled signal-orange route pixels, zero saturated non-red hues
Performance
- Duration: ~15 min (started 2026-10-21T23:55:27Z, completed 2026-10-22T00:10:10Z)
- Tasks: 2/2
- Files: 3 created, 2 modified (theme/package.json touched by both tasks)
Accomplishments
theme/basemap/build.mjs(node-builtins ESM): fetches the liberty style, parsestheme/src/tokens.css(plan-verbatim regex), computes the 12-role cartography palette in-script (D-09), transforms 107 of 110 layers, probes the glyphs endpoint for the Rams font stacks, guards against accent leaks (hex + rgb forms), writesliberty-rams.jsonwith full derivation provenance instyle.metadatatheme/basemap/liberty-rams.json(110 layers): grayscale/muted land + water (ground #CDCAC4, parcels #C6C2BB, water #B9B1B1), desaturated roads (minor cores #F5F4F4, major cores #ECEBE9 slightly darker, machined casings #5C5151 — hierarchy by weight only), muted labels (matte-black rgba 0.82/0.6, ground-tone halos, zero halo blur), faux-3D building extrusions removed, raster relief desaturated —signal-orange/functional-red/olive-greenabsent from every layer (build guard + verify command + manual sweep all clean)theme/basemap/index.html: MapLibre GL v4 proof page at the KLCC anchor (101.6958, 3.1513, zoom 13), page chrome token-only via../dist/rams.css(matte-black header band rgb(26,26,26) confirmed), signal-orange route placeholder (6px + dashed centerline, square caps) read from the token package at runtime, layers added LAST, README comment marking the vector as consumed by Phase 5- Real-browser render proof (scratch tooling; plan 04 owns the committed capture script): 8/8 checks pass — zero MapLibre style-validation errors, zero failed requests / non-200 responses (tiles via timestamped TileJSON path, glyphs, sprite, unpkg all 200), 296 sampled orange route pixels (signal-orange vector renders), 0 saturated non-red-hue pixels (no green/blue cartography colors anywhere — grayscale grounding proven pixel-level), 100.0% warm-gray map area, header chrome exactly matte-black
- OpenFreeMap glyphs endpoint survey: only
Noto Sans Regular/Bold/Italichosted — Space Grotesk/Geist 404 (probed at build time as the plan anticipated); substitutions recorded inmetadata["rams-font-substitutions"]
task Commits
Each task was committed atomically:
- task 1: basemap build script deriving style from tokens —
891caa1(feat) - task 2: browser proof page (dummy route + rendered basemap) —
2332779(feat)
Plan metadata: (see final commit below — docs: complete plan)
Files Created/Modified
theme/basemap/build.mjs— the generator: token parse (55--color-*), role palette viadesaturate()(pure hex→HSL helpers), layer classification (roads/tunnels/bridges by body suffix; casings/rails/fills; symbols by label rank), font probing + substitution recording, accent guard, derivation report to stdouttheme/basemap/liberty-rams.json— the generated basemap (valid MapLibre style: version 8, glyphs + sources kept on OpenFreeMap hosted endpoints, 110 layers,layers[0].paint= derived ground #CDCAC4; ~106KB pretty-printed for review)theme/basemap/index.html— proof page: token-driven chrome, KLCC-centered map, placeholder route (Phase 5 consumes),map.on('error')console hook for validation surfacingtheme/package.json—"basemap": "node basemap/build.mjs","basemap:preview": "python3 -m http.server 8123"
Verification Evidence
- Task 1:
npm run basemap --workspace theme→ 0 exit, derivation report printed; plan verify command →OK layers=110(exit 0);grep -c "tokens.css" build.mjs= 4 (≥1); non-role color props in layers: none; original liberty colors remaining: none; accent hexes in layers: none (guard +/EB8008|ED3F1C|736B1E/iverify + rgb-triple sweep) - Task 2: verify command (file + maplibre + liberty-rams.json + KL coords greps) → PASS; render proof 8/8 (details above); fixed sample points: header rgb(26,26,26), map top-right rgb(106,106,104), map bottom-left rgb(184,176,174), map center rgb(152,145,141) — all warm-gray family
- Plan
<verification>: all four bullets satisfied (build 0-exit, no accent hex in paint, basemap renders via the preview server, derivation provenance grep ≥1)
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] Rail-layer classification regex missed all 12 rail layers
- Found during: task 1 (first build run — rails listed under "untouched" in the derivation report)
- Issue:
/_((major_rail|transit_rail))(_hatching)?$/demanded an extra leading underscore, but thetunnel_/road_/bridge_prefix strip had already consumed it — rails would have kept liberty's colors - Fix: anchor without the leading underscore:
/^(major_rail|transit_rail)(_hatching)?$/— 12 rail layers now carry the derived #827373 - Files modified: theme/basemap/build.mjs (fixed before the task-1 commit)
- Verification: re-run report: 107 transformed / 3 untouched (icon-pattern-only layers) / 1 removed
- Committed in:
891caa1
2. [Rule 1 - Bug] line-join: "square" is invalid in MapLibre GL — route layers dropped by style validation
- Found during: task 2 render proof (proof check "style/load errors" caught the two validation errors; the READY log showed 110 layers instead of 112)
- Issue: MapLibre's line-join allows only
bevel|round|miter(only line-CAP supportssquare); with the invalid join both route layers failedaddLayer, leaving the route unrendered (the 21 "orange" pixels were the footer swatch) - Fix:
line-join: 'miter'— sharp orthogonal joins preserve the mockup's square-joint intent;line-cap: 'square'kept (valid) - Files modified: theme/basemap/index.html
- Verification: re-run proof: zero style errors, READY log
layers=112, 296 sampled orange route pixels - Committed in:
2332779
3. [Rule 1 - Bug] Plan's basemap:preview script doubly broken
- Found during: task 2
- Issue: plan-verbatim
"cd theme/basemap && python3 -m http.server 8123"fails twice: (a)npm run --workspace themeexecutes with cwd = theme/, socd theme/basemaptargets nonexistent theme/theme/basemap; (b) even served from basemap/ alone, the docroot would strand../dist/rams.cssand../fonts/(404s — token chrome and self-hosted fonts dead) - Fix:
"basemap:preview": "python3 -m http.server 8123"in theme/package.json (workspace cwd = theme/ serves one docroot with basemap/, dist/, fonts/); open http://localhost:8123/basemap/ — documented in the index.html header comment - Files modified: theme/package.json
- Verification: all proof dependencies returned 200 over the preview server (page, liberty-rams.json, dist/rams.css, woff2 fonts)
- Committed in:
2332779
Total deviations: 3 auto-fixed (all Rule 1 bugs — one found by reading the build report, two caught by the real-browser render proof) Impact on plan: No scope changes. The scratch proof script itself needed 3 calibration fixes (readyLog sampled too early; orange threshold assumed a longer route than the ~380px corridor; the saturated-pixel counter initially flagged the route's own antialiased orange→ink blends) — scratch-only, not committed, documented here for honesty.
Issues Encountered
- This model cannot read PNG images — the plan's "human eyes verify" visual check was executed as the stronger pixel-level render proof (D-13 spirit: evidence, not eyeball claim), with the screenshot retained at /tmp/opencode/basemap-proof.png for plan 04's gallery/capture flow and human review
- OpenFreeMap
sourcesuse a TileJSONurl(.../planet→ timestamped.../20261018_080001_pt/{z}/{x}/{y}.pbf); the untimestamped wildcard path returns empty 200s — MapLibre resolves the TileJSON itself, so no action needed (noted: direct tile probes must use the TileJSON'stilestemplate) - Out-of-scope discoveries logged to
.planning/phases/01-design-tokens-rams-foundation/deferred-items.md: colored POI icons from the hosted ofm sprite (z15+ layers, outside the paint-derivation list), 3 upstream libertynullwarnings (verified present with the original style too), sprite-pattern layers (wetland/road_area patterns)
Known Stubs
- Placeholder route geometry —
theme/basemap/index.html: the Jalan Ampang → KLCC polyline is a hand-placed preview vector, intentional per plan ("labeled in a README comment as consumed by Phase 5"); Phase 5 replaces it with Valhalla route geometry + guidance state. Its color is already token-derived; only the coordinates are placeholders. - MapLibre GL from unpkg CDN — intentional per plan ("CDN unpkg maplibre-gl@^4"); Phase 3 wires the product copy into the Mini App build.
Threat Flags
None — the only network surface is build.mjs fetching the liberty style JSON from the fixed HTTPS URL already named in the plan's own trust boundary (basemap build → OpenFreeMap API); no user input, no new endpoints, no auth/file access.
User Setup Required
None — build + preview are local scripts; the render proof ran fully automated.
Next Phase Readiness
- Phase 3 (Mini App shell) / Phase 5 (guidance): load
theme/basemap/liberty-rams.jsondirectly as the MapLibre style; the reserved signal-orange accent + route layer slot (added last, above the grayscale) is the documented hand-off point; if Rams label fonts are wanted in-cartography, a Worker-served glyphs route for the self-hosted Space Grotesk/Geist is the recorded path (seemetadata["rams-font-substitutions"]reason field) - Plan 04 (gallery/audit): chromium-1248 render path proven against network tiles; the basemap proof page + screenshot are available as gallery inputs; the D-13 audit's sanctioned literals for the basemap view: the two route-layer literals in index.html are token-read-at-runtime, not hardcoded
- Re-run after token changes:
npm run basemap --workspace themeregenerates deterministically (upstream liberty style changes are the only external variable) - STATE.md/ROADMAP.md updates deferred to the orchestrator (wave-parallel execution)
Self-Check: PASSED
All 4 created/modified files exist (theme/basemap/build.mjs, theme/basemap/liberty-rams.json, theme/basemap/index.html, theme/package.json); both task commit hashes verified in git log (891caa1, 2332779); must-have artifact markers verified (build.mjs → "tokens.css" ×4, liberty-rams.json → "glyphs" present + version 8, index.html → "maplibre" + "liberty-rams.json" + KL coords 3.1513/101.6958); plan verify commands re-run → all PASS.
Phase: 01-design-tokens-rams-foundation Completed: 2026-10-22