A new manifest capability poiCategories (up to 4, icon from an allow-list,
strict #rrggbb color) and the hook poiCategoryProvider.getPois behind the
hook:poi-category-provider permission. GET /api/plugin-pois in the plugin
contributions module answers them in the /api/maps/pois shape, with
search_plugin_pois as the MCP twin. plugin: ids no longer count as Google
place ids. SDK 1.8.0 carries the types, validation and docs.
On the map, one hook merges the built-in categories with the ones active
plugins declare, and the pill shows the plugin chips after a divider on
desktop and phone, scrolling sideways instead of shrinking a chip below
its touch target. A plugin chip searches through a repo that refuses
offline and parses the answer strictly. Its pins, on Leaflet and GL, use
the category's own icon and color, both checked again before they reach
any markup, and the hover card lists the plugin's details as escaped
text. A plugin POI can be added as a place like any other. The admin
panels on desktop and phone show a chip for the new permission and the
categories a plugin would add, from one shared module.
Addresses discussion #1781.
On a day a flight, train, ferry, cruise or bus leaves and lands, tonight's stay was seated
by its check-in time. A hotel in Munich checked into from 15:00 stood ahead of a 15:15 flight
out of Hamburg, so the drive went from the Munich hotel to Hamburg airport and reported the
flight as nine hours late. A check-in is the earliest arrival, not an appointment: such a
stay now waits behind the landing when it lies nearer the arrival than the departure, and
its check-in only holds it when the ride lands before the room is ready. A time typed on the
stop keeps its seat.
With the stays at both ends of a day switched on:
- a day with nothing but the next stay's check-in and a ride booked across it gets no drive
from one hotel to the other, the same rule the day plan follows since #2476;
- a stay is never joined by road to a terminal more than 2000 km away. The check moved to
shared/roadtrip so Days and the road trip read one copy.
A departure terminal's pin is dated: reaching it more than half a day late no longer moves
the flight to the next day without a word, it is a late warning with the full minutes.
A check-in evening also carries the stay's check-in time, as a label only.
calculate_roadtrip runs the same code and its description says all of the above.
With the Unplanned filter on, a click on a stop in the day list moved the
map while a click on the stay chip did nothing: the map only went to a
selected place it showed as a pin, and the GL map also to one of the open
day's stops, which a stay is neither. Both renderers now look the target
up the same way, the pin first, then the day's stop, then the selected
place itself, so the map goes to the stay whatever filter hides its pin.
A day that checks out of one hotel and into another, with nothing else
planned, falls back to a drive from the first hotel to the second
(#1297). A flight, train, ferry or coach booked on that day is the move
itself, but one saved without its stations leaves no waypoint behind, so
the day map, the trip overview and the phone plan all drew the full road
between the two hotels, and the phone showed it above and below the
flight (#2476). With its stations the day was already right.
- The fallback drive between the two hotels is no longer drawn when any
such booking is on the day, located or not. A day without a booking
keeps it. No line beats a wrong one.
- The phone plan takes the evening hotel leg from the last drive that
reaches the hotel, not the first, so an earlier drive to the same spot
no longer stands in for the one that closes the day.
- Google Maps and CoMaps exports of such a day, which could only ever
describe that same drive, return nothing, and on the desktop and the
phone the two buttons are left out whenever fewer than two stops
remain, instead of doing nothing on a tap.
A hotel stop planned by hand ahead of the flight already sorts behind
it through the ride seating of #2474; a test now pins that for the days
view as well.
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.
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.
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.
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
Every file the release touches was read once more, and what survived an
independent check against the code is fixed here, with a test for each.
Data and credentials
- A document sync connection edited to a new address no longer carries the
stored token along: a blank form under another origin is refused, the same
rule the probe route already had. Dawarich and AirTrail testConnection send
the stored key only to the host it was stored for.
- The key rotation script now covers document_connections.secrets and
trip_document_links.webhook_secret, which it had skipped.
- Turning a booked night into a pause in the road trip popup asks first and
says that the booking and its expense go with it. Deleting a place says the
same when a night is booked there, on the desktop and on the phone.
- A moved version marker over unchanged bytes no longer replaces the TREK file
and its attachments; a real revision carries the attachments over.
Planner
- A drop in the day plan names the row it lands ahead of in the full day, so a
hidden hotel stop no longer shifts it one slot early; the store sorts before
it splices. Via re-anchoring, the booked-night guard and the sidebar's day
membership read the stored day, the index space the server uses.
- A hotel stop placed mid-day re-pins the road trip vias of that day, and the
booking session receives the new stop order too. The Public API and the iCal
feed list a booked hotel once. A plugin can no longer write a stay against
another trip's day.
Road trip and map
- The route overview paces its requests to the public routers and retries a
rate limit, and it says which legs it could not route instead of quietly
reporting a shorter trip. A click on a via handle no longer drops a second
via; handles hide when zooming out. Read-only members cannot edit the driving
limits. The phone shows a way back when no day is picked and prints the night
drive departure in the chosen clock format.
Integrations and search
- Google transit keeps conventional trains when the train chip is on.
TREK_PLACES_ENABLED=false stops the details and enrichment calls too, and
accepts the whole boolean family. A caller-supplied description no longer
lands in the instance-wide enrichment cache. Nominatim searches carry a
timeout. An OSM place id is checked for shape before it reaches Overpass.
- Dawarich puts a stay on the trip whose dates hold it, the Atlas hand-off
knows what the Atlas already shows, and a host change with a new key clears
the old instance's state.
- Document sync: a webhook address pasted by hand works on the token alone,
deleting a connection unregisters its subscriptions, Nextcloud under a
sub-path connects, Synology answers a name collision before uploading and
verifies a move into the bin, Paperless titles are capped at 128 characters,
a DNS failure reads as unreachable rather than as a policy verdict, videos
from the store are accepted like uploads, and remote_missing has a way out.
Conflict buttons only for the owner, a refused choice is reported, a refused
credential gets a Reconnect button, disconnecting asks first on the desktop
as it does on the phone, and the folder picker no longer spins forever.
Release
- The server holds the release notice back from a bundle that cannot draw it,
so the previous shell served from the service worker cache after an update
no longer shows bare keys and no longer lets a click use the notice up.
- Import into an existing list gets the same body limit as a new list. Chat
images say when the limit is reached and the new controls are translated.
The transit backend switch reports a failed save. The Helm chart carries the
Amap credentials in its Secret; the shipped templates document the Places
API and Amap variables and the Overpass default. The wiki follows.
A trip that models one building as several stops — drop the bags, check in,
the museum inside the hotel — drew every pin on the same pixel, and only the
top one could be clicked, hovered or seen. Clustering is the mechanism for
exactly that: the plugin keeps children no zoom level can separate in one
cluster and fans them apart on click. disableClusteringAtZoom pinned the
group's own maximum zoom to 8, which both removed every cluster above it and
moved the only fan trigger into a zoom band a trip planner never operates in,
so the spiderfyOnMaxZoom set beside it could never fire.
The cutoff is gone. maxClusterRadius is a function of zoom instead: the same
generous radius as before in the overview, a pin's width above it, so stops
that are genuinely apart keep their own pins and a pile becomes a counted
bubble that fans open. Selecting a buried stop from the places rail now opens
the bubble it is hiding in rather than doing nothing visible, and it does that
without moving the camera, which stays the selection's own concern.
The GL renderer has no fan to open, so folding a pin there removes the only
thing that was left rather than providing a way in. It folds at a far tighter
radius — genuine coincidence, where nothing was reachable to begin with — and
draws the selected member of a pile, so the rail reaches it there too. A map
object without a projector now skips folding instead of dropping every marker.
The public share page spreads the same options and has no rail to fall back
on, which made it the worse case; it is fixed by the same change.
Closes#2344
A place with an uploaded image showed a plain disc of its category
colour on the map, while a picked suggestion looked fine. The marker
sized its img with width and height attributes, and stylesheet rules
beat those: Tailwind's preflight gives every img height: auto, and
Leaflet's marker pane adds width: auto. Suggested and auto photos reach
the marker as 48px square thumbs, so nothing showed. A phone photo kept
its own proportions on the GL map, a sliver inside the circle, and its
full pixel size on Leaflet, where only the rounded-off corner of a
4000px image lands inside the circle. Both maps now build the img
through one helper that sizes it in its style, so object-fit: cover
crops any photo to the circle.
The thumb loading on both maps was keyed on place ids, so a change of
picture under the same place went unnoticed until a reload:
- Taking an upload off a place left the category dot instead of
bringing its auto photo back.
- Picking another suggested photo kept the thumb of the first one,
which was cached under the provider id. A picked photo is now its own
cache key.
A day is routed as one request over all of its stops, and OSRM's car profile
forbids a u-turn at an intermediate waypoint unless asked. When it cannot obey
it refuses the whole request rather than the one leg, so a single stop on a dead
end took every leg of the day with it. Gouffre de Padirac snaps onto a cave
access road that goes nowhere else: each of that day's pairs routes fine alone,
all five together come back 400 NoRoute, and the same five with
continue_straight=false come back with their legs.
The permission goes on every request that has a stop in the middle, in the
browser and in the server's planning path, which had the same defect. Two-point
requests keep the url they had.
OSRM refuses an unknown query parameter outright rather than ignoring it, so a
router that has never heard of this one is recognised by its InvalidQuery,
asked again without it, and remembered per origin.
**A saved stay left an invisible sheet standing, and the day swipe stayed dead behind it.**
The stay sheet reopened 'rtstop' by spreading its own payload, which carries a place id.
That sheet is located by day and assignment, so it resolved to nothing and drew nothing,
while `shell.sheet` still pointed at it and `menuOpen: !!shell.sheet` kept the day swipe
blocked until something else closed it. The row rides along now, both ways, and with no row
to go back to the sheet closes rather than opening one that resolves to nothing.
**"End the day here" showed off for a day that already ends there, and switching it on
deleted that day end.** Two things close a day: the stop's own flag and a manual boundary
filed against it. The writer has always known about both; the phone switch read only the
flag, so the tap ran through the writer's boundary branch and cleared the boundary. The
question is asked once now, in the planner beside that writer, and the desktop reads the
same answer it always computed inline.
**The road trip chain printed 24-hour clocks at a reader on a twelve-hour one.** The model
hands clocks over as HH:MM, which is what the schedule computes in, and the chain, the head
card and the up-next card printed it straight. The desktop rail, the day timeline and this
tab's own sheets all honour the setting; these go through the same formatter, carried in the
row chrome so no two rows can disagree.
**The up-next card's navigate button read "places.navigate".** No locale has that key. It is
`inspector.navigation`, which is what the stop sheet beside it, the place inspector and the
place sheet all use, with the single-target label when there is only one.
**Offered roads were invisible on a satellite basemap.** The imagery is anchored under the
first layer TREK draws, found by prefix, and the roads offered for a leg are added before
the route source so they sit under the current route. That also puts them before the first
`trip-` layer, so the raster went straight over them. The prefix list knows them now.
**A journal grew a day section for every day Dawarich recorded, all year.** The hook reads
every open stay the account has, and the timeline took all of their dates. A week in May
therefore sprouted sections from September. Bounded by the journal's own span now, its
entries together with the dates of the trips it links, and with no span at all it offers
nothing rather than everything.
SonarCloud's S2871 fires on a bare .sort() over strings and asks for localeCompare, and it
had five of them on new code, which is what put new reliability at D and failed the gate.
All five sort ISO dates or query parameter names, where the default lexicographic order is
exactly the one wanted. Taking the rule's suggestion literally would have been the wrong
fix twice over: a locale-aware comparison reorders dates differently per runtime, and on
the Amap signature it would produce a canonical string Amap itself does not compute, so
every signed request would come back rejected.
So the comparator is written out rather than changed. Byte order, explicitly, with a line
saying why it has to stay byte order. Behaviour is identical: the 43 Amap cases, the 40
track cases and the 30 client cases over these modules all pass unchanged.
The credit was a round (i) under the locate button, and on a full-bleed phone map a
lone control there is the first thing the eye lands on. It is off on the phone now:
one rule in mobile.css against both engines' containers, so Leaflet, MapLibre and
Mapbox cannot drift apart. The desktop map is untouched and still shows the full
credit with its links. Owner's call, made knowingly.
With the credit gone the band the round controls floated on had nothing under it to
clear, so the 38px credit row comes out of --bottom-nav-h and out of the camera
padding the offered roads are framed with. The controls now stand on the map floor.
The night pause pills go with it. On the desktop one is a drag handle with a hint;
a phone has neither hover to explain it nor room to drag, and the markers come from
the whole drive rather than the stage on screen, so day 1 drew day 2's pill as well.
Both via lists go through the same filter, because the plan tab carries them too.
The desk offers alternative routes from the drive band between two stops;
the phone had nothing. A leg in the phone chain now carries one round
button that asks the router for other ways and switches to the map, where a
bar takes the stage bar's place: the ways as chips with their colour, time,
distance and what sets them apart, and a confirm button. On glass there is
no hover, so a tap on a chip or on a line only previews that road; confirm
writes it. The button shows only where the desk offers it (a routed leg
between chosen stops), for someone who may edit the day, and not offline,
since vias are written online only.
The picker could not have stayed open on a phone at all: the planner closed
it whenever road trip mode was off, and that mode is off on a phone by
design, so the next render shut it. The close now follows where the picker
can be seen (road trip mode on the desk, the road trip tab on a phone), and
useRouteAlternatives returns a memoised object.
The rules both shells need are shared: legReroutable in the row model,
alternativesPhase and alternativeSubline next to the overlays, and the desk
bar and sidebar read them too with no visible change. Both map engines take
an optional fitPadding, so the phone frames the leg between the day chips
and the bar instead of under them.
The credit button sat in the middle of the round controls band, beside the
locate button. On the trip map it now has the bottom right corner to
itself, a gap above the dock (or above the stage bar on the road trip
tab), and the band floats one credit row higher, so the switcher, the
compass and the locate button stay on one line.
MMapArea marks its layer as a credit corner and defines the floor the map
ends at; mobile.css places both GL engines' compact control there, keeps
an opened GL credit's button in the corner, moves the Mapbox wordmark into
the same row instead of under the compass, and unfolds the Leaflet credit
to the left of its button. MapAttributionToggle reads the same two
variables with today's values as the fallback, so the other phone maps and
the desktop keep their placement.
Two faults behind one round button on the phone map.
The base layer switcher that both engines draw in the bottom left corner
sat on top of the GL compass, which started 8px to its left and ran on
under the frosted shell, so it read as a second button showing through.
The compass now stands beside the switcher, placed off the switcher's own
exported inset and size so the two cannot drift apart again.
Satellite never drew on GL because applySatellite gave up whenever
isStyleLoaded() was false and only retried on styledata. That flag is false
while any source has tiles or a setData in flight, which on a phone is the
usual state, and styledata does not fire when those finish. On the road
trip stage every tap met a busy style, because the settings write
re-rendered the shell and handed the map fresh stage arrays. The guard is
gone (the pass only runs after load, and addSource/addLayer only refuse a
style document that is not in yet), an existing layer just flips its
visibility, and a source left without its layer still gets the layer.
The stage data in MMapArea is memoised, so a store write no longer refits
the camera or sets the route, access and place sources again.
A road trip tab on the mobile trip screen: the stage as a chain, the same
map underneath it, the sheets a passenger needs, and a search along the
route.
The addon was desktop only because road trip MODE is a data switch rather
than a view switch. It rewrites `assignments` and `places` for the whole
planner hook, and every mobile sheet is permanently mounted and reads those
same two lists, so turning it on there brought back documented regressions.
The mode flag is therefore untouched. A second flag, `roadtripFeedActive`,
feeds only the routing round and does it off the STORED assignments, so the
phone gets the drive without what the mode drags along with it.
The map is one instance shared by the plan tab and the road trip tab,
hoisted into a single JSX expression so React keeps it in the same position
and the WebGL context is never torn down on a tab switch.
The corridor search is scoped to what a phone can afford: a window of the
day rather than all of it, a smaller tile ceiling, a capped retry pass and a
deadline. The standard search is three requests where the desk makes
fifteen. The categories are deliberately not narrowed, because they all ride
one query per box, so a second one costs nothing.
Two gaps found while wiring it up are closed here as well. The fuel search
drew its offers nowhere on the phone, so the button did nothing visible;
they are in the dry band now, with the detour, the spare range and an accept
button. And the map credit opened as a two line ribbon across a screen the
map fills: both GL engines now start it collapsed, and Leaflet, which has no
collapsed mode of its own, is given one.
iconSize is a PointExpression, which has no x on its tuple form, so the client typecheck refused the size assertion; it now compares against L.point. The test ids follow the FE-PAGE-SHARED-MAP scheme in the order they run, and the comments around the cluster group say in a line what they said in a paragraph.
The Atlas country check timed out. It asked Dawarich for a year of visited
cities in one request, and Dawarich computes that over every point in the
window with nothing cached, so a dense recorder never got under the 15 s the
server allows. The year is now asked for a month at a time, one request after
another, and folded back together. The windows sit a second apart because
Dawarich reads the bounds as inclusive.
The recorded route only appeared after a reload. Every Dawarich call from the
browser carried the shared 8 s timeout, so the browser gave up while the server
carried on and cached the answer for the next page load. Calls that make the
server talk to Dawarich now wait two minutes, the Atlas four. On the GL
renderers the draw also gave up whenever isStyleLoaded() was false, which it is
while any tile is still loading, and only retried on style.load. It now waits
for idle. The hazard overlay had the same guard and gets the same wait. The
pill shows a ring while the route loads, so a slow first fetch no longer looks
like "done, nothing there".
A collapsed day now takes its recorded route off the map, following the same
folds the planner already applies to places, the road trip sidebar's included.
The ways of driving one leg come from OSRM, except the toll-free or
motorway-free offer beside them: the shipped OSRM hosts are built without
excludable road classes and answer exclude= with HTTP 400, so that one is
asked of the avoidance router instead.
Their figures were then read against each other. The quickest was elected
across both, which is what decides the road drawn as the one being driven,
and every other road was labelled with how far behind that election it sat.
The two engines agree on the road to a tenth of a percent and not at all on
how fast it may be driven: over twelve European legs the per-leg time ran
from 14.7 % under OSRM's to 13.2 % over, with the sign following the region.
On a Spanish motorway leg that gap alone is enough to crown a toll-free
detour and call the motorway somebody is on the slow way round.
An offer now carries the engine that timed it. It takes no part in electing
the quickest, no difference is printed across the two, and the bar says where
the figure came from. Each route still reports its own time; only the
subtraction is refused. An instance running its own OSRM through the MLD
pipeline answers exclude itself, so there both figures are one engine's and
the comparison works exactly as before.
Dawarich landed on dev untested, which is why release PR #2273 reads 79.0% on
SonarCloud's 80% new-code gate: 1352 of the release's 4610 uncovered units are
Dawarich's own. 946 cases across 30 files take the surface to 100% in shared,
99.7 in server and 99.6 in client, and the gate to roughly 85%.
What stays uncovered is thirteen type-narrowing guards and defensive fallbacks
the only caller cannot reach, each argued in the file beside it rather than
forced with a cast. No source, config or coverage threshold is touched.
A map keeps the datum it was built with, and Leaflet cannot change one
afterwards. The basemap could still turn into Amap's after the fact: a first
settings request that fails leaves the store on defaults for the session and is
healed later, and the raster layer then drew GCJ-02 tiles under a map still
projecting WGS-84, putting every marker, route and cluster a few hundred metres
off the street beneath it until the planner was left and reopened. The datum is
part of the element's identity now, so a flip rebuilds the map. Only the datum:
keying on the whole tile URL would throw the view away on every template edit.
Index POIs were labelled by an exact lookup of a term the index matches as a
substring, so any leaf that is not literally a term missed and took the first
category of the request instead. In a corridor search that is whichever pill
was tapped first, and the panel groups and colours on that field: a trattoria
came back as a petrol station, or a petrol station as tonight's hotel. The
label now comes from the term that actually matched, longest first.
Two outbound reads were missing what the house rule asks for. The brand-logo
fetch had no deadline at all next to a Wikidata hop that allows six seconds, so
a stalled CDN held a request context and a socket for undici's five-minute
default, and its size cap only applied after the bytes were in memory. The
Wikimedia photo proxy beside it had the same shape. And the Amap provider
buffered every answer uncapped and trusted a cast for its arrays, so a `pois`
object rather than a list threw a TypeError that surfaced as a 500 instead of
the OpenStreetMap answer.
Asked for in the feedback: a wall of round photos, and no way to tell which is
which. "Name and displaying any custom rating would be very beneficial to quickly
seeing what is around you that is of interest."
The collections map had hover switched off. The trip map has told you what a
marker is all along, so it is the same card, now shared instead of written a
third time — it already existed twice, once per renderer, and the two had drifted
in how they resolved the category icon.
The card gains a rating, and so does the marker itself: an amber disc on the
corner where a trip's day numbers sit. The two never collide, because a numbered
stop is one already planned into a day and this answers the question asked before
that. Nothing is drawn for an unrated place, which is most of them.
Also from this round: the place panel's labels move up beside the name, where a
label belongs — read three rows down they looked like a property of the rating
above them — and the rating gets a ground of its own so a line of stars stops
reading as another row of text.
Opening a place sent it from the bottom left of the map to the top, which reads
as the control moving house rather than as room being made. Nothing was in its
way: the inspector is a card centred between the sidebars and at most 800 wide,
so the corner it was clearing is one the card never reaches.
Both renderers, since the switch is not supposed to move when an instance changes
its map.
The trip planner and the journal get a "From Dawarich" list: the stays
recorded for those dates, each one a row that can be dismissed or turned
into a place or an entry after a review step that shows time, name, note
and day. A place that came from a recording carries the Dawarich mark
next to its name so its origin stays visible afterwards.
The recorded route lies over the trip and journal maps as an overlay,
coloured per day and cut along the reader's own timezone rather than
UTC. It is fetched for the window on screen and never stored.
The Atlas gets its own panel beside the map for the countries and cities
the recordings prove, and the wish list is ticked off there, on the day
of the visit, undoably. The phone has its own take on all of it, in the
mobile shell's colours and with native sheets.
Three small things noticed while working nearby, none of them Dawarich:
The map's layer switcher showed the browser's own tooltip while every
control beside it showed TREK's, so hovering along the row produced two
different boxes in two different styles.
The journey entry editor mixed TREK's date picker with a native time
input, and the date, which is the longer of the two, had the narrower
column: "12. Sept. 2026" wrapped while the clock sat half empty. The row
is split in the date's favour now and the picker is disabled together
with the rest of the form instead of staying live behind it.
On the phone's journey, the dock's "+" now turns into an upload button
while the Gallery tab is open, which is what that tab's one big action
actually is.
The share page drew every stop as its own pin while the planner drew the same
trip as a handful of clusters, so a shared link opened on three dozen
overlapping markers. On a 300px-tall map that covers a whole city, the pins
underneath can be neither read nor tapped (#2343).
The option block and the icon factory move to components/Map/markerCluster.ts,
which both maps import, so the two cannot drift apart again. The planner's
options are unchanged.
leaflet.markercluster's two stylesheets were imported by MapView.tsx alone.
The share page never mounts MapView, so it imports them itself: without them
the bubbles keep the library's default styling and spiderfy's legs are
invisible.
A short transport hop is hidden once its two ends sit closer than a few
dozen pixels, so a booking that would draw as a dot does not clutter the
map. That check compared the endpoints, which was the whole line while
every hop was straight. A car booking is routed along the road now, and a
road that leaves a town, rounds a lake and comes back to the next town along
can run across half the screen while its ends project sixty pixels apart.
The whole drive vanished until the map was zoomed far enough in for the
ends to separate, which is the gap in the route the report shows.
The decision now walks the line that will actually be drawn, road route or
arcs, and keeps the hop as soon as that reaches the floor. A straight
two-point hop measures exactly what it measured before, so every existing
threshold holds for the case it was written for. One helper serves both
renderers, which also folds the two hand-copied floor tables into it.
A day that runs past midnight used to keep every one of its stops on its own
card, so day one ended at 03:12 with three places nobody reaches on day one and
day two began with a drive that had already happened. The rail now lays the
stops out by the date they are actually reached: when the clock goes backwards
between two arrivals the ones after it move to the following day, into a block
that says where they came from, and the night drive and its kilometres go with
them. It is an arrangement, not a write — the day planner beside it is
untouched, nothing reaches the database, and a shorter stay the evening before
puts everything back on its own.
The map follows the same arrangement, because a boundary drawn in the rail and
not on the map is two answers to one question. Day one now ends where its last
reachable stop is and the line, the legs and the totals are cut to match.
Two switches in the driving settings, off by default. "Connect the days" routes
the gap between one day's last stop and the next day's first, which is real
driving the rail has never drawn, and counts it. "A colour per day" then puts
the days back into a line that has become continuous, and tints each day header
to match.
Collapsing a day from its header takes its route and its places off the map as
well as out of the rail, and the categories in the corridor panel fold the same
way. The refuel band is one full-width red band with the pump as its button,
the results are chips, and the driving dialog is two columns of panels with the
figures on one right edge.
Also fixes a via that would not adjust the route once dragged. Five separate
causes, all measured rather than guessed: leg threading used the card index
instead of the owning day's, the anchor lookup returned chain indices, it
filtered by the card rather than by the anchor's owner, seams were routed
without their vias, and a seam once routed was never re-fetched.
Four things that only showed up once it was on screen.
The range budget treated a petrol station and a charger as the same thing, so an electric
car pausing for coffee at a filling station had its battery refilled on paper: the
warnings went quiet for the rest of the day and the driver was told nothing. A vehicle can
now be named, and then only what actually refills it counts — and the search asks for that
kind alone instead of offering pumps to a car that cannot use them. Not saying keeps the
old behaviour, which is what everybody who never opens the dialog is on.
The band was its own shape in a rail made of drive bands. It is one now: the same rail,
the same height, the same type size, a warning tint instead of a ring, and the figures
where a drive band keeps them. The buttons are icons of the same 18px square the shuffle
mark uses, so the band stays one line beside its neighbours.
The old range warning stood down where the band shows. Both at once said the same thing
twice in the wrong order — the answer above the problem — because the warning marks the
stop somebody finds out at while the band sits on the leg the fuel ends on.
Two measured corrections to the search itself. The server turns a box into a circle of
half its diagonal and silently narrows anything past 20 km, so a box of ±25 asked for 35
and came back flagged as narrowed EVERY time, which read on screen as "we could not
check"; ±14 asks for 19.8 and is answered in full. And the circle is centred 12 km before
the dry point rather than on it, because only road already driven is any use — centred on
the point, half the circle was spent on road nobody can reach.
An answer that finds nothing now leaves the button rather than only a close. The place
search is a shared public service that does time out, and "it did not answer" with no way
to ask again reads as broken rather than as busy.
Extract the day route builder out of useRouteCalculation into a pure module and
aggregate it over the whole trip: one map with every day's route, each day in its
own colour, the mode per stage and the trip's total distance. Straight lines first,
roads as they answer; a refused leg keeps its line and adds no distance.
Give the floating map controls one shared drop shadow while adding the toggle, so
the new pill and the base-layer switcher stand off the tiles the way the locate
button always has. Document the control in wiki/Map-Features.md.
Addresses #1736