Surface + waytype proportion bars on route detail, from the BRouter waytags the Planner already computes (and currently discards on save). Design records the data-source decision: persist a compact distance-weighted breakdown for Planner-created routes (accurate, no external call); Overpass map-matching for imports/uploads is parked as a future option. Validates with `openspec validate --strict`. Specs only; no code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2.2 KiB
2.2 KiB
Why
"How much of this is gravel?" is a top-three question for anyone choosing a
route — Komoot's surface/waytype breakdown bars are one of its most-used
features, and @trails-cool/map-core already ships the surface/highway colour
palettes. We also already compute accurate per-segment surface and highway
tags from BRouter while planning (route-merge → EnrichedRoute.surfaces[]),
then throw them away on save. This change captures that data and shows it.
What Changes
- On route detail, show surface and waytype proportion bars (e.g.
"62% asphalt · 28% gravel · 10% path"), coloured with the
map-coreSURFACE_COLORS/HIGHWAY_COLORSpalettes, with per-segment km/percent. - Persist the breakdown for Planner-created routes: compute a distance-weighted surface + waytype distribution from the BRouter waytags at save time, send it in the planner→journal handoff, and store it on the route.
- Degrade gracefully: routes without breakdown data (older routes, or activities / manually-imported GPX that never had waytags) simply don't show the bars.
Capabilities
New Capabilities
route-surface-breakdown: the surface/waytype proportion bars on route detail, how the distribution is derived from BRouter waytags, persisted at save, and rendered.
Modified Capabilities
Impact
- Schema: a nullable
surface_breakdownjsonb column onjournal.routes(additive —pnpm db:push, no data migration). - Planner:
api.save-to-journalcomputes the distance-weighted breakdown from the sessionEnrichedRoute(surfaces/highways + coordinates) and adds it to the POST body. - Journal: the route callback (
api.routes.$id.callback) accepts + stores it; the route detail loader exposes it; aSurfaceBreakdowncomponent renders the bars (reusingmap-corepalettes +stats.tsdistance formatting). - No new external dependency — BRouter already returns the tags; no Overpass call. Imported activities/uploads are out of scope for v1 (see design).