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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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".
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Changing PORT left the Dockerfile and compose healthchecks, the chart's
containerPort, Service target and probes on 3000, so the container went
unhealthy and Kubernetes restarted it in a loop. All of them follow PORT
now. The chart gains a startup probe (ten minutes for a first boot, a
long migration or a restore at boot, which liveness used to kill after
about 105 s), a readiness probe on the new /api/health/ready that asks
the database, extraEnv and extraEnvFrom for the settings the chart does
not list, slots for the four secrets it had none for, and the
DEFAULT_LANGUAGE and MCP_MAX_SESSION_PER_USER values it documented but
never set. The Dockerfile no longer hides a failed npm ci behind a ';',
pins gosu, and the language lists name all 27 locales.
Resetting a locked-out or compromised admin left every session on that
account valid, because the script did not move password_version on the
way every password change in the app does. It also looked the address
up case-sensitively and created a second account when the stored one
had capitals.
Both locales had their notification texts, but the index never imported
them, so their users got English mails and the parity gate passed
because the files existed. The locale map is now typed against the
supported languages, so the next one left out fails to compile.
TREK's codes 'br' (Brazilian Portuguese) and 'gr' (Greek) went straight
into <html lang> and into ten Intl calls. 'br' is Breton, so Brazilian
users got Breton month names and screen readers announced the page as
Breton; 'gr' is no language at all, so Greek users got the default
locale. getIntlLanguage maps both, and every one of those sites goes
through it.
Logging out deleted this user's offline database straight away, so a
change made offline and not yet sent was lost without a word. With a
connection, the logout now flushes the queue first, for at most five
seconds.
The trip sync and every repo list only ever upserted, so a place a
collaborator deleted while this device was offline came back on the
next offline read, and a trip the user had been removed from stayed on
the device. A sync and a full list now replace the trip's rows, keeping
only the ones created offline that have not synced yet, and a cached
trip the server no longer lists is cleared.
A queued change the server answered with a 5xx was put back at the head
of the line and the whole flush stopped, every 30 seconds, forever: one
bad payload held every later change of every trip. Now only its own
trip waits, with gaps doubling from 30 s, and after eight failures it is
parked as failed. A DELETE answered 404 counts as done instead of
showing as a failed sync. Settings > Offline offers to try parked
changes again or discard them.
A connect still waiting for its ws token when the user logged out went on
to open a socket authenticated as that user, which the next login on the
same browser then reused. And a socket in its reconnect backoff waited up
to 30 s after the network came back or the tab became visible. Both
events now reconnect right away.
The desktop day panel stays mounted across a day switch, so a forecast
or the stays still loading for the previous day overwrote the new day's
once they arrived. The location search in the transport form showed
results for text the user had already changed, or refilled the list
after a pick. Both now drop an answer for a request that no longer
matches.