The router thread now takes one job at a time from a queue kept on the
main thread, so a job's clock starts when the thread takes it up. A job
that times out ends only its own thread, and only while that thread is
still the current one; the jobs in line go to a fresh thread instead of
waiting out their own timers and killing the new thread in turn. A
thread that dies fails the job it held, and a thread that cannot start
fails each job on its own.
- the daily refresh fetches changed areas two at a time and stops once
the TREK Places breaker opens; the rest keep their older copies
- the winter index cap is on the unpacked JSON fetch hands over, so it
follows from the host's 8 MB gzip cap (64 MB) instead of matching it
- a build name is read to the rule a ski tour stores it by, so a build
TREK could not save is refused when the winter layer is read
- an unused import in the piste router contract is gone
A ski tour now saves the options its legs were planned with and the piste
data build they ran on, as one JSON column (a new migration, since the
ski migrations have shipped on this branch); the server keeps only the
options its discipline reads. Both stay off every other type. The refusal
of a ski time is lifted: the piste router works one out, and the planner
sends it only when every leg followed the piste data. get_tour_plan names
the legs of a tour the router planned as routed, and list_tours says what
the new fields hold.
A new Nest domain plans the legs of a ski and winter tour on the runs,
lifts, cross-country trails, ski touring and winter paths the Places host
builds from OpenStreetMap, one SGR1 graph per ski area. TREK fetches the
index once, downloads an area by its id when a planner first plans in it,
checks it against the index's SHA-256 and keeps it in data/ski; no
clicked point ever leaves the server. A leg whose area is slower than two
seconds answers loading, and an older copy stands in while the host is
down.
The reader decodes the writer's golden file byte for byte, including the
two edge flags it added (area_has_run, type_unknown). The search is A* in
a worker thread over a profile per discipline (alpine, snowboard,
cross-country by technique, touring, snowshoe, winter hike) with the
times and penalties of the cost model, up to five snap candidates per
point, lift stations as entry points, legs that go on from each other,
pieces of a split network stitched by their boundary nodes, other ways by
the penalty method, and a failed leg explained by relaxing one rule at a
time (difficulty, lift class, technique, riding down, uphill) without ever
handing out the relaxed line.
POST /api/trips/:tripId/ski/route, GET .../ski/area-near and
GET .../ski/network/:areaId sit behind Tours and the trip, with
plan_ski_route as their MCP twin. A daily job looks for a new build and
sweeps areas unused for 90 days. The admin switch ski_routing_enabled is
on wherever TREK Places is.
The ways and routes layers now count failures on a breaker of their own, so
busy or slow layer answers no longer send place search to the fallback for a
minute, and a layer's 404 no longer clears the count of search failures.
When a later batch of /v1/ways fails, the batches that answered are kept and
only the ids still open go to the Overpass mirrors.
The wiki, the compose file and the chart now say that the way ids of a planned
tour and the horse routes box go to the TREK Places API.
The trail grades under a tour (BIK-05, MAP-03, MAP-04, RUN-02) and the
horse routes around a map view (HOR-01) asked the public Overpass mirrors.
The places host now serves both from its own planet build: GET /v1/ways
for the tags of ways by id and GET /v1/routes for route relations per box,
each in Overpass element form.
trek-places.client gains trekPlacesWays (batches of 500, one after
another) and trekPlacesRoutes, both through getJson with its off switch,
breaker, timeout, size cap and instance header, each element validated on
its own. TrekPlacesLayersService in the maps domain hands their answers to
the tours services, or why there is none (switched off, layer not built,
failed), and logs that once per layer until it answers again. Both tours
services ask the layer first and fall back to the existing Overpass path;
the responses TREK sends its clients are unchanged.
A fresh install gets Tours enabled from the addon seeder. An install that
already has the row is switched on by a migration that runs once, so an
admin who turns Tours off afterwards keeps it off.
The get_tour_plan description named the estimate of a tour "such a field"
right after the values of a recording, so it could be read as one of them.
It now follows the sentence on derived fields and says outright that it is
one of them, worked out from distance and heights or the pace of a run.
The arrow between a tour's start and end points from the start to the end
in a right-to-left language as well. A tour just added to a day, which
carries no tour_place_id until the next refresh, now says its overrun like
the row above it. A phone row of an import with a total the travellers
entered shows that one time, the estimate joining its words as on the
desktop. The German overrun line reads naturally without an item name.
A hike imported from GPX, KML/KMZ or wanderer had no time at all, so
several hikes on one day could not be planned against each other. It now
gets the walking time the planner times a hike by (DIN 33466), worked out
from its own distance, ascent and descent; a run gets its distance at the
pace entered. Without heights in the track the time is unknown rather
than the time on the flat, and types timed by a router, by a speed of
their own or by nothing get no estimate.
The estimate is derived on every read and never stored, so tours imported
before get it too and no stored or entered time is touched. The rule moved
to @trek/shared, where the planner, the client and the server read it.
The tour list, the day rows on desktop and phone and the tour detail say
it as an estimate, apart from a planned total; list_tours and
get_tour_plan carry it as time_estimate and name it as derived.
The collection tour fixture no longer writes the dropped winter_activity
column, the drafts are saved as version 11, and a hike no longer offers
the pistes layer, which belongs to a ski track only.
The winter label and four-discipline migrations are already on
feat/tours-board-rest, so a database that ran them would refuse to boot
or fail the old CHECK. 045500, 045520 and 046100 are back as they
shipped, and a new migration drops both winter_activity columns and
adds ski_discipline again with the six disciplines. The entity and
table types follow the column to its new place.
German, Hungarian, Indonesian and English no longer name the whole type
with the ski touring word, and the info dot speaks of ski tours and
tracks. search_tour_pois and its wiki row list rides and ski and winter
tours. Snowshoeing gets a snowshoe glyph instead of the walk's
footprints, a ski import without a discipline can be planned over (the
planner asks for it), and an overlay shown by default tries its tiles
afresh each time it comes on.
Ski becomes "Ski & winter", a type the planner plans with six disciplines
(alpine, snowboard, cross-country, ski touring, snowshoeing, winter hiking),
nothing preselected; saving needs a discipline, and a technique for
cross-country. Every leg is drawn straight between the points through
planner/skiRouting.ts, the one place the piste router will plug in, with its
heights from the elevation source and no ski time worked out.
The hike's winter label goes entirely: its migration, column, field, marks,
GPX rule, MCP field and strings. A hike is summer only; a ski tour with the
snowshoe discipline exports as snowshoeing. The unreleased ski migration takes
the six disciplines and the collection migration loses the winter column.
The pistes are offered for a ski tour, shown until switched off, and with no
tour open, never for a hike. Export, copy, collections, stages (untimed),
actual tracks, the atlas, list and day marks (one glyph per discipline), 3D,
planning over a ski import and list_tours/get_tour_plan know the planned ski
tour.
The confirmed field is checked through z.object(...).pick() as the dawarich
and doc-sync tests read their tool shapes, and the stage edits pass typed
waypoints through the request contract as the walk tests do. The previous
forms sent the test typecheck out of memory.
Raw request bodies are objects, the collection snapshot names the run, ski,
winter and track time fields, the walk write says it has no hiking limit,
the MCP field shapes are read as zod shapes, and the stage edits go through
the tour request contract the API parses them with.
Expectations follow createAssignment's options, the stage_ends column, the editor's trip access and the broadcasts of the take-over. Refusals in the run and ski e2e tests go one by one so supertest does not reset a connection under another request. A parity test keeps the source hint enums in step with the contract.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
An import without control points of its own answers the two ends of its
recorded track as its points, and the one leg between them was labelled
routed, as if the router had planned it. The assistant read a false fact.
Such a leg now says track, and the tool description and the wiki explain
the kind.
An assistant reading a tour over MCP now gets everything the saved plan
holds, from the same reads the app uses, so its suggestions start from the
actual plan:
- legs lists every step from one control point to the next with how its
line came to be (routed, via ferrata, drawn by hand, portage), keyed by
the points' stable keys, and the length of a fixed leg's own line; a
routed leg has no length stored apart and says so with null.
- line gives the saved line in brief: its point count, whether it carries
heights, and a preview thinned to 200 points; null while the way waits
to be planned.
- A city walk can be read for one of its days (dayId): each station then
carries its visit status that day and run what is still open. Without a
day no visit is told, so one day's ticks are never passed off as the
walk's; dayId on another type or a day the walk is not on is refused.
- derived names every field worked out on reading rather than stored,
including the control points an import without its own answers from the
ends of its track.
Reading still writes nothing: no revision moves and no visit changes, and
drafts in a browser stay invisible. The wiki row of the tool says the same.
A ride sends its full line with heights and every horse route section's
own line besides, about fifty bytes a point, so a ride along a few
thousand points of bridleway hit the API's 100kb body ceiling and the
save failed with a 413. Creating and editing a tour are now measured
against TOUR_WRITE_MAX_BYTES (2 MB), like the book and list file
routes; every other route, the tour copy among them, keeps 100kb.
The horse routes kept in memory are now bounded by section points as
well as by count (64 answers, 500k points), so panning a dense map can
no longer grow the server by hundreds of megabytes.
A search that found no connection can be asked again on the view whose
routes are shown, and a ride with a recorded time shows the horseshoe
beside its riding time instead of footprints.
A tour can now be a ride (HOR-01). The planner offers "Horse ride" as a
tour type with its own horseshoe, and a Horse routes card beside the
waypoints looks, when asked, for the route=horse relations OpenStreetMap
knows in the map view. A route opens in a dialog that shows it on a map in
the sections it is made of, split at forks, gaps and crossings, so nothing
is ever joined across a gap. The rider picks a section and its direction,
sees what the horse=* tags along it say (open, limited, closed, or unknown
where untagged) and whether it is signposted the other way, and takes it as
one undoable step. A section that does not start where the ride ends is
only joined by a straight manual stretch once the rider switched that on.
Nothing of a ride is routed: every leg follows a section or is drawn by
hand, and the waypoint list and the map say which (a section as a solid
line with its route, a manual stretch dashed and marked unchecked). A point
at a section stays where the route has it, a ride works out no riding time,
and distance, heights, day assignment, GPX import and export, the 3D view
and the reversed ride keep working with each leg's origin saved and opened
again. A failed lookup leaves the ride and the routes already found as they
were.
The server reads the routes through the shared Overpass client
(GET /api/trips/:tripId/tours/horse-routes, Tours addon, trip access),
keeps an answer twelve hours per grid-aligned view, and the tour contract
refuses a routed leg, a via ferrata or a riding time on a ride and a horse
route section on any other type. A migration enables the horse tour type.
SKI-01. The Tours planner's layer switch gains a winter layer: OpenSnowMap's
transparent piste overlay over whichever base map is shown, so a ski area
can be looked at while preparing a winter trip. It is offered with no tour
open (a resort without any route), beside a hike for winter hiking and on an
imported ski track; it needs neither a GPX import nor the ski type, and a
bike tour, a run and a paddle tour do not offer it. It is off until switched
on, and the choice belongs to the map view: switching it changes no point,
no step and no route, and it stays on from no tour to a hike and into 3D,
where the terrain draws it too.
The switch goes by its icon alone, named by its tooltip, so a hike's row
stays short. While the layer shows, a small key button rides beside it in
the same pill, its badge the four grade colours. The key opens above the
map as a popover: the downhill grades in OpenSnowMap's own colours (novice,
easy, intermediate, advanced, expert, freeride), cross-country trails,
winter and snowshoe trails, toboggan runs and lifts, a note that this is
mapped data that says nothing about what is open or groomed, the snow or
avalanches, and a link to the full key at OpenSnowMap. If the tiles stop
coming, the switch turns to the warning tone with its badge and says that
the map and the route still work, and the key stays away.
Only the overlay is used, never OpenSnowMap's relief or base maps. Like
every overlay it loads only the tiles in view: nothing is prefetched or
cached by the Service Worker, as their terms forbid bulk downloads. The
tiles go out with the page's origin as the referer, the attribution credits
OpenSnowMap (CC BY-SA) and the OpenStreetMap contributors, and the layer
stops at zoom 18, beyond which the server answers a picture instead of a
transparent tile. The CSP admits tiles.opensnowmap.org for the GL map,
which reads tiles through fetch.
SKI-02. Ski joins the types a GPX file can be imported as, offered in the
import preview's type field only: TREK routes no ski track, so the planner
never offers it. The preview asks for the discipline on purpose (alpine,
snowboard, cross-country or ski touring, none chosen at first) and, for
cross-country, the technique (classic, skating or unknown); Import waits
until both are named, and the technique stays in sight but dimmed for the
other disciplines. The track is stored exactly as recorded, with its file
name as provenance, its distance and heights from the file and no time:
no ski time is worked out and no hiking estimate stands in for one.
Opened in the planner, a ski track gets a rail card with its discipline on
a tinted tile, a cross-country technique as a badge beside it, the ski time
said as not worked out, and an info dot on what TREK does not say (snow,
avalanches, open runs, and that the 3D gradient colours are no difficulty
or avalanche rating). Change opens a dialog to classify the track again;
only the classification changes, through the offline core, with the
revision it was made at. Planning over the import is not offered for ski.
The existing 3D terrain view, the elevation profile and its focus work on
the track as for any import, with the standard map, topo and satellite and
no via ferratas. Lists, the day plan and the detail show the discipline as
a frosted corner badge on the tile and as a pill with the technique inside;
day assignment, copies and take-overs work as for any tour. The corner
badge is now one component that the winter label shares.
The server keeps tours.ski_discipline and tours.ski_technique (CHECK lists,
NULL on every other type), switches the ski type on without a routing
profile, refuses a ski import without its discipline, a technique outside
cross-country and ski fields on any other type, and takes a planned save's
classification off. PUT /api/trips/:tripId/tours/:placeId/ski classifies a
ski track again (place_edit, 409 on a stale revision, 400 for a tour that
is no ski track) and broadcasts tours:changed. GPX export names the
discipline (alpine_skiing, snowboarding, cross_country_skiing,
backcountry_skiing), and list_tours describes both fields with what a ski
track does not say. Ski is labelled plainly as Ski in every language, so
it no longer reads as ski touring.
RUN-02. Trail running joins Running as a run profile, chosen on purpose in
the planner and in the GPX import. It asks Valhalla's pedestrian costing for
tracks wherever they serve (use_tracks 1) and admits mountain paths up to T2
(max_hiking_difficulty 2), never T3 and above, with ferries only where no
way on land exists. Checked with trace_attributes against the public
instance: above Freiburg Guenterstal up to the Schauinsland the route went
from 82 % roads and 83 % paved (running) to 78 % forest tracks and 81 %
compacted, gravel and dirt; Rathen to the Bastei took 28 % of T2 path that
running leaves out; Heidelberg's old town stayed as it was. Leaning on
footways (walkway_factor) was tried and left out, it moved runs onto paved
footways.
While a run is planned, a card says what it runs on: its surfaces, the
hiking grades of its paths (sac_scale, T1 to T6, as mappers gave them) and
its kinds of way, each as shares with lengths that point at their stretches
on the map. A way without a grade or a surface is its own row, unknown and
never easy, and an info dot says the grades say nothing about whether a way
can be run. The head carries the unpaved share and the hardest graded path
as badges. The line is matched on foot for this, so steps and mountain
paths stay on it; a bike tour's is matched as before. Reading fails, goes
offline or is retried as for a bike tour, and nothing of it changes the run.
Pace and time are RUN-01's: the running time stays the distance at the
runner's pace, with climbs, descents and difficulty left out, whatever the
profile. A profile change plans the route again and keeps the pace; a
failed trail route keeps the last route and holds Save back. The card's
building blocks moved out of the bike card into RouteWaySections, so both
cards share one set of rows, bars and status lines.
The run_profile column already allowed trail (RUN-01), so no migration.
GPX export names a trail run trail_running, list_tours describes the
profile as a routing preference, never a statement that a way can be run.
RUN-01. Run joins the tour types in the planner, with Running as its first
profile, chosen on purpose like a bike tour's. A run is routed on foot for
runnable, mostly paved ways: tracks and ferries only where there is no other
way (use_tracks 0, use_ferry 0, measured against the public Valhalla), no
hiking limit and no via ferrata. The runner types a pace in min/km; the
running time is the distance at that pace and nothing else, shown under the
field as badges (8 km x 6:00 /km = 48 min). TREK suggests no pace: without
one the time stays unknown and Save waits, text that is no pace or a pace
outside 2:00 to 20:00 is marked. The info dot, the overview and the head say
climbs, descents and difficulty are not in the time, and the time reads as
planned everywhere, so it never passes for a recorded one; the heights are
shown as for any tour on land.
Type, profile, pace and route come back on opening, survive a draft reload,
an undo of the type, a copy and a take-over; switching to another type drops
profile and pace. Day assignment, the 3D terrain view (with topo and
satellite, no ferratas) and a routing failure keeping the last route work as
for every tour. A GPX file can be imported as a run with its profile and no
pace, and planned over as a run. Runs export to GPX as running and get their
own places along the way (water, toilets, parks, viewpoints).
The server keeps tours.run_profile (CHECK list, already holding trail for
RUN-02, since SQLite cannot widen a CHECK in place) and
tours.run_pace_seconds_per_km (120 to 1200), switches the run type on,
refuses a run without a profile and run fields on any other type, and
list_tours names both with what the planned time does not include.