A range could only be typed as one number, and that is the number fewest travellers actually know. A tank size is on the filler flap, a consumption is in the manual, a battery capacity and a kWh per hundred are on the data sheet. All of them divide into the same figure, so the dialog now takes either and does the division itself. What the parts say wins over what was typed, because it is the more specific answer, and the range row goes read-only when they do so there is never a second answer to the same question. Of ABRP's list only battery wear is here. Plug type, reference speed, relative speed and drive style shape a consumption PREDICTION; this addon does not predict one, it divides a capacity by a consumption the traveller states, so those fields would add typing without moving a kilometre. Wear is different in kind: it shrinks the capacity itself, and on an older car by enough to decide whether the last leg of a day arrives. Storage stays metric throughout, the same rule the range itself follows. An imperial traveller types gallons, miles per gallon and kWh per 100 miles and gets them back unchanged; mpg is the inverse of litres per hundred rather than a scale factor, which a straight multiplication would have stored as a car four times as thirsty. The gauge under the picker is the argument that these two ways of stating a range are the same thing, so it answers while the figures are still being typed rather than after the field is left. It draws the tank the day it left the factory: the filled part is what a stop actually puts back in, the hatched tail is what age has taken off, and both are things the bare number cannot say. Also here, from the same round: a fill-up that stops short of full. Nobody charges to 100 % on the road, and counting every stop as a full tank overstated everything after it by that last fifth. Stops that refuel now carry a badge saying how far they fill.
@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.