Commit Graph

3970 Commits

Author SHA1 Message Date
Maurice b284095ee0 fix(tours): count a multi-day tour in the activity sums only once all its stages are on days
The activity sums never read which stage a day holds, so a multi-day tour
counted its whole route on the last dated stage day while other stages
were on no day yet, and a recording on one stage took the place of the
whole plan beside it. With stages on days of their own, the route now
counts once every stage is on a dated day of the user's, on the last of
them, and is uncounted until then; a recording counts as what was done
and the plan is not added beside it.
2026-10-20 17:00:27 +02:00
Maurice e40e576e39 fix(tours): preset a ski tour's actual track as ski, and cover the run and ride presets
Ski has been an importable type since SKI-02, yet a ski tour's recording
still opened in the GPX preview as a hike. It now opens as ski with the
tour's discipline, and its technique for cross-country. The run profile
fallback and the ride preset get their tests.
2026-10-20 16:57:27 +02:00
Maurice 4d9b84f681 test(tours): give the packing link copy test an id of its own
TOURREPO-012 was used twice in the tours repository test.
2026-10-20 16:56:56 +02:00
Maurice 223b056de2 fix(tours): name a round hike's distance bounds {from} and {to}
The three round hike strings passed formatted distances as {min} and
{max}, and {max} is a quantity param, so i18n parity read them as count
strings outside a plural group and failed. The bounds are now {from} and
{to} in every locale and in the card.
2026-10-20 16:56:45 +02:00
Maurice 981c372640 fix(tours): let only the latest read set a collection's kept tours
Two reads of the same collection, after two changes by other members or
the first read overlapping one, could answer in either order and an older
list replace a newer one. A refused removal also restored the list from
before it, dropping changes read meanwhile; it now only puts its own tour
back.
2026-10-20 16:56:15 +02:00
Maurice 839d736905 fix(atlas): keep tour exclusions right when their answers come back out of order
Each exclusion replaced the whole list with its answer, so two quick
exclusions answered in the other order, or a read answered after an
exclusion landed, put back a tour as counted although the server held it
excluded. Only the answer to the latest request is now taken whole, with
the exclusions still on their way laid over it; an older answer only
confirms its own tour.
2026-10-20 16:55:34 +02:00
Maurice b9391a9921 fix(tours): hold an actual track change back while the tour is offline or queued
Linking or unlinking a recorded track went out even in offline mode and
behind a queued write of the same tour, and its answer replaced the whole
cached tour with the server's older state, losing the local change. Both
now refuse as online only in those cases and for a tour the server has
not got, the view says so, and the answer only updates the cached
tour's recordings.
2026-10-20 16:54:35 +02:00
Maurice 5e0f216b0c fix(tours): name an actual track's metrics and a tour's stages as derived in get_tour_plan
What a recorded track measures is read off its line on every read, and a
multi-day tour's stage count and stops are counted from its stage ends,
yet derived did not list them, so an assistant would take them for saved
facts.
2026-10-20 16:52:08 +02:00
Maurice a249c861e6 fix(tours): read and write a run's pace in the user's distance unit
The pace was always min/km and the run's time sum forced kilometres, so
with distances in miles the run card disagreed with every other figure in
the planner. The pace is still kept per km, but is typed, shown and
checked per mile when distances are in miles, and the time sum reads in
that unit too.
2026-10-20 16:51:34 +02:00
Maurice 2a40b884a1 fix(tours): keep a kept tour's remove button in sight, dimmed
It was invisible until hover or focus, so a phone or tablet had no way to
find it. It now shows dimmed and comes up fully on hover or focus.
2026-10-20 16:48:09 +02:00
Maurice f5be556c27 fix(tours): list a kept tour with its type's marks, pace and stages
The collection copy keeps the winter, run, ski, bike and stage columns,
but its list item only carried the older fields, so a kept ski track lost
its discipline mark, a snowshoe hike its winter mark, a run its pace and
a multi-day tour its stage count. The item now carries them, and a kept
tour's card shows the pace and the stages beside its measures.
2026-10-20 16:48:00 +02:00
Maurice 6b1488994f fix(tours): give an actual track its own mark and a winter tile its label's icon
Footprints stood for a snowshoe hike, an actual track and a walk's moving
time at once, with the snowshoe pill and the track pill side by side in a
tour's head. An actual track now goes by the done mark on the list row,
in the head and on a recording's tile, and a winter tile's corner takes
the icon its head pill shows.
2026-10-20 16:46:08 +02:00
Maurice 2ee4ea1e65 fix(tours): keep a multi-day tour's days in sight in the list row
The stage count sat in the row's fixed-height line of badges, so a tour
with stages and a recorded track wrapped and lost its days badge. The
count now sits on the fold button, a moon with the number at its corner,
or beside the row where it does not unfold, and the days come before the
measures so the ascent is what gets cut.
2026-10-20 16:44:55 +02:00
Maurice a1d683d7bc fix(tours): show a run's ways in the hike's trail details card
A run described its ways in a card of its own, with the bike's sections,
the bike's paint for the SAC grades (T5 and T6 merged) and its own words,
so the same path read differently on a hike and on a run. The run now
uses the hike's card, matched as a run, and keeps only the unpaved share
and the hardest grade in its head. The run's grade labels and the now
unused SAC paint go.
2026-10-20 16:42:47 +02:00
Maurice ae726ed370 fix(tours): time a run's stages at its pace and leave a ride's stages untimed
A multi-day run's stages took a share of the foot router's time while the
run itself is timed at the runner's pace, so the stages did not add up to
the run. A ride has no time at all, so its stages have none either.
2026-10-20 16:38:42 +02:00
Maurice f4750352e6 fix(tours): fit the merged tracks together after integration
Types, test expectations and helpers the seven tracks each changed on their
own, brought in line with the integrated code. tours.service.ts and the tours
repository grew past the size limit with every track and hand their row and
write helpers to modules of their own.
2026-10-20 16:30:55 +02:00
Maurice 65ee472d66 chore(db): regenerate entities and kysely types for the tours board 2026-10-20 15:40:31 +02:00
Maurice 73fa7aeb19 test(tours): give the tests the merged tracks added side by side ids of their own
Runs, winter hikes and ski tracks numbered their ToursRepository tests 016
to 018, as multi-day tours did; the multi-day list_tours tests took
TOURS-MCP-015 and 016 next to get_tour_plan's. They now read TOURREPO-022
to 024 and TOURS-MCP-024 and 025, so every id names one test.
2026-10-20 15:38:07 +02:00
Maurice fbba4fd73b fix(tours): refresh the wanderer watch after a fixed token and truly disable its switch offline
The watch row only re-read its state when the connection came or went, so after
a passing connection test or a newly saved token it kept saying "Token refused"
until a reload. The connection card now hands it a key built from the saved
connection and its last check, and the row re-reads the watch whenever that
changes.

Without a connection the switch was only dimmed: the button stayed in the tab
order and was announced as active. ToggleSwitch gains a disabled prop that
disables the button and marks it aria-disabled, and the reason stays reachable
through a focusable wrapper carrying the tooltip.
2026-10-20 15:35:32 +02:00
Maurice 2e1dca9fe9 feat(tours): let a background check hint at changes of your own wanderer trails
Settings, Integrations, wanderer gains an optional background check of the
trails you imported, off until you switch it on. While on, TREK reads the
short record of each such trail at most once a day (never its GPX file) and
leaves a hint when the trail may have moved on: "may have changed" when only
wanderer's change time moved, "changed" when its name or description proves
it, and "not checkable" when the trail is gone, no longer yours or carries no
change time. Nothing is ever said to be unchanged. A new hint arrives as a
notification that opens the tour (a new ?place= link in the planner); the
tour's provenance shows it with the source check right beside it, and the
card lists all open hints in a dialog, each leading to its tour. The check
reads the source in full as before and spends the hint; taking a change in
stays the confirmed reimport. No tour is ever changed or deleted.

The check only asks through your own connection, only about trails your
account imported into trips you can still open, and stops when you switch it
off, remove the connection, wanderer refuses the token (until a test passes
or a new token is saved) or the admin switches wanderer off; hints and
snapshots stay. A run asks at most 25 connections for at most 10 records
each, one after another, and an instance that fails (an outage, a rate
limit) is left alone for an hour, doubling up to two days.

Server: migration 046120 adds the switch and the run bookkeeping to
wanderer_connections and the tour_source_hints table (one row per snapshot,
for its importer, gone with it). WandererWatchService and an hourly
wanderer-watch job on the CronRegistrar live in the tours domain and read
through WandererService; GET and PUT /api/integrations/wanderer/watch and GET
/api/trips/:tripId/tours/wanderer/sources/:placeId/hint (place_edit, like the
check) answer with @trek/shared contracts. A new user notification event,
wanderer_source_hint, can be muted per channel. Saving a new token now clears
what the last check said about the old one. There is no MCP tool, as for the
rest of the personal wanderer connection. All 27 locales carry the strings,
and the admin wiki page says what the check does.
2026-10-20 15:35:32 +02:00
Maurice aacc7f2c8d fix(tours): open a tour's packing list on Packing, and ask before letting go of it while Packing is off
"Open packing list" switched to the Lists tab but left it on whichever
sub-tab was open last, so someone who had been in the to-dos landed there.
It now picks the packing side first, and a Lists tab already on screen
follows the choice; storage that is blocked no longer throws either.

With the Packing addon off, "Remove link" let go of the link on one click,
and the link cannot be made again until Packing is back on. It now opens
the tour's packing dialog with only the dimmed link and the Remove link and
Cancel buttons, so nothing changes without a second, deliberate click.
2026-10-20 15:35:09 +02:00
Maurice f833a05461 feat(tours): point a tour at its trip's packing list
A tour can now point at the packing list of its trip, as a whole or at one
of its categories (say "Hiking gear" for a hike), and shows it in its detail.
The new Packing list section reads the list live as the user may see it: the
backpack tile with the item count on its corner and the category's own
colour, how many are packed with a thin progress bar, the first items ticked
off or not with their quantity, how many more there are, and a button that
opens the trip's Lists tab. Who may change the tour links one, changes it or
lets go through a dialog that lists the whole list and every category with
its colour and item count, and writes only on its confirm.

It is a reference, never a copy: the items stay in Packing, are ticked off
and edited there, and each traveller sees in the tour only the items Packing
shows them, so an item kept to oneself never shows through a tour. A
category that is renamed, cleared or only holds items of others reads as
missing, an empty one as empty, a link that could not be read says so with a
retry, and while Lists are switched off the link stays, dimmed, and can
still be let go. Removing the link, or the tour, leaves the list and every
item as they are. Nothing suggests or changes an item by itself.

On the server the link is a row of its own in the tours domain
(tour_packing_links, migration 046110), one per tour and gone with it.
GET, PUT and DELETE /api/trips/:tripId/tours/:placeId/packing-link read it
with the trip and change it with place_edit; linking needs the Lists addon
and, for a category, an item of it the user can see. The writes answer the
tour with its link and tell the trip through tours:changed, so the offline
client queues them as writes of the tour and replays them like one. A copied
trip keeps its tours' links, a tour saved as new or taken over does not.
The MCP tools get_tour_packing, link_tour_packing and unlink_tour_packing run
over the same service and checks; the read also hands the tour's facts as
context for suggesting what to pack, which only becomes an item through the
packing tools. The empty category placeholder now lives in @trek/shared so
both sides leave it out alike. All 27 locales carry the new strings, and
the wiki describes the link and the tools.
2026-10-20 15:35:08 +02:00
Maurice 65fe8cd6e5 fix(tours): explain a kept tour without a line, and say the take-over once
Taking a tour kept in a collection into a trip left the user guessing in
two places when the tour had no line. The map box stayed an empty grey
square that read as a broken map; it now says that there is no line yet,
only the waypoints. Why "Line only" is dimmed was only in the tooltip of a
disabled button, which never shows on hover, focus or touch; the reason now
stands under the two choices, and a disabled tile sits in a span so its
tooltip can show as well.

A successful take-over said the same sentence twice, as a toast and as the
dialog's last word with "Open trip". The toast is gone.

The collection strings move out of tours.ts into a tourCollection.ts per
locale, keeping their keys, so no locale's tours file grows past the
1000-line limit. The tours locale spec now holds both files to their own
counts.
2026-10-20 15:34:09 +02:00
Maurice 3fe34061fd feat(tours): keep whole tours in collections and take them into trips
A saved tour can now be kept in a collection, apart from any trip, and
later made into a tour of another trip. The bookmark button in a tour's
footer and in the planner's head (waiting for the save while there are
unsaved changes) opens a dialog with every list of the user; a list they
may only read stays in it, dimmed with a lock and a tooltip. The list
keeps an independent copy: the line, the type, every measure and setting,
the waypoints in order with their roles, the origin, and the trip and tour
it was saved from as they were called then. Its days, its links to places
of the trip and its obstacle decisions stay in the trip, and later changes
to the tour do not reach the copy.

A collection shows its kept tours as cards above its places: the grade
tile with the source badge, measures and the waypoint count as badges,
the trip it came from, and who kept it. The owner or an admin of the list
can remove one after a question; tours made from it stay. "Add to a trip"
shows the kept tour on a map with its facts and numbered waypoints, lists
the user's trips (the ones they may not add tours to dimmed), and asks
outright whether the waypoints come along. "Line only" keeps the line as
saved and adds no waypoints; a tour kept without a line can only bring
them. The new tour has an id of its own, no days and no new places, and
names the trip and tour it was saved from; the dialog then offers to open
that trip. Neither the collection nor the original tour changes.

On the server the kept tours are a table of their own in the tours domain
(collection_tours, migration 046100), each row written whole once. Every
copy, the in-trip "save as new tour", the take-over from another trip and
both directions here, now goes through one copy model (tour-copy.helpers)
instead of a second one. Reading and removing follow the collection's
roles, keeping one also needs access to the source trip, and taking one
over needs place_edit on the target trip; the kept tour is read again
inside the write, so one removed meanwhile leaves no half-made tour. The
MCP tools list_collection_tours, get_collection_tour,
save_tour_to_collection, take_over_collection_tour and
delete_collection_tour run over the same service and checks. All 27
locales carry the new strings, and the wiki describes the flow.
2026-10-20 15:32:47 +02:00
Maurice 3659297af1 feat(tours): import KML and KMZ routes as tours, with a preview first
A route planned in Google My Maps or Google Earth can now become a tour
without converting it to GPX first. The import button and a file dropped on
the tours workspace take .kml and .kmz files beside .gpx, through the same
preview, the same import routes and the same transaction as a GPX file.

The preview shows every line of the file on its own, numbered, with its
distance and heights; two lines of one placemark are numbered after its name
and never joined. Heights count only where they are heights: kept for an
absolute line, dropped for any other altitude mode and for the all-zero
placeholder My Maps writes, so a missing height stays unknown. What the file
holds besides its lines (pins, areas, overlays, links to other files) is
counted in a dashed card, and nothing is fetched from elsewhere.

The KML reading reuses the place import's codec. A KMZ only ever has its KML
entry unpacked, counted byte by byte against the decompressed cap whatever
its header claims. A file that cannot be read is refused with its reason
(damaged, an archive without KML, too large unpacked), and the toast names
the file and the reason. A KML or KMZ import keeps the gpx origin of a file
import; its provenance names the real format from the stored file name.
2026-10-20 15:30:18 +02:00
Maurice f57a0dbd31 fix(tours): let an unwanted stage go and read a tour's stages once
Two review findings of MDT-02 in the planner's list and the day plan:

- A stage asked for while the draft has unsaved changes stayed pending when
  the changes were kept or the tour failed to open, so opening that tour
  later from its row jumped to a stage nobody asked for then. The rail now
  has cancel(), which the discard question calls when it is dismissed and
  the open calls when it fails, and it lets a stage go once the opened
  tour's points are in and none of its stages starts at that stop.
- Every day holding a stage of a tour read the whole tour on its own, so a
  five stage tour sent five identical requests on top of the list's own.
  tourPointsRepo shares one read per trip, tour and revision among the
  views showing it at the same time, through the tour repo as before, and
  drops it once the last view lets go or the read fails.
2026-10-20 15:28:23 +02:00
Maurice 3748bdb815 fix(tours): keep the day assignments repository under the size limit
The stage queries of MDT-02 took DayAssignments.repository.ts past the
1000 lines lint:size allows. Two concerns move out of it instead:

- The stages of a multi-day tour that days hold are a tour question, so
  findTourStage and deleteTourStagesNotIn move to ToursRepository, next to
  isOnDay and listStagePoints, which AssignmentsService already asks. No
  constructor changes, and the tests move with them (TOURREPO-019 to 021).
- The rows the repository answers with and the Kysely table shapes its
  statements are typed against move to day-assignment-rows.ts, so the file
  holds the statements alone and has room again.

tours.service.ts stays at its 1000 lines.
2026-10-20 15:28:23 +02:00
Maurice 8fa75b3c4e feat(tours): show a multi-day tour's stages in the list and put each on a day
A multi-day tour in the planner's list of the trip's tours now unfolds into
its stages (MDT-02), so the stages can be seen together and fitted into the
trip without leaving the one route they are cut from.

- The fold beside a multi-day tour lists its stages in route order, each
  from stop to stop with its distance, climb, descent and moving time, read
  off the saved tour the way the planner reads them, and below them what the
  whole tour adds up to. A value no stage tells is left out, a total some
  stages do not tell is marked as at least that much and says for how many,
  and a stage whose route is not planned yet says so.
- Each stage has its own day: a pill opens the trip's days, moves the stage
  to another one or takes it off again. A stage without a day shows a dashed
  "No day" pill. A day holds a tour, or one of its stages, once, and a stage
  is on one day at most; the plan's own offline path writes it.
- A stage opens in the planner: the tour opens as any tour does and the
  stage is shown once its points are in.
- The day plan names the stage a day holds with its own figures, and the
  day's route runs to that stage's start and on from its end; the trip map
  draws that stretch only. The tour list still counts a tour once, however
  many days hold it or its stages.
- A saved change that takes a stage away (its stage end gone, the tour a day
  tour again, back to the recorded track) takes it off its day in the same
  write and tells every client; the planner says so before the save.
- REST and the assign_place_to_day tool take the stage by the key of the
  stop it starts at, with the same checks; list_tours names the stops
  between the stages and the stage each day holds.

A day assignment remembers its stage by that key (migration 046010); every
assignment made before holds the whole tour, as it did.
2026-10-20 15:28:22 +02:00
Maurice 6ec06c90f4 fix(tours): keep a lost stage end visible on paddle tours
A stage end set on a lock or portage point the water router placed left
the point automatic, so the router could drop it with its leg later,
silently and without an undo step. Ending a stage there now makes the
point the paddler's own, like any other edit of it.

The notice for stage ends a change could not keep also vanished as soon
as the router laid its points in again after the change. It now stays
until the next change the user makes, an undo or redo, a save, or
another draft.
2026-10-20 15:26:43 +02:00
Maurice 2449616ee9 feat(tours): plan a multi-day tour as one route split into stages
A hut-to-hut hike or a paddle trip over several days is now planned in the
Tours planner as one tour: the whole route is planned as before, and stops
along it are marked as stage ends (MDT-01). The stages are read off that one
route in its order, never kept as copies, so a change inside a stage changes
the whole tour and every figure on it.

- A Day tour / Multi-day tour switch beside the tour type; a stop's menu
  ends a stage there or lets it go on. Back to a day tour removes every stage
  end after a confirmation, as one step undo brings back.
- The list of points is headed per stage with its distance and moving time;
  a stop that ends a stage carries a moon at its number, on the map too.
- The overview lists the stages from stop to stop with distance, climb,
  descent and time (hiking rule per stage, paddle estimate per stage, the
  router's time shared by distance otherwise), marks where each ends across
  the elevation profile, and a stage of more than a day on the move warns.
- A stage end a change cannot keep (its stop removed, moved to an end or
  made a shaping point) is dropped and said so, with undo, never moved
  to another point.
- A multi-day tour may take a day a stage, so its time limit grows with its
  stages; the tour keeps its one type, portages included.
- The list and the MCP tools show the stage count and where stages end.

Stage ends are a flag on the waypoint (migration 046000); a tour without one
is a day tour, as every tour saved before.
2026-10-20 15:26:18 +02:00
Maurice acc294dd8e fix(atlas): count shared days for every member, keep routes and multi-day tours honest (STA-02)
A day of a tour that names nobody now counts for every member of the trip,
not only for its owner. In the planner an empty participant list means
everyone, and a full member list is stored back as empty, so members got
nothing for most shared tours. Only a day that names participants without
the user stays theirs. The tours of a shared trip on no day or only on
undated days are now named as not counting for members as well.

A whole route a multi-day tour's stages were planned from is no longer
dropped on the server as soon as any stage has a day. Each entry carries
derived_from_place_id and the shared summary leaves the route out only
while one of its stages is done, counted and in the period and type. A
route walked last year now still counts when a stage of it is planned for
next month or was left out because it fell through, in the Atlas and over
MCP alike.

A tour on several days is dated on the last of them, as is a recording on
no day, so it no longer counts while it is still under way.

The info text, the get_tour_activity_stats description and the wiki say so.
2026-10-20 15:23:55 +02:00
Maurice bde2ad2ca8 feat(atlas): sum up the tours you have done, by period and type (STA-02)
With the Tours addon on, the Atlas panel gets a third tab, Tours, beside
Stats and Bucket List: what you have done on your tours across all your
trips. A period (all time, this year, the last 12 months or an earlier year)
and a tour type narrow it; the tab shows the distance large with the number
of tours at its corner, the ascent and descent, and the types with their
distance. "Show tours" opens every tour of the period in a dialog with the
same filters, the sums, the split by type and one row per tour.

What counts is said, never guessed. A tour counts once the day it is on is
over, with the values of the planning, badged "Planned" and explained as
not measured. A linked actual track (ACT-01) replaces those values for its
execution, badged "Recorded" and named, and is never added on top nor
counted as a tour of its own. A tour on several days counts once, a whole
route does not count beside the stages planned from it, a paddle tour sums
no heights, and a value nobody knows stays out of the sum, which then reads
"at least". Only the user's own part counts: a day names them among its
participants, or names nobody on a trip of their own; being able to open
somebody else's trip makes none of its tours theirs. Tours still ahead, on
no day or only on undated days are named as not counting. A tour that fell
through can be left out of the sums ("Leave out"): it stays in the plan
and in the list, dimmed, and "Count again" brings it back. Only the user's
own numbers change.

Server: GET /api/tours/activity-stats answers the user's activity entries
across every trip they can open, and PUT /api/tours/activity-stats/exclusions
leaves a tour out or counts it again (the tour must be in such a trip, else
404). A tour_activity_exclusions table keeps the exclusions per user and
tour. The sums are added up by a shared summary in @trek/shared, so the
Atlas and the assistant say the same numbers. MCP: get_tour_activity_stats
and set_tour_activity_excluded over the same service. The tab reads live
and needs a connection, like the rest of the Atlas statistics.
2026-10-20 15:23:55 +02:00
Maurice ae79ab8dad fix(tours): offer the map comparison only where the plan map can draw it (ACT-02)
The compare switch in a tour's detail showed whenever the detail sat beside
the plan map, but the map draws no comparison in road trip mode or with the
trip overview open. Pressing it there turned the switch on while nothing
appeared on the map.

The plan map now tells the compare store whether it can draw a comparison.
The detail offers the switch only then, and turning to the road trip or the
overview, or leaving the map, ends a running comparison.
2026-10-20 15:23:18 +02:00
Maurice 6a0c54cb2f feat(tours): compare a tour's plan with the track it was actually done with (ACT-02)
Each actual track in a tour's detail now stands beside the plan instead of
on its own: a table names the plan and the actual track as columns, each with
the line it is drawn as on the map, and sets distance, ascent, descent and the
time with breaks side by side. The difference is shown, signed and with an
arrow, only where both values are known and mean the same thing; a planned
total that leaves a stay out reads "at least" and is not subtracted from. A
value one side does not carry is named as missing ("Not in the plan", "Not in
the track", "No time stamps"), never estimated. Below the table, how far the
track left the planned line: its farthest point and the recorded stretches
more than 50 m away, or that there is no line to compare.

On the desktop plan map, "Compare on map" draws the actual track dotted in
the colour of done next to the plan, which stays picked in its own track
colour (and is put back should it be hidden on the map). The recording leaves
the map as a tour of its own meanwhile, so it is never drawn twice or in a
plan's colour. A frosted legend over the map names both lines and ends the
comparison; so do closing the detail, comparing another track, or the track
being unlinked. Comparing only reads: neither the plan nor the track changes.

The track is laid into the day's route lines the map already draws, so both
renderers show it without a layer of their own. distancesToLine in the shared
route corridor measures many points against one line laid flat once; a long
recording is measured in at most 1500 samples.
2026-10-20 15:23:18 +02:00
Maurice a8be1c5b45 fix(tours): keep unknown actual tracks unknown and name every track a link replaces (ACT-01)
A tour detail that cannot be read (offline without a cached copy, or a server
error) no longer shows "No actual track yet" while the trip list names tracks
of the tour; the section says they could not be loaded and offers no link over
them.

The day a track was recorded on is the viewer's calendar day of its first time
stamp, not the UTC one, in the tour detail, the link dialog and the GPX
preview alike, through one helper.

Taking a tour off a day keeps its actual track without a day, so "no day" can
hold several. A change now starts on the track it was asked for, and the link
dialog names every track it replaces, as the server clears all of them. The
migration, repository, service and MCP texts say so.

The refusal for linking a tour to itself or a track with tracks of its own now
reads "This track cannot be linked to this tour", which is true of both, and
the offline helper of the tour cache sits above the doc of replaceTripTours
again.
2026-10-20 15:23:10 +02:00
Maurice d0cc6fc2f4 feat(tours): link the track a tour was actually done with, and show its own values (ACT-01)
A planned tour can now be given the track it was actually done with. In the
tour's detail, "Add actual track" opens a dialog that asks for the day the
tour was done on (or none) and a recording of the trip: an import nobody
planned over, or a new one through the existing GPX import, its preview
preset to the tour's type. What the choice measures is shown before anything
is written, and only "Link" writes. The linked track keeps its own name and
source; the detail shows its distance, heights, profile and, only where the
file carried time stamps, the time it took, marked as actual values next to
the untouched plan. A track can be changed or unlinked again.

Server: a tour_activities table points a tour's execution (a day assignment,
or none) at a recording of the same trip, and the tours GPX import keeps the
first and last time of a track whose every point carries one. The values are
read from the recording's own line on every read, so a changed recording
updates them while the plan's line, measures and revision stay as they were.
Every check runs inside the write: the recording must be an import of this
trip not planned over, documenting no other tour; the day must be one of this
tour; a refusal names its reason and changes nothing. PUT and DELETE
/api/trips/:tripId/tours/:placeId/activities answer the detail, which now
carries the actual tracks; the list names them per tour and per recording.
MCP: assign_tour_track and unassign_tour_track over the same service and
checks, get_tour_plan reads the values without the line.

A recording linked as a tour's actual track is no longer counted as a tour of
its own in the trip's tour summary, and the GPX preview imports a city walk
as one instead of as a hike.
2026-10-20 15:22:55 +02:00
Maurice f016972a4e fix(tours): explain and offer to re-ask hike ways that could not be asked
A way OpenStreetMap could not be asked about this time now carries a tooltip
saying so, and the unknown list offers to ask again whenever such a row is
there, not only when nothing at all was answered.
2026-10-20 15:19:11 +02:00
Maurice c80152f41d fix(tours): keep a non-canonical sac_scale as written instead of lowercasing it
A value such as Alpine_Hiking is not one of the six SAC values word for
word, so it now reads as an unclear grade with the raw value OpenStreetMap
holds, rather than as a known T4 the planner normalised itself.
2026-10-20 15:19:03 +02:00
Maurice f745ca13c9 feat(tours): suggest round hikes of about a wished length from a stop (RTE-03)
A hike can now be planned as a round out of one of its stops without
building the loop point by point. "Round hike from here" in a stop's menu,
or the round button in the head of the waypoints for the start, opens a
card over the planner map: the start (any stop of the tour, picked from a
list), the wished length in the traveller's unit, the window of a fifth
either way it allows, and the tour's hiking limit, which every question
to the router keeps to together with the hike's preferences.

TREK looks for up to three different rounds itself on the hiking router it
already uses. It steers loops round circles in several directions from the
start, asks a loop that missed the length once more at a corrected size,
and learns from each answer how much longer the paths are than straight
lines. A loop that walks most of its ground twice (a way there and back), a
spot too far from any path, and a round that repeats one found are left
out, and nothing outside the window is offered. The rounds show up in
their colours on the map and in 3D as they are found, the map frames the
area they can lie in, and each lists its length, how far it lies off the
wish, climb, descent and walking time once the heights are in. Fewer
rounds than three, none, or a router that does not answer are said so.

Showing, choosing and closing change nothing. "Take this round" puts the
start, the steering spots as shaping points and the way back in place of
the plan as one step of the history, with a "Round taken" note and its
undo; a tour that held more than its start asks first. The round is then
an ordinary tour to edit and save. A start moved or a limit changed while
rounds are shown lets them go and the card says why.

The card shares its frame, buttons and rows with the comparison of a
section's ways, and the map corner still holds one card at a time.
2026-10-20 15:19:03 +02:00
Maurice 4f68029b2c fix(tours): keep one card in the map corner and offer only ways that change a section
While the other ways of a hike's section were compared, a click on a via
ferrata or a place on the map opened its card in the same corner, stacked
over the comparison. The corner now holds one card: whichever opened last
stays and the other closes, before paint, so the two never show together.

The router's first answer between the two ends is its own way under the
hike's rules, which the tour already routes the section by. Offered as a
choice, taking it only planned the leg again, usually to the same line, and
the card closed without "Way taken" or an undo step. It is no longer
offered, so every way taken is held by shaping points, changes the route and
can be taken back in one step.
2026-10-20 15:18:48 +02:00
Maurice 64445a5204 feat(tours): compare other ways for one section of a hike (RTE-02)
A routed section of a hike between two neighbouring points can now be
compared with the other ways the router knows between the same two points,
under the same limit and preferences. "Compare ways for the next leg" in a
waypoint's menu opens a card over the planner map: the way taken now and up
to three others, each drawn on the map in its own colour, with length,
climb, descent and walking time by the signposts' rule. Figures without
heights stay unknown, and the differences to the current way are shown
only where both are known. When the router knows no other way, or does not
answer, the card says so.

Showing, hovering and choosing change nothing. Only "Take this way" does:
the router is first asked whether a few shaping points hold the chosen way
(the check the road trip's picker already uses), and only then are they set
into that one leg as a single step of the history, with a "Way taken" note
and its undo at the foot of the map. Other legs, fixed lines, days and
points stay as they were, the totals follow from the replanned route, and
the shaping points can be dragged like any other. A section that changed
while the router was asked, or a way it does not keep to, is not written,
and the card says why. The ways are drawn in 3D too.
2026-10-20 15:18:38 +02:00
Maurice bff9bcde92 fix(tours): read ways past the grade cap as not asked instead of untagged
A long hike or mountain bike tour can run over more distinct ways than one
grade request may ask about. The ways past that cap were never asked, yet the
facets read them as carrying no surface, way type or grade in OpenStreetMap.
They now land under 'Could not be asked this time' with the unanswered ones.
2026-10-20 15:18:01 +02:00
Maurice 3572f4b8a3 feat(tours): show the known surfaces and way types along a hike (MAP-04)
The hike's "SAC grades" card becomes "Trail details": one switch, then a
segmented choice between SAC, Surface and Way type, three things kept
apart and drawn along the route on the planner map one at a time. Every
facet reads from the same answer: the ways under the line are matched on
foot once, their OpenStreetMap values fetched once through the
trail-grades read MAP-03 already uses, and each stretch is placed against
the tour's own line once. Switching facets asks nothing new.

The server now hands on a way's surface and highway tags as OpenStreetMap
wrote them, only trimmed and bounded. The planner groups them (asphalt or
concrete, paving stones, compacted, gravel, natural ground, grass, rock,
wood or metal, unpaved without a kind, other; path, footway, steps, farm
or forest track, cycle path, minor road, road, via ferrata, other), and
each row's tooltip names the values recorded, such as surface=gravel,
surface=pebblestone. The router's own surface reading is never used: it
is normalised and a default where nothing is recorded.

What is not known stays unknown and says why, in every facet: no value
recorded, a value no group takes or two at once (unclear, never the
nearest group), no way clearly under the tour's line, or a way that could
not be asked about. Imported tracks and hand-drawn stretches keep their
geometry; a matched way only counts where it keeps within 25 m of it.
A summary badge gives the known share and the most common class; the SAC
facet keeps its highest step and routing limit badges. Nothing about the
tour, its places or its days changes, and no routing preference is set.
2026-10-20 15:17:52 +02:00
Maurice ea9e997338 feat(tours): show the known SAC grades along a hike (MAP-03)
A hike in the planner, being edited or viewed as an import, now has an
"SAC grades" card. Switched on, it asks the router which OpenStreetMap
ways lie under the line (matched on foot, every grade of path open) and
the server reads their sac_scale through the Overpass mirrors, the same
trail-grades read the mountain bike grades use. The grades are drawn
along the route on the planner map in one ramp of a single hue, T1 to
T6, and listed by share and length; a row draws its class alone. Each
row names its source in a tooltip: the SAC hiking scale and the
sac_scale value OpenStreetMap wrote.

What is not known stays unknown and says why, never T1: a way without
a grade, a grade that names no single step (handed on as written), a
stretch with no way clearly under the tour's line, or a way that could
not be asked about. For imported tracks and stretches drawn by hand a
matched way only counts where it keeps within 25 m of the tour's own
line, so the original line is kept and nothing is guessed. The router's
own reading of the scale is not asked for, since it may be normalised.

The routing limit of a planned hike sits apart as a setting with its
own badge, and a known step above it is marked. Nothing about the tour,
its places or its days changes; switched off, the map is as it was.
No regional colour categories and no grade derived from slope, height
or trail markings.
2026-10-20 15:17:23 +02:00
Maurice ad2d653e9a fix(tours): save_tour_change says nothing was saved and answers a proposal saved once already
A save refused by the rules of the tour now says "Nothing was saved and
nothing changed." instead of the preparation's wording: lay() takes what
the changes are laid for, and refusalText() leads with that action.

A save sent again after it committed (a retry after a lost answer) used
to meet the conflict of a tour that moved on, which tells the assistant
to prepare the change anew, so an add could be saved twice. The save now
looks up the step its proposal was saved as from the revision it was
prepared on, before the stale check and once more inside the write, and
answers already_saved with that step instead, writing nothing.
2026-10-20 15:14:30 +02:00
Maurice b177daea3c fix(tours): point undo_tour_change at the revision prepare_tour_undo returns
The expected_revision field told assistants to read now.revision of the
preview, but prepare_tour_undo never returns that field: the revision sits
at undo.expected_revision. An assistant following the description either
guessed or left the value out and ran into a validation error or a revision
conflict. The description now names the real field, and the schema spec
pins it.
2026-10-20 15:14:30 +02:00
Maurice 63a7a4d397 feat(tours): undo_tour_change takes back a saved change to a tour once the traveller confirmed it
An assistant can now take back a change it saved with save_tour_change,
the way the change was saved: listed, shown, then taken back only after
the traveller said yes. Until now a saved step could be followed but not
undone; the traveller had to rebuild the walk by hand in the planner.

- list_tour_change_steps lists the steps of a tour, the latest first, with
  the changes as confirmed and where each stands: latest, undone, later
  change on top or no readable earlier state. Planner saves are no steps.
- prepare_tour_undo shows what taking a step back does without writing
  anything: the tour now, with the stations visited or skipped, set
  against the tour exactly as the step kept it, the stations kept, coming
  back and removed, and the distance and walking time stored then.
- undo_tour_change restores that tour whole or not at all, through the
  write the planner's save uses: line, stations with their keys and the
  measures stored then, in one transaction with the mark on the step, and
  the trip is told after the commit. It needs place editing on the trip
  and checks everything again inside the write.
- Only the tour's latest change goes back. A step something was saved on
  top of since, in the planner or by another step, is a conflict on the
  tour's one revision (no second one), so no later edit is lost unseen; a
  preview read at another revision is a conflict too. Taking back a step
  that would remove a station visited or skipped since, or bring back a
  station whose place of the trip is gone, is refused with every reason,
  and so is a step without a readable earlier state. Every refusal and
  failure leaves the tour as it is.
- A step is taken back once: tour_change_steps records the revision that
  raised the tour to, who confirmed it and when, and nothing else is kept.
- None of it touches a planner draft: the planner's own undo stays in the
  browser, and discarding a draft never changes a saved tour. The tool
  descriptions and the wiki say so.

The write of a tour's line and points and the tour as the answer gives it
now come from one place for the save and the undo.
2026-10-20 15:14:29 +02:00
Maurice f4533fc69b feat(tours): save_tour_change saves a confirmed change to a city walk whole or not at all
An assistant can now save the change prepare_tour_change prepared, once the
traveller said yes to it exactly as shown. Until now a proposal could only
be looked at; the traveller had to redo it by hand in the planner.

- The save names the proposal: the same changes, the revision it was
  prepared on, the proposal_id the preparation answered and confirmed true.
  Anything else, a change altered on the way or no yes, is refused and
  nothing is saved.
- Place editing on the trip and the tour are checked again before anything
  is written. A tour changed since the proposal was prepared is a conflict
  on the tour's one revision (no second one), telling the assistant to
  prepare the change anew instead of writing over newer edits. The changes
  are laid on the saved tour once more inside the write, so a station
  visited or skipped meanwhile, or a place of the trip gone, refuses the
  save with every reason.
- The change goes through the write the planner's save uses, in one
  transaction: line, stations, keys, measures and visits together or not at
  all, and the trip is told only after the commit. Stations kept keep their
  keys and visits. Where the router gives no way on foot the walk keeps its
  stations only and waits for the planner, as a created one does.
- Every save is kept as a step of the tour (tour_change_steps): the
  proposal, the revision it was prepared on and the one it raised the tour
  to, the changes and the tour as it was before them, so the step can be
  followed and taken back later. Nothing of the conversation is kept.
- A new walk has no revision to name and stays with create_city_walk; both
  tool descriptions and the wiki say which is which.
2026-10-20 15:14:29 +02:00
Maurice 1993ed4d28 fix(tours): prepare_tour_change drops the shaping of legs it takes apart and guards a loop's closing leg
A prepared change to a city walk took a station out or put one in without
touching the shaping points around it. The way after the change was then
routed through points steering legs that no longer exist, so the walking
effects showed a detour that is not real, and the points handed on for a
save could start or end on a shaping point.

- A shaping point belongs to the leg it steers. Removing, replacing or
  moving a station drops the shaping points of its legs in and out, and a
  stop put after a station drops those of the leg it splits. Shaping points
  of other legs stay. The answer names every dropped one in data_gaps
  (shaping_dropped), and the walk always starts and ends on a station.
- The closing leg of a loop counts as the leg into its first station. A
  closing leg drawn by hand now refuses a change to the first station and a
  stop after the last one, instead of quietly moving the end of a leg drawn
  in the planner. The hand-drawn check also covers every sub-leg between two
  stations, not only the one next to the station.
- for_later leaves out a new station already linked to a place of the trip,
  so following it no longer creates a second copy of that place.
2026-10-20 15:14:01 +02:00
Maurice 1969ca57d9 feat(tours): prepare_tour_change prepares a change to a saved city walk for review
An assistant can now prepare a change to a saved city walk and show the
traveller what it would do before anything is saved. Preparing writes
nothing: no tour, station, visit or day plan changes, and turning the
proposal down keeps the plan as it was.

- The changes apply in order and name the saved stations by their keys:
  add a station after another (a spontaneous stop is an add after the
  station the traveller is at), remove, replace and move. Stops put after
  the same station keep the order they were given in.
- What the travellers did stays as it is: a station visited or skipped on
  any day of the walk is never removed, replaced or moved, nothing goes
  before the last station already done, a protected station keeps its
  place and a leg drawn by hand stays as drawn. A proposal breaking a rule
  is refused whole, naming every change and why. Other tour types, and a
  walk that is a recorded track, are refused too: so far only a city walk
  takes prepared changes, since the others plan their way with routing of
  their own the server cannot run.
- The answer sets the walk after the change against the saved one: each
  station with what changed it and where it stood, what was removed, the
  way on foot before and after planned by the same router (the stored
  distance and walking time given apart, never mixed in), the stays as far
  as known, every leg with the new ones marked, the gaps that could not be
  worked out, a warning for a must station removed or replaced, and each
  new station as create_place takes it, to keep it for later as a place of
  the trip instead.
- The proposal names the revision it was prepared on, so a save of it is
  refused once the tour has moved on, and says whether the traveller may
  change the trip's places at all.

The way on foot of a new walk, its creation and a prepared change now come
from one CityWalkFootWayService instead of a copy in the briefing service.
2026-10-20 15:14:00 +02:00