From 92ccd24eed47e1068a966c6f7faaf3543bff95eb Mon Sep 17 00:00:00 2001 From: Maurice Date: Thu, 17 Sep 2026 16:38:17 +0200 Subject: [PATCH] fix(roadtrip, journey): six the audit turned up, each one verified before it was touched **A saved stay left an invisible sheet standing, and the day swipe stayed dead behind it.** The stay sheet reopened 'rtstop' by spreading its own payload, which carries a place id. That sheet is located by day and assignment, so it resolved to nothing and drew nothing, while `shell.sheet` still pointed at it and `menuOpen: !!shell.sheet` kept the day swipe blocked until something else closed it. The row rides along now, both ways, and with no row to go back to the sheet closes rather than opening one that resolves to nothing. **"End the day here" showed off for a day that already ends there, and switching it on deleted that day end.** Two things close a day: the stop's own flag and a manual boundary filed against it. The writer has always known about both; the phone switch read only the flag, so the tap ran through the writer's boundary branch and cleared the boundary. The question is asked once now, in the planner beside that writer, and the desktop reads the same answer it always computed inline. **The road trip chain printed 24-hour clocks at a reader on a twelve-hour one.** The model hands clocks over as HH:MM, which is what the schedule computes in, and the chain, the head card and the up-next card printed it straight. The desktop rail, the day timeline and this tab's own sheets all honour the setting; these go through the same formatter, carried in the row chrome so no two rows can disagree. **The up-next card's navigate button read "places.navigate".** No locale has that key. It is `inspector.navigation`, which is what the stop sheet beside it, the place inspector and the place sheet all use, with the single-target label when there is only one. **Offered roads were invisible on a satellite basemap.** The imagery is anchored under the first layer TREK draws, found by prefix, and the roads offered for a leg are added before the route source so they sit under the current route. That also puts them before the first `trip-` layer, so the raster went straight over them. The prefix list knows them now. **A journal grew a day section for every day Dawarich recorded, all year.** The hook reads every open stay the account has, and the timeline took all of their dates. A week in May therefore sprouted sections from September. Bounded by the journal's own span now, its entries together with the dates of the trips it links, and with no span at all it offers nothing rather than everything. --- client/src/components/Map/MapViewGL.test.tsx | 22 ++++++++++++++ client/src/components/Map/MapViewGL.tsx | 6 +++- .../screens/trip/roadtrip/MRoadtripRows.tsx | 14 +++++++-- .../screens/trip/roadtrip/MRoadtripTab.tsx | 13 ++++---- .../screens/trip/roadtrip/MRtStaySheet.tsx | 12 +++++++- .../screens/trip/roadtrip/MRtStopSheet.tsx | 18 +++++++++-- client/src/pages/JourneyDetailPage.tsx | 30 +++++++++++++------ .../src/pages/tripPlanner/useTripPlanner.ts | 19 +++++++++++- client/tests/helpers/mobileTrip.ts | 3 ++ .../unit/mobile/trip/MRoadtripRows.test.tsx | 20 +++++++++++-- .../unit/mobile/trip/MRtStaySheet.test.tsx | 26 +++++++++++++--- .../unit/mobile/trip/MRtStopSheet.test.tsx | 23 ++++++++++++-- 12 files changed, 177 insertions(+), 29 deletions(-) diff --git a/client/src/components/Map/MapViewGL.test.tsx b/client/src/components/Map/MapViewGL.test.tsx index c10a58491..513e1ddc1 100644 --- a/client/src/components/Map/MapViewGL.test.tsx +++ b/client/src/components/Map/MapViewGL.test.tsx @@ -2037,6 +2037,28 @@ describe('MapViewGL', () => { ) }) + it('FE-COMP-MAPVIEWGL-100: the imagery goes under the offered roads too, not just under the route', async () => { + // The roads offered for a leg are added before the route source so they sit beneath the + // current route, which also puts them before the first `trip-` layer. Anchoring the + // imagery on that first `trip-` layer therefore painted it straight over them, and an + // offered road on a satellite basemap could not be seen at all. + loadOnAttach() + useSettingsStore.setState({ + settings: { ...useSettingsStore.getState().settings, map_base_layer: 'satellite' }, + } as never) + glMap.getStyle.mockReturnValue({ + layers: [{ id: 'background' }, { id: 'road' }, { id: 'route-alt-hit' }, { id: 'route-alt-line' }, { id: 'trip-route' }], + }) + + render() + await flushFrames() + + expect(glMap.addLayer).toHaveBeenCalledWith( + expect.objectContaining({ id: 'trip-satellite-raster', type: 'raster' }), + 'route-alt-hit', + ) + }) + it('FE-COMP-MAPVIEWGL-075: with the default basemap no imagery is fetched at all', async () => { loadOnAttach() useSettingsStore.setState({ diff --git a/client/src/components/Map/MapViewGL.tsx b/client/src/components/Map/MapViewGL.tsx index e72b138ef..db8584d28 100644 --- a/client/src/components/Map/MapViewGL.tsx +++ b/client/src/components/Map/MapViewGL.tsx @@ -74,7 +74,11 @@ const GPX_HIT_LAYER_ID = 'trip-gpx-hit' const SATELLITE_SOURCE_ID = 'trip-satellite' const SATELLITE_LAYER_ID = 'trip-satellite-raster' /** Everything TREK draws is prefixed; the imagery is inserted before the first of them. */ -const OWN_LAYER_PREFIXES = ['trip-', 'trek-'] +// Everything TREK draws on a GL map, so the satellite raster can be slipped underneath all +// of it. `route-alt-` earns its place here: the roads offered for a leg are added before the +// route source so they sit under the current route, which also puts them before the first +// `trip-` layer, and anchoring the imagery there painted right over them. +const OWN_LAYER_PREFIXES = ['trip-', 'trek-', 'route-alt-'] type PlaceWithCoords = Place & { lat: number; lng: number } diff --git a/client/src/mobile/screens/trip/roadtrip/MRoadtripRows.tsx b/client/src/mobile/screens/trip/roadtrip/MRoadtripRows.tsx index dd1bc50cf..c784d5385 100644 --- a/client/src/mobile/screens/trip/roadtrip/MRoadtripRows.tsx +++ b/client/src/mobile/screens/trip/roadtrip/MRoadtripRows.tsx @@ -5,6 +5,7 @@ import MIconBtn from '../../../components/MIconBtn' import { formatDurationShort, serviceColor } from '../../../../components/Roadtrip/roadtripModel' import { STOP_KIND_BY_KEY } from '../../../../components/Roadtrip/stopKinds' import { formatDistance } from '../../../../utils/units' +import { formatClockTime } from '../../../../utils/formatters' import type { StopRow } from '../../../../components/Roadtrip/roadtripRowModel' import type { RefuelSearch } from '../../../../components/Roadtrip/useRefuelSearch' import { REFUEL_EMPTY_KEY, REFUEL_WORDS, refuelBandState, type RefuelCandidate } from '../../../../components/Roadtrip/refuelSuggestion' @@ -29,6 +30,15 @@ import type { TranslationFn } from '../../../../types' export interface RowChrome { t: TranslationFn unit: DistanceUnit + /** + * Whether this reader is on a twelve hour clock. + * + * The model hands every clock over as `HH:MM`, which is the shape the schedule computes + * in, and printing that straight to the screen ignored the setting the desktop rail, the + * day timeline and this tab's own sheets all honour. It rides in the chrome rather than + * being read per row: the chain draws dozens of rows and they must not disagree. + */ + is12h: boolean } /** @@ -151,7 +161,7 @@ export function RtStopRow({ row, chrome, onOpen, onPickKind }: { // dir=ltr so a clock reads the same way round in an RTL locale. {row.pinned && } - {row.time} + {formatClockTime(row.time, chrome.is12h)} )} @@ -470,7 +480,7 @@ export function RtAutoRow({ phase, time, chrome }: { {phase === 'end' ? t('roadtrip.window.stop') : t('roadtrip.window.resume')} - {time && {time}} + {time && {formatClockTime(time, chrome.is12h)}} ) } diff --git a/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx b/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx index c3314e1e7..6d4dca398 100644 --- a/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx +++ b/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx @@ -13,6 +13,7 @@ import { formatDurationShort } from '../../../../components/Roadtrip/roadtripMod import { getNavigationTargets } from '../../../../components/Planner/placeNavigation' import { useSettingsStore } from '../../../../store/settingsStore' import { formatDistance } from '../../../../utils/units' +import { formatClockTime } from '../../../../utils/formatters' import { isRtlLanguage } from '../../../../i18n' import type { MTripTabPanelProps } from '../MTripShell' import { legReroutable, type StopRow } from '../../../../components/Roadtrip/roadtripRowModel' @@ -40,7 +41,8 @@ export default function MRoadtripTab({ planner, shell }: MTripTabPanelProps) { // the map read one controller, so a leg shows pressed for exactly the picker on the map. const alts = useMRtAlternatives(planner, shell) const unit = useSettingsStore(s => s.settings.distance_unit) - const chrome: RowChrome = { t, unit } + const is12h = useSettingsStore(s => s.settings.time_format) === '12h' + const chrome: RowChrome = { t, unit, is12h } // The same gesture the day timeline uses, called rather than rebuilt: 370 lines of // worked-out conflict avoidance, down to the dead 24px gutter that stops iOS from @@ -176,7 +178,7 @@ export default function MRoadtripTab({ planner, shell }: MTripTabPanelProps) { {t('mobileTrip.rtStart')} - {rt.clocks.start ?? '-'} + {rt.clocks.start ? formatClockTime(rt.clocks.start, is12h) : '-'} @@ -184,7 +186,7 @@ export default function MRoadtripTab({ planner, shell }: MTripTabPanelProps) { {t('roadtrip.stay.arrive')} - {rt.clocks.arrive ?? '-'} + {rt.clocks.arrive ? formatClockTime(rt.clocks.arrive, is12h) : '-'} @@ -291,6 +293,7 @@ function UpNext({ planner, shell, rt, stageDayId, onOpen }: { onOpen: (row: StopRow) => void }) { const { t } = planner + const is12h = useSettingsStore(s => s.settings.time_format) === '12h' const next = rt.upNext if (!next) return null const late = next.minutesUntil < 0 @@ -320,7 +323,7 @@ function UpNext({ planner, shell, rt, stageDayId, onOpen }: { {next.row.stop.name} {next.row.time && ( - {next.row.time} + {formatClockTime(next.row.time, is12h)} )}
@@ -332,7 +335,7 @@ function UpNext({ planner, shell, rt, stageDayId, onOpen }: { className={`flex h-11 flex-1 items-center justify-center gap-[7px] rounded-full bg-m-act text-[0.8125rem] font-semibold text-m-actfg shadow-[0_10px_24px_-10px_rgba(0,0,0,.45)] ${targets.length ? '' : 'pointer-events-none opacity-40'}`} >