The hike's "SAC grades" card becomes "Trail details": one switch, then a segmented choice between SAC, Surface and Way type, three things kept apart and drawn along the route on the planner map one at a time. Every facet reads from the same answer: the ways under the line are matched on foot once, their OpenStreetMap values fetched once through the trail-grades read MAP-03 already uses, and each stretch is placed against the tour's own line once. Switching facets asks nothing new. The server now hands on a way's surface and highway tags as OpenStreetMap wrote them, only trimmed and bounded. The planner groups them (asphalt or concrete, paving stones, compacted, gravel, natural ground, grass, rock, wood or metal, unpaved without a kind, other; path, footway, steps, farm or forest track, cycle path, minor road, road, via ferrata, other), and each row's tooltip names the values recorded, such as surface=gravel, surface=pebblestone. The router's own surface reading is never used: it is normalised and a default where nothing is recorded. What is not known stays unknown and says why, in every facet: no value recorded, a value no group takes or two at once (unclear, never the nearest group), no way clearly under the tour's line, or a way that could not be asked about. Imported tracks and hand-drawn stretches keep their geometry; a matched way only counts where it keeps within 25 m of it. A summary badge gives the known share and the most common class; the SAC facet keeps its highest step and routing limit badges. Nothing about the tour, its places or its days changes, and no routing preference is set.
@trek/shared
Single source of truth for TREK's API contracts, expressed as Zod schemas and consumed by both the server (request validation + inferred DTO types) and the client (typed requests/responses).
This package is part of the incremental NestJS + React 19 migration (see the "Brownfield Rewrite" board). It is intentionally dormant until modules start importing it — adding it changes nothing for users.
Rules
- One folder per domain:
src/<domain>/<domain>.schema.ts(+.spec.ts). - Domain-agnostic building blocks live in
src/common/. - A route is only considered migrated once its contract lives here.
- Schemas are the source of truth; server DTOs and client types are inferred from them
(
z.infer<typeof schema>), never hand-duplicated.
Consumption (dev)
Both apps resolve @trek/shared to this package's TypeScript source:
- Server (
tsx): viapathsinserver/tsconfig.json. - Client (
vite): viaresolve.aliasinclient/vite.config.ts(+pathsfor the type-checker).
Production packaging (Docker / workspace wiring) is introduced in card F2, when the server first depends on this package at runtime. Until then prod builds are untouched.
Not yet here
The canonical error envelope is finalised in card F5 (it must match TREK's current Express error responses byte-for-byte), so it is deliberately not invented in F1.