fix(roadtrip): the phone chain drops the placeholder leg into an automatic night

`dayWindow.stationary()` puts a leg between the last stop of a day and the automatic
night that closes it: same coordinate at both ends, nothing measured, no texts. It is
bookkeeping, not a drive. Drawn as a pill under the last stop it said "No route" — and
before the template was guarded, a bare " in " — under a place the traveller had simply
arrived at.

Matched on the shape rather than on the next stop being a night, because what makes it
not a drive is that it goes nowhere: a day ended at a boundary dragged down the road HAS
a leg with a distance, and that one keeps its pill. A leg nothing has routed yet keeps
its row too, so it can still say it is pending.
This commit is contained in:
Maurice
2026-09-17 13:26:10 +02:00
parent 0b1ca1c98a
commit 911f3e2583
2 changed files with 52 additions and 1 deletions
@@ -98,6 +98,37 @@ describe('roadtripRows numbering', () => {
expect(stopRows(rows).map((r) => r.stop.name)).not.toContain('Nacht')
})
it('FE-RTROW-040: the placeholder leg into an automatic night is not a row at all', () => {
// `dayWindow.stationary()` puts one of these between the last stop of a day and the
// night that closes it: same coordinate at both ends, nothing measured. It is
// bookkeeping, not a drive, and as a pill it read "No route" under a stop the
// traveller had simply arrived at (and, before that was guarded, a bare " in ").
const stops = [stop('Hamburg'), night()]
const at: [number, number] = [53.54, 10.01]
const stationary: RouteSegment = {
mid: at, from: at, to: at, distance: 0, duration: 0,
walkingText: '', drivingText: '', distanceText: '',
}
const rows = roadtripRows(day(stops, { legs: [stationary] }))
expect(rows.map((r) => r.kind)).toEqual(['stop', 'auto'])
})
it('FE-RTROW-041: a night the traveller drove to keeps its leg', () => {
// A boundary dragged down the road ends the day somewhere else, and that IS a drive.
const stops = [stop('Hamburg'), night()]
const rows = roadtripRows(day(stops, { legs: [seg(0)] }))
expect(rows.map((r) => r.kind)).toEqual(['stop', 'leg', 'auto'])
})
it('FE-RTROW-042: a leg nothing has routed yet still gets its row, so it can say it is pending', () => {
const stops = [stop('Hamburg'), stop('Bremen')]
const rows = roadtripRows(day(stops, { legs: [undefined] }))
expect(rows.map((r) => r.kind)).toEqual(['stop', 'leg', 'stop'])
})
it('FE-RTROW-003: the automatic night carries no dwell and takes its time from the schedule', () => {
// Die Nacht traegt dwellMinutes 480, aber keine Zeile, die eine Aufenthaltsdauer zeigen koennte.
const rows = roadtripRows(
@@ -109,6 +109,26 @@ function stopRow(
* card it is drawn on. The rows follow the card, because that is what the traveller
* reads, and anything writing back has to use `ownerDayId` instead.
*/
/**
* Whether a leg is the model's placeholder for "the day ended where you were standing".
*
* `dayWindow.stationary()` puts one of these between the last stop of a day and the
* automatic night that closes it: same coordinate at both ends, nothing measured, no
* texts. It is bookkeeping, not a drive, and drawn as a pill it said either " in " (the
* separator of `roadtrip.leg.driveText` with both slots empty) or, once that was guarded,
* "No route" under a stop the traveller had simply arrived at.
*
* Matched on the shape rather than on the next stop being an automatic night, because
* what makes it not a drive is that it goes nowhere. A night the traveller moved to a
* point down the road HAS a leg, with a distance, and that one keeps its pill.
*/
function isStationary(seg: RouteSegment | undefined): boolean {
return !!seg
&& seg.distance === 0
&& seg.from[0] === seg.to[0]
&& seg.from[1] === seg.to[1]
}
export function roadtripRows(day: RoadtripDay): RoadtripRow[] {
const rows: RoadtripRow[] = []
const spills = day.spills ?? []
@@ -140,7 +160,7 @@ export function roadtripRows(day: RoadtripDay): RoadtripRow[] {
// The leg AFTER this stop, plus the dry point that falls on it. Both belong
// between two stops, so they are emitted here rather than in their own pass.
const seg = day.legs[i]
if (i < day.stops.length - 1) {
if (i < day.stops.length - 1 && !isStationary(seg)) {
rows.push({ kind: 'leg', index: i, seg, mode: day.stops[i + 1]?.incomingLegMode ?? stop.legMode ?? null })
const dry = (day.dryPoints ?? []).find(p => p.legIndex === i)
if (dry) {