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.
The Tours tab's list of the trip's tours gains a button beside the GPX
import: take a tour over from another trip. Its dialog lists the tours of
the user's other trips, searched by name and filtered by trip and type,
each with its trip and the measures it has (time, distance, ascent; one it
lacks is left out, a paddle tour's climb too). Choosing one shows it on a
map with where it comes from, its type, distance, time, heights and number
of waypoints, and the name it gets in this trip, its own name to start
with. The note under the name says the copy is a tour of this trip of its
own, on no day yet, and that later changes stay apart.
Only the button in the foot takes it over. The new tour then comes into
the trip's lists and map and opens in the editor, unless the editor holds
unsaved work, which it never replaces. Nothing is written before the
confirm, and closing writes nothing. The library is online only: offline,
or when the list fails, the dialog says so and offers to try again; a list
cut at 100 tours says to search or filter.
A tour taken over names its source in its provenance: "Taken over from"
with the trip and the tour as they were called then.
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.
The planner's cards fold to their head: waypoints, the paddle time estimate,
the wind and the checklist on the left, the open tour's summary on the right.
The title or the chevron folds a card, and the choice is kept in this
browser, so a reload finds the rails as they were left. A folded card still
says what matters in its head: the checklist what is open, the wind its day,
and the time estimate now its total as a badge, in the warning tone while
parts of it are unknown. The "planning values" badge and info dot of the
estimate go; what the starting values are is said once under its figures.
The obstacle entry dialog sits outside the checklist's body, so an obstacle
picked on the map still opens with the card folded.
The trip totals start folded to their counts and show each type's sums as
the row's white badges, each named in its tooltip, an incomplete one marked
as at least that much with how many tours it is known for. The info text is
shorter.
The Tours locale contract counts the 574 general keys the planned cards
brought in.
The totals counted a tour as on a day by the list's planned flag, which
lags behind a day assignment made a moment ago. They read the day lists
now, as the row's day badge does, so the count follows at once.
The Tours tab's list of the trip's tours gains the filter the plan's tour
list has, and above the list a card with what the tours shown add up to:
how many, how many are on a day and how many not, then per type distance,
ascent and descent, and the three kinds of time apart, the router's for
tours planned in TREK, the one an import brought, and the planned one with
breaks. Nothing is added up across types. The filter narrows the list and
the totals alike.
Each tour counts once however many days it is on; the list is one row a
tour. A value no tour has is left out rather than shown as zero, and a sum
some tours lack is shown as at least that much, its info dot saying for how
many of how many it is known. Paddling sums no heights: on the water a climb
says nothing, and a planned paddle tour's heights would be the banks'.
The card folds to its counts, remembered in this browser. The empty state
for a filter that leaves nothing is shared by both lists now.
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.
The two ways to add a stop sat side by side as dashed boxes of different
widths, and the first changed its words while the map waited for a click,
which cut them off. They are now the two halves of one bar in the look of
the editor's other switches: equal width, the one in use raised, the label
the same in both states. While the map waits for a click the half is raised
with its icon in the accent, and its tooltip says to click the map.
The last rounds left the client a few warnings over its baseline. Opening a
saved tour reads its editable fields through openedTourFields instead of
spelling out each default in the handler; the status badge's words come from
statusLabelOf; the hooks that adopt or refuse a revision name it in their
dependencies; the hiking preferences' ref is kept by the setters alone, as
the limit's is; the paddle notes' pane is made before the first line instead
of through a state set in an effect; the tour workspace is back to its
length; and a test reads the walking time without a non-null assertion.
A shaping-point test follows the route choices handed to the router (MF-08).
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.
The lake cost only added half again within 12 m of the bank, so the cheapest line round a bend ran one grid cell off the water's edge (7 to 19 m on the big lakes), and pulling it straight could draw it over land. A clearance ramp now makes the water nearer than 80 m (40 m along the shore) steadily dearer, which also centres the route in anything narrower than twice that, the shore style rides a band 100 to 350 m out instead of the bank itself, and a shortcut may not come nearer the shore than the path it replaces. Measured on nine lakes: no sample on land (was up to two), direct keeps 51 to 78 m at its closest (was one cell), along the shore the median is 60 to 100 m (was 30 to 40) and under 1 % of it within 25 m (was up to 22 %); lengths change by under 1 %.
Paddle tours are planned on the water by TREK's own router: a water network
per paddle region, built from the same Geofabrik extract as the obstacles and
searched in a worker thread of the server (no sidecar). Rivers, canals and
lakes (direct or along the shore), portages round weirs, locks by policy,
whitewater by boat, gaps marked. Found portages and locks become points of
their own; an unplannable leg says why and offers a way out.
- Shaping points: a click on the line sets a point that steers the route
without being a stop (hikes and paddle tours); recompute a single leg.
- Lake style direct/shore, suggested from the wind forecast.
- Regions load by themselves where a paddler plans (admin setting: automatic,
on request, off; size cap and a daily count), with requests for the admins,
a notification, and the load followed on the leg.
- Portage from the last point, portages follow footways via the walking
router (or stay straight), "put in here" on slipways and put-ins.
- MCP plan_paddle_route with the REST route's rules.
On the server, linting a file's base text with type-aware rules threw (no program for a text that is not on disk), and the TypeError ended the whole ratchet run. A lint that throws now counts as unknown: the file's current warnings of the rule are told, marked as not compared.
lint:warnings failed with the total and the files holding the most of the rule, which are old ones the baseline allows; the file that tipped the count was never named, so finding it meant linting by hand. When a rule grows, the ratchet now lints the files that differ from the base twice, as they are and as they were there, and lists every one holding more of the rule, line and message. The base is TREK_LINT_BASE (the CI fetches the pushed-over commit on a push and the target branch on a pull request) or origin/dev; without it the report stays as it was.
The list of who a linked trip lets in took MJourneySettingsSheet past the 300-line limit for a component (max-lines-per-function), which turned the client lint on dev red. It is MTripAccessList beside it now; the markup is unchanged.
Under the contributors, the journey settings on desktop and phone get a section of their own, Through trips, with everyone a trip's journal link lets in, their role and the trip they come through. It is read only there, since the trip's settings manage it, and it leaves out whoever is a contributor already. getJourneyFull answers it as trip_access.
The settings button was a bare round icon next to the trip's back pill; it is the same pill now, with the gear and the word Settings. The settings dialog opens centred again: its fixed height already keeps it from jumping between areas, so it does not need to be pinned to the top.
The dialog's nav buttons are named by their area alone (aria-label), so a hint line naming invites no longer matches the invite button. The sharing cases open the Sharing area first, the wiring and More sheet tests drive the settings button and row, the hook test reads the toasts as keys (renderHook loads no locale), and the lazy list and store registry know the journal panel and store.
linkRecipientUserIds and listTripIdsForJourney had no caller and kept src/db/repositories under its 99 % statement floor. TJLREPO-001..007 cover the link rows, the members list, the reach across trips ending with the membership, the hide-skeletons choice of a link member, the role precedence and leaveJournalLink.
The departure port needed a global binding that every suite building TripMembersModule without AppModule lacked. The roster's repository now deletes the leaver's row on the trip's journal link itself (leaveJournalLink), inside the transaction that removes the member: no edge into journey, no DI, no test stub, and the eight suites the stub touched are back as they were.
The schema inventory tests count the two new tables and their NOT NULL columns, the legacy step map lists the migration as post-legacy, and the TJL scenes give their users numbered names so cases sharing one database do not collide.
lint:i18n-keys only reaches a key through a literal t() or a *Key property: the three save toasts were passed to run() as bare keys and the tab label came out of TRIP_TAB_LABEL_KEYS, so four new en keys counted as unused. They are translated where they are named now.
TripJournalLinksRepository spelled 'viewer' | 'editor' by hand while the generated entity exports the same roles, which knip then counted as an unused export. The repository now derives its type and values from the entity.
Declared in trip-members, the port gave journey an edge into the roster, which reaches journey through other domains: the cycle again, the other way round. In the shared kernel neither domain owns it.