The token in the sign-in bodies was declared deprecated and "read by
nothing of ours", but the web client still decided an SSO sign-in by
reading data.token from GET /api/auth/oidc/exchange, an endpoint the
deprecation notice left out. Removing the field later, as documented,
would have broken SSO sign-in.
The exchange now answers { success: true, token } and the login hook
signs in when the response is OK and success is true, so it never reads
token. The field stays for API clients and is now on the deprecation
list in the API docs, the controller comments, server/CLAUDE.md and the
wiki (Security-Hardening, OIDC-SSO). Adding a field to the body breaks
no existing client.
The planner hook tests spy on the store's actions for the visit times,
and the note now goes through a store action as well. Asserting on that
action instead of on the raw note route keeps these tests about the hook
and keeps the file inside its lint:size entry, which the response stub
the route needed had pushed three lines past.
The place dialog stopped reloading the days after a save, so the new
visit note only reached the Zustand store. Nothing wrote it to Dexie,
the tab never sees its own assignment:updated echo, and the time save
before it cached the server row with the old note. A trip reopened
offline then showed the old note until the next full sync.
The note now goes through assignmentRepo.setNotes, which caches the
saved row on its day online and queues the write behind any other
write of the same visit offline, like setTimes does. A new store
action applies it, so the page hook no longer patches the store by
hand. usePlaceEdits drops its direct API import and leaves the
offline baseline.
OAuthAuthorizePage reloads the user on mount and useDashboard's retry
reloads it too. The real loadUser then registers the background sync
triggers, a 30 s heartbeat and window listeners that nothing in either
spec unregisters, so they outlive the tests the same way they did in the
account specs before 27db5dd5e. Both specs now stub the triggers, which
neither is about.
login, completeMfaLogin and register start tripSyncManager.syncAll()
and the system notice fetch without waiting for them, and every
sign-in, loadUser included, registers the sync triggers with their
30 s heartbeat. #2613 stubbed the sync in the push spec after it was
caught logging while the worker shut down ("Closing rpc while
onUserConsoleLog was pending"); the same shape was left in the other
specs that sign in for real.
authStore.test.ts ran the real sync on every successful sign-in and
logged DatabaseClosedError from it, and its notice fetch went past MSW
to the network and logged a failed fetch, both after the tests that
started them. The sync is now stubbed there, and the login, MFA and
register tests assert that it was started. The notice store is reset
before each test, since FE-STORE-AUTH-024 left a stubbed fetch behind.
LoginPage.test.tsx signs in through the form, and AccountTab.test.tsx
and MSettingsAccount.test.tsx reload the user after an MFA change; they
now stub the sync triggers (and the login spec the trip sync), which
none of them is about.
GET /api/system-notices/active gets a default MSW handler returning no
notices, so a login or a mounted notice host anywhere in the suite
settles that fetch in the test instead of on the network. The notice
store's own tests still override it.
The planner's two time edits of a visit called the time route straight:
saving the place dialog opened from a visit, and removing a visit's time
before a reorder. They skipped the visit repo's check for an older write
to the same visit still in the queue, so with such a write parked or
backing off the new time landed first and Try again (or the next flush)
replayed the older time over it, with no version token to refuse it.
Both now use the store's setAssignmentTimes, which writes through
assignmentRepo.setTimes, queues behind the older write when there is one
and keeps the optimistic update and rollback the other time edits have.
The dialog no longer reloads the days afterwards, since the store already
holds the new time and a reload would show the server's older one while
the new one waits in the queue; a changed day note is set on the visit in
place instead. The retry docs drop the exception they made for these two
paths.
Saving a reservation or a transport, deleting a booking, finding or
creating the place an imported stay belongs to, the review that walks
imported items through their editors with its state, and the bridge
that picks up an import or a scanned receipt from the background tasks
move out of useTripPlanner into useBookingEdits.
useTripPlanner calls the hook where the save handlers used to sit,
after the day add, delete and clear wiring and before the booking
detail, so the bridge still runs between the rail snapshot and the
splash and the planner returns the same object. The review state only
moves later in the render, which no effect observes. The two existing
loose record types in the save handlers move along unchanged.
Putting a place on a day, moving a stop to another day, taking one off,
reordering a day with the vias it carries along, renaming a day and
reordering the days move out of useTripPlanner into useDayPlanEdits.
None of it runs an effect, and useTripPlanner calls the hook where the
day plan's edits used to sit, ahead of the day add, delete and clear
wiring, so the planner returns the same object and behaves exactly as
before.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The fallback from "Tracks" to "All" once the last track disappears lived in
the desktop sidebar and the phone places browser, neither of which is mounted
while the phone map is in front: the map could sit empty under a filter no
control offers any more. useTripPlanner now owns it, once, for every screen.
The minimum-rating filter lived in the desktop sidebar's local state, so it
only ever narrowed the desktop list: the map markers and the phone's places
list ignored it. It now sits in the trip store beside the pool and category
filters, and one module (utils/placesFilter) holds the pool, category and
rating matchers that the desktop list, the phone list and mapPlaces all use,
instead of three hand-copied versions of the same checks.
Admin > Notifications gets a Defaults for users matrix: each event and channel starts On, Off (a user may still turn it on) or Blocked (off for everyone). A user's own choice is kept against the default, and a value equal to it is stored as no row, so a later change of the default reaches everyone who never chose. Blocked cells show as locked in the user settings on desktop and phone, and the server ignores any attempt to turn one on. (#1536)
Admin > Settings > Google options gets a daily limit with today's count beside it. Every call through the Places and Routes clients is counted per UTC day; once the limit is reached the Google key reads as absent until the next day, so search, details and photos fall back to OpenStreetMap the way a keyless install does, and the admin sees that the day is used up. (#1582)
The handles on the day plan and the places panel only listened to the mouse, so on an iPad they did nothing, and in the portrait band they were left out. They now follow a finger as well, show a grip on touch screens, keep the page and the map from taking the gesture, and stay in the narrow layout within the room the map needs. As separators they also move with the arrow keys. (#1012)