fix(roadtrip): file a via on the drive between connected days after the stop it leaves

With connected days the drive from one day's last stop to the next day's
first is drawn at the head of the arriving card, in the colour of the day
before. anchorFor only knew that stretch as a night spill, so a click on it
anchored to the arriving card's first stop: the via was stored on the leg
after that stop, and the next day drove out to the point, turned and came
back, while the stretch that was clicked kept its shape.

assembleRoadtrip now names the stop that joined drive leaves from, and
anchorFor treats it like the stop a night drive left from. Placing and
dragging both resolve to the previous day's last stop, which is the anchor
the seam request already reads its vias from.
This commit is contained in:
Maurice
2026-09-14 22:36:29 +02:00
parent 1b49995c15
commit 2ffe4d6054
5 changed files with 131 additions and 9 deletions
@@ -1691,6 +1691,49 @@ describe('useTripPlanner road trip: a card that opens with yesterday stop', () =
expect(rt.vias.add).toHaveBeenCalledWith(5, 1, 53.3, 10.7)
})
/**
* A card on a trip with connected days: the drive from where day 5 ended to where day 6
* begins is drawn at the head of card 6, in day 5's colour, with no night drive involved.
* Day 5 ends on its third stop, so the index the via has to take differs from anything
* card 6 could offer.
*/
const connectedCard = () => {
seedTrip({ days: [buildDay({ id: 5, day_number: 1 }), buildDay({ id: 6, day_number: 2 })] })
rt.corridor.day = { dayId: 6, dayNumber: 2 }
rt.routes.days = [{
dayId: 6,
dayNumber: 2,
stops: [drawn(1103, 52.52, 13.4, 6, 0), drawn(1104, 52.0, 14.5, 6, 1)],
geometry: [[53.55, 9.99], [53.0, 11.5], [52.52, 13.4], [52.3, 14.0], [52.0, 14.5]],
spills: [],
arrivingFrom: drawn(1102, 53.55, 9.99, 5, 2),
}]
}
it('FE-TP-ROAD-087: a via dropped on the drive between two connected days follows the stop that drive left from', async () => {
// The reported bug: the click was filed after the first stop of the day the drive
// arrives on, so that day ran out to the point, turned and came back, while the
// stretch the traveller meant to bend stayed as it was.
connectedCard()
const { result } = await renderRoadtrip()
await act(async () => { await result.current.addRoadtripVia(53.0, 11.5) })
expect(rt.vias.add).toHaveBeenCalledWith(5, 2, 53.0, 11.5)
// Past the first stop it is the card's own leg again.
await act(async () => { await result.current.addRoadtripVia(52.3, 14.0) })
expect(rt.vias.add).toHaveBeenLastCalledWith(6, 0, 52.3, 14.0)
})
it('FE-TP-ROAD-088: a via on the drive between connected days can be dragged along it without changing hands', async () => {
connectedCard()
const { result } = await renderRoadtrip()
await act(async () => { await result.current.moveRoadtripVia(5, 9, 53.2, 11.0) })
expect(rt.vias.move).toHaveBeenCalledWith(5, 9, 53.2, 11.0, 2)
})
it('FE-TP-ROAD-067: a drag keeps to the day the via is stored on, wherever that day stops are drawn', async () => {
// Not "the card with that id". After a night drive the stops of day 5 are drawn on
// card 6, and measuring the drag against card 5 alone leaves it with no anchor to
+10 -8
View File
@@ -1469,10 +1469,12 @@ export function useTripPlanner() {
if (best && hit.offRouteKm >= best.offRouteKm) continue
// Which stop the via follows: the last one the car passes before reaching it.
const stopsAlong = day.stops.map(stop => projectOntoRoute({ lat: stop.lat, lng: stop.lng }, spine)?.alongKm ?? 0)
// Before the card's first stop means the incoming night drive, which is drawn here
// but leaves from a stop on the card BEFORE this one (`nightSpill.ts`). Anchoring
// it to this card's first stop would file the via on the leg AFTER that stop, and
// the route would run forward, double back to the point, and carry on.
// Before the card's first stop means a drive that arrives here but leaves from a
// stop on the card BEFORE this one: the incoming night drive (`nightSpill.ts`), or
// on a trip with connected days the drive from where yesterday ended, which is drawn
// at the head of this card in yesterday's colour. Anchoring either to this card's
// first stop would file the via on the leg AFTER that stop, and the route would run
// forward, double back to the point, and carry on.
//
// Asked of the distance rather than of the index, because the index cannot answer
// it: `insertIndexForAlong` clamps to at least 1 for any list of two or more, and
@@ -1480,11 +1482,11 @@ export function useTripPlanner() {
// index this read as a guard and behaved as dead code, so a via dropped on the
// night stretch went to the first drawn stop after all, which is the exact failure
// the paragraph above describes.
const spilledIn = day.spills?.find(sp => sp.at === 0)?.fromStop
if (spilledIn && hit.alongKm < (stopsAlong[0] ?? 0)) {
const owner = spilledIn.ownerDayId ?? day.dayId
const arrivedFrom = day.spills?.find(sp => sp.at === 0)?.fromStop ?? day.arrivingFrom
if (arrivedFrom && hit.alongKm < (stopsAlong[0] ?? 0)) {
const owner = arrivedFrom.ownerDayId ?? day.dayId
if (onlyDayId !== undefined && owner !== onlyDayId) continue
best = { dayId: owner, afterIndex: spilledIn.ownerIndex ?? 0, offRouteKm: hit.offRouteKm }
best = { dayId: owner, afterIndex: arrivedFrom.ownerIndex ?? 0, offRouteKm: hit.offRouteKm }
continue
}
const at = insertIndexForAlong(stopsAlong, hit.alongKm) - 1