Commit Graph

3068 Commits

Author SHA1 Message Date
Maurice 7e6af9c8fc refactor(client): the ways into the place form and the stop popup move into usePlaceFormOpeners
The right click on the map, the POI marker and the hit on the drive with
the check-out days a night can end on, the service stop added by hand
with the legs it can go on, the popup handing over to the full form with
its duplicate check, and the place editor move out of useTripPlanner into
usePlaceFormOpeners.

None of it runs an effect, and useTripPlanner calls the hook right after
the place edits, where the place editor used to sit, so the planner
returns the same object and behaves exactly as before. The note on adding
a POI with its day now sits directly above the handler it describes.
2026-10-08 05:02:48 +00:00
Maurice a3fed9e392 refactor(client): saving, deleting and recategorising places move into usePlaceEdits
The place delete question with the booked nights it warns about, telling a
tour from a place, saving the place form with the day it was added from
and the vias that stop pushes along, deleting one place, a tour or several
places, and moving places to another category move out of useTripPlanner
into usePlaceEdits.

None of it runs an effect, and useTripPlanner calls the hook where the
place form's save used to sit, ahead of everything that asks whether a
place is a tour, so the planner returns the same object and behaves
exactly as before.
2026-10-08 05:02:47 +00:00
Maurice b771eae61b refactor(client): the via anchoring, refuel stops and dropped hits move into useRoadtripDriveShaping
Working out which stop a point on the drive belongs behind, where a place
chosen by hand goes, adding, dragging and removing vias, asking for and
accepting a refuel stop, and a corridor hit dropped onto the route move
out of useTripPlanner into useRoadtripDriveShaping.

None of it runs an effect, and useTripPlanner calls the hook where the via
anchoring used to sit, so the planner returns the same object and behaves
exactly as before. The note on placing a via now sits directly above the
function it describes.
2026-10-08 05:02:47 +00:00
Maurice d2706d3d1e refactor(client): the route alternatives picker and the map framing move into useRoadtripAlternatives
The other ways of driving one leg, the road the pointer is on, the
overlays and the stretch of map they cover, the automatic day points and
their markers, the point the map is asked to bring into view, the rail
snapshot a choice is checked against, and asking for and choosing an
alternative move out of useTripPlanner into useRoadtripAlternatives.

useTripPlanner calls the hook where the picker used to sit, after the
road trip feed and the stop edits, so the highlight reset, the close gate
and the rail snapshot still run in the same order and the planner returns
the same object. The note on what the map frames now sits directly above
the value it describes.
2026-10-08 05:02:47 +00:00
Maurice 9681fdf321 refactor(client): the edits to one stop on the drive move into useRoadtripStopEdits
Saving the stop popup as a pause or as a night, the stay release that
asks before a booked night is dropped, the kind, fill and stay of a stop,
moving a stop within its day or onto another one from the rail, ending
the day at a stop, and the stay dialog with the call that opens it move
out of useTripPlanner into useRoadtripStopEdits.

None of it runs an effect, and useTripPlanner calls the hook where the
stop edits used to sit, so the planner returns the same object and
behaves exactly as before.
2026-10-08 05:02:47 +00:00
Maurice bde734c50f refactor(client): the road trip feed and the layers it draws move into useRoadtripFeed
The road trip mode and feed flags, the driving preferences and the limit
save, the day boundaries with their reset and drag controls, the vias,
the refuel search, the routes, the corridor search, the followed track,
the via counts and the via index helpers, the days folded in the rail and
the map layers that follow those folds move out of useTripPlanner into
useRoadtripFeed.

useTripPlanner calls the hook where the feed used to sit, right after the
trip overview, so the preference load, the boundary and via hooks, the
routing round and the corridor search still run in the same order and the
planner returns the same object. The helpers, the folds and the boundary
controls are plain memos and callbacks, so computing them earlier changes
nothing.
2026-10-08 05:02:47 +00:00
Maurice 1db227cb5b refactor(client): the map marker set and the day order badges move into useMapPlaces
The list filters the map shares with the sidebars, the declutter of
collapsed days, the compact and pending marker flags, the Tracks filter
fallback, and the selected day's order badges and fit list move out of
useTripPlanner into useMapPlaces.

useTripPlanner calls the hook where the marker set used to sit, right
after the trip's websocket, so the store subscriptions and the Tracks
fallback still run in the same order and the planner returns the same
object. The order badges and the fit list are plain memos, so computing
them earlier changes nothing.
2026-10-08 05:02:47 +00:00
Maurice 41dabbda6e refactor(client): the planner map view toggles and first fit move into usePlannerMapView
The day route toggle and its travel profile, the whole-trip overview, the
map lock, the recorded Dawarich trail, the fit key and the effect that
fits each trip once move out of useTripPlanner into usePlannerMapView.
A new test calls every toggle and the automatic route, which no test
reached before.

useTripPlanner calls the hook where that code used to sit, so the
Dawarich fetch and the first fit still run in the same order and the
planner returns the same object. handleSelectDay now lists the map lock
ref, the phone ref and setFitKey it receives from the hook; they never
change identity, so nothing behaves differently.
2026-10-08 05:02:47 +00:00
Maurice 87ca3df781 refactor(client): the planner dialogs and their URL intents get their own hook
The place form, the service stop form, the stop popup, the stay release
question, the trip and member dialogs, the booking, transport and transit
editors, the importers and the booking detail move out of useTripPlanner
into usePlannerDialogs, together with the ?create= and ?tab= effects and
the AirTrail sync. plannerTypes.ts holds the option types the planner's
sub-hooks share.

useTripPlanner calls the new hook where that code used to sit, so every
effect still runs in the same order and the planner returns the same
object. The state setters it now receives from the hook are added to the
dependency lists that use them; they never change identity, so nothing
behaves differently.
2026-10-08 05:02:47 +00:00
Maurice e2dab1aebb refactor(client): the trip summary and the day header move to RoadtripSidebarHeaders
TripSummary leaves RoadtripSidebar.tsx for its own module and is
imported back by name; Unit and splitValue go with it unexported.

The day card's header becomes DayHeader in the same module. Its body is
the header element the card drew inline, together with the reads only
the header used: the language, the distance unit, the clock format,
the missed ride and the day colour. The card keeps its translation and
passes the day, the fold state and the track details straight through.
DayHeader returns the bare header with nothing around it, so the markup
is unchanged, and it sits at module scope so it never remounts.

The code moves as it was otherwise, with the dashes in the moved
comments rewritten as colons and commas, and the imports only these
two used leave the sidebar, which is down to about 700 lines. Nothing
renders differently.

New tests render both on their own: the summary's figures split from
their units, narrow and still loading; the day's facts and its fold
control; a day over the driving limit, roads it could not avoid, a
missed flight, the track badge with and without its name, and the
wash in the day's own colour.
2026-10-08 05:02:47 +00:00
Maurice 8e2a2725f0 refactor(client): the stop rows move to RoadtripSidebarStopRows and share one badge row
ServiceStop, RouteViaStop and Stop leave RoadtripSidebar.tsx for their
own module and are imported back by name; effectiveFill goes with them
unexported, and the imports only they used leave the sidebar.

The badge row under a stop's name was written out twice, once in each
stop shape. It is now the local StopBadgeRow, which both render in its
place. It returns the same span with nothing around it, so the markup
is unchanged, and it reads the fill setting and the vehicle the two
shapes used to read themselves. Those are plain store reads, so moving
them one component down changes nothing either.

The code moves as it was otherwise. The note on the stop kind table now
sits at the lookup it describes, the comment on the two settings says
where they are read now, and the dashes in the moved comments become
colons, commas and full stops; each theme-lint-disable stays on the
line of its style. Nothing renders differently.

New tests render the rows on their own: a plugin halt with and without
its name and stay, the kind picker on a pause and on a numbered stop by
click, Enter and Space, the drive findings and the walk from the road
on both shapes, including the range and unknown findings the rail never
draws, and the fill badge with the traveller's default.
2026-10-08 05:02:47 +00:00
Maurice 5cfc546630 refactor(client): the rows a booking stands for move to RoadtripSidebarBookingRows
TerminalStop, BookendStop, RideBlock, BookingChips and UndatedRides
leave RoadtripSidebar.tsx for their own module and are imported back by
name. RIDE_DASH, DiscRow, CodeChip, TimetableClock and CheckInBadge go
with them unexported, since only these rows use them, and the imports
only they used leave the sidebar. The code moves as it was; the one
change is that UndatedRides spells out the type of its booking callback
instead of reading it off the sidebar's props, so the new module never
imports from the sidebar. Nothing renders differently.

New tests render each row on its own: a departure with its clock and
check-in, a hire desk, a booked night that is left after check-out, a
ride that is caught, missed or has nothing on its timetable, the chips
a reader may and may not open, and the rides on no day.
2026-10-08 05:02:47 +00:00
Maurice 2552b37e90 refactor(client): the drive band and the night drive block move to RoadtripSidebarDriveBands
DriveBand and SpillBlock leave RoadtripSidebar.tsx for their own module
and are imported back by name. MODE_ICON goes with them, unexported,
since only the drive band reads it, and the imports only these two used
leave the sidebar. The code moves as it was, with the dashes in its
comments rewritten as colons, commas and full stops. Nothing renders
differently.

New tests render both on their own: every travel mode's sign, the
alternatives button and its pressed state, a hop, a ride with and
without its booking and timetable, and a night drive that is automatic,
has no leg yet or misses its figures.
2026-10-08 05:02:47 +00:00
Maurice 8e4874841f refactor(client): the fill-up band moves to RoadtripSidebarRefuelBand
RefuelBand leaves RoadtripSidebar.tsx for its own module and is
imported back by name; the imports only it used go with it. The code
moves as it was, with the dashes in its comments rewritten as colons
and parentheses. DriveBand's note, which the band used to separate from
its function, now sits directly above DriveBand. Nothing renders
differently.

New tests render the band on its own: idle, searching, offers with and
without the right to add one, each empty answer, and the battery words
for an electric car, which no existing test reached.
2026-10-08 05:02:47 +00:00
Maurice 97a93bb719 refactor(client): the stop badges and the arrival clock move to RoadtripSidebarBadges
OffRoadBadge, FillBadge, NightCheckIn, StayBadge, DriveFindingBadge,
DayCarry, Arrival and LateBadge leave RoadtripSidebar.tsx for their own
module and are imported back by name. The code moves as it was. The doc
blocks that sat stranded above other functions now stand above the
badge they describe, StayBadge's two notes as one, and the dashes in
the moved comments become colons and commas. Nothing renders
differently.

New tests render each badge on its own, including the walk from the
road and the range and unknown arms of the drive finding, which the
rail never reaches.
2026-10-08 05:02:47 +00:00
Maurice 7bd351f9b7 refactor(client): the rail's shared styles move to RoadtripSidebar.constants
RAIL_GRID, RAIL_DASH, DISC, STAT_LABEL and DAY_BADGE leave
RoadtripSidebar.tsx for a plain module beside it, so the parts the
sidebar is split into next can share them without importing from the
sidebar itself. Values and comments move as they were, except that the
dash in the DISC note becomes a colon; the two style objects are typed
as CSSProperties from a type import. Nothing renders differently.
2026-10-08 05:02:47 +00:00
Maurice b8c1fbf192 refactor(client): the plan map and its floating controls move out of the planner page
The plan tab's MapView with the route alternatives bar, the overview and
trail stack, the POI and compass cluster, the phone compass and the phone
POI portal move to pages/tripPlanner/PlanMapStage.tsx. It returns the same
children as a fragment in the same place, so the DOM and the portal order
stay as they were. Its props are picked from the planner hook's own types.

The four physical sides that moved are map geometry and carry an
rtl-lint-disable marker on their line, so the page's rtl baseline drops
from 17 to 13. Two moved comments lose a dash.
2026-10-08 05:02:47 +00:00
Maurice 7d8dcb02c8 refactor(client): the lists tab moves out of the planner page
ListsContainer, the packing and to-do subtabs of the lists tab, moves
unchanged to pages/tripPlanner/ListsContainer.tsx, taking along the
imports only it used. The page renders it exactly as before.
2026-10-08 05:02:47 +00:00
Maurice 3b9e5606ce refactor(client): the planner's lazy panels and their boundary get modules of their own
The lazyWithRetry declarations of the planner page move to
pages/tripPlanner/plannerLazy.ts, a plain module so Fast Refresh keeps
treating the page as a component boundary, and LazyPanel moves to
pages/tripPlanner/LazyPanel.tsx. The page imports both. Comments travel
with their code; two of them lose a dash. Nothing renders or loads
differently.
2026-10-08 05:02:47 +00:00
Maurice 5189d7fcca ci(client): source files may not grow past their size baseline
lint:size counts the lines of every source file under client/src, tests
aside. A file may hold 1000 lines; the 24 files that are longer today are
listed in scripts/size-baseline.json with their current length and fail
the check when they grow. --update lowers an entry once a file shrinks
and never raises one, so a long file is split by concern instead of
growing. A baseline that cannot be read or parsed stops the check with
its reason, and --update leaves it as it is. It runs in the client job
next to the reading direction check.
2026-10-08 05:02:47 +00:00
Maurice bee500880e fix(i18n): every language says one file and five files its own way
Finishes the plural work the previous wip commit started. t() picks a
count string's form by the language's own Intl.PluralRules, and every
locale now spells out the forms its language reaches with whole
numbers, about 160 count strings in all: one, few and many in Russian,
Ukrainian and Polish, one and few in Czech and Slovak, five forms in
Arabic, one in the others, none in Japanese, Korean, Chinese, Thai,
Vietnamese and Indonesian. Each language was translated and then
reviewed on its own. English no longer reads "1 files", "1 updates
available" or "photo(s)".

Count labels shown next to a number now take that number too: the
atlas and admin statistics, the trip cards, journey and shared trip
pills, packing templates, receipts, storage usage, the Studio export
note. A heading that shared its key with a counted label keeps a key of
its own (the boarding pass heading, the Bookings tab), so languages that
put a noun after a number in the singular keep a plural heading. The
plugin hosts chip passed its count through .replace() after t() and
never got a form; three toasts sent the count as text. Mail and push
texts for shared photos and suppressed storage failures pick their form
in code through pluralForm.

A whole number sent as text counts too, which is how in-app
notification params arrive, and the plural rule follows the language of
the strings on screen while another one still loads.

The parity check knows which forms each language needs and refuses
missing and impossible ones. It also reads keys that contain a colon
now: it skipped them, which hid 58 missing plugin permission texts in
Catalan and 35 in Vietnamese, both translated. The client parity test
covers every language of the registry instead of a hand-kept list of 19,
and the placeholder spec lets a form spell its number out only where
its category is that one number. Brazilian Portuguese error screens
were in Breton, and an Arabic dashboard label held Hebrew text.
2026-10-08 00:01:59 +00:00
Maurice 55697b75c1 test(db): the legacy step map knows the tour type migration
Migration20200101042300_every_tour_type_the_contract_names runs after
the legacy steps like the other post-legacy migrations, but was missing
from the test's list of migrations without a step, so LEGACYMAP-001
failed.
2026-10-08 00:01:43 +00:00
Maurice 960520c6d9 test(e2e): the iPad case checks what broke #1432, the guides read today's texts
The iPad case could no longer fail on its own regression: a draggable
row behind the long-press bridge is what tablets get by design since
#1616, and nothing in the case swiped. What turned swipes into drags in
#1432 was the drag-drop-touch polyfill, so the case now asserts that a
tablet never loads it.

Two help-media guides waited for "1 files" and "2 flight(s) imported",
which now read "1 file" and "2 flights imported".
2026-10-08 00:01:43 +00:00
Maurice d4eccb39a3 fix(client): three more places follow the reading direction in Arabic
On a phone in Arabic every system notice, the release notes included,
opened as an empty sheet: its three-slot strip is shifted to show the
middle slot, and in a right-to-left row that shift moved all three out
of view. The shifts point the other way in a right-to-left page now,
which the swipe handling already assumed.

The budget's total amount kept its parts in order but sat on the
opposite edge from its label, because the whole row was made left to
right. Only the amount itself is now. The phone chat's bubble tails
were still physical corners while the desktop ones follow the row; both
use logical corners now.

lint:rtl takes comments from the TypeScript parser instead of scanning
lines, so a "/*" inside a string (accept="image/*") no longer hides the
lines after it.
2026-10-08 00:01:43 +00:00
Maurice 0126ef3401 fix(client): the version notice asks about a change during the session only
Three gaps in the notice from the previous commit, found in review.

A page served from the network and claimed later by its own build's
worker counted as already taken over, so Reload went to that worker,
got the old build again and spent the session's reload: no notice and
no new build until the next start. Only a worker that replaced the one
that served the page skips the handover now, as at launch.

A reload that came before the new worker could take over (a slow
precache, a failed update, an install gone redundant) also spent that
guard, so the launch behind it could no longer finish the handover with
its own reload. That reload leaves the guard alone now.

An install whose server never reports the bundle's version (a hand-set
APP_VERSION) showed the notice in every session, and Reload could not
clear it. The store keeps the first version a page hears of, from the
app config at launch, and the notice only asks when a later report
differs: a different build at launch is the launch handover's to fix.

The server's wire-envelope test pins the welcome frame's fields and now
expects the version beside the socket id. The wiki's update page says
what an open tab shows after an update.
2026-10-08 00:01:33 +00:00
Maurice 254a214b7d fix(shared): the tour metrics pass the strict shared typecheck
computeTourMetrics indexed points[i - 1] and points[i], which the shared
package's noUncheckedIndexedAccess reads as possibly undefined, so the
shared typecheck in CI failed since the metrics moved there. It walks the
points pairwise from the first one now; the numbers are the same.
2026-10-07 23:09:42 +00:00
Maurice 8dca372c47 ci: run the browser flows and the map bundle check on every change
Both only ran on a developer's machine, and both had stopped telling the
truth. The end-to-end flows now run in CI in Chromium and in WebKit (the
iPad and iPhone cases need the engine every iOS browser uses), with the
browsers cached on the lockfile and the traces uploaded when a flow
fails. A second job builds the client and fails when one chunk carries
both map engines.

Making them pass meant fixing what had rotted. The setup closed every
first-run notice but the release notes, whose X is called Close, so the
notes covered the dashboard and every flow after login timed out on a
click. The iPad case still expected undraggable place rows; tablets drag
on purpose since #1616, behind the long-press bridge, so it now checks
what #1432 is about, a swipe that scrolls and raises no drop overlay,
with locators for the scroller and the Plan tab that match today's
markup. The iPhone case read the body scroll lock through
window.scrollBy, which moves an overflow: hidden viewport in every
engine; it now reads the lock where it takes effect and checks the page
keeps its place.
2026-10-07 22:49:36 +00:00
Maurice a094f79efa feat(client): offer the new version when a release lands during a session
A tab kept open across a deploy ran the previous build until its next
start. The server now names its version in the realtime welcome frame,
which every open client reads again when it reconnects after the
restart a deploy brings. When that version is not the build the page
runs, a notice offers to reload. The reload goes through the same
service worker handover a launch uses: it waits for the new worker to
take over, or for a short timeout, and records the version only when
the takeover happened.

The notice reads the error boundary's texts for the same situation, so
every language has it already. It is one component for both shells,
sits above the offline pill, stays away offline and after a reload for
that version that did not bring it, and can be closed for the session.
2026-10-07 22:40:12 +00:00
Maurice ba70ad28b8 fix(client): spacing and alignment follow the reading direction
Arabic runs the app right to left, but margins, paddings, insets, text
alignment, borders and corner radii were written for the left side, so
icons sat against the wrong edge of their labels, indents and badges
pointed the wrong way and amount columns aligned left. They now use the
logical forms (ms-, ps-, start-, text-start, marginInlineStart,
insetInlineEnd and their CSS equivalents), which are the very same sides
in a left to right language. Geometry stays physical on purpose: maps,
positions measured from the pointer or a bounding box, time axes,
sliders, transforms and print output.

Amounts drawn as several spans keep their order in a right to left row,
the time picker keeps hours before minutes, and two switches whose thumb
slid out of its track in Arabic now move the right way.

lint:rtl counts what is still physical per file against a baseline that
may only shrink (1512 uses in 370 files before, 319 in 113 now), and CI
runs it, so new code reaches for the logical forms.
2026-10-07 22:33:58 +00:00
Maurice de128cc4ae wip(i18n): plural forms through Intl.PluralRules
Unfinished, continued in the next session: t() resolves key.one/.few/...
by the language's own plural rule, the parity check knows which forms a
language needs, 16 singular/plural key pairs are merged in every locale
and every count === 1 branch in the client uses the plural form. Still
open: the forms each locale needs for the remaining count strings, the
two new labels in every locale, and 35 tests that expect the old English
wording.
2026-10-07 22:57:51 +02:00
Maurice 588e483205 fix(calendar): VTIMEZONE spells out the offset changes of its span
Each zone's VTIMEZONE carried one fixed offset, so a client that reads it
literally showed every event after a change to or from summer time an hour
off. It now lists every offset change in the calendar years its events
fall in, each as its own observance after the offset in force before the
first one. A zone without changes, or a span with no readable date, keeps
the single fixed offset.
2026-10-07 22:56:35 +02:00
Maurice ab343b0a7b fix(client): a phone turned sideways stays in the phone layout
The phone shell was chosen by width alone, and most phones are wider than
767px in landscape, so turning one swapped the whole route tree for the
desktop planner and an open sheet, its input and the map position were
lost. The phone query now also covers a touch screen under 500px tall,
which no tablet is. It lives in one place: the shell, the planner and todo
hooks, the warnings banner, dialog focus and the stylesheets read it, and
six components that measured the window once on mount now follow it.
2026-10-07 22:06:40 +02:00
Maurice e5ceba00f3 fix(tours): wait for the public Valhalla's turn before every request
The public instance allows about one request a second per client, and the
Tours elevation questions went out right behind the route request with no
pause, so they could be refused with a 429 and leave the tour without the
elevation it needs to be saved. Every request to that host now waits for
its turn, across callers; an operator's own Valhalla is not paced.
2026-10-07 21:39:57 +02:00
Maurice e0efa0314a feat(tours): tours read and write through the offline core
A tourRepo replaces the direct toursApi calls: reads fall back to a new
Dexie tours table, and a tour saved or edited offline is written there and
queued like a place, under one temporary id for the place and its facet.
The queue moves both to the real id on replay and drops both when the
server refuses the create. Trip sync caches each trip's tours while the
addon is on, so a tour no longer reads as a plain place offline.

The tour metrics move to shared so the offline copy shows the server's
numbers, and useTourPlaceIds keeps one Set per change as intended.
2026-10-07 21:30:45 +02:00
Maurice f7edd849ab fix(tours): one tour type catalogue, and no column nothing writes
tour_types now holds every key the shared contract accepts, all but hike
disabled, and create and edit turn a disabled type away with a 400 instead
of failing on the foreign key. tours.tour_group_id is dropped: nothing ever
wrote or read it. difficulty and wanderer_ref stay for the Wanderer import.
2026-10-07 20:54:10 +02:00
Maurice f64f9e58de fix(client): give the context menu a menu role, wrap the tour filter callback 2026-10-07 20:35:15 +02:00
Maurice bd8e1a287b test(oauth): the module now provides the token retention job 2026-10-07 17:55:25 +02:00
Maurice e6e270610c fix(places): type the tour mark on the single-place row 2026-10-07 17:37:26 +02:00
Maurice 57df945318 style: plain punctuation in the new comments and test names 2026-10-07 17:12:26 +02:00
Maurice 40bce5554d docs: the key check, the restore guard, the new probes, failed offline changes and reminders
Updates the wiki for the changes in this branch: TREK refuses an
ENCRYPTION_KEY that disagrees with the key file, the rotation script
covers the AI Parsing key and moves the file along, a restore refuses a
newer backup and keeps the replaced database, the chart's startup and
readiness probes, PORT and extraEnv, retrying or discarding failed
offline changes, trip reminders catching up, and why a Portainer
recreate used to keep reporting the old version.
2026-10-07 17:11:56 +02:00
Maurice 6b037e0fa0 refactor(tours): measure distances with the shared haversine
The Tours metrics carried their own copy of the great-circle formula
next to nest/common/geo.ts, which exists so there is one.
2026-10-07 17:10:59 +02:00
Maurice e4ed4bfc7b fix(mobile): the booking, transport and place sheets ask before dropping what was typed
On the phone a flick of the sheet's top edge, a tap on the scrim or
Escape closed the booking, transport and place forms and threw away
whatever had been typed, without a word; the desktop dialogs have asked
since #2253. The question moves into a shared hook that DialogShell and
MSheet both use, and the three sheets pass their form state to it.
2026-10-07 17:10:14 +02:00
Maurice c3c21fb0b4 fix(client): five loads that catch their own error recover from a deploy too
The PDF export on desktop and phone, the language switch, the holiday
regions and the PDF's map image load a chunk on demand and catch the
failure themselves. After a deploy the old page asks for chunk names the
new build no longer has, so they showed their own error on every retry
(or, for the language, silently stayed in the old one) instead of
reloading onto the new build the way an uncaught chunk error does.
importChunk reloads once for a missing chunk and still hands the caller
its error.
2026-10-07 17:05:36 +02:00
Maurice 0a1a5092f7 fix(ui): the context menu answers the keyboard and closes when the page moves
The shared context menu closed only on a click elsewhere: Escape did
nothing, the arrow keys did nothing, and a scroll left the fixed menu
hanging over content that had moved on. It now takes focus on its first
item, moves with the arrow keys, and closes on Escape, scroll and
resize. rules-of-hooks is an error again; its one hit was a test helper
named like a hook.
2026-10-07 17:03:47 +02:00
Maurice 094a46a3cf fix(vacay): stop swallowing a failed delete as a holiday API error
One try around the holiday fetch and the deletes that follow it caught
both, so a database error while removing vacation days that fall on a
public holiday was logged as nothing and left the days standing. Only
the fetch is tolerated now. Also drops two files long gone from the
Sonar coverage exclusions.
2026-10-07 16:59:52 +02:00
Maurice 48f9e7d132 perf(db): index every foreign-key column
86 foreign-key columns had no index. SQLite does not add one by itself,
so every cascading delete of a trip or a user scanned each such child
table in full, inside the transaction, while every other request waited
on the one connection. One migration adds them all, and the entities are
regenerated to match.
2026-10-07 16:56:58 +02:00
Maurice 1a4df59bf3 ci: give every job a timeout
No job had timeout-minutes, so a hung step held a runner for GitHub's
six-hour default. Test and housekeeping jobs stop after 45 minutes, the
image builds after two hours.
2026-10-07 16:52:26 +02:00
Maurice c60277c4d2 fix(tours): a tour another member creates is a tour on the phone too
The list read marked tour places with tour_place_id, but the single-place
read behind place:created and place:updated did not, and the phone's
places browser kept a second tour list of its own that no realtime event
updated. A tour another member made, or one imported from GPX, showed as
an ordinary place on the phone until the screen remounted, and every
tour did offline. The single read carries the mark now, the browser asks
the planner, and the tour id set is memoised instead of rebuilt on every
render.
2026-10-07 16:52:05 +02:00
Maurice 72fbbd55d4 fix: a trashed note file leaves its note, quieter MCP sweep, bounded compose logs
A note's attachment moved to the trash from the file manager kept
showing on the note; the hydration read now skips it, as the chat one
already did. The MCP session sweep logged a line every minute while any
session was open; it logs only when it cleaned something. The compose
file caps the container log, and three test mocks that pointed at
modules long gone are removed.
2026-10-07 16:47:53 +02:00
Maurice 76df44d8de fix(oauth): purge tokens whose refresh token expired a month ago
Every refresh writes a new oauth_tokens row and nothing ever deleted the
old ones, so an MCP client refreshing hourly grew the table, and every
backup with it, without end. A nightly job now deletes tokens whose
refresh token expired more than 30 days ago, leaves first, and keeps any
row a live chain still points at for replay detection. Also types the
backup's per-entry store flag.
2026-10-07 16:45:21 +02:00