Commit Graph

1849 Commits

Author SHA1 Message Date
Maurice e877d35e0b fix(tours): name the surface, way and trail classes as types only 2026-10-19 22:22:55 +02:00
Maurice f3a39d9452 fix(tours): keep the bike paints, classes and costing to their modules, and pin the profile column to the contract 2026-10-19 22:15:22 +02:00
Maurice b374fdc989 fix(tours): reach the Overpass client through its service file, and format the take-over list 2026-10-19 22:03:54 +02:00
Maurice b8eb2d909e fix(tours): drop two unused test imports, and keep the take-over list clear of its scrollbar 2026-10-19 21:53:45 +02:00
Maurice e429b49ef6 fix(tours): the repository test names the bike profile, and the add menu reads its list outside render 2026-10-19 21:44:19 +02:00
Maurice 73237ffedb feat(tours): bike tours in the planner, CyclOSM, and the planned cards' last points (BIK-01 to BIK-06, RTE-01, IO-01, LIB-02)
The tour type is a dropdown; a bike tour picks its profile, routes for it,
says when a point lies off every way, and shows what it runs on: surfaces,
kinds of way and, for a mountain bike, the trail grades, each pointed at on
the map. A type's own map (OpenHikingMap for a hike, CyclOSM for a bike tour)
is offered while a tour of that type is open, with an admin switch each.

From the planned cards: a hike avoids steep climbs, steps or ferries, nothing
else; a saved tour can be shared where the platform takes GPX files; and the
tours rail adds a tour from one menu: plan new, import GPX, or take one over
from another trip.
2026-10-19 21:33:49 +02:00
Maurice c3229afcba fix(tours): sort the place categories of a search key by their names 2026-10-19 20:38:49 +02:00
Maurice 9382fa6dc6 test(tours): place the store of the places found along a tour in the test registry 2026-10-19 18:27:53 +02:00
Maurice 75d8351fec fix(tours): export only what is imported, and keep the planner page at its size 2026-10-19 17:00:21 +02:00
Maurice ce07c4dc72 fix(tours): keep the planner page at its size, and format the GPX test
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.
2026-10-19 16:49:03 +02:00
Maurice 827e682684 feat(tours): places along the tour, and a found place as a stop (PLN-02, PLN-03)
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.
2026-10-19 16:24:59 +02:00
Maurice 692373c8b8 feat(tours): take a tour over from another trip, the dialog (LIB-02)
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.
2026-10-19 15:28:53 +02:00
Maurice 9cd227fd81 feat(tours): fold the rail cards, remembered across reloads; trip totals as badges
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.
2026-10-19 14:18:28 +02:00
Maurice e4b49e1148 fix(tours): count the trip totals' tours on a day from the day lists (STA-01)
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.
2026-10-19 13:56:05 +02:00
Maurice a87ce65899 feat(tours): the trip's tour totals by type above the tour list (STA-01)
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.
2026-10-19 13:55:07 +02:00
Maurice 06e6e25b25 feat(tours): OpenHikingMap as a hiking base map of the tour planner (MAP-01)
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.
2026-10-19 13:07:36 +02:00
Maurice d322363e53 feat(tours): adding a point and picking a trip place as one quiet bar
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.
2026-10-19 13:07:19 +02:00
Maurice a20b682087 refactor(tours): hold the planner to the client's warning baseline
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).
2026-10-19 13:07:19 +02:00
Maurice ad86713fdc feat(tours): route choices for a hike, each checked against 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.
2026-10-19 11:31:31 +02:00
Maurice 66525c66b7 fix(tours): paddle notes under the route, and a lost water network heals itself
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.
2026-10-19 11:29:29 +02:00
Maurice 7a484a09d4 feat(tours): stops that stand for places of the trip (PR1-10)
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.
2026-10-19 10:52:26 +02:00
Maurice d72352911e feat(tours): tour revisions, conflicts and a live editor (P2C-03)
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.
2026-10-19 10:22:04 +02:00
Maurice 134ab64bce feat(tours): download a saved tour as a GPX file (PR1-06A)
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.
2026-10-19 09:42:13 +02:00
Maurice bb39745a23 fix(planner): the reorder days dialog opens centred
It was pinned to the top of the window; centred it sits where the other short dialogs open.
2026-10-18 23:39:54 +02:00
Maurice cda6e4bfd1 style(tours): the repo test's busy leg wrapped the way Prettier writes it
The client format gate refused client/src/repo/paddleRouteRepo.test.ts, a new file outside the format baseline.
2026-10-18 21:35:01 +00:00
Maurice dcc1c18fa9 fix(tours): the lint the paddle work grew, back to the baseline
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.
2026-10-18 21:21:32 +00:00
Maurice 7e9297d867 test(tours): a failed leg in the repo test carries its at, as the contract has it 2026-10-18 21:21:32 +00:00
Maurice cd64a2f542 test(tours): type the region load answer in the repo test as the contract's literal 2026-10-18 21:21:32 +00:00
Maurice 1bc82f97fd feat(tours): route paddle tours on the water, shaping points and auto-loaded regions
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.
2026-10-18 21:21:32 +00:00
Maurice b76a298bb3 ci(lint): a base version the typed rules cannot lint no longer crashes the report
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.
2026-10-18 23:17:33 +02:00
Maurice 17029d364a ci(lint): lintTextClient stays inside the client ratchet
Only check() uses it, as its default; exported it was an unused export to knip, which turned the server quality job on dev red.
2026-10-18 23:16:37 +02:00
Maurice f458278e88 ci(lint): a grown warning count names the file and line that grew it
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.
2026-10-18 22:46:00 +02:00
Maurice fca42d46d9 refactor(journey): the phone's trip access list as its own component
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.
2026-10-18 22:36:57 +02:00
Maurice 532010aa79 feat(journey): the journal settings list who a linked trip lets in
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.
2026-10-18 22:04:41 +02:00
Maurice 451167bde3 fix(trips): the settings button as a pill beside the trip's, and the dialog centred
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.
2026-10-18 22:04:40 +02:00
Maurice 92181cdefb test(trips): the suites follow the settings dialog's areas and the journal link
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.
2026-10-18 20:49:38 +02:00
Maurice 3db20dd71c chore(client): theme baseline down one for the share button the navbar lost 2026-10-18 19:13:36 +02:00
Maurice 0751b3e8c9 fix(trips): the journal link's toast and tab texts as literal keys
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.
2026-10-18 19:04:10 +02:00
Maurice 89c96ed554 fix(trips): the embedded journal's offsets as classes on its wrapper, not in index.css
index.css is held at its line baseline. The two variables the embedded journal overrides (--nav-h on the desktop, --m-safe-top on the phone) now sit as arbitrary properties on the wrapper that needs them.
2026-10-18 18:04:34 +02:00
Maurice 889e88c362 refactor(trips): the journal member choice in a hook of its own, the test seed without an await loop
useTripJournalLink had grown past the 120-line limit for a function. The member list (candidates per journal, roles, who is new) is now useJournalMemberChoice. What loaded is kept with the journal it belongs to, so a new pick shows nothing until its own people are in, without resetting state by hand. The integration test adds its trip members with Promise.all.
2026-10-18 17:55:41 +02:00
Maurice fe05afec19 feat(trips): trip settings with a linked journal tab
The Share button becomes the trip's Settings, a round button beside the back
pill. The dialog is rebuilt around three areas (People, Sharing, Journal) with
a side navigation, a fixed height and 960 px width on wide screens.

Journal links one of your journals from the Journey addon to the trip. Every
trip member is ticked as a reader by default, each can be raised to editor or
left out, and the people a save would let in for the first time are named
before it saves. The link can be changed or removed. With a link the trip gets
a Journal tab on desktop and phone that shows the Journey addon's own view,
live through its sync and trip:journal:changed. Nothing shows with the addon
off.

members.shareTrip and nav.share are gone with the button that used them.
2026-10-18 17:41:25 +02:00
Maurice 80b6739362 feat(journey): a trip can link a journal and open it to the members it picks
A trip can now show one journal from the Journey addon as its own: any
journal its owner picks, and one journal on as many trips as they like.
The trip members chosen for the link get into the journal as viewers or
editors, see it in their own journal list and follow it live, for as long
as the link holds them and they stay on the trip.

That access is never copied into journey_contributors. It is read where
journal access is read: the contributor repository answers with the
stronger of a direct role and a link role, the journal list and the live
update recipients take the link members in, and the photo access check
goes the same way. Every one of those reads starts from linkReach, which
only counts members still on the linking trip, so unlinking, deselecting
someone and their leaving the trip all end exactly what the link gave.
A member who leaves is also taken off the link, so coming back later does
not quietly reopen the journal.

Linking or changing the members takes the journal's owner and the new
journal_link trip permission (trip owner by default); unlinking takes
either. /api/trips/:tripId/journal answers the link as each member may see
it, /candidates says who a journal would be opened to for the first time,
and four MCP tools do the same over the same service. A trip:journal:changed
event tells the trip to look again.
2026-10-18 15:59:33 +02:00
Maurice e725d4f0a9 test(packing): sorted A to Z only the category grip stays draggable 2026-10-18 14:43:55 +02:00
Maurice 36f1b5b7d9 refactor(journey): read the stored dismissed suggestions as the list they are written as
The reader also took {id, dismissedAt} records, a shape nothing writes.
2026-10-18 14:07:25 +02:00
Luffy acce063dbf fix: fix journey test fail 2026-10-18 14:04:23 +02:00
Luffy d229613e0e fix(journey): add localized error messages for dismissing suggestions 2026-10-18 14:04:22 +02:00
Luffy 5622113348 fix(journey): persist dismissed suggestions in local storage 2026-10-18 14:04:22 +02:00
Maurice cecfdf2dcc fix(packing): tell a dragged category by its data type, not by a state flag
A flag set on dragstart could still be unrendered when a quick drag reached
the next card, and the drop was lost. The category now travels in the drag
under its own type, read from the first dragover on and at the drop.
2026-10-18 13:21:54 +02:00
Maurice 23b28a1f3f feat(packing): drag a category by its head to move it among the others
Categories showed in the order they were created, which a list kept per day
cannot undo once the days swap. A grip in each category's head band moves
the whole category onto another's place on desktop. A category has no
position of its own, it shows where its first item is, so the move reorders
the items as one block through the item reorder the list already has: no
new column, and the order reaches everyone the way a dragged item does. The
other view's categories keep their order.
2026-10-18 13:21:54 +02:00
Maurice 097c1da0af fix(mobile): a transport card shows the notes of its booking
On the phone the Transports tab never rendered a booking's notes, so what
someone typed into a flight was only to be found by opening its sheet,
while the booking cards next to it showed theirs. Both cards now take the
notes from one part, sized on the caption tier so the text size setting
reaches them.
2026-10-18 13:21:53 +02:00