2796 Commits

Author SHA1 Message Date
Maurice 3a67a8e279 fix(planner): trip places in every manual location field, never at 0,0
The trip's places went into the field's search results as they were. A place
without a pin was picked as 0,0, because Number(null) is a finite 0, and a
highlight left over from a longer search could point past the shorter list
and throw on Enter. The phone's form, the train stations and the car stops
were not offered the places at all.

The places are now prepared once per form (named, with a real coordinate,
each once) and handed to every location field of the manual tab, on the
desktop and on the phone. The field shows them while it is empty or holds
fewer than three characters, narrowed by what was typed, and derives that
list rather than writing it into the search results, so a late search answer
cannot replace it and a place already picked does not reopen it.
2026-09-23 13:41:31 +02:00
hiro-nikaitou cc5957dc6a fix(planner): offer trip places in manual transport
Signed-off-by: hiro-nikaitou <vieteviete@proton.me>
2026-09-23 13:34:02 +02:00
Maurice 83d088e842 fix(roadtrip): a merged drive keeps its first piece ends when a leg comes without them
A leg a routing provider answers without its from and to points threw in
the merge, and the day it belonged to never finished routing. Such a leg now
keeps the first piece's ends; the texts are still written from the sums.
2026-09-23 12:53:24 +02:00
Maurice e921799580 test(roadtrip): mount the phone case of a refused way the way the phone suites do
The desk helper waits for road trip mode, which never turns on at phone
width, so the case timed out before it asked anything. It now waits for the
drive tab, as the phone feed suites already do.
2026-09-23 12:30:10 +02:00
Maurice 34c1dc79b5 fix(roadtrip): a drive bent through a via prints its whole length, and a refusal is said once
A via cuts a drive into one routed leg per waypoint pair. Folding them back
onto the stop pair summed the metres and seconds but kept the first piece's
printed texts and end point, and the phone reads the texts first: a drive
into the next day of 3.9 mi in 14 min showed 1.9 mi in 6 min. Every choice
under "Other ways" writes vias, so every choice ran into it. The pieces are
now merged by one shared helper that writes the texts from the sums in the
reader's unit, for a day's own run, for the drive between two days and in
the server's plan; the drive between two days is asked for again when the
unit changes, as a day's run already is.

On the desk a refused way was said in the bar's status line and again in a
toast laid over that line. The desk keeps the status line; the phone, whose
bar has no room for the sentence, keeps the toast.
2026-09-23 12:03:00 +02:00
Maurice 451e6d240b fix(roadtrip): seat rides and the drive into tomorrow by one rule, and say why a way was not taken
The slot the desktop stores for a ride within one day was worked out over the rows the day
list shows, so a hotel it hides on the far shore was left out, the ferry was filed behind
it and the road trip drove there overland before the crossing. It is now worked out over
every stop the drive reads, timed the way the drive times it. An overnight ride no longer
seats its arrival by the slot seeded on the day it left, and opens the day it lands on
unless somebody placed it there. A via behind a day's last stop goes once that stop is no
longer last, for the rail's drags and removals, the list's reorder and the server's sorts
and seated nights alike; some of them used to bend a leg of the day through it instead.

A leg OSRM drew while the avoidance router did not answer is now known as such: the picker
no longer reads it as that router's road, has the leg routed again when its own road is
chosen and says so, and a join that fell back is asked for again and flagged on its card.
A refused way that leaves a class out names the setting that drives it, the ferry hint
covers a crossing that lands the next day, a road another engine timed names that engine,
and the desk bar keeps the keyboard on the chip while a choice is checked and announces
the outcome. A ride on none of the trip's days is no longer said to have no date.
2026-09-23 11:28:24 +02:00
Maurice ab3a81a9eb fix(roadtrip): a booked ferry is driven to, and one without a date says so
A ferry between two stops without a clock was seated by the clock alone, which files a
ride with nothing timed before it at the end of the day. The desktop day list stored that
slot, and the road trip drove overland to the far shore and back to the pier before the
crossing. A ride that leaves and lands on the same day, with both terminals located, now
takes the seat between the clocks that adds the fewest straight-line kilometres. One rule
in shared seats it for the day list, the slot the desktop stores, the phone and PDF lists
and the road trip in the browser and on the server. Slots already stored are left alone.

A flight, train, ferry, cruise or bus on no day made no seam and said nothing, while the
map still drew its arc. The rail and the phone tab now name such a booking with a way to
open it, and calculate_roadtrip lists it under undatedRides.
2026-09-23 11:28:24 +02:00
Maurice e6daed9403 fix(roadtrip): the drive between two days can take another way too
The drive from where one day ended to where the next begins was the one stretch of a
connected road trip that offered no other ways. The band above a card's first stop had no
button on the desk, and the phone did not show that drive at all. A via behind a day's last
stop, where the map already files a point dropped on that drive, was deleted when the day
was reordered under Days, and was left on no stop when that last stop was removed or
dragged up the day. Switching the avoided road classes or the day's profile kept the old
road and minutes on it until a reload.

The drive in now opens the same picker as any leg, on the desk band and on a new row at
the head of the phone's chain that names where it leaves. A choice is proven with the
seam's own router and written behind the last stop of the day before, also when a day with
a single stop lies between. The rule for that via lives once in shared (carriedSeam): it
stays with the last stop while that stop stays last, and is read like any other once it is
not, for the planner's drags and removals as for the server's sorts and seated nights. The
cached answer for a seam now records its mode and avoided classes, so changing either asks
for it again.
2026-09-23 11:28:24 +02:00
Maurice 53a422d22d fix(roadtrip): other ways come from the router that draws the leg, and a choice is saved once it holds
The picker of other ways asked OSRM with nothing avoided while the rail drives a trip
that avoids a road class through Valhalla, so the list had no current road, called a road
the rail was not on the fastest, and choosing it changed nothing. A choice was stored as
one via where the offer strayed furthest, without asking whether the router then drove
it: a point on a ferry was pulled to the pier and the drive went overland, and a way
weighed away from motorways kept the motorway after its one point. An OSRM answer standing
in for a Valhalla that did not respond was cached as the avoided road, unmarked.

The rail now hands out a router per leg with the leg's own mode, avoided classes and
engine, and its days carry the line of every leg. Offers come from that engine, and the
rail's own leg always heads the list as Current. A choice is pinned and proven against
that router with at most three pins before anything is written; a way it will not follow
is not saved and says why, with a hint when it crosses by ferry, and a leg that changed
meanwhile or a device offline writes nothing. The OSRM stand-in is filed under the plain
key only and flags the day as not avoided.
2026-09-23 11:28:24 +02:00
Maurice 6124e0b056 fix(maps): amap hours are read whole or not at all, and the tip stash lives on the service
The translated hours were taken as soon as any segment parsed. A day the
parser skipped, such as a weekend written 周六、周日, then had no period, and the
client reads a day without a period as closed, so a shop open on Saturday
showed Closed there. Text that did not parse went out as a one-line list,
which the client reads as Monday and splits at the first colon, and which
kept the enrichment from falling back to OpenStreetMap's hours.

amapOpeningToOsm now also reads Amap's documented form (a colon after the
days, remarks in brackets with their own semicolons), 每天, 周末, 工作日, 星期,
到, 、, full-width colons, tilde ranges, 次日, 全天 and 24小时, and a day named
only as closed. It returns null unless every weekly segment reads, and no
hours are sent then.

The autocomplete tips move from a module-level map into AmapTipStash, one
instance on MapsService: a provider is built per request, and the pick and
its details lookup are two requests. Only the tips actually served are kept,
and a stashed tip without a coordinate answers null, so the client falls back
to its text search instead of receiving a place it cannot pin.
2026-09-23 09:36:04 +02:00
pipipiper d52154c765 fix(maps): amap answers for tip-only places, region-bearing fallback queries, and Chinese hours 2026-09-23 09:36:04 +02:00
Maurice aceb75949b fix(planner): closing the place dialog resets the whole search block
The planner keeps the place dialog mounted while it is closed, so its search
block survived a closing. Clearing the field on close (#2465) left the result
list under the empty field, together with the "Search Google instead" line
offering that list's query again, and a pick from that list wrote its name,
address and coordinates over whichever place the dialog was opened for next.

Closing now drops the list, the suggestions, the Google line's query and the
billing session, and cancels a pending autocomplete. A full search or a
suggestion lookup that answers after the dialog closed is discarded instead
of filling the next opening, and no longer ends the session a new opening may
have started.
2026-09-23 09:11:48 +02:00
Luffy 4f5a6bb7d8 fix(planner): clear place search input when modal closes 2026-09-23 09:07:48 +02:00
Maurice d609e1e202 Merge pull request #2466 from liketrek/docs/wiki-mcp-openwebui-callback
docs(wiki): the Open WebUI callback carries mcp: in front of the connection ID
2026-09-23 08:49:56 +02:00
Maurice 6f3977e4c1 docs(wiki): the Open WebUI callback carries mcp: in front of the connection ID
The MCP setup page gave Open WebUI's redirect URI as
{your Open WebUI URL}/oauth/clients/{connection id}/callback. Open WebUI puts
mcp: in front of the ID it builds that path from, so a client registered by
hand with the bare ID never matched and the consent step answered
"Unregistered redirect_uri". The page now names the real path and adds how
Open WebUI connects: it registers and trades the code from its own server, so
its container has to reach the public TREK address, and its WebUI URL has to
be the public https address.
2026-09-23 08:28:29 +02:00
Maurice 615e74aff6 fix(pwa): an upgrade counts as applied once the new build has taken over
After a server upgrade the app wrote the new version into its marker
before the service worker had fetched the new build. When that fetch
failed, when the new precache broke off halfway, or when the browser had
already swapped workers before the version check answered, the page kept
the old bundle, and every later launch took the marker at its word and
never tried again. An installed PWA went on showing the previous UI.

The marker now moves only once the handover is done or turned out to be
unnecessary, so a failed attempt is simply retried on the next launch. A
failure no longer reloads into the shell the old worker serves again, a
worker that took over before the check answered still gets its reload,
and no path reloads more than once per version and session. Caches and
workers are still never deleted.
2026-09-22 21:48:23 +02:00
Maurice bb515ac3cc fix(planner): booking codes stay blurred in the edit forms too
With "Blur booking codes" on, the bookings list and the transport details
hid a code, but opening the same booking to edit it printed the code in
the clear: the reservation and transport forms with every per-segment
code of a flight or train, the hotel editor in the day panel and the
three phone sheets. The stay's own code in the day panel, the booking
strip in the place inspector and the booking lines under a stay and a
stop on the phone showed it plainly as well.

One shared BookingCodeInput and BlurredCode now carry the setting to all
of them. A filled code field stays blurred until it is hovered or
focused, so clicking, tapping or tabbing into it still reveals the code
for editing, and the value is saved exactly as typed. With the setting
off nothing changes, and the PDF export and the share pages stay as
they were.
2026-09-22 21:48:22 +02:00
Maurice 93dddaa4af fix(mobile): booking routes on the phone map follow the day they run on
A booking switched on with "On map" stayed drawn on every day of the
phone's plan map, so a flight from the first day crossed the map of the
third, although a day chip frames one day and drops the other days' pins.
The plan map now draws such a booking only on the days it runs on, the
arrival day of an overnight one included, and the all-days view still
shows every booking that is switched on. The switch itself stays
trip-wide, and the desktop, collections and the road trip stage draw
exactly as before.

"On map" in a booking's sheet now reads as on only where the plan map
draws the route. From any other day a tap takes the map to the booking's
day, switching the route on when it is off, instead of hiding a route
that is not drawn there anyway.

The phone settings also gain the "Always show booking routes" switch the
desktop already had. As on the desktop it sets the default for trips not
yet touched on this device, so a trip where a route was switched on or
off by hand keeps that choice.
2026-09-22 21:48:22 +02:00
Maurice 1a8d5a3dd9 fix(roadtrip): a stop with a start is driven where the day list shows it
The day list draws a day by its starts, the road trip drives the order
it is stored in. Moving a stop with a start onto another day, or adding
a place that carries one, stored it at the end of the day or where it
was dropped, so the list showed it among the stops its hour falls
between while the road trip went there last. It is now stored right
behind the row the list draws it after, with a booked night read by its
check-in as the server's time sort reads it. A stop without a start
lands exactly where it did before, and wherever a moved stop lands in
the middle of a day, the via points drawn on that day stay on the legs
they were drawn for.

Behind a later start the road trip reached such a stop late, and the
night spill took its earlier clock for a midnight: the stop and every
stop after it opened the next day's card, and a day left with one stop
fell to the placeholders at the bottom. A stop the schedule marks late
now stays on the day the schedule files it on, and the stops behind it
are read from its hour, so a clock that goes back past that hour still
starts the next day. The desktop rail, the phone's road trip tab and
the MCP calculate_roadtrip tool share that spill.
2026-09-22 21:48:22 +02:00
github-actions[bot] ed8acf4c4b chore: bump version to 4.3.1 [skip ci] v4.3.1 2026-09-22 12:17:29 +00:00
Maurice a280517ff3 Merge pull request #2450 from liketrek/audit/431-fixes
fix(release): the audit round over v4.3.1
2026-09-22 13:30:38 +02:00
Maurice 2dd77aaac1 docs(wiki): the pages the 4.3.1 changes left behind
The check-in is an anchor now, not an earliest arrival, and a booked night
leads its check-in day, which the road trip and accommodation pages said
otherwise. Upgrading gains a 4.3.1 section for the reseat migration and for
what better-sqlite3 13 asks of a source install. The collaboration page knows
that links are chips and editable, the audit log knows the new admin action
and the SSO register row, and the MCP pages know the provider parameter and
the Google-only switch.
2026-09-22 13:30:12 +02:00
Maurice 85b5756e89 fix(client): the audit round over the 4.3.1 release
Ride minutes and late marks follow an edited timetable instead of the routing
that fetched them; a landing day's join band reads as the ride it is rather
than a nine hour drive of no length; the refuel offer is there on the road
out of a terminal or a pick-up desk; the phone's stage map draws the ride;
the share page numbers its stops the way the planner does; the PDF lists a
day the way the day plan lists it, from both shells; a poster-less clip draws
its play badge everywhere, not only in the editor; the hero title has room
for its descenders and the list card keeps its badge clear; the release
notice keeps its close control in reach when it stacks; a link chip's actions
are thumb-sized and its delete asks first; the Google retry line and the
Google-only switch appear only where Google answers.
2026-09-22 13:30:12 +02:00
Maurice cb85ba915c fix(roadtrip): one ride-gap rule for both corridor searches, and two seam edges
The corridor search in the browser cut its tiles around a ride, the MCP tool
did not: it tiled and measured along the straight line between two terminals,
so an assistant searching a flight day got boxes and hits under the flight
path. The gap logic moves into shared/roadtrip/corridor.ts and both sides use
it. A departure less than the check-in allowance after midnight no longer
pins on the previous evening, and a hire car whose pick-up desk has no
position is not seamed at its return desk.
2026-09-22 13:30:12 +02:00
Maurice 81292d4d2b fix(server): the booked night settles in one pass, and the sibling paths seat it too
The audit over the 4.3.1 release found the seating rule reading a night by one
value and its neighbours by another, so two nights on one day never agreed:
the migration reported moves that changed nothing, flipped the pair on the
next run and dropped the road drawn between them, and in the service every
notes edit swapped them. One rule now lives in accommodations/night-seat.ts,
measures every night by its own check-in, breaks a tie by the booking made
first, and carries the vias; the service, the reseat migration and a trip
date change all seat through it, the last of which used to append the night
at the end of its day again. An account created by a first SSO login writes
the user.register row the password path writes. Tests cover the equal and
absent check-ins, the date change, the booking form path and a day that is
nothing but its night.
2026-09-22 13:30:12 +02:00
Maurice 757967bf18 fix(map): the location button follows the viewport instead of the mount
Both renderers read window.innerWidth once while rendering, so the button
that turns GPS on was decided by the width the map happened to see when it
mounted: a phone turned sideways crosses the 768px cut and kept whatever it
had, and turning back did not bring it home either. Both now use the same
useIsPhone the shell switches on. The test stub for matchMedia answers width
queries from window.innerWidth and re-evaluates on a resize, so a breakpoint
can be driven from a test the way the browser drives it.
2026-09-22 12:57:04 +02:00
Maurice b0c19bfac1 fix(settings): the client registration form says what the server refused
Registering an OAuth client by hand and getting only "Failed to register
OAuth client" left nothing to act on: the server names the rule it broke (a
scope, the ten-client cap, a redirect URI it will not take) and the toast
swallowed it in a bare catch. Both shells show the server's message now and
keep the generic text for a failure that carries none.
2026-09-22 12:12:41 +02:00
Maurice da32eb8a3c fix(mcp): answer the discovery documents under the server address too
A client handed https://host/mcp as the server address looks for the
well-known documents below that path instead of asking the origin, and every
one of those probes fell through to the web app: 200 with an HTML body, which
reads to the client as "this server has no OAuth" (Open WebUI reports it as an
unexpected mimetype). The three documents are served under /mcp/.well-known
now, anything else there answers a JSON 404, and the RFC 8414 path-inserted
form /.well-known/oauth-authorization-server/mcp serves the metadata instead
of 404. The wiki says which redirect URI belongs to the client and that a
plain-HTTP APP_URL leaves the discovery document pointing at localhost.
2026-09-22 12:12:41 +02:00
Maurice a44df6cf11 fix(planner): place edit and delete controls follow place_edit, not day_edit
With places set to "Trip owner" a member who may edit days still saw Edit and
Delete on a place in the day plan's context menu, the inspector's action row,
its inline rename and the phone timeline's edit circle, and every one of them
ended in the server's 403. The day plan gates the two entries on place_edit
(taking a place off the day stays a day right), the inspector drops its edit,
delete and update handlers without the right the way the places pool already
does, the phone timeline offers the pencil only with it, and openPlaceEditor
and handleDeletePlace guard like their siblings.

Closes #2446
2026-09-22 11:29:43 +02:00
Maurice e510ff25ab fix(mcp): refuse an argument a tool never declared instead of dropping it
The SDK wraps a raw shape in a plain z.object, which strips an undeclared
key and runs the handler as if nothing was wrong: an assistant that wrote
stopType for stop_type was told the place was updated while nothing had
changed. The registry registers every shape as z.strictObject, so the call
answers "Unrecognized key" and tools/list advertises additionalProperties
false. The nested objects a tool takes, authored inline or spread from a
shared schema (legs, stops, links, transit places), are strict at this
boundary for the same reason; the shared schemas themselves stay open for
REST. A whole Zod schema, the way a plugin's manifest-built tool arrives,
passes through as its author wrote it.
2026-09-22 11:06:13 +02:00
Maurice 34f3aeee5f fix(trips): a trip created without a currency takes the display currency
The service wrote `data.currency || 'EUR'`, and only the trip dialog ever
sends a currency, pre-filled from the display currency in Settings. MCP
create_trip and the plugin RPC send none, so every trip an assistant created
was a EUR trip whatever the person reads amounts in. The service now asks
SettingsService for the owner's display currency, admin default merged in,
and falls back to EUR only when that is left on "Trip currency". The REST
route inherits the fallback; the web client always sends the field, so it
sees no change.
2026-09-22 11:06:13 +02:00
Maurice bdd326163f fix(client): take the reported version as one anchored match before storing it
The dev branch gate on SonarCloud has been red since the 4.3.0 cycle on this
one line: the version from /api/config is written to localStorage, and the
taint engine did not read the replace() plus equality check as sanitisation
(S8475). The value is the match of ^[\w.+-]{1,64}$ now, the same characters
and length as before, in the shape the engine recognises.
2026-09-22 10:32:08 +02:00
Maurice b1baf3baa9 fix(mobile): reservation-detail widgets mount on the phone's booking cards
A plugin declaring the reservation-detail slot mounted at the foot of every
card in the desktop ReservationsPanel and nowhere else: the phone's transport
and booking cards never rendered the slot, so on a phone in portrait the
reservation was there and the widget was not, and turning the phone sideways
made it appear. ReservationPluginSlots mounts one PluginFrame per widget under
each card, scoped to that reservation with surface detail-slot, outside the
card's body button and gone in compact mode.

Closes #2440
2026-09-22 10:32:08 +02:00
Maurice fdab3bb347 Merge pull request #2437 from liketrek/fix/mcp-search-provider-test
test(mcp): search_place hands the provider option on
2026-09-21 23:05:25 +02:00
Maurice d288459449 test(mcp): search_place hands the provider option on, and refuses any but Google
The tool passes `{ googleOnly }` to the search since #2436; four assertions
still expected the four-argument call. Pinned the new argument, and the one
case the contract added: `provider: 'google'` reaches the search as
`googleOnly: true`, any other value is refused by the schema before the
search is asked.
2026-09-21 23:04:26 +02:00
Maurice e35d95da71 Merge pull request #2436 from liketrek/feat/places-google-only
feat(places): send a search to Google, once or always
2026-09-21 22:23:59 +02:00
Maurice 6b1d65b06c fix(i18n): the Google-only strings carried a doubled escape in English 2026-09-21 22:21:02 +02:00
Maurice b6fcaced2e feat(planner): the places filter is one select, not four pills
With the tracks tab the pill row had grown past what the rail can hold: four
pills sharing one line clipped a word as soon as the panel narrowed. The
filter is the app's own select now, the count riding on the trigger the way
it rode on the active pill, and every option carrying its own in the menu.
2026-09-21 22:18:51 +02:00
Maurice e12ae950df feat(places): send a search to Google, once or always
Since 4.3.0 the TREK index and OpenStreetMap answer every search first and
Google is only asked when they find nothing, so a list with the wrong place
on it never reached Google, whatever key the instance had, and the provider
choice only decided who fills the empty case.

Two ways to change that, both only where Google holds the key slot. Under a
result list that did not come from Google, one quiet line sends the same
query to Google Places alone (`provider: 'google'` on the search contract,
the MCP search tool takes it too). And beside the key, an admin switch
"Search with Google only" hands every search and every suggestion to Google
and asks nothing of the index or OpenStreetMap; off by default, read by the
search after the keyed provider so an install without a key, or one on Amap
or OpenStreetMap, is untouched. The panel says so beside the switch.

Both shells share the one rule for when the line is offered (placeSource),
and the wiki names both switches.
2026-09-21 22:14:44 +02:00
Maurice 11b1399b5c fix(pdf): list a day the way the plan lists it, without the booked night's stop
Booking a night writes the hotel onto its check-in day as a stop, and since
4.3.1 that stop heads its day. The day plan hides it and shows the booking as
the day's accommodation block, but the desktop toolbar hands the export the
store's own assignments, filter and all, so the PDF printed the hotel place
above a flight that left in the morning.

The export now drops that stop before it merges the day, as the list does. The
mobile export already received the filtered set and was right all along.

Closes #2434
2026-09-21 20:54:23 +02:00
Maurice 0644106f7a fix(collections): a marker click shows the place, every time
A marker click toggled the selection, so the second click on the marker of the
place that was open closed it again, which read as the details refusing to
open: every other click on the same marker showed nothing. A marker click now
selects and never clears; the map background and the close control do that,
and a list row still toggles as before.

Closes #2431
2026-09-21 19:35:41 +02:00
Maurice 903378f8ad fix(maps): build the day's road from the stops the day plan shows
Booking a night writes the hotel onto its check-in day as a stop, for the road
trip's driving chain. The day plan hides that stop and draws the hotel as the
day's bookends instead, but the route builder behind the map line and the
phone's travel connectors still read it from the store. It carries no time of
its own, and a timed booking is seated behind the last timed stop, so on a day
that opened with a flight the hidden stop led the day: the map drove from the
hotel to the departure airport and back to the hotel after landing.

The builder now leaves the booked night's stop out, as the list does, and the
bookend rules put the hotel where it belongs: behind the flight, on the road
from the arrival airport. A hotel the traveller placed as a stop of their own
is untouched.

Closes #2430
2026-09-21 18:09:44 +02:00
Maurice 84d3dd0c4a fix(roadtrip): a hop between two desks keeps its line and drops the pill
The hire desk beside the terminal is a 200 m leg, and the pill under it read
"0 km in 0 min" where the eye wants the next stop. Under 150 m and two
minutes a leg is a hop: the rail keeps the dashed line, the phone chain skips
the row, and neither offers other ways of driving it. A note a plugin
attached keeps the row.

Refs #2428
2026-09-21 17:16:19 +02:00
Maurice bdb031a1e9 feat(roadtrip): hire car desks, bookings under stops, the ride as one block
A hire car is no seam, but its desks are where the drive starts and ends: the
pick-up opens its day when nothing timed comes first, the return closes the
day it is handed back on, the road runs through both and the range budget
starts from a full tank at the pick-up. The return is only placed when the
booking names a return day and a located return desk.

A table, tickets or a tour on the day hang under the stop they are pinned to
or booked at as chips that open the booking; one for no stop is listed under
the day. A ride that leaves and lands on the same day is one block on the
rail and in the phone chain, not two terminal rows and a band. Both shells
read the same helpers (carrierRide, stopBookings, roadtripRowModel).

Server reads car bookings into the plan context; the MCP descriptions say so.
The pt-BR locale had the three ride strings in Breton, fixed alongside.

Refs #2428
2026-09-21 17:05:42 +02:00
Maurice 0b4230ff4c fix(roadtrip): the booking a terminal opens has somewhere to open
The day panel owns the transport detail dialog, and in road trip mode the
day panel is not mounted: a terminal row, a ride pill and a map endpoint all
set the booking to show and nothing showed it. The rail brings the dialog
along now. Also drops a translation key nothing read.
2026-09-21 16:17:49 +02:00
Maurice fa35966ab8 feat(roadtrip): a flight, train or ferry booking seams the drive at its terminals
The road trip routed straight from one stop to the next, whatever stood
between them: a flight booked for the afternoon became an eight-hour drive
on the map, with the hotel reached by road from a town the traveller had
flown out of (issue #2428). The day plan has treated such bookings as
carriers for a while, and the drive now reads them the same way.

A flight, train, ferry, cruise or bus with located terminals is a seam. Its
two terminals are seated among the day's stops where the day plan lists the
booking, as stops of their own: no number, no stay, no kind, not draggable,
and an id below every assignment and every automatic night, because they
stand for none. The road ends at the departure terminal and starts again at
the arrival one; the leg between them is the ride, in the booking's own
mode, with the timetable's minutes and no distance, and no router is ever
asked for it. The departure terminal is pinned a check-in ahead of the
timetable and left on the timetable's minute, so a drive that cannot make
the airport in time is late the way a missed pin is. A ride that lands on a
later day seats its arrival at the start of that day; with connected days
the join between the two is the ride, and with daily travel times set the
ride runs on the booking's clock through the night, with no day end placed
on a flight.

The map draws the ride as the booking's own arc, the one the day plan
already draws, beside the roads. The day's distance and driving time count
the roads only, the range budget spends nothing on a ride, and after one the
car cannot take it starts afresh. The corridor search leaves the ride out
and drops a hit under the flight path; a via cannot be filed against a
terminal, and other ways of driving are offered for the road into a
departure terminal only. The server plan reads the same three tables and
seats the same stops, so calculate_roadtrip answers the same as the rail.

Closes #2428
2026-09-21 16:03:21 +02:00
Maurice 4be2314856 docs(wiki): name the toolchain npm asks for on install since better-sqlite3 13 2026-09-21 11:09:38 +02:00
Maurice f43b89be13 test(journey): the editor tile for a clip with and without its poster 2026-09-21 11:08:38 +02:00
Maurice ac23773352 fix(journey): the map waits for its first view before it pans to the active entry
The journey map sets its first view on a rAF after the build, and the active
entry asks for its pan on a 50 ms timer. In a background tab, or on a slow
frame, the timer wins. Leaflet does not refuse a pan on a viewless map, it
takes it as the first view with the zoom still undefined: the tile layer aborts
its own add on that, the load event stops halfway, and every entry marker
queued behind it never reaches the map. The catch that was meant for this
never fired, because nothing threw. The next rebuild, after any save on the
page, then tore down markers that were never added and died in Leaflet's icon
removal, and the error boundary took the whole journey page with it.

Both pans go through whenReady now: at once on a map with a view, otherwise
right after the fit lands.
2026-09-21 11:02:01 +02:00
Maurice 8138c83558 fix(journey): the video player brings its own icons instead of asking cdn.plyr.io
Plyr's defaults point at its CDN twice: the icon sprite for the controls is
fetched over XHR, and on teardown the element is pointed at a blank clip there
to abort the stream. connect-src and media-src both refuse that host, so the
clip played but every button in the player was blank, and each close of the
lightbox logged a blocked request and an uncaught XHR error.

The sprite ships with the bundle now, served from this origin, which Plyr then
references directly instead of loading it. plyr's exports field hides the file,
so it goes through the same resolver alias the rtl-text plugin already needed.
The teardown source is empty: that aborts the stream just the same, and the
element is unmounted in the same tick.

Refs #2341
2026-09-21 11:02:01 +02:00