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)}
)}