The road trip routed straight from one stop to the next, whatever stood between them: a flight booked for the afternoon became an eight-hour drive on the map, with the hotel reached by road from a town the traveller had flown out of (issue #2428). The day plan has treated such bookings as carriers for a while, and the drive now reads them the same way. A flight, train, ferry, cruise or bus with located terminals is a seam. Its two terminals are seated among the day's stops where the day plan lists the booking, as stops of their own: no number, no stay, no kind, not draggable, and an id below every assignment and every automatic night, because they stand for none. The road ends at the departure terminal and starts again at the arrival one; the leg between them is the ride, in the booking's own mode, with the timetable's minutes and no distance, and no router is ever asked for it. The departure terminal is pinned a check-in ahead of the timetable and left on the timetable's minute, so a drive that cannot make the airport in time is late the way a missed pin is. A ride that lands on a later day seats its arrival at the start of that day; with connected days the join between the two is the ride, and with daily travel times set the ride runs on the booking's clock through the night, with no day end placed on a flight. The map draws the ride as the booking's own arc, the one the day plan already draws, beside the roads. The day's distance and driving time count the roads only, the range budget spends nothing on a ride, and after one the car cannot take it starts afresh. The corridor search leaves the ride out and drops a hit under the flight path; a via cannot be filed against a terminal, and other ways of driving are offered for the road into a departure terminal only. The server plan reads the same three tables and seats the same stops, so calculate_roadtrip answers the same as the rail. Closes #2428
@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.