Files
arah2/.planning/phases/01-design-tokens-rams-foundation/01-03-SUMMARY.md
araharah-executor 1544b92b31 docs(01-design-tokens-rams-foundation-03): complete Rams cartography basemap plan
- 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)
2026-10-22 08:11:33 +08:00

162 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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, parses `theme/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), writes `liberty-rams.json` with full derivation provenance in `style.metadata`
- `theme/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-green` absent 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/Italic` hosted — Space Grotesk/Geist 404 (probed at build time as the plan anticipated); substitutions recorded in `metadata["rams-font-substitutions"]`
## task Commits
Each task was committed atomically:
1. **task 1: basemap build script deriving style from tokens** — `891caa1` (feat)
2. **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 via `desaturate()` (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 stdout
- `theme/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 surfacing
- `theme/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/i` verify + 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 the `tunnel_`/`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 supports `square`); with the invalid join both route layers failed `addLayer`, 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 theme` executes with cwd = theme/, so `cd theme/basemap` targets nonexistent theme/theme/basemap; (b) even served from basemap/ alone, the docroot would strand `../dist/rams.css` and `../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 `sources` use a TileJSON `url` (`.../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's `tiles` template)
- 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 liberty `null` warnings (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.json` directly 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 (see `metadata["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 theme` regenerates 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*