A day header in the journey timeline now carries the Dawarich mark for an
editor whose Dawarich answers (dimmed with the reason while offline). It opens
a dialog that names the journey and the day and reads that one local day from
the caller's own connection: a sketch of the route, distance, first and last
time, pieces, gaps without recording, modes and whether tracks or thinned
points answered. The day is cut in the zone Dawarich records in where its
answer shows one, else in this device's, and a picker cuts it in any other.
Nothing is kept before "Keep route". On a phone the same sheet opens from the
journey's More options for the day of the card shown.
What is kept is the preview that was seen: the save reads the day again and
refuses with recording_changed when its hash moved, and the dialog shows the
new version. The same day taken over twice keeps one version and announces
nothing; a different version is replaced only on "Replace", never touching an
entry or text. A failed read throws before any write, so the kept version
stays. Gaps stay gaps: points are split where silence ends elsewhere, and no
day is joined to the one before. The dialog says how many other people will
see the route; editors may keep their own recording.
Kept days show in the day header (distance, whose recording, since when,
still running) and on the desktop and phone journey maps in the colour of
their day's pins, winning over the live trail of their date, and stay drawn
without Dawarich. A trip's GPX track is now drawn dashed as the plan, recorded
routes solid. Shared links do not show recorded days. Remove route asks first
and works without a connection.
Server: migration 046160 adds journey_recorded_days (one row per journey,
local day and source, segments as JSON, zone, window, fetch time, hash,
importer kept past their account). The journey domain owns the rows through
JourneyRecordedDaysService; the new journey-recordings domain takes a day
over through DawarichTracksService.fetchDay, which reads past the cache, asks
a window wide enough for every zone and refuses with DomainErrors. REST under
/api/journeys/:id/recordings (list, preview, PUT and DELETE /:date) with
@trek/shared contracts, and MCP list_journey_recorded_days,
preview_journey_recorded_day, save_journey_recorded_day and
delete_journey_recorded_day on the same service and access. A new user event
journey:recording:updated keeps every open map current. JourneyTrack gains an
optional kind. All 27 locales carry the strings; the Journey and Dawarich wiki
pages and the MCP tool list say what it does.
The pair rule wants TourDayAddButton to import stageSpread; the items it
builds belong there anyway. The addon manager test answers the Tours
switches, so a failed list is the only thing that toasts.
The vias the spread keeps on their legs are taken over in a layout effect
instead of during render, the route redraw takes the planner's async
updateRouteForDay, and the menu items for whole or stages move out of the
button component into their own module. Two leftovers of the old day pills
in tourParts go. The hook test's reanchor reloads the vias like the real
one does, and the detail test finds the button by its label, Add to Day.
GHSA-22jm-h49p-29qw and GHSA-6prh-2h8m-c8cw (MCP SDK, high) and
GHSA-5r4p-p66f-jhc7 (hono, moderate) are fixed in these versions. npm audit
for what the server installs reports none left.
Spreading a multi-day tour wrote through the store directly and skipped
the via re-anchoring every other write on the plan does. A tour with a
start counts as a road-trip stop, so taking the whole tour, a stale stage
or a moved stage off the middle of a day shifted every later stop, and the
vias anchored by position shaped the wrong leg. The undo had the same gap
when it put the whole tour back mid-day. Each stop a spread or its undo
takes off a day now carries the day's vias along (reanchorAfterRemove),
and the whole tour put back carries them the other way
(reanchorAfterInsert). The stops are read from the store as each write
goes out and the vias are kept as the last correction left them, since
one run can touch a day twice before the plan renders.
The phone's place sheet greyed out a day that holds a multi-day tour, so
its stages could not be spread from there. It now checks the day the way
the desktop menus do and leaves the choice to the plan, which opens the
card with only the stages offered.
The second step of the day menu names the day picked, and a greyed-out
"Whole tour on this day" says why right under it. The toast for a tour
that sat whole on several days now says "days" instead of "day", in all
27 languages.
Putting a multi-day tour on a day through any of the usual ways put the
whole tour on that one day. Now the way it goes on is a choice: the whole
tour on this day, or stage 1 on this day and each next stage on the next
day of the trip.
The day menu of the tour lists and the detail's "Add to day" ask in the
same menu, as a second step after the day; a tour dragged onto a day, or
added from a place where no menu asked, gets a small dialog on the desktop
and a card on the phone. Only the trip's days are used: when fewer days
are left than stages, the menu says up to which stage fits and the rest
stay without a day. A stage already on a day moves to its new day, the
whole tour comes off the days it was on, and a stage the route no longer
has comes off too. The writes run in order through the store, the repo
and the mutation queue, so they work offline, and one undo takes the
spread back, the whole tour onto its day again.
The plan lives in stageSpread.ts, the writes and the question in
useTourDayChoice, which every way onto a day now passes through on both
the desktop and the phone.
The unused day pills go, a client-only type stops being exported, the place
form context moves out of the page hook so its view model stays the first
object it returns, the place form test helpers split the stub component
from the plain setup, and four router files are formatted.
The place form's time section moves into PlaceFormTimeSection, its tour test
into PlaceFormModal.tour.test.tsx over a shared tests/helpers/placeForm setup,
the page hook hands the place form its close, day plan and tour, and the day
plan's surface classes and tones move into dayPlanSurfaces. The size baseline
drops to the new counts.
ski-areas.service checks the pending load and download against null and
undefined, the area refresh lane recurses instead of awaiting in a loop,
TourSkiRoute is inferred from its schema, the ways answer is read by
complete === false, and /v1/routes no longer needs a truncated field.
The saved ski route compared against an undefined skiKey, so opening a saved
ski tour threw. The held seed now lives in useSavedSkiSeed and is matched by
skiRouteKey(skiChoice). The ski route facts move to skiRouteFacts.ts so the
summary and leg rows only export components, the rail renders
SkiPlannerSummary, and openTour, useTourRoute and useAddonManager shed the
branches and lines the new ski code had added.
A ski leg is routed where the piste router planned it and free elsewhere;
the description said both. The collection tours entity takes its generated
form again after the merge.
A saved ski tour now opens with the line, heights, time, build and
planned legs it was saved with, read from the place this device keeps,
and nothing is asked of the piste router until a point, an option or the
discipline changes. Opened offline, while TREK Places is down or with
piste routing switched off, it no longer turns every leg straight, and a
rename saves the planned tour as it was. A kept line whose length is not
the tour's is another save's, and the tour is planned afresh.
- the save names the legs the router planned (ski_planned_legs, checked
against the tour's own legs); get_tour_plan reports each leg routed or
drawn from them instead of from the build alone, a tour that names none
counting as planned only with the router's time
- copies keep ski_options in every path: in the trip, taken over, kept
in a collection and made from one (collection_tours gets the column in
the same unshipped migration)
- the route summary shows the data build, the km of runs, the height
skied down on them and the lift rides, and the credit the server sends
in place of a fixed one; a tour opened as saved says so
- answers refused while piste routing is off, or planned on an older kept
copy, are not remembered, so they are asked again later
- terrain heights between known ones are filled in one walk along the line
- i18n: five summary keys in all 27 locales
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 with a discipline now sends its legs to the piste router and
takes back runs, lifts and trails: each stretch is coloured by its grade
in the area's scheme, links and lift rides down are dotted, and a leg the
router cannot plan stays straight and unrouted with its reason and the one
change that would plan it. Snowshoe and winter hike legs off the piste
data follow summer paths instead, said so. The router's time and heights
are used once every leg is planned, and the options, the build and the
time are saved with the tour.
- skiRouteRepo and api: one request per tour, batched, remembered unless
the answer may change, offline legs failed as offline
- planner: router options per discipline, leg steps and failure rows with
fixes, a piste route summary, draft and reopen keep the options
- admin: switch for the piste router under Tours
- i18n: new ski keys in all 27 locales, ski info and time texts rewritten
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 binary heap and the flat-frame distance helpers are not about water;
the piste router needs the same two. They move to src/lib/geo, where any
domain may import them without reaching into paddle-routing. Nothing
changes in what they do.
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.
Since the estimate rule, an import without its own time shows the worked-out estimate on the day plan tile, and the sidebar metrics lead with it. Add the estimate field to the RS-01 fixture.
Move workedOutEndHint and totalText into tourDayTimeHint.ts so TourDayTimeParts only exports components, render the collection row's type icon without a component made during render, and pass the chase camera's lens and scene as one view object.
The flyover asked wheel and pinch to zoom around the centre with enable()
on handlers that were already on. MapLibre returns from enable() at once
in that case, so the option never took and a wheel zoom still went to the
cursor before the camera pulled back to the line. Each handler is now
switched off and on again with the centre anchor, and the same way back to
the cursor on pause, stop and unmount. The map stub refuses an enable() on
a handler that is on, like MapLibre does, so the tests see the real anchor.
The GPX file kept for the share sheet was only dropped when the tour's id
or name changed. A route saved elsewhere arrives live with a new revision
and the dialog kept handing out the old file. The kept file now carries the
revision it was read at; a new revision drops it and fetches again, and a
file a tap kept is not shared for another revision.
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 chase camera set a fixed zoom and pitch on every frame, so wheel,
pinch and the +/- keys were undone at once. It now keeps the zoom and
tilt of the user's latest gesture and only follows position and
bearing. While a zoom is still easing the flyover holds for those
frames, since any camera move would cancel the gesture. Wheel and pinch
zoom around the rider while flying, and drag panning is off until the
flyover stops. Starting over returns to the flyover's own zoom.
Sharing a tour fetched the GPX file first and only then called
navigator.share, so the tap that asked had often run out by then. The
retry path kept that file for one more tap only, and a tap while a sheet
was still open made the browser throw InvalidStateError, which read as a
failed share.
The share button now fetches the file ahead where sharing is offered
(not while the planner has unsaved changes), and every tap hands a fresh
File built from the kept bytes to the sheet before anything is awaited.
A file a tap had to fetch is kept for the next tap. A tap while a sheet
is open is let be, and InvalidStateError and a closed sheet stay quiet;
only a real failure still says the download is left.