Primary finding
/api/activity returns raw Date objects, breaking the documented JSON contract consumed by ActivityView
highapi-contracthigh
- apps/web/app/api/activity/route.ts:14-24
- apps/web/app/activity/page.tsx:24-31
- apps/web/app/activity/ActivityView.tsx:14-33
The server component in page.tsx explicitly converts Date fields (lastSweepAt, lastReceiptAt, events[].ts) to ISO strings before handing them to ActivityView. The /api/activity route does NOT do that conversion — it passes the raw result of loadFleetActivity() (which the page treats as having Date objects) directly into NextResponse.json. NextResponse.json will serialize Date via toJSON() to an ISO string, so the wire format is actually compatible — but only by accident of JSON.stringify default behavior. More importantly, if loadFleetActivity ever returns nested non-Date fields or the types diverge, there is no normalization point. The two endpoints producing the same JSON contract should funnel through a single serializer (e.g., the same toJson helper used in page.tsx). Today this is a latent maintainability/contract bug: there are two divergent serializers for the same wire shape, and adding a new field to FleetActivity is liable to be ISO-stringified in one path but not the other. Severity high because the page.tsx path is explicit about field-by-field conversion and the route.ts path silently relies on Date.prototype.toJSON.
Recommendation
Extract the page.tsx mapping into a `toFleetActivityJson(data)` helper colocated with the FleetActivityJson type and call it from both page.tsx and route.ts. This removes the implicit reliance on Date.prototype.toJSON and guarantees both endpoints emit the same wire shape.