A place closing at midnight is no longer read as open past it: the hours
run on only where the next date opens at midnight (any window of the day
counts, as written order is kept), and an impossible date such as
2026-02-31 reads as unknown instead of rolling into March. The hours
source names the trip's places rather than "your own" entries, in all
27 locales, as the pill beside it does.
The day a walk's arrivals are checked for shows its date on the pill,
not only "Day N". The 12 hour clock is tested for a window to midnight.
The along-the-tour term table is built once again, now that the one test
that stubbed the places client without its real exports spreads them.
New test seeding goes through the ORM factories.
Along the tour (PLN-02, PLN-03): the list shows one line per place (its
icon, name and distance from the route) and scrolls past ten. A place
picked in the list or on the map opens its card over the map, where the
other cards of the planner sit, with what the source says and the way to
take it in as a stop. What taking it in did is one line in the list. The
intro, the theme line and the out-of-date note are gone: the themes say
what they look for on hover, and the search button says when to search
again.
A stop taken in from there keeps the kind of place it was found as
(migration 045400, poi_category, read back only where the contract knows
the kind) and its number carries that kind's icon at the corner, as a
via ferrata's end does.
City walks (CW-02, CW-03, CW-10): a stay nobody entered says nothing.
While no stay is entered at all the walk is planned as its walking time,
with no "unknown" and no "at least". A must-see rings its number, an
optional stop has a dashed ring, a protected one a shield at the corner;
only the stay stays under the name, below the coordinates.
An assistant connected over MCP can now prepare a city walk from what the
traveller tells it. propose_city_walk takes a start (and an end), a time
frame on a day of the trip, the themes of CW-05, the stations the
traveller wishes for, the places of the trip it may use and fixed
appointments with their times, and saves nothing. Places for the themes
come from the search along a tour, at most eight, each with the theme,
category and source that brought it; nothing is made up. The stations are
ordered on estimates over the straight distance, and a place of a theme or
of the trip goes in only where it is open on arrival, keeps the frame and
the appointments, and leaves every station already on the walk open when
the walk gets there. The walk in that order is then planned on foot by the
router the road trip uses (the traveller's own routing server where they
set one), and its legs give the walking distance and time from one station
to the next. Where the router cannot be reached, or a station lies more
than 300 m off every way (the planner's limit, now shared with the
server), the times are estimates over the straight distance with an
allowance for streets at 4.5 km/h, and the answer says so and why. The
answer gives the arrival and leave times, the stay where the traveller
gave it and unknown otherwise, which makes every later arrival the
earliest one, and the opening hours checked against the arrival on that
day with the helpers the planner uses. Walking and stays are counted
apart, the total is complete only when every stay is known, and the
minutes the frame leaves for the unknown stays are named. Free time before
an appointment or at the end, wishes and appointments that cannot be kept
as asked, and places left out with their reason are listed; a tour of the
trip offered as a station is named as a tour. A wish or an appointment is
never dropped; an appointment is a must and protected. A place's default
duration is not read as a stay: every place carries one, so it says
nothing.
Once the traveller confirms, create_city_walk saves the walk through the
same creation the planner's save uses: one write, a key for every station,
the links to the trip's places checked, the trip told, with place editing
permission on the trip. Its way on foot is planned by the same router and
saved with the walk, together with its distance and walking time. Where
the router gives no way, the walk is saved with its stations only and the
answer says why: no line, no distance and no walking time. The tours list
says so (route_pending), the tour detail says in one line that the way is
planned in the planner, and opening the walk in the planner plans its way
at once and offers to save it, with a note once it is planned that it was
just planned. Such a walk can be saved as a new tour and taken over into
another trip, starting at its first stop, and is never offered as a place
to stay. A tour without a line needs two points of its own.
get_tour_plan now spells out every field of a city walk's stations: key,
name, stay (null when not known), importance (null for normal), protection
(false when not set) and opening hours (null when none are known);
list_tours names the station count and whether a walk waits for its way,
get_city_walk_progress gives each station's key and opening hours, and
take_over_tour says a walk keeps its stations with their protection. No
tool changes a walk's stations; deleting a walk's place takes its stations
along, protected ones included, and both delete tools now ask the
assistant to name those and ask the traveller first. REST answers the
stations as before.
A tour create refused for a type that is switched off, a description that
is too long or planned times out of range is a domain refusal now, so an
assistant gets the same text REST sends; REST still answers it as an error
at 400.
When the name of maps.app.goo.gl or goo.gl could not be looked up for a
moment, the walk import said the short link leads somewhere other than
Google Maps. It now says the link could not be followed just now and answers
502, as for any other failure on the way; a name that leads into this
network is still refused.
A short link Google answers with 404 or 410 is one it does not know, and
asking again changes nothing. The dialog now says so in the reader's
language and asks to share the route from Google Maps again, with a 400 and
the reason short_link_unknown, instead of suggesting to try again later. A
busy or blocking answer from Google stays a failure that may pass.
The MCP tool preview_google_maps_walk is marked open world, since it follows
a short link to Google on every call, and the English text REST and MCP send
for each reason is held to the English text the planner shows.
"Add tour" in the planner now offers "Import walk from Google Maps". The
dialog takes the link of a route shared in Google Maps, either a
maps.app.goo.gl short link or the long google.com/maps/dir link, or the
share text of the Google Maps app with the one link in it. It shows the
stops in their order with their names, marks a stop the link gives no
position as such, says that the route comes from Google Maps with a link to
open it there, and that TREK plans the way between the stops itself and
does not take the line drawn in Google Maps over. Nothing is written until
"Load as walk": that opens a new, unsaved city walk in the planner with the
positioned stops and their names, routed on foot by TREK, and warns first
when it replaces unsaved work. A stop without a position is left out and
never looked up or guessed; with fewer than two positions nothing can be
loaded. While the walk is not saved, a calm notice in the planner says
where it came from, also after a reload of the draft. Cancelling or a
failure leaves every tour as it was.
Only a route the link marks unambiguously as on foot is read. A car, bike
or transit route, a link that does not say, or one that says two things is
refused with a reason the dialog gives in the reader's language. Each stop
takes its position from its own block of the link's data, so a line
dragged through extra points or a stop given as coordinates no longer
leaves the stops without positions or shifts them. The road trip and
places imports read links as before.
A short link is followed by a resolver of its own: every hop is checked
against the Google Maps short link shapes and the strict SSRF guard before
it is looked up or asked, so ALLOW_INTERNAL_NETWORK and
ALLOW_LINK_LOCAL_IPS do not open it. Each request goes to the vetted
addresses only, follows nothing by itself, reads no body and carries no
credentials; the chain has one deadline including name lookups, at most
three hops and a cap on the Location header, and the long link it ends on
must be Google Maps and is never fetched. The preview is POST
/api/trips/:tripId/tours/import/google-maps/preview for members who may
edit places, with a 502 when Google could not be reached, and the MCP tool
preview_google_maps_walk reads the same.
A suite that stubs the places index client without its term table could no
longer load the maps service, because the list of every term was built when
the module was first imported. It is read where a row is judged now.
A city walk created or changed while offline now carries its stops, the
minutes its stays are entered with and how many have none yet, worked out
from its points the way the server will answer them. The day row, the tour
detail and the summary show the walking time with the stays as soon as the
walk is saved, with "at least" where a stay is missing, and a stay changed
while the write waits shows its new total rather than the one of the last
answer. The server and the client count the stops in one shared place.
The planner plans a walk whose walking time is not known yet with at least
the stays entered, as its overview says, so the planned field and the check
against the longest plan read the same total.
In the tour detail a longer time such as an "at least" total wraps on a
phone instead of being cut, and the boxes of a row keep one height. A total
entered by hand falls back to its short name where its column is too narrow.
A stay, a name, an importance or a protection set in a stop's dialog no
longer plans a city walk's route again: the walk stays ready to save, its
time overview follows at once, and one undo takes the change back without
routing either. A stop moved or added plans the route again as before. A
walk saves no breaks, so a cafe never counts as a stay and as a break at
once, and a break left from another type neither counts nor holds its save
up.
The planner and the tour detail show a walk's walking time, its stays and
their total apart and in the same words: 45 minutes of walking, a museum of
90 minutes and a cafe of 30 minutes make 2 h 45 min. A stay not entered yet
makes the stays and the total at least what is known, stays nobody entered
read not known yet, and stays entered as 0 read 0 min. A total entered by
hand keeps its own label, and the computed total shows beside it. The head
of the tour detail, the day row on the desktop and the phone, the tour
summary and the library of other trips show that same total, with a ≥ where
a stay is missing. Their time on the move is called walking, and the summary
says a walk is planned with its stays rather than breaks.
The tour list and the library carry the minutes a walk's stops are entered
with and how many have no stay yet, counted from the walk's points, so the
way back of a loop is no stop and its start's stay counts once. A loop run
the other way keeps its start as the stop there, with its key, name and
stay.
The planner's waypoint list keeps the rows of a leg in a file of their own,
and the tour detail its name heading in a part of its own, so both stay
within their size limits.
A walk saved from the planner kept its stops without the keys the server
gave them, so the next save from the same open planner handed every stop a
new key and dropped the visits checked off in the day plan meanwhile. The
planner now takes over the keys a save answers, point by point, without
planning the route again, so a stop keeps its key and its visits from one
save to the next.
The edits of a stop's dialog now reach the planner: a changed name, stay,
importance or protection counted as no change and was dropped. It is a step
of its own that can be undone, Save stays available, and the route is not
planned again for it.
A save that takes stops out of a walk, or makes it another type, tells every
client of the trip which visits are left on each day the walk is on, the
client that saved included, so the progress in the day row no longer counts
a stop the walk has lost.
The way back of a loop is the start again and no stop of its own. Closing a
loop gives it only the start's spot and the place it stands for, and the
planner's stays, the stops of the tour detail, the stop count of the tour
list, the visits and the progress an assistant reads leave it out, so a loop
through three stops counts three, the start's stay counts once and the way
back cannot be checked off on its own. A loop saved before keeps working.
The rule lives in one helper the server and the client share, and the tour
list takes its stop count from the walk's points through it.
The tests that seed a walk for the assistant's progress go through the ORM
factories.
Marking a stop of a city walk visited or skipped answered with a server
error every time: the write that keeps a changed status stamped its time as
a function call, which neither SQLite nor Postgres takes. It now uses the
clock the database itself spells, so a stop checked off, skipped or taken
back stays that way and the day plan of everyone on the trip follows.
The Save button of a stop's dialog in the planner did nothing, because it
sat outside the dialog's form. It saves the name, stay, importance and
protection again. A walk saved with two points that carry one key, as a
loop back to the start does, keeps that key on the first point and gives
the other one of its own, so checking one off never marks both. A walk put
on one day twice is read for an assistant from its first assignment.
The server tests cover the visits from the repository to the routes: per
day, through a moved and a removed assignment, a copy of the walk and a
copied trip without visits, a removed stop taking its visits along, and
the refusals for another trip, a stop the walk does not have, a member who
may not edit days and an unknown key or body. The MCP progress of a walk,
the station fields and counts of the tour list, and the contracts in
shared are tested too; the existing tests expect the keys every saved
point now carries, the station count of a list row and the new entity.
The client tests cover the stop dialog and its pills, the walk themes and
categories of the search along a tour, the stop menu of the waypoint list,
the walk's timing, routing and type choice, and answer the trip planner's
read of the visits in the tests instead of leaving it unanswered.
The filter for a walk's stations in the planner, the opening windows of a
walk's schedule and the station shape of a walk's progress were exported,
although nothing outside their own file reads them, so the check for unused
exports counted three more than it allows. They are local to their files now;
a city walk plans, checks off and times its stops as before.
While the public Overpass mirrors do not answer, every search along a hike
with viewpoints, huts or drinking water waited out the whole twelve second
deadline only to name those kinds as not searchable. Once every mirror has
failed the search along a line, and no other search along a line was
answered in the meantime, the next searches for three minutes leave Overpass
alone and name its kinds at once, next to the places the index found; a later
stretch of a long line that was already being searched does not wait either.
A stretch answered shortly before keeps its places from the cache during the
pause. After the three minutes the next search asks again for real, and any
answer ends the pause. The log holds one line per pause with the time it
ends, next to the line of the search that failed. The explore buttons of the
map and the other Overpass lookups are unchanged.
The search along a tour found nothing. It asked the places index for all
kinds of a stretch at once, above the 100 rows and 200 characters of terms
the index takes, so the index refused and every stretch went to the public
Overpass mirrors, which do not answer from this server. Now the index is
asked in small circles strung along the line, one request per kind and
circle within its limits, and an empty circle counts as an answer, so a
city walk lists its sights, museums, parks, cafes, restaurants and markets
in well under a second. The kinds the index does not know (viewpoints,
huts, drinking water, toilets and the like) are asked of Overpass along the
whole line at the same time, one query per stretch of up to 50 km, three at
a time, each with a short deadline of its own; a line past 400 km is
searched along its start and says so. When the index answers no circle at
all, its kinds go to Overpass beside the others instead of after them. A
kind no source could be searched for is named in a calm notice above the
list instead of reporting nothing found, and the reason is written to the
log. Unnamed taps, toilets, shelters, picnic spots, viewpoints, summits,
slipways and repair stands stay in the list and on the map under the name
of their kind; an unnamed garden, cafe or memorial is left out. The places
nearest the line are kept when there are more than sixty, listed from the
start, with the opening hours the source has.
A walk's parks are parks and gardens only, and its museums are museums
and galleries only, no longer the beaches, summits, theatres and cinemas of
the map's explore buttons; a water park the index files under parks is not
one of them. The index also answers markets and summits. The explore
buttons keep their single request, and a search of several kinds that
would exceed the index limits is asked one kind at a time instead of being
refused. The MCP tool search_tour_pois and its wiki row describe the walk
kinds and the new unavailable list, and the OVERPASS_TIMEOUT_MS row says
the search along a tour keeps its own shorter deadline.
The day assignments entity carries the station visits that hang off it, as
the generator writes it. A city walk gets its own placeholder for an untitled
name, the tour service tests hand in the visits repository the service now
takes, and the files the walk touched are formatted. The theme icons of the
place search overlap by their start margin, so they stack the same way in
both reading directions.
City walk is a type of the planner: routed on foot without a hiking limit, on
the standard map. Each stop is edited in its own dialog, its name, its stay
(empty is not known, 0 a point passed through), its importance and whether a
proposed change must leave it; none of it plans the route again. The walk's
time is walking plus the stays, at least that much while a stay is unknown,
with no breaks on top.
A tour can be a city walk. Its points are stations that keep a key across
saves, a name, a stay (0 for a point passed through, empty when unknown), an
importance and a protection; the visits of a walk's stations are kept per day
assignment, so another day or a copy starts with nothing visited. A station
that is gone takes its visits with it. An assistant reads how far a walk got
on a day, what is open and which times are known.
The list at the top of test.yml now names every step group its jobs hold,
the contributing guide names the parity job as it is called now, and
db-mock.ts says test-db reaches the auth controller only when a rate limit
is reset.
test-db.ts imported AuthPublicController at the top for resetRateLimits,
which pulled the auth graph (bcrypt, otplib, nodemailer, the MCP SDK) into
every file using the helper; most never reset a rate limit. Every caller
already awaits the helper.
The test jobs waited for the quality jobs (~9 min) and the client suite ran
on one runner (20-35 min), so a run took 45-60 min before ci-ok answered.
Every test job now starts right after changes. The client suite runs in
eight slices and the server suite in six, each with coverage into a blob
report; client-coverage and server-coverage merge the blobs, check the
thresholds against the whole and upload the lcov the scan reads. The scan
needs the gates directly, so a failed gate still stops it.
A tour can be a bike tour with a routing profile (touring, road, gravel,
mtb), stored per tour by a new migration. The server grades the ways of a
mountain bike route from OpenStreetMap (mtb:scale) through one shared
Overpass client, offers CyclOSM behind its own admin switch, and exports a
bike tour's GPX with the activity of its profile. A hike's route choices
are now exactly the three the card names: steep climbs, steps and ferries.
The rail reads the trip's tour ids from the workspace it already gets,
so the page line stays as it was; the map's size entry follows the
lines it lost.
The editor gains a card under the waypoints: along the tour. It offers what
the tour type stops for as chips (a hike viewpoints, huts and shelters,
drinking water, food, summits, toilets, picnic spots, campsites, sights; a
paddle tour put-ins, boat rental and the like, the first three ticked) and
how far from the route to look, 250 m to 2 km. Only its button searches.
Each place found shows how far it lies from the route, straight across, and
where along the route; its pin is on the map in its category's colour, and
a click there opens it in the card. Opened, a place says its kind and
source, its address, opening hours as the source has them with a note to
check them, and its website. The answer says when it is out of date (the
route or the choices changed since), offline, failed, partly searched, cut
short at the first part of a long tour, or more than shown. Nothing about
the tour changes by searching.
An opened place can go in as a stop: after the point chosen, the one before
it along the route suggested, by the button alone, as one step undo takes
back. It becomes a stop of the tour on the place's spot, not a place of the
trip. A leg drawn by hand or a fixed line (a via ferrata, a portage) is not
cut: the point after which it would go says why and offers no button. Once
the new route is in, the card says what the stop added, as the difference
of the route planned before and after; while it is planned it says so, and
where either route could not be planned it says that rather than guess.
The search merges a place OpenStreetMap maps twice, as a point and as its
outline, by its name and spot. The library repository takes its offline
error from a shared online-only module, which the search uses too.
POST /api/trips/:tripId/tours/poi-search looks for what a hike or a paddle
tour stops for along a line: viewpoints, peaks, shelters, drinking water,
food, toilets, picnic spots, campsites and sights, and on the water
slipways and boat rental. The line comes in the body, so a draft not yet
saved can be searched, thinned to at most 1000 points; the corridor is
50 m to 2 km wide.
The line is cut into stretches as the road trip's corridor search does,
each searched through the map search (the TREK places index first,
OpenStreetMap after), three at a time; a long line is searched along its
first twelve stretches and the answer says so (capped). The places kept
lie within the corridor of the line itself, each once, nearest the start
first, up to 60, each with its source, kind, address, website, phone and
opening hours as the source has them, its distance straight across to the
line (distance_m, never called a detour) and how far along it lies. A
stretch that fails leaves the others' places and marks the answer partial;
a cut answer is truncated. Nothing is written. It is a read, so anyone who
sees the trip may ask.
The map search's OSM filters gain those outdoor categories; the explore
pill does not offer them. MCP: search_tour_pois, along a saved tour's line.
GET /api/tours/library lists the tours of every trip the user owns or is a
member of, with the trip each is in and how many tours each trip holds, to
choose one to take over into the trip being planned. It is narrowed by a
part of the name (matched ignoring case, a "%" or "_" just a character), a
type or one trip, and the asking trip can be left out; up to 100 tours, and
the answer says when there were more. GET /api/tours/library/:placeId
previews one: its facts and its line thinned to 400 points, its ends kept.
Neither is trip-scoped, so every answer is bounded by the user's own trips,
read anew each call: a tour of a trip out of reach is "not found", nothing
hints that it exists. Measures stay null where the tour has none.
POST /api/trips/:tripId/tours/take-over copies one into this trip, which the
guard checks with 'place_edit'; the source trip is checked against the
user's trips as they are at that moment, and the source is read again inside
the write. The copy is a tour of its own: its line, measures, settings and
control points, without days, without its stops' links to places of the
other trip and without the obstacle decisions taken there; it keeps the
import record of its line. A copy that fails leaves nothing behind.
Where it came from is kept as text (tours.taken_over_from_trip_title and
taken_over_from_tour_name, migration 045100), not as a pointer, and read
back in the provenance as takenOverFrom; a copy of such a copy names it too.
The in-trip copy (P2C-07) and the take-over share one copy routine.
MCP: list_tour_library and take_over_tour, over the same service and
checks. The repository tests follow the hiking preferences column of MF-08.
While a hike is planned, the planner's map switch offers "Hiking" beside the
map, topo and satellite: OpenHikingMap, with marked trails over the terrain.
It is a choice of the planner's view alone, like topo: the global map
setting, the tour and its days stay as they are. With no tour open, over an
import viewed as it came in and for a paddle tour it is not offered, and a
choice made earlier falls back to the standard map there.
openmaps.fr's tile usage policy is followed: tiles load only as the map
shows them, with the browser's Referer, from the server's own caching; no
service worker rule keeps them and the 3D flyover's warm-up passes their
source by. Its credit and donation link are on the map; the 3D map, whose
attribution is folded away under the panel, shows them as a line of its own
above its controls.
The service is best effort. A run of failed tiles while it is shown marks
the switch, with a tooltip and a status for screen readers that say it is
not answering and that the tour is untouched; one tile arriving clears it.
The tile health the seamark overlay had is now useTileHealth, shared by both.
The source is free for non-commercial installs up to 500,000 tiles a month.
tours_hiking_map_enabled is an admin switch under Tours (admin tours-hiking-map,
audited), on by default, for an install that is commercial or larger to
switch off until it has an agreement with the operator. /api/addons carries
it as toursHikingMap, true only while Tours is on.
Leaflet, GL and the 3D map read one table of the planner's raster bases
(tourRasterBases), so topo and the hiking map are drawn the same way.
tile.openmaps.fr is on the CSP's connect-src for the GL maps.
A hike can ask the router for more than its difficulty limit: avoid steps,
avoid ferries, prefer footpaths, shorter over faster. They sit under the
limit in the planner's details as switches, each with what it really does,
and the info dot says they weigh the way and give way where there is no
other, and that none grades a path. Switching one plans the route again as
one step of the history, and the choice is kept in the draft.
Only preferences the public Valhalla was seen to honour are offered, with
the measured cases beside hikingCostingOptions: step_penalty took Würzburg
to the Festung from four flights to one, use_ferry 0 sent Kiel round the
Förde and Hamburg through the Elbe tunnel, walkway_factor moved Garmisch to
the Partnachklamm onto the paths, shortest cut the way to the Eckbauer.
use_hills and use_tracks showed no effect on foot and are left out, as are
marked, paved and rough ways, for which the router has no option.
tours.hiking_preferences (migration 045000) holds them as JSON, NULL for
none, so no tour saved before routes differently. Only a hike keeps them;
a paddle tour naming one is refused. A copy carries them, and list_tours
and get_tour_plan read them.
The trip copy's waypoint links now follow the copied places inside
TourWaypointsRepository.insertForPlace.
The lines of the paddle notes (a rule over a whole stretch, an obstacle's
extent) were drawn over the tour's own route, so where a rule covered the
river the route under it could not be seen. They now go under it: on
Leaflet in a pane placed right before the overlay pane (the app levels every
pane, so the order decides), on MapLibre/Mapbox before the route's casing.
The markers stay on top, where they are picked.
A region whose network file was gone answered every leg with "The route
planner is busy", which no retry could change. The router now forgets such
a network and its legs say the area has no water data, so loading the
region (PAD-11C) builds the network again and the planner plans on.
The boot sweep left a database without any network alone instead of
clearing every file it did not know: a test's empty database, or one
restored without its data folder, no longer throws away networks another
database still names.
A tour's waypoint can stand for a place of its trip, chosen on purpose from
"Trip places" in the planner: searchable, nearest to the tour first, grouped
into near the tour and the rest, and a place already in the tour says so. A
pick adds the stop on the place's spot as one step of the history; the row
names it after the place and says it is a place of the trip. The point keeps
its own coordinates, the place stays a place of its own and its days are
left alone. Moving the point or "Unlink from the place" drops the link;
removing it never touches the place.
tour_waypoints.linked_place_id (migration 044900) references places with
ON DELETE SET NULL, so a deleted place only unlinks. A save checks the links
inside its transaction: a tour of the trip is refused, a place that is gone
or of another trip leaves the point unlinked, the same either way. The trip
copy points the links at the copied places, the GPX export writes the linked
stops as named waypoints, and get_tour_plan names them.
nearnessToLine in @trek/shared measures a point against a route, for the
picker and for the search along a route that comes next.
A saved tour now has a technical revision, raised inside the transaction of
every write that changes it: the route and its controls, the plan laid onto a
recorded paddle track, going back to that track. A write that names the
revision it was based on (expected_revision) is refused with a 409
{ code: 'tour_revision_conflict', revision } before anything of it is
written, so a stale draft never overwrites a newer tour unseen. A write that
names none writes as before, so older clients and queued offline writes keep
working; the queue never carries a base. A copy starts at 1, a source's
version stays provenance. Migration 044800 adds the column; list_tours and
get_tour_plan read it.
The editor follows the trip's tours live: a newer revision of the open tour
loads at once while the editor has no changes of its own, and shows a notice
while it has. A refused save keeps the draft and asks: save it as a new tour,
load the newer version, or overwrite on purpose.
useTourPlanner was at the size limit; its derived facts, undo history and
draft storage move into tourPlannerFacts, usePlannerHistory and
useTourDraftStorage, unchanged in behaviour.
A saved tour can be handed over as a GPX file on purpose: its whole saved
line as one track with the heights it has and none it lacks, its name,
description and link, and the activity in <type>. The file is named after
the tour. A tour without a line of two points is refused with 422 rather
than written as a file that imports as nothing; the export changes nothing.
GET /api/trips/:tripId/tours/:placeId/export.gpx, readable by anyone who
sees the trip, and the MCP tool export_tour_gpx with the same rules. The
tour detail gets a "GPX" action beside the navigation, the planner head a
download button that waits while the tour has unsaved changes.
The tours rail head now uses the same plain icon buttons as the editor.
A shortcut was held to the dearer of its two end cells, so an end out on open water lifted the ceiling just as a bank end did before: a leg from a point out on a lake was pulled straight across, through water the search had gone round, for a SUP beyond its reach and for the shore style beyond its band. The same happened midway once a kept vertex lay in water past the boat's reach. A shortcut is now priced along its line on open water and taken only when it costs no more than the stretch of cell path it replaces (with 5 % to spare); the shore distance check stays as it was.
Measured on the WATER-005 lake and a larger one with a bay and a spit: legs from bank to bank are as before within a few metres, except a kayak no longer crosses 409 m of water past its reach that the search avoided; from a point 500 m out a SUP covers 321 m past its reach (300 m cannot be avoided) instead of 1301 m, a canoe 133 m instead of 1132 m, and the shore style follows the bank. WATER-012 holds the case from a point out on the lake.
The paddle obstacles domain held 90 % of its branches, under its 92 % floor, because the new region load and size question had their failure paths untested. Now covered: a notice to the admins that fails never undoes the request, a list out of reach with something other than an error, the smallest of several held regions, a request filed meanwhile that is not announced twice, and sizeOf asked with HEAD, unknown without a usable size or answer, and refused for an address the list could not name.
WATER-005 found the shore style crossing straight over like the direct one. Pulling a cell path straight allowed a shortcut through water as costly as its dearer end, and since the clearance ramp a cell at the bank costs up to five times, so a leg from a river mouth lifted that ceiling above the open water and was pulled across the lake. A shortcut is now held to the cost of the open water alone (the style's band and the boat's reach); how near the bank it may come was already held by the path it replaces. The search costs are unchanged.
Measured on the test lake and a larger one with a bay and a spit: direct, canoe and SUP legs are exactly as before; along the shore the leg no longer crosses (median 98 to 161 m off the bank, was up to 500), and no more samples on land than before.
ENT-003 counts 142 entities and CLASSFIELD-002 494 uninitialised NOT NULL scalars with paddle_region_requests (region_id, name, reason) in, and NPREF-005b expects paddle_region_requested among the admin scoped events.
The notices to the admins brought NotificationsModule into PaddleObstaclesModule, which needs RealtimeService and opens the storage registry at init. The suite now mounts RealtimeModule with a FakeRealtimeService, as the tours and wanderer suites do, and its DataPathsService stand-in names an uploads and a backups folder in its temp dir.
PaddleRegionLoadService injects DemoService, which only the app graph provided through the @Global DemoModule. A TestingModule that pulls PaddleObstaclesModule in through ToursModule (tours.paddle-check, tours.mcp, wanderer e2e) could not resolve it. The module now imports DemoModule itself, as BudgetModule and FeedsModule do.
knip found PaddleRegionRequestsReason exported and imported nowhere. Like the boat, water and lake style columns, the CHECK list of the column (044700) is now held against paddleRegionWaitReasonSchema, so a reason one side knows and the other refuses fails a test instead of an insert.
lint:boundaries refused PaddleRegionLoadService injecting the AppSettings repository, which the settings domain owns. SettingsService now reads a registered instance setting (getAppSetting) and writes several of them in one transaction (setAppSettings), and the region load asks it instead.
That also puts the two writes of updateSettings into one transaction, which lint:tx asked for. PLOAD-013 shows that a failed second write leaves the first one unchanged.
load is tagged @txIndependent: it decides one region after the other with Geofabrik asked for a size in between, and a region added or a request filed stands on its own, so no transaction may span it.
The region load and obstacle helpers move out of their component files (paddleRegionLoad.ts, paddleObstaclePlace.ts), so those files export components only (react-refresh). TourPlannerMapStage takes the take-from-card rule out into paddlePlaceFromCard and works out the cards' end inset once (complexity 24, back under 20). The tour tests reach list items through at() and must() instead of non-null assertions, a position change in the shaping test is voided rather than left floating, and PLOAD-004 seeds its regions with Promise.all.