forked from c-fu/arah2
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)
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
---
|
||||
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*
|
||||
Reference in New Issue
Block a user