feat(roadtrip): remember which track a day follows

The vias already made the day follow the road; nothing said which road. After a
reload the badge could only say "shaped by hand", and re-fitting after the stops
changed meant finding the track again by eye.

One row per day, written in the same transaction as the chain it describes: a
day claiming to follow a road whose vias never landed would be worse than a day
claiming nothing. Three states rather than two, which is why the field is an
object and not an id — absent leaves whatever the day followed alone, null says
it follows nothing, an object records the road it now takes. A bare nullable id
could not tell "do not touch this" apart from "forget it", and a hand-dragged
via on the same day must not wipe the label.

The foreign key is the point: delete the imported track and the label cascades
with it, so no day is left claiming to follow a line nobody can see. The vias
stay, because they are the drive.

Both write paths check that the place is a track of this trip before it becomes
a label — permission alone would let another trip's place id, or an ordinary
place, become a name that can never be drawn.

Also clears three reliability findings the scanner raised on this branch: two
default sorts given an explicit comparator, and a trailing-slash regex the
scanner is right to call backtracking, walked instead.
This commit is contained in:
Maurice
2026-09-05 00:49:59 +02:00
committed by Maurice
parent 6fca238440
commit 676c82a308
40 changed files with 356 additions and 31 deletions
+2 -1
View File
@@ -61,6 +61,7 @@ import {
type PluginActionDescriptor,
type PluginActionResult,
type PluginInstallRequest,
RoadtripDayTrack,
RoadtripVia,
RoadtripViaBatchRequest,
RoadtripViaCreateRequest,
@@ -1225,7 +1226,7 @@ export const mapsApi = {
export const roadtripApi = {
/** Every via of the trip, so all days can be routed without a request per day. */
listVias: (tripId: number | string) =>
apiClient.get(`/trips/${tripId}/roadtrip/vias`).then(r => r.data as { vias: RoadtripVia[] }),
apiClient.get(`/trips/${tripId}/roadtrip/vias`).then(r => r.data as { vias: RoadtripVia[]; tracks: RoadtripDayTrack[] }),
addVia: (tripId: number | string, dayId: number | string, body: RoadtripViaCreateRequest) =>
apiClient.post(`/trips/${tripId}/roadtrip/days/${dayId}/vias`, body).then(r => r.data as { via: RoadtripVia }),
/**