diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md new file mode 100644 index 0000000..8a37663 --- /dev/null +++ b/.planning/PROJECT.md @@ -0,0 +1,126 @@ +# araharah + +## What This Is + +A privacy-respecting GPS navigation app delivered through Telegram: a waze-like +route navigator, but Telegram-first instead of an app store install, built on +OpenStreetMap data, and designed to Dieter Rams' principle of "less, but better". +Users search a destination (e.g. "KLCC") with the bot, tap into a Telegram Mini +App (webview hosted on Cloudflare Pages), and receive live route guidance while +driving. First users are in the Kuala Lumpur metro area. + +Reference design docs: `DESIGN.md` (design philosophy), `IDEAS.md` (vision + +stack), `stitch_minimalist_route_navigator/` (4 UI mockup screens), and +`.planning/codebase/` (codebase map; current state is design-only, no app code). + +## Core Value + +A user finds a local destination via Telegram and gets there with reliable +turn-by-turn guidance — tracked by nothing beyond what they explicitly opted in +to. + +## Requirements + +### Validated + + + +(None yet — no app code exists; project is greenfield-brownfield hybrid with +design assets only) + +### Active + + + +- [ ] User can open the araharah Telegram bot and start the app (/start, menu) +- [ ] User can search a destination by query and get a matching local address/POI +- [ ] User can open the Telegram Mini App (Cloudflare Pages webview) from the bot +- [ ] Mini App obtains the user's current location +- [ ] User sees a chosen route (or short list of routes) to the destination +- [ ] User receives live guidance while driving (in-app live map when + foregrounded; chat turn-cards / map snapshots when backgrounded — hybrid) +- [ ] The experience visually matches the Telemetry Rams design system + ("design fidelity" — flagged as the top risk) + +### Out of Scope + + + +- Incident reports (/accident, /traffic, /hazard + broadcasts) — navigation + first; reports ship after the core works +- Gamification, points, leaderboards — depends on reports; deferred with them +- Regional clusters via Telegram groups — unvalidated social hypothesis +- Voice-to-text reporting, offline mode, anonymized public API — future ideas +- Push notifications infra beyond Telegram chat (Resend email etc.) — Telegram + itself is the channel +- Payment/monetization nothing planned + +## Context + +- Telegram bot is the primary UX: command menu, destination search, Mini App + launch. Mini App should be a proper Telegram WebApp (Telegram WebApp JS API). +- OSM ecosystem: OpenStreetMap data (geocoding must understand KL landmarks like + "KLCC"), routing (Valhalla — chosen over OSRM for free-first + native toll-free + costing), tiles (OpenFreeMap, replacing Maptiler), hosting on Cloudflare + Workers with static assets. +- Privacy stance chosen: persistent storage only for what users opt in to + reports (later), opt-in anonymized telemetry for traffic weighting. Nothing + stored without explicit consent. +- Region: KL metro first — local geocoding quality is a v1 correctness factor. +- Design system "Telemetry Rams": colors (Signal Orange #EB8008, Functional Red + #ED3F1C, Olive Green #736B1E, Soft Beige #D9D2C6, Matte Black #1A1A1A), + Helvetica Neue/Inter, 8px grid, line icons, Rams motto. Mockups live in + `stitch_minimalist_route_navigator/` (HTML + Tailwind CDN, single-file). +- Known quality risk: turning single-file mockups into a real frontend without + losing the Rams look; user explicitly flagged design fidelity as the most + likely sink for v1. + +## Constraints + +- **Platform**: Telegram bot + Telegram Mini App (WebApp API) — the webview flow + is the product; no independent mobile app in v1 +- **Hosting**: Cloudflare Workers with static assets (research finding: Cloudflare now directs all new projects to Workers, not Pages) — one wrangler project serves Mini App statics + `/telegram/webhook` + `/api/*` +- **Free-first infra**: every service must be open-source or genuinely free-tier (verified stack: grammY, MapLibre GL, OpenFreeMap, Valhalla, Photon — all $0); no paid APIs. Only infrastructure cost candidate is a small VPS for Valhalla + Photon; Oracle Cloud Always Free is the zero-cost option with a reliability asterisk +- **Privacy**: no location history / profiles stored without explicit opt-in; + opt-in only telemetry +- **Region**: KL metro tuning for v1 (geocoding, POI search) +- **Design**: must match Telemetry Rams design system; Rams values are + non-negotiable per DESIGN.md +- **Ops appetite (implied)**: IDEAS.md lists RabbitMQ, Prometheus/Grafana, Neon, + Resend — treat as future/aspirational; v1 should prefer the minimal set of + services that the core value actually needs (final choice deferred to research) + +## Key Decisions + + + +| Decision | Rationale | Outcome | +|----------|-----------|---------| +| Routing engine = **Valhalla** (replaces OSRM) | User wants free as much as possible; both are free OSS, but Valhalla natively supports toll-free routing (`use_tolls` costing), elevation, and localization — unlocks the KL commuter table-stakes feature | — Pending | +| Free-first infra mandate | User priority: paid APIs are out; stack is all-OSS/free-tier; VPS (optionally Oracle Cloud Always Free) hosts Valhalla + Photon | — Pending | +| Navigation-only v1 (no incident reports) | Prove the nav core before social mechanics; user walkthrough was pure navigation | — Pending | +| Privacy = opt-in telemetry only | "Privacy-respecting" is the brand promise; storage follows explicit consent | — Pending | +| KL metro first | Local landmark search quality depends on regional tuning | — Pending | +| Telegram Mini App (not plain web link) | Needs user position, back button, tight Telegram context | — Pending | +| Hybrid guidance: live map in-app + chat turn-cards when backgrounded | Battery + Telegram-context UX; matches walkthrough and mockups | — Pending | +| Design fidelity guarded as top risk | Stated as most likely to sink v1; deserves early design-system phase | — Pending | + +## Evolution + +This document evolves at phase transitions and milestone boundaries. + +**After each phase transition** (via `/gsd-transition`): +1. Requirements invalidated? → Move to Out of Scope with reason +2. Requirements validated? → Move to Validated with phase reference +3. New requirements emerged? → Add to Active +4. Decisions to log? → Add to Key Decisions +5. "What This Is" still accurate? → Update if drifted + +**After each milestone** (via `/gsd-complete-milestone`): +1. Full review of all sections +2. Core Value check — still the right priority? +3. Audit Out of Scope — reasons still valid? +4. Update Context with current state + +--- +*Last updated: 2026-10-23 — Phase 3 complete (Mini App Shell + Geolocation): Mini App live on the same Worker ([assets] binding), Rams map + location chain verified on Android/Desktop/browser after 6 in-gate debug rounds (telegram-web-app.js integration; HMAC signature-field erratum)* diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md new file mode 100644 index 0000000..7917f87 --- /dev/null +++ b/.planning/REQUIREMENTS.md @@ -0,0 +1,132 @@ +# Requirements: araharah + +**Defined:** 2026-10-22 +**Core Value:** A user finds a local destination via Telegram and gets there with reliable turn-by-turn guidance — tracked by nothing beyond what they explicitly opted in to. + +## v1 Requirements + +Requirements for initial release. Each maps to roadmap phases. + +### Onboarding & Entry + +- [x] **ONBD-01**: User can open the araharah bot and start with /start — a concise explanation plus a always-available command menu (/help, /privacy) +- [ ] **ONBD-02**: User can open the Mini App from a search result with their destination preloaded (no re-entry in the webview) +- [ ] **ONBD-03**: User can add araharah to their phone home screen and launch search/navigation from there (addToHomeScreen + startapp deep-link) + +### Destination Search + +- [ ] **SRCH-01**: User can type a destination query (e.g. "KLCC") and receive a shortlist containing the right KL-metro landmark/POI +- [ ] **SRCH-02**: User gets useful results despite abbreviations and typos (Jln↔Jalan, Tmn↔Taman, missing spaces, common Malay address shorthand) +- [ ] **SRCH-03**: For a chosen destination, a native venue card is sent in chat, confirming where we're going before navigation opens + +### Current Location + +- [x] **GEO-01**: The Mini App can obtain the user's current location via Telegram LocationManager, with permission persisting across sessions +- [x] **GEO-02**: When in-app location fails (denied permissions or Desktop Telegram without geolocation), the chat "send location" keyboard fallback obtains the origin +- [x] **GEO-03**: The user's location is never written to persistent storage — only ephemeral TTL server state exists during the active trip + +### Routing & Route Choice + +- [ ] **RTNG-01**: User sees a calculated route from current location to destination drawn on a Rams-styled map (grayscale grounding, accent route) +- [ ] **RTNG-02**: User can choose among up to 3 alternative routes presented as quiet Rams cards (time, distance, one-line difference) + +### Turn-by-Turn Guidance + +- [ ] **GUID-01**: While driving, the user sees a prominent next-maneuver banner (distance + arrow + street name) and a collapsible full maneuver list +- [ ] **GUID-02**: After a missed turn, the app detects it and auto-recalculates from the current position without asking or blaming +- [ ] **GUID-03**: The user sees ETA and remaining distance progress with each location fix (honest, self-paced ETA — no fake traffic confidence) +- [ ] **GUID-04**: Guidance surfaces haptic cues at maneuver points (eyes-free, voice-free v1) +- [ ] **GUID-05**: When the user minimizes the app mid-drive, the bot pushes chat turn-cards (map snapshot + next maneuver + ETA) for the remainder of the trip — best-effort projections +- [ ] **GUID-06**: Re-opening the Mini App mid-trip resumes live guidance seamlessly at the user's progressed position + +### Platform Hygiene (Mini App) + +- [ ] **PX-01**: The Telegram BackButton walks app states (search → routes → guidance → back to search) and never dead-ends +- [x] **PX-02**: The app follows Telegram light/dark themes for chrome while the map keeps the Rams cartography constant +- [ ] **PX-03**: During guidance, safe areas are respected, vertical-swipe-close is disabled, and closing shows the "abandon route?" confirmation +- [ ] **PX-04**: Guidance supports fullscreen and the screen remains usable in landscape (driving posture) + +### Design Fidelity & Privacy Promise + +- [x] **DSGN-01**: All screens render from a centralized Telemetry Rams token set (palette, type, spacing, radius-0) — no off-token values; map basemap cartography follows the same tokens +- [x] **DSGN-02**: Each UI phase produces a mockup-vs-implementation gallery comparison reviewed for fidelity before proceeding +- [x] **PRIV-01**: The privacy promise is visible in-product: /privacy command plus in-app trip start/end messaging ("trip ended, data deleted") +- [x] **PRIV-02**: The backend holds no persistent per-user location history or profile — only TTL-expiring ephemeral state (privacy by construction, verifiable by architecture audit) + +## v2 Requirements + +Deferred to future release. Tracked but not in current roadmap. + +### Routing Extras + +- **RGNG-01**: Toll vs toll-free route toggle (Valhalla use_tolls costing — engine already supports it) +- **RGNG-02**: Speed limit display where OSM maxspeed data exists ("show nothing rather than wrong values") +- **GUID-07**: Voice guidance — spoken turns via Web Speech API, mute-by-default, sendVoice fallback (gated on iOS webview TTS spike) +- **TRIP-01**: Live trip share via sendLocation(live_period) + editMessageLiveLocation with ETA — user-initiated, expires + +### Reports & Social (v2 cluster) + +- **REP-01**: User can report incidents (/accident, /traffic, /hazard) with location sharing +- **REP-02**: Nearby users receive concise incident broadcasts +- **OPTN-01**: Opt-in anonymized speed pings for aggregate traffic-weighted ETAs (seed of the v2 report crowd data) +- **GAME-01**: Minimalist gamification (points for validated reports, Rams-badge achievements) + +## Out of Scope + +Explicitly excluded. Documented to prevent scope creep. + +| Feature | Reason | +|---------|--------| +| In-app ads / sponsored POI pins | Breaks the brand promise on a driving surface; privacy-violating profiling | +| Engagement push notifications | Drive-away behavior for a privacy brand; only transactional messages ship | +| Persistent accounts / contact requests | Telegram identity is the only identity; never requestContact() | +| Offline maps product | Contrary to the Telegram-first premise; in-session prefetch only (v1.x candidate) | +| "AI assistant" flavored onboarding | Deterministic commands are the model; Rams simplicity | +| Group/location echo broadcasts | Crosses privacy boundaries of non-consenting users | +| Ops-stack services (RabbitMQ, PostGrafana monitoring, Resend) | Stripped for v1 — no producer/user for them before reports exist | +| Regional clusters / carpool groups | Unvalidated social hypothesis per PROJECT.md | + +## Traceability + +Which phases cover which requirements. Updated during roadmap creation. + +| Requirement | Phase | Status | +|-------------|-------|--------| +| DSGN-01 | Phase 1 | Complete | +| DSGN-02 | Phase 1 | Complete | +| ONBD-01 | Phase 2 | Complete | +| PRIV-01 | Phase 2 | Complete | +| PRIV-02 | Phase 2 | Complete | +| GEO-01 | Phase 3 | Complete | +| GEO-02 | Phase 3 | Complete | +| GEO-03 | Phase 3 | Complete | +| PX-02 | Phase 3 | Complete | +| SRCH-01 | Phase 4 | Pending | +| SRCH-02 | Phase 4 | Pending | +| SRCH-03 | Phase 4 | Pending | +| ONBD-02 | Phase 4 | Pending | +| ONBD-03 | Phase 4 | Pending | +| RTNG-01 | Phase 5 | Pending | +| RTNG-02 | Phase 5 | Pending | +| GUID-01 | Phase 6 | Pending | +| GUID-02 | Phase 6 | Pending | +| GUID-03 | Phase 6 | Pending | +| GUID-04 | Phase 6 | Pending | +| GUID-05 | Phase 6 | Pending | +| GUID-06 | Phase 6 | Pending | +| PX-01 | Phase 6 | Pending | +| PX-03 | Phase 6 | Pending | +| PX-04 | Phase 7 | Pending | + +**Coverage:** +- v1 requirements: 22 total +- Mapped to phases: 22 ✓ +- Unmapped: 0 + +Staging notes (each requirement maps to exactly one owning phase): +- PRIV-01: /privacy command delivered in Phase 2; the in-app "trip ended, data deleted" messaging completes at the trip boundary in Phase 6 (success criterion there). +- PX-01 (BackButton full state walk) and PX-03 (safe areas, swipe-close, abandon confirmation): shell grounding built in Phase 3; observable behaviors owned by Phase 6 where the full state graph exists. + +--- +*Requirements defined: 2026-10-22* +*Last updated: 2026-10-22 — roadmap created, traceability filled (22/22 mapped)* diff --git a/.planning/STATE.md b/.planning/STATE.md new file mode 100644 index 0000000..15af468 --- /dev/null +++ b/.planning/STATE.md @@ -0,0 +1,84 @@ +--- +gsd_state_version: 1.0 +milestone: v1.0 +milestone_name: milestone +status: planning +stopped_at: Phase 4 context gathered +last_updated: "2026-10-23T14:33:06.676Z" +last_activity: 2026-10-23 +progress: + total_phases: 7 + completed_phases: 3 + total_plans: 11 + completed_plans: 11 + percent: 100 +--- + +# Project State + +## Project Reference + +See: .planning/PROJECT.md (updated 2026-10-22) + +**Core value:** A user finds a local destination via Telegram and gets there with reliable turn-by-turn guidance — tracked by nothing beyond what they explicitly opted in to. +**Current focus:** Phase 03 — mini-app-shell-geolocation + +## Current Position + +Phase: 4 +Plan: Not started +Status: Ready to plan +Last activity: 2026-10-23 + +Progress: [░░░░░░░░░░] 0% + +## Performance Metrics + +**Velocity:** + +- Total plans completed: 11 +- Average duration: — +- Total execution time: — + +**By Phase:** + +| Phase | Plans | Total | Avg/Plan | +|-------|-------|-------|----------| +| — | — | — | — | +| 01 | 4 | - | - | +| 02 | 3 | - | - | +| 3 | 4 | - | - | + +## Accumulated Context + +### Decisions + +Logged in PROJECT.md Key Decisions table. Affecting current work: + +- Routing = Valhalla (toll-free stays v2); self-hosted Photon for lookup; OpenFreeMap tiles + MapLibre GL JS v6 +- grammY on one Cloudflare Worker (static assets + webhook + /api/*) — Workers, not Pages +- KV-only storage, TTL-only (chat_sessions minutes / guidance_sessions ~2h); guidance_sessions envelope designed in Phase 5 (upstream pre-work for GUID-05/06) + +### Pending Todos + +None yet. + +### Blockers/Concerns + +- Design-source conflict (root `DESIGN.md` typography vs `telemetry_rams/DESIGN.md` + mockups) — Phase 1 resolves; mockup markup is layout reference only, design doc tokens are binding (scrub rounded/blur utilities) +- VPS provisioning starts in Phase 4 (Photon bake-off); free-first mandate: Valhalla + Photon on one VPS, Oracle Cloud Always Free candidate +- Research-flagged phases needing `/gsd-research-phase`: 3 (geolocation client matrix), 4 (Photon KL bake-off + gazetteer), 6 (backgrounded turn-card framing + snapshots) +- Project skill `.agents/skills/design-taste-frontend/` applies to UI phases (existing-design-system special case; Rams tokens binding) + +## Deferred Items + +| Category | Item | Status | Deferred At | +|----------|------|--------|-------------| +| v1.x features | Voice guidance, live trip share, toll toggle, speed limits | Deferred to v1.x milestone (after user validation) | Roadmap init 2026-10-22 | +| v2 | Incident reports, gamification, traffic-weighted ETA | Out of scope for v1 | Requirements defined 2026-10-22 | + +## Session Continuity + +Last session: 2026-10-23T14:33:06.657Z +Stopped at: Phase 4 context gathered +Resume file: .planning/phases/04-destination-search-kl-gazetteer/04-CONTEXT.md diff --git a/.planning/config.json b/.planning/config.json new file mode 100644 index 0000000..ad2f915 --- /dev/null +++ b/.planning/config.json @@ -0,0 +1,69 @@ +{ + "model_profile": "inherit", + "commit_docs": false, + "parallelization": true, + "search_gitignored": false, + "brave_search": false, + "firecrawl": false, + "exa_search": false, + "git": { + "branching_strategy": "none", + "phase_branch_template": "gsd/phase-{phase}-{slug}", + "milestone_branch_template": "gsd/{milestone}-{slug}", + "quick_branch_template": null + }, + "workflow": { + "research": true, + "plan_check": true, + "verifier": true, + "nyquist_validation": true, + "auto_advance": false, + "node_repair": true, + "node_repair_budget": 2, + "ui_phase": true, + "ui_safety_gate": true, + "text_mode": false, + "research_before_questions": false, + "discuss_mode": "discuss", + "skip_discuss": false, + "code_review": true, + "code_review_depth": "standard" + }, + "hooks": { + "context_warnings": true + }, + "project_code": null, + "phase_naming": "sequential", + "agent_skills": {}, + "features": {}, + "mode": "yolo", + "granularity": "coarse", + "model_overrides": { + "gsd-planner": "opencode-go/minimax-m3", + "gsd-roadmapper": "opencode-go/minimax-m3", + "gsd-phase-researcher": "opencode-go/minimax-m3", + "gsd-project-researcher": "opencode-go/minimax-m3", + "gsd-research-synthesizer": "opencode-go/minimax-m3", + "gsd-codebase-mapper": "opencode-go/minimax-m3", + "gsd-ui-researcher": "opencode-go/minimax-m3", + "gsd-doc-writer": "opencode-go/minimax-m3", + "gsd-executor": "opencode-go/glm-5.2", + "gsd-debugger": "opencode-go/glm-5.2", + "gsd-verifier": "opencode-go/glm-5.2", + "gsd-plan-checker": "opencode-go/glm-5.2", + "gsd-integration-checker": "opencode-go/glm-5.2", + "gsd-nyquist-auditor": "opencode-go/glm-5.2", + "gsd-pattern-mapper": "opencode-go/glm-5.2", + "gsd-ui-checker": "opencode-go/glm-5.2", + "gsd-ui-auditor": "opencode-go/glm-5.2", + "gsd-doc-verifier": "opencode-go/glm-5.2" + }, + "branching_strategy": "none", + "quick_branch_template": null, + "intel": { + "enabled": false + }, + "graphify": { + "enabled": false + } +} diff --git a/.planning/phases/01-design-tokens-rams-foundation/01-CONTEXT.md b/.planning/phases/01-design-tokens-rams-foundation/01-CONTEXT.md new file mode 100644 index 0000000..d418908 --- /dev/null +++ b/.planning/phases/01-design-tokens-rams-foundation/01-CONTEXT.md @@ -0,0 +1,117 @@ +# Phase 1: Design Tokens + Rams Foundation - Context + +**Gathered:** 2026-10-22 +**Status:** Ready for planning + + +## Phase Boundary + +Distill the Telemetry Rams design system into a single centralized, importable token package — including a Rams-styled MapLibre basemap derived from OpenFreeMap `liberty` — proven with a rendered component sheet and a user-approved mockup-vs-implementation side-by-side gallery, before any product screen is built. Ends the inline-Tailwind-duplication and design-source conflict. Bot, Mini App shell, search, and routing are separate phases; this phase builds no product screens. + + + + +## Implementation Decisions + +### Token package format +- **D-01:** Tailwind v4 CSS-first package: tokens declared as CSS custom properties plus the `@theme` directive. No v3-style JS config. CSS variables double as the consumption path for MapLibre basemap styles and later phases. +- **D-02:** Token naming mirrors the mockup vocabulary verbatim (~40-token Material-style scale: `signal-orange`, `matte-black`, `space-md`, `display-lg`, ...) so mockup-derived markup maps 1:1 and fidelity comparison stays direct. +- **D-03:** The token package ships together with a rendered component sheet that consumes it — the sheet itself proves token completeness (DSGN-01's "every rendered component derives from tokens" is demonstrated, not asserted). +- **D-04:** Pre-slot the dual Telegram light/dark theme boundary now (themeable shell surfaces as token slots), while map cartography stays Rams-constant. Phase 3 (PX-02) then maps two background values onto slots instead of restructuring tokens. + +### Source-of-truth resolution +- **D-05:** Root `DESIGN.md` is superseded for specifics: keep it as Rams philosophy prose with a header banner pointing to the token package. `telemetry_rams/DESIGN.md` content (the mockup-matching system) flows into the package; the losing typography claim (Helvetica/Inter + 5 colors) is explicitly marked superseded. +- **D-06:** The four mockup `code.html` files stay untouched, read-only generated reference artifacts (fine as-is per CONCERNS.md). Their inline `tailwind.config` blocks are superseded but NOT edited. Fidelity comparison targets rendered output, not markup. +- **D-07:** The token package owns all numeric values (hex, px, rgba, font sizes). Design docs describe principles and link to the package; docs never restate hex codes or dimensions — the drift that caused the conflict cannot recur. + +### Basemap styling depth +- **D-08:** Full Rams pass on the OpenFreeMap `liberty` fork in this phase: grayscale/muted land + water grounding, desaturated roads, muted label styling; `signal-orange` accent reserved exclusively for the route line (rendered in Phase 5). Orange remains navigation-only semantics. +- **D-09:** Basemap colors are derived from the token package (values read/synced from the package), not hand-tuned in the style JSON — honoring DSGN-01's "map cartography follows the same tokens". Exact derivation mechanism (generation script vs synced values file) is planner/researcher's choice. +- **D-10:** Map labels adopt Rams typography (Space Grotesk / Geist), coherent with the HUD, using the same font assets. + +### Component sheet + gallery +- **D-11:** Component sheet scope is mockup-derived: everything the 4 mockup screens actually show — buttons, cards, chips/badges, the maneuver banner, inputs, and a typography specimen. No invention of unseen components. +- **D-12:** The approval gallery is a static side-by-side HTML page: rendered implementation components against the four mockup screens' renders, token-by-token. This page is the DSGN-02 mechanism template — every later UI phase extends the same pattern. +- **D-13:** DSGN-01's audit sweep is automated: a script that scans rendered CSS/classes and flags any color/spacing/typography/radius value not in the token set. "Zero off-token values" is evidence, not eyeball claim. +- **D-14:** Gallery renders are produced with Playwright real-browser screenshots (script committed for reuse by later UI phases under the design-taste-frontend skill's real-browser inspection rule). + +### Build pipeline +- **D-15:** Minimal `package.json` + Tailwind v4 CLI and npm scripts; design gallery/sheet + basemap style packaged in one design folder consumed by later phases. No app framework scaffold — the Mini App shell shape is decided in Phase 3. +- **D-16:** Self-host Geist + Space Grotesk woff2 in the repo as part of the token package; Google Fonts CDN links are removed going forward (may remain in mockups). +- **D-17:** Local npm scripts only (build / gallery / audit) in Phase 1; CI workflows come with Phase 2's deploy pipeline. Keep tooling minimal. + +### OpenCode's Discretion +- Exact derivation/refresh mechanism basemap ← tokens (script vs synced file) +- Gallery page file layout within the design folder +- Audit script implementation details and threshold handling +- Icon strategy details within the sheet (Material Symbols vs inline SVG usage observed in mockups) + + + + +## Specific Ideas + +- "Map cartography stays Rams-constant" — the map background must look identical under Telegram light and dark themes; only shell surfaces re-theme. +- Mockup screens are the spec — the gallery side-by-side against their rendered output is the fidelity bar. +- Scope creep avoided by design: motion/animation polish, incident components (reporter screen's modal flow) render only as static sheet entries where the mockups show them; full interactive flows belong to later phases. + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Design system (binding) +- `stitch_minimalist_route_navigator/telemetry_rams/DESIGN.md` — canonical token vocabulary, component rules, zero-radius / no-blur constraints +- `DESIGN.md` — Rams philosophy (kept for principles; its typography/palette specifics are superseded per D-05) +- `stitch_minimalist_route_navigator/active_route_guidance/code.html` — markup/layout reference (read-only) for maneuver banner and incident telemetry band +- `stitch_minimalist_route_navigator/navigate_map_overview/code.html` — layout reference for map screen + layer controls +- `stitch_minimalist_route_navigator/community_incident_reporter/code.html` — layout reference for incident card components +- `stitch_minimalist_route_navigator/telegram_live_traffic_feed/code.html` — layout reference for chip/badge components + +### Planning inputs +- `.planning/ROADMAP.md` §Phase 1 — goal, success criteria, notes (repo hygiene, skill usage, no research phase) +- `.planning/REQUIREMENTS.md` — DSGN-01, DSGN-02 (this phase's requirements) +- `.planning/codebase/CONCERNS.md` — Zone.Identifier debt, design-source conflict, mockup fragility (Phase 1 resolves these) +- `.planning/codebase/STACK.md` — token duplication, Play-CDN caveat, design constraints +- `.agents/skills/design-taste-frontend/SKILL.md` — applies in existing-brand special case: keep Rams palette/type/dashboard house rules; real-browser inspection for the gallery artifact + + + + +## Existing Code Insights + +### Reusable Assets +- Inline `tailwind.config` block in each `code.html` — byte-identical token source to extract into the v4 package (matches `telemetry_rams/DESIGN.md:2-148`) +- Mockup `/code.html` + `/screen.png` pairs — ready-made inputs for the side-by-side gallery +- SVG patterns in mockups (route vectors, `stroke-linecap="square"`, 2px strokes) — reuse for sheet icon components + +### Established Patterns +- kebab-case token/class names (`bg-matte-black`, `text-signal-orange`) — carry into the v4 package +- 8px baseline grid, 4px/8px multiples for every dimension +- Uppercase labels with expanded tracking; Material Symbols Outlined iconography +- Color semantics: orange = navigation/primary, red = hazards/errors, green = police-only (per mockups); keep strict + +### Integration Points +- The token package is consumed by: the rendered sheet + gallery (Phase 1), the Mini App shell (Phase 3), every UI phase's Tailwind build +- The basemap style JSON becomes the MapLibre style loaded by Phase 3/5/6's map views +- Playwright capture script becomes the DSGN-02 gallery evidence toolchain for all UI phases + + + + +## Deferred Ideas + +- Interactive mockup/implementation compare tool — a static page suffices for v1; revisit if gallery review becomes painful +- Full incident-report interaction flows — Phase 1 renders reporter-screen components statically only; the privacy boundaries of incident reporting ship post-v1 (v2 cluster REP-01/02, per REQUIREMENTS.md) +- CI workflow for gallery + audit — deferred to Phase 2 deploy pipeline (D-17) + +None — discussion stayed within phase scope. + + + +--- + +*Phase: 01-design-tokens-rams-foundation* +*Context gathered: 2026-10-22* diff --git a/.planning/phases/01-design-tokens-rams-foundation/01-DISCUSSION-LOG.md b/.planning/phases/01-design-tokens-rams-foundation/01-DISCUSSION-LOG.md new file mode 100644 index 0000000..ef8de27 --- /dev/null +++ b/.planning/phases/01-design-tokens-rams-foundation/01-DISCUSSION-LOG.md @@ -0,0 +1,57 @@ +# Phase 1: Design Tokens + Rams Foundation - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions captured in CONTEXT.md — this log preserves the analysis. + +**Date:** 2026-10-22 +**Phase:** 01-design-tokens-rams-foundation +**Mode:** default (interactive) + +## Areas Discussed + +### Token package format +| Question | Options presented | Selection | +|---|---|---| +| Package form | Tailwind v4 @theme / v3 JS config / plain CSS vars + adapter | Tailwind v4 CSS-first (@theme) | +| Naming | Mirror mockup names / semantic rename / you decide | Mirror mockup names | +| Bare tokens vs tokens + rendered sheet | Tokens + sheet / tokens only / you decide | Tokens + rendered sheet | +| Telegram light/dark pre-planning | Pre-slot dual theme / defer to Phase 3 / you decide | Pre-slot dual theme | + +### Source-of-truth resolution +| Question | Options presented | Selection | +|---|---|---| +| Root DESIGN.md conflict | Supersede + keep philosophy / merge + rewrite / archive | Supersede, keep philosophy | +| Fate of mockups + inline Tailwind blocks | Stay read-only / migrate mockups / you decide | Mockups stay read-only | +| Owner of numeric values | Package owns / docs own / you decide | Package owns values | + +### Basemap styling depth +| Question | Options presented | Selection | +|---|---|---| +| Rams-ification depth | Full Rams pass / grayscale only / iterate in gallery | Full Rams pass | +| Color sourcing | Derived from tokens / fork + hand-tune / you decide | Derived from tokens | +| Label typography | Rams fonts / stock / compare in gallery | Rams fonts in labels | + +### Component sheet + gallery scope +| Question | Options presented | Selection | +|---|---|---| +| Sheet components | Mockup-derived set / core primitives / you decide | Mockup-derived set | +| Gallery form | Static side-by-side / interactive tool / you decide | Static side-by-side page | +| DSGN-01 audit | Automated / manual / you decide | Automated token audit | +| Render production | Playwright / manual captures / you decide | Playwright screenshots | + +### Build pipeline choice +| Question | Options presented | Selection | +|---|---|---| +| Phase 1 tooling | Minimal npm + v4 CLI / full framework / CSS only | Minimal npm + v4 CLI | +| Fonts | Self-host woff2 / keep Google Fonts CDN / you decide | Self-host fonts | +| CI scope | Local scripts / CI day one / you decide | Local scripts first | + +## Corrections Made + +No corrections — user accepted recommended options throughout. + +## Deferred Ideas + +- Interactive compare tool (revisit if gallery review painful) +- Incident-report interaction flows (v2: REP-01/02) +- CI workflow (Phase 2) diff --git a/.planning/phases/01-design-tokens-rams-foundation/01-REVIEW-FIX.md b/.planning/phases/01-design-tokens-rams-foundation/01-REVIEW-FIX.md new file mode 100644 index 0000000..3a1297a --- /dev/null +++ b/.planning/phases/01-design-tokens-rams-foundation/01-REVIEW-FIX.md @@ -0,0 +1,149 @@ +--- +phase: 01-design-tokens-rams-foundation +fixed_at: 2026-10-22T02:05:00Z +review_path: .planning/phases/01-design-tokens-rams-foundation/01-REVIEW.md +iteration: 1 +findings_in_scope: 6 +fixed: 6 +skipped: 0 +status: all_fixed +--- + +# Phase 1: Code Review Fix Report + +**Fixed at:** 2026-10-22T02:05:00Z +**Source review:** `.planning/phases/01-design-tokens-rams-foundation/01-REVIEW.md` +**Iteration:** 1 + +**Summary:** +- Findings in scope: 6 (BLOCKER CR-01 + WR-01..WR-05; INFO findings out of scope per orchestrator) +- Fixed: 6 +- Skipped: 0 + +**Note on provenance:** a prior fixer run applied all six fixes as atomic commits (61d15c2…43b7d99) +but terminated before writing this report. This pass *independently re-verified* each committed fix +(diffs audited against the review's prescribed mechanism, all four verification gates re-run +empirically, see Verification section) — no fixes were re-applied; master was already clean. + +## Fixed Issues + +### CR-01: Dead `--radius-DEFAULT` token — bare `rounded` compiles to `0.25rem` (BLOCKER) + +**Commit:** `61d15c2` +**Files modified:** `theme/src/tokens.css`, `theme/scripts/audit.mjs`, `theme/scripts/audit.test.mjs` +**Applied fix:** Replaced the inert v3-style `--radius-DEFAULT: 0px` with the deprecated bare +`--radius: 0px` — the key Tailwind v4.3.3 actually drives the bare `rounded` utility from +(`node_modules/tailwindcss/theme.css` "Deprecated" block). Block comment corrected (bare +`rounded` is now var-driven; `rounded-none`/`rounded-full` are literal built-ins; the DEFAULT +sub-key must not reappear). Audit radius drift guard extended to `/--radius(?:-[A-Za-z0-9-]*)?…/` +so a future non-zero bare key trips exit 2. Regression tests T9 added: bare-key presence, +inert-DEFAULT absence, non-zero bare key → exit 2, and a controlled build asserting +`.rounded` compiles to `var(--radius)` with `--radius: 0px` emitted by `@theme static`. + +### WR-01: Prose candidate leakage compiles Rams-forbidden `.shadow`/`.blur` into the shipped stylesheet + +**Commit:** `689bc9f` +**Files modified:** `theme/src/theme.css`, `theme/src/tokens.css`, `theme/scripts/audit.test.mjs` +**Applied fix:** (1) `@source` narrowed from the whole mockup tree to the four mockup markup +files, plus `@source not "../scripts"` / `@source not "../basemap"` so toolchain prose can never +mint utility classes. (2) Defense in depth at the key: the deprecated bare `--shadow`, +`--shadow-inner`, `--drop-shadow`, `--blur` namespaces are set to `initial` in `@theme static`, +so bare shadow/blur utilities can never compile from any candidate source (scale keys remain — +mockup-worn `shadow-*` classes still compile and stay under adjudication 1). T5 regression +checks added: no `.shadow{` / `.blur{` in `dist/rams.css`, no `border-radius:.25rem` anywhere +in dist. + +### WR-02: `familiesFromShorthand` logic bug — off-token families in `font:` shorthands pass unflagged + +**Commit:** `5e95741` +**Files modified:** `theme/scripts/audit.mjs`, `theme/scripts/audit.test.mjs` +**Applied fix:** Two-layers, contract-true: (1) any literal (non-`var()`) `font:` shorthand is +flagged outright with the convention message (`font: var(--type-*)`), naming the extracted +families; `var()` shorthands pass. (2) `familiesFromShorthand` itself repaired — iteratively +strips the leading style/variant/weight keyword run (any CSS-grammar order, incl. oblique +angles), then the size[/line-height] component (incl. `calc()`), per comma group, so the family +is genuinely extracted for the message instead of being silently dropped by the old +single-strip + numeric-filter bug. T10 regression tests: literal shorthand → exit 1, Arial +extracted, sanctioned-family shorthand still flagged, `var()` shorthand passes. + +### WR-03: Color-evasion gaps — named keywords, modern color functions, `text-shadow` all pass + +**Commit:** `ee48e76` +**Files modified:** `theme/scripts/audit.mjs`, `theme/scripts/audit.test.mjs` +**Applied fix:** (1) Full CSS Color keyword list (`NAMED_COLORS`, ~148 entries) flagged on the +color-bearing property set (`COLORISH_PROPS` incl. shorthands, fill/stroke, borders, +text-decoration, accent-color); `currentcolor`/`transparent` and css-wide keywords stay +sanctioned. (2) Modern color functions — `oklch()/oklab()/lab()/lch()/hwb()/color()/color-mix()` +— flagged on any property unless the value is `var()`-bearing (Tailwind's own `color-mix` +emissions are var-driven and pass). (3) `text-shadow` added to `shadowViolations`: any +non-none value forbidden (Rams Shapes). T11 regression tests: `color: red`, `oklch(...)`, +`color(display-p3 ...)`, non-hex `text-shadow` all exit 1 and are listed; sanctioned keywords +pass. Audit header (§5) updated to document the extended surface. + +### WR-04: D-13 contract shortfall — spacing and typography numerics not audited + +**Commit:** `5e254cb` +**Files modified:** `theme/scripts/audit.mjs`, `theme/gallery.html` +**Applied fix:** Implemented the missing checks rather than amending the claim. (1) Spacing: +`padding`/`margin`/`gap` families (logical variants included) — literals must equal a +`--spacing-*` token value, be zero, or `auto`; dimensional geometry (width/height, offsets, +border widths, the 18×18 checkbox) is spec-per-components-header and out of scope +(adjudication 6). (2) Typography: `font-size`/`font-weight`/`line-height`/`letter-spacing` +validated against the `--type-*` scale, parsed from the compound token grammar (`weight +size/line-height family…` + `-tracking` companions); `normal`/`bold` keyword equivalents +sanctioned; `@font-face` blocks exempt from numeric scale as asset metadata (D-16) but still +color/family-checked (adjudication 7). `npm run audit` output now reports +"6 spacing values / 10 type sizes / 9 trackings". Gallery D-13 evidence wording updated to +"colors, spacing, typography, radius, shadows, blur". T12 regression tests: off-scale +numerics (`padding: 13px`, `font-size: 17px`, weight 650, tracking) → exit 1 and listed; +token-value literals (1rem, 0.25rem, 14px, 700, 20px, -0.04em), zero/auto, and font-face +metadata pass. + +### WR-05: Static server path guard uses prefix match without separator + +**Commit:** `43b7d99` +**Files modified:** `theme/scripts/screenshot.mjs` +**Applied fix:** `sep` imported from `node:path`; guard changed to +`if (target !== THEME && !target.startsWith(THEME + sep))` — the docroot prefix must end at a +path separator, so a decoded `..%2FthemeX` (which joins to a `themeX`-sibling path passing the +old bare `startsWith(THEME)`) now 403s. + +## Skipped Issues + +None — all six in-scope findings fixed and verified. + +## Verification (all four gates, empirically re-run in this pass) + +1. **Build radius gate** — `npm run build --workspace theme`; `grep -o 'border-radius:[^;}]*' theme/dist/rams.css | sort -u` → + `border-radius:0`, `border-radius:var(--radius)`, `border-radius:3.40282e38px` + (the sanctioned `rounded-full` ≥1e30px literal clamp). **No `.25rem` / non-token radius forms.** + `.rounded` compiles to `border-radius:var(--radius)` with `--radius:0px` emitted by `@theme static`. ✅ +2. **Audit gate** — `npm run audit` (repo root) → + `AUDIT CLEAN — 354 declarations checked … — 0 off-token values. token sheet: 55 colors / 9 radius + tokens (all zero) / 7 sanctioned font families / 6 spacing values / 10 type sizes / 9 trackings`. ✅ +3. **Test suite** — `node theme/scripts/audit.test.mjs` → **38 passed, 0 failed** + (was 16/16 pre-fix; now includes T9 CR-01, T10 WR-02, T11 WR-03, T12 WR-04, extended T5 WR-01). ✅ +4. **Screenshot server guard (live probe)** — server started under the real capture run + (`node theme/scripts/screenshot.mjs`, listener located by PID), probed with `curl --path-as-is`: + - `/..%2FthemeX/secret` → **403 forbidden** (the exact WR-05 exploit shape: a `themeX` sibling + that the old bare prefix check passed) + - `/..%2FthemeX/gallery.html` → **403** + - `/..%2Fstitch_minimalist_route_navigator/…` → **403** + - `/src/sheet.html` → 200 (correct content — no over-blocking) + - `/gallery.html` → 200 — ✅ + (Method note: a first probe port grab had hit Chromium's own devtools listener — PID-matched + listener selection fixed that; the guard result above is from the real capture server.) + Bonus: full capture run completed `CAPTURE OK — 6/6` non-blank captures. + +Also verified byte-for-byte: deep diff audit of all six fix commits against the REVIEW.md +prescribed fix shapes (`git show 61d15c2 689bc9f 5e95741 ee48e76 5e254cb 43b7d99`) — mechanisms +are real (key-level token disarmament, regex/grammar repair, parser extension, guard change), +not cosmetic suppressions; per CR-01 guidance the elimination of the banned utility is the +intended goal, so post-fix compiled output genuinely lacks `.rounded`/`.shadow`/`.blur` +non-zero/off-token forms. + +--- + +_Fixed: 2026-10-22T02:05:00Z_ +_Fixer: OpenCode (gsd-code-fixer)_ +_Iteration: 1_ diff --git a/.planning/phases/01-design-tokens-rams-foundation/01-REVIEW.md b/.planning/phases/01-design-tokens-rams-foundation/01-REVIEW.md new file mode 100644 index 0000000..b522b8b --- /dev/null +++ b/.planning/phases/01-design-tokens-rams-foundation/01-REVIEW.md @@ -0,0 +1,312 @@ +--- +phase: 01-design-tokens-rams-foundation +reviewed: 2026-10-22T01:08:55Z +depth: standard +files_reviewed: 13 +files_reviewed_list: + - package.json + - theme/package.json + - theme/src/tokens.css + - theme/src/theme.css + - theme/src/components.css + - theme/src/sheet.html + - theme/basemap/build.mjs + - theme/basemap/liberty-rams.json + - theme/basemap/index.html + - theme/scripts/audit.mjs + - theme/scripts/audit.test.mjs + - theme/scripts/screenshot.mjs + - theme/gallery.html +findings: + critical: 1 + warning: 5 + info: 7 + total: 13 +status: issues_found +--- + +# Phase 1: Code Review Report + +**Reviewed:** 2026-10-22T01:08:55Z +**Depth:** standard (with empirical verification: controlled Tailwind builds, live audit runs, test-suite execution, machine verification of the 6,089-line generated `liberty-rams.json`) +**Files Reviewed:** 13 +**Status:** issues_found + +## Summary + +The phase delivers a genuinely well-engineered token package: `npm run audit` exits 0 on the +real build (354 declarations checked), the TDD suite passes 16/16, all 7 self-hosted woff2 +files exist and are wired correctly (no CDN fonts), the compiled `dist/rams.css` emits the +`--color-*`/`--spacing-*`/`--type-*` tokens plus the D-04 shell slots with the dark-scheme +media query, and `liberty-rams.json` was machine-verified clean (no accent hex/rgb forms, no +`line-gradient`, no `icon-color`, no non-zero halo blur, only opacity ramps preserved, +font substitutions honestly recorded in `metadata`). + +However, the adversarial pass found one blocker in the foundation deliverable itself, plus a +cluster of audit-gate holes proven by end-to-end evasion tests: + +1. **BLOCKER (CR-01):** `--radius-DEFAULT: 0px` is a dead token in Tailwind v4.3.3 — the bare + `rounded` utility is driven by the deprecated `--radius` key, not `--radius-DEFAULT`. The + shipped `theme/dist/rams.css` therefore contains `.rounded{border-radius:.25rem}` — a + non-zero radius value owned by **no token**, violating the binding zero-roundness + constraint, D-07 ("the token package owns all numeric values"), and the tokens.css comment + that asserts "every token-driven rounded-* utility compiles to 0". +2. **Vocabulary leakage (WR-01):** `.shadow{…}` and `.blur{…}` (Rams-forbidden constructs, + with off-token values `#0000001a` / `blur(8px)` baked in) compile into the shipped + stylesheet from **prose**, not from any mockup class attribute — so audit adjudication 1's + rationale ("utilities the mockups wear") does not even cover them. +3. **Audit-gate holes (WR-02/03/04):** proven evasions — `font: 700 14px/18px Arial` passes + unflagged (logic bug in `familiesFromShorthand`), as do `color: red`, `oklch(...)`, + `color(display-p3 …)`, non-hex `text-shadow`, and all spacing/type-scale numerics + (`padding: 13px`, `font-size: 17px`), contradicting the tool's own coverage claims and + D-13's stated contract. + +None of these are caught by the current green gates, which is exactly why they need fixing +before later phases consume the package. + +## Critical Issues + +### CR-01: Dead `--radius-DEFAULT` token — bare `rounded` compiles to `0.25rem`, violating the binding zero-roundness constraint (BLOCKER) + +**File:** `theme/src/tokens.css:98-109` (token), `theme/src/theme.css:13-15` (build entry); evidence in compiled `theme/dist/rams.css` (minified, line 2): `.rounded{border-radius:.25rem}` +**Issue:** The radius block sets `--radius-DEFAULT: 0px` intending to zero the bare `rounded` +utility (v3-style `DEFAULT` key), but Tailwind v4.3.3 does **not** read `--radius-DEFAULT` for +that utility — the bare `rounded` is driven by the deprecated `--radius` key (see +`node_modules/tailwindcss/theme.css:508`, `@theme default inline reference { --radius: 0.25rem; }`). +Empirically verified with controlled builds against the repo's own Tailwind 4.3.3: + +- `@theme { --radius-DEFAULT: 0px; }` → `.rounded { border-radius: 0.25rem; }` (token ignored) +- `@theme { --radius: 0px; }` → `.rounded { border-radius: var(--radius); }` (correct override) + +Consequences: +- The shipped `dist/rams.css` contains `.rounded{border-radius:.25rem}` — a numeric value + owned by no token, in the phase deliverable whose thesis is D-07 ("the token package owns + all numeric values") and the "zero off-token values" evidence claim. +- The tokens.css comment (lines 98-101) is factually wrong: it carves out only + `rounded-none`/`rounded-full` as literal built-ins and asserts every other token-driven + `rounded-*` compiles to 0. Bare `rounded` compiles to `0.25rem` with the current token set. +- Any later phase wearing `rounded` (a Tailwind-default habit; the class *is* in the compiled + vocabulary) silently gets 4px corners. The audit does flag it once worn (verified with a + fixture page: `theme/dist/rams.css:2: .rounded { border-radius: .25rem } — non-zero + border-radius`), but the package is supposed to prevent, not be caught by, its own gate. +- The audit's radius drift-guard (`theme/scripts/audit.mjs:173`, + `/--radius-[A-Za-z0-9-]*\s*:\s*(…)/`) only matches `--radius-*` with a dash, so a future + bare `--radius` override escapes the drift guard entirely; `radiusAllowed()` + (`audit.mjs:409`) also passes any `var()` unconditionally. + +**Fix:** +```css +/* theme/src/tokens.css — @theme static radius block */ +@theme static { + --radius: 0px; /* v4's deprecated bare key — THE key that drives the bare `rounded` utility */ + --radius-xs: 0px; /* …rest unchanged… */ +} +``` +Delete `--radius-DEFAULT: 0px;` (inert in v4) or keep it only with a comment marking it +inert; correct the block comment to state that `rounded`/`rounded-none`/`rounded-full` are +literal built-ins and that `--radius` must be overridden explicitly. Rebuild and confirm +`.rounded{border-radius:0px}` in `dist/rams.css`. Extend the audit drift-guard: + +```js +/* audit.mjs — cover the deprecated bare key too */ +for (const m of text.matchAll(/--radius(?:-[A-Za-z0-9-]*)?\s*:\s*([^;}]+?)\s*;/g)) { … } +``` + +## Warnings + +### WR-01: Prose candidate leakage compiles Rams-forbidden `.shadow`/`.blur` utilities into the shipped stylesheet + +**File:** `theme/src/theme.css:24` (`@source "../../stitch_minimalist_route_navigator"`), plus the CLI's implicit auto-scan of `theme/` +**Issue:** `@source` points at the whole mockup tree — including `telemetry_rams/DESIGN.md` +prose — and the CLI auto-scan ingests the toolchain's own text (e.g. `scripts/audit.mjs:36` +prose "shadow/backdrop-blur/bg-black"). Tailwind extracts class candidates from natural +language. Result in `dist/rams.css` (all verified present, none worn by any mockup class +attribute or implementation page): + +- `.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a)…box-shadow:…}` — bare `shadow` is worn by **no** mockup (mockups wear `shadow-md`/`shadow-sm`/`shadow-[…]` only) +- `.blur{--tw-blur:blur(8px);…}` — bare `blur` likewise unworn anywhere +- `.rounded` (see CR-01) entered the vocabulary the same way (e.g. `telemetry_rams/DESIGN.md:155` "devoid of rounded ornament") + +These carry off-token values (`#0000001a`, `8px`) into the artifact, and audit adjudication 1 +("utilities the mockups wear but the design doc rejects") does not cover them — they are not +mockup-worn at all. The audit skips them only because no page harvests those classes, so the +"zero off-token values" evidence holds by accident of non-usage. +**Fix:** Scope `@source` to the four mockup markup files and exclude tooling prose from the +auto-scan: +```css +@source "../../stitch_minimalist_route_navigator/active_route_guidance/code.html"; +@source "../../stitch_minimalist_route_navigator/navigate_map_overview/code.html"; +@source "../../stitch_minimalist_route_navigator/community_incident_reporter/code.html"; +@source "../../stitch_minimalist_route_navigator/telegram_live_traffic_feed/code.html"; +@source not "../scripts"; +@source not "../basemap"; +``` +After the fix, `.rounded`/`.shadow`/`.blur` should disappear from `dist/rams.css` (verify by +grep); only mockup-worn classes remain in the compiled vocabulary. + +### WR-02: `familiesFromShorthand` logic bug — off-token families in `font:` shorthands pass the audit unflagged + +**File:** `theme/scripts/audit.mjs:449-455` (helper), consumed at `audit.mjs:439-441` +**Issue:** The strip regex removes only the **first** shorthand token: +`'700 14px/18px Arial'` → strips `'700 '` → leaves `'14px/18px Arial'`, which the +`/^[0-9.]/` filter then drops as "numeric" — so the family list comes out empty and **no +`font:` shorthand can ever be flagged**, regardless of family. Verified end-to-end: +`.a-font-shorthand { font: 700 14px/18px Arial; }` produces zero violations (Arial is +off-token and should be flagged per the T2 contract). The test suite only covers the +`font-family:` longhand, so the hole is untested. The package's own convention is +`font: var(--type-*)` everywhere, so nothing currently trips it — but the gate's contract +(T2: "off-token font → flagged") is silently incomplete. +**Fix:** Simplest contract-true fix — flag any literal (non-var) `font:` shorthand outright, +matching the package convention: +```js +if (prop === 'font' && !/var\(/i.test(value)) { + violations.push('literal font: shorthand — use font: var(--type-*) (Rams typography tokens)'); +} else if (prop === 'font-family' && !/var\(/i.test(value)) { + violations.push(...fontViolations(value, tokens)); +} +``` +(Alternatively repair `familiesFromShorthand` to iteratively strip weight and size/line-height +components.) Add a T2-style test case for the shorthand form. + +### WR-03: Color-evasion gaps — named keywords, `oklch()`/`lab()`/`lch()`/`hwb()`/`color()`, and `text-shadow` all pass the audit + +**File:** `theme/scripts/audit.mjs:361-394` (`literalHexViolations` — hex/`rgb()`/`hsl()` only), `audit.mjs:419-429` (`shadowViolations` — `box-shadow`/`backdrop-filter`/`filter` only) +**Issue:** Verified evasions (fixture run through the real audit — none flagged): +`.b { color: red; }`, `.c { background: oklch(0.6 0.2 30); }`, `.e { fill: color(display-p3 1 0 0); }`, +and `.h2 { text-shadow: 0 1px 0 red; }` (any non-hex `text-shadow`). Modern CSS Color 4 +functions and named keywords are fully unaudited escape hatches, and `text-shadow` (a shadow +Rams forbids) is not in `shadowViolations`. For a tool whose purpose is "zero off-token +values is evidence, not an eyeball claim", these are known-open doors that later phases can +walk through accidentally (e.g. `color: white`, `oklch()` from a design-tool export). +**Fix:** Extend the literal checks: +```js +const NAMED = new Set(['red','green','blue','white','black','gray','grey','yellow','orange', + 'purple','pink','brown','silver','gold','cyan','magenta','lime','navy','teal','olive', + 'maroon','aqua','fuchsia']); +// in literalHexViolations: flag NAMED keywords on color-ish props +// (keep currentcolor/inherit/transparent sanctioned); +// flag /\b(?:oklch|oklab|lab|lch|hwb|color|color-mix)\(/ unless the value contains var(. +// in shadowViolations: add text-shadow (any non-none value). +``` + +### WR-04: D-13 contract shortfall — spacing and typography numerics are not audited at all + +**File:** `theme/scripts/audit.mjs:433-446` (`checkDeclaration` — no spacing/type-size/weight/line-height properties), claims at `audit.mjs:8-9`, `theme/gallery.html:155`, `.planning/phases/01-design-tokens-rams-foundation/01-CONTEXT.md` (D-13) +**Issue:** D-13 states the audit "flags any color/**spacing**/**typography**/radius value not +in the token set", the audit header says "any color / typography / radius / shadow / blur +value", and the gallery tells the approver "colors, typography, radius, shadows, blur **all** +derive from theme/src/tokens.css". Verified: `padding: 13px; gap: 7px;`, `font-size: 17px; +font-weight: 650; line-height: 1.7;` all pass unflagged. The typography check is font-family +only; spacing is entirely absent. The evidence claim is broader than the implementation. +**Fix:** Either implement the missing checks (spacing literals validated against the 8px-grid +scale; `font-size`/`font-weight`/`line-height` validated against the `--type-*` scale) or +amend the claim wording in `audit.mjs`'s header and `gallery.html` to "color families, font +families, radius, shadows, blur" so the recorded DSGN-01 evidence matches what actually runs. + +### WR-05: Static server path guard uses prefix match without separator — sibling `theme*` paths escape the docroot + +**File:** `theme/scripts/screenshot.mjs:68-71` +**Issue:** `join(THEME, decodeURIComponent(url.pathname))` then `target.startsWith(THEME)`. +A URL-encoded `..%2FthemeX` decodes to `../themeX`, joins to `/…/araharah/themeX`, which +**passes** the `startsWith('/…/araharah/theme')` check while being outside the docroot — the +classic incomplete-prefix-traversal idiom. Exploitability is low (bound to `127.0.0.1`, +ephemeral port, alive only during capture, no CORS headers), but the guard exists precisely +to prevent this and fails at its boundary condition. +**Fix:** +```js +import { sep } from 'node:path'; +// … +const target = join(THEME, decodeURIComponent(url.pathname)); +if (target !== THEME && !target.startsWith(THEME + sep)) { + res.writeHead(403); res.end('forbidden'); return; +} +``` + +## Info + +### IN-01: Dead `send` script + +**File:** `theme/package.json:13` +**Issue:** `"send": "mkdir -p dist/fonts && cp -r fonts/geist fonts/space-grotesk dist/fonts/"` +is referenced by nothing (not delegated from the root, not a dependency of any other script), +and its output is unused — `dist/rams.css` references `url(../fonts/…)`, which resolves from +`dist/` to the tracked `theme/fonts/` directly; nothing serves `dist/fonts/`. +**Fix:** Remove the script (or document its purpose if one is intended). + +### IN-02: `playwright` is a runtime `dependency` of a CSS package + +**File:** `theme/package.json:8-10` +**Issue:** `@araharah/theme` ships no runtime JS, yet `playwright` sits under +`dependencies` — any consumer installing the theme package for its CSS pulls the browser +toolchain as a prod dependency. +**Fix:** Move `playwright` to `devDependencies` (it is only used by `scripts/screenshot.mjs`, +a dev-time capture tool). + +### IN-03: `python3` dependency in an otherwise Node-only toolchain + +**File:** `theme/package.json:17` +**Issue:** `basemap:preview` shells out to `python3 -m http.server 8123`, introducing a +Python runtime requirement into the Node-first toolchain (AGENTS.md free-first/Node-first +stack). A tiny Node static server already exists in `screenshot.mjs` and could be extracted +and shared. +**Fix:** Extract the `serveTheme()` server (with a port argument) into a small shared script, +or accept and document the Python requirement. + +### IN-04: Root scripts miss the workspace's `test` and `basemap` entries + +**File:** `package.json:8-14` +**Issue:** The root delegates `build`/`sheet`/`gallery`/`audit`/`capture` but not `test` or +`basemap` — `npm test` at the repo root fails ("Missing script") even though the theme +workspace defines a passing 16/16 test suite, and the basemap rebuild documented in +`theme/basemap/build.mjs:31` must be invoked with the longer `--workspace` form. +**Fix:** Add `"test": "npm run test --workspace theme"` and +`"basemap": "npm run basemap --workspace theme"` to the root scripts. + +### IN-05: Font probing — no fetch timeout; partial substitutions unrecorded + +**File:** `theme/basemap/build.mjs:305-315` (`probeGlyph`), `build.mjs:321-345` (`resolveFontStacks`) +**Issue:** (a) `fetch` has no `AbortSignal.timeout`, so a stalled glyphs endpoint hangs the +basemap build indefinitely. (b) If *some* of a role's intended stacks are hosted (e.g. +`'Geist Regular'` 200 but `'Space Grotesk Regular'` 404), `served.minor` silently becomes the +partial list with no `rams-font-substitutions` record — undermining the "honest fallback +provenance" the feature exists to provide. (Current committed output hit the clean +all-404 → full-fallback path, so both are latent.) +**Fix:** Add `{ signal: AbortSignal.timeout(10_000) }` to the probes; record a substitution +entry whenever `served[kind]` differs from `FONT_INTENT[kind]`, not only on full fallback. + +### IN-06: Sheet inputs have no accessible name + +**File:** `theme/src/sheet.html:167-168` +**Issue:** Both `input-field` text inputs are labeled by placeholder only — no `