trails/openspec/changes/archive/2026-07-13-route-surface-breakdown/tasks.md
Ullrich Schäfer fc5e361a11
chore(openspec): archive route-surface-breakdown
The route-surface-breakdown change is fully implemented (18/18 tasks,
all artifacts done). Archive it via `openspec archive` (CLI 1.6.0):

- Moves the change to openspec/changes/archive/2026-07-13-route-surface-breakdown/
- Promotes its 5 delta requirements into a new capability spec at
  openspec/specs/route-surface-breakdown/spec.md (surface/waytype
  breakdown from BRouter waytags, async Overpass backfill, live SSE
  update, proportion bars, localized labels)

Purpose paragraph filled in (the CLI leaves a TBD placeholder). Both the
spec and archived change pass `openspec validate --strict`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 21:21:58 +02:00

41 lines
3.1 KiB
Markdown
Raw 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 1 (this PR): synchronous Planner path + bars. PHASE 2 (follow-up):
async Overpass backfill + SSE (sections 56, e2e in 7.3). -->
## 1. Shared derivation + shape
- [x] 1.1 `computeSurfaceBreakdown(coordinates, surfaces, highways)` in `@trails-cool/map-core``{ surface, highway }` metres; unit-tested.
- [x] 1.2 `SurfaceBreakdownSchema` (Zod) in `@trails-cool/api`.
## 2. Schema
- [x] 2.1 Nullable `surfaceBreakdown` jsonb on `journal.routes` **and** `journal.activities`; pushed to dev + e2e DBs.
## 3. Path 1 — synchronous (Planner routes)
- [x] 3.1 `SaveToJournalButton` computes the breakdown from `readComputedRoute` and sends it; `api.save-to-journal` forwards it (journal is the authoritative validator — planner has no `@trails-cool/api` dep).
- [x] 3.2 Journal route callback validates the optional field with `SurfaceBreakdownSchema` and persists via `updateRoute` (overwrite on re-save).
## 4. Render
- [x] 4.1 Route + activity detail loaders expose `surfaceBreakdown`.
- [x] 4.2 `SurfaceBreakdown` component (stacked bar per dimension, `map-core` palettes, legend category · % · km largest-first, unknown → "other", hidden when empty); mounted on `routes.$id.tsx` and `activities.$id.tsx`.
- [x] 4.3 i18n `journal.surface.*` (surface/waytype labels, common categories, "other") en + de.
## 5. Path 2 — async backfill + SSE (PHASE 2 — this PR)
- [x] 5.1 Journal-side Overpass client (`overpass-ways.server.ts`): `way[highway]` in bbox + `out tags geom`, configurable `OVERPASS_URLS` upstreams, timeout + oversized-bbox guard.
- [x] 5.2 Map-matcher (`surface-match.server.ts`, pure): nearest OSM way per segment → surface/highway; unmatched → unknown. Unit-tested.
- [x] 5.3 Typed `surface-backfill` pg-boss job: load geom → skip if breakdown exists → Overpass → match → `computeSurfaceBreakdown` → store. Idempotent, retry-safe, best-effort; registered in `server.ts`.
- [x] 5.4 Enqueued from `createActivity` (covers Komoot/Garmin imports + manual uploads) and owner-on-open in the route + activity detail loaders (covers non-Planner routes + pre-existing rows), deduped via `singletonKey`. (A dedicated sweep CLI is unneeded — on-open covers existing rows lazily.)
- [x] 5.5 Emits a `surface_breakdown` SSE event (`{ kind, id }`) to the owner; detail pages subscribe (`useSurfaceBackfillUpdates`) and `revalidate()` when it matches.
## 6. Privacy
- [x] 6.1 Documented the backfill's Overpass bbox lookup in the privacy manifest (DE + EN); `OVERPASS_URLS` can point at a self-hosted instance.
## 7. Tests & checks
- [x] 7.1 Unit: `computeSurfaceBreakdown` weighting + unknown bucket; `matchSurfaces` nearest-way + unknown bucket.
- [x] 7.2 Component (jsdom): bars/proportions, largest-first, hidden when empty/null.
- [x] 7.3 Job test (mocked Overpass/db/events): stores + emits; skips when breakdown exists or no ways. (A full live e2e is intentionally skipped — external Overpass + SSE timing are flaky; rendering is covered by the component test + browser, the job by its mocked test.)
- [x] 7.4 typecheck + lint + unit (map-core 36, journal 333) green; Phase 1 verified in the browser.