Choosing another road on a leg that already carried a via appended the new point behind the old one, so the day drove south to the first and north to the second: a zigzag matching neither the line drawn under the button nor the distance printed on it. The alternatives are computed for the two bare endpoints, so the write has to replace the leg, which is what the direct branch beside it already did. A day's vias were only loaded while road-trip mode was being looked at, and the three handlers that re-anchor them after a stop is added, removed or reordered are reachable from the place inspector in both modes. With the switch off they computed their correction from an empty list and wrote nothing, so a deleted stop left the detour on a leg nobody chose, for the traveller and for every collaborator. They are loaded whenever the addon is on now. Seam routing paced itself by sleeping after each request, and storing a result shortened the key its own effect depends on: React tore the effect down mid-sleep, the cleanup aborted it, and the next request went out immediately. Thirteen seams left at round-trip speed, the shared routing hosts refused the tail, and a refused seam changes no state so nothing ever retried it. The wait now happens before the request, against a clock that outlives the effect. The "avoidance not honoured" badge could never appear on the rail: the field behind it was published every round and never written. Only the second engine reports what it achieved against what was asked, and the rail plans in the browser, so it never saw the server's own copy — a motorway crossing read as a motorway-free drive. Two smaller ones. `trip.toast.loadError` has been called since the first commit and exists in no locale, so both call sites printed the key itself; it is written now in all 23. And the phone shell honoured the road-trip session flag while having no rail, no drive lines and no switch to leave it, so narrowing a desktop window took the trip overview away and listed every booked night twice.
@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.