Commit Graph

4013 Commits

Author SHA1 Message Date
Maurice beea4c762b refactor(server): move the routing heap and the metre helpers out of the paddle domain
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.
2026-10-20 21:12:15 +02:00
Maurice d924dcf99d fix(tours): keep the Places layers off the search breaker and keep partial way answers
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.
2026-10-20 21:12:09 +02:00
Maurice 371083ed47 feat(tours): read way tags and horse routes from the TREK Places layers
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.
2026-10-20 21:12:09 +02:00
Maurice eaeaf68352 chore(server): format the tours MCP test 2026-10-20 21:11:44 +02:00
Maurice 5156fa2c0a test(addons): check the Tours migration against the addon table as it was before
The table holds the seeded addons too, so compare against a snapshot taken before the migration with only tours switched on.
2026-10-20 21:11:44 +02:00
Maurice 6c6e285c3d test(tours): expect the time estimate on imported tours and hikes without a time
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.
2026-10-20 21:11:43 +02:00
Maurice 93e1156aa4 refactor(tours): bring the tour time parts, collection rows and flyover back under the lint baseline
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.
2026-10-20 21:11:43 +02:00
Maurice dc19807126 fix(tours): zoom the flyover around the rider and keep no stale GPX file
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.
2026-10-20 20:56:19 +02:00
Maurice 5580ff71a9 feat(addons): Tours is on for every install
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.
2026-10-20 20:56:19 +02:00
Maurice e42622d71d feat(tours): let the user zoom during the 3D flyover
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.
2026-10-20 20:56:19 +02:00
Maurice 94674e7572 fix(tours): open the GPX share sheet inside the tap
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.
2026-10-20 20:56:19 +02:00
Maurice 7f14dde362 fix(tours): tidy the day times of a tour after review
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.
2026-10-20 20:52:31 +02:00
Maurice 9765cf3fee feat(tours): give a tour its start and end in the day plan
A tour on a day had no place in the day's timeline: its time was not tied
to the slot it was planned in, so several hikes on one day could not be
lined up. A tour now sits there the way a place does, on the times of its
day entry, one model for every tour type. With a start and a known time
(the planned total, else the estimate of an import) its end follows as the
start plus that time; it is shown fainter, marked with an approximate sign
where it comes from an estimate, and says in its tooltip how it was worked
out. An end typed by hand wins. A day that holds one stage of a multi-day
tour gets no end from the whole route.

When the end runs past the start of the day's next timed item, the row
says so in one quiet line, as a late connection between two city walks
does; nothing is moved. The existing time fields of the day entry, on the
desktop form and the phone sheet, say what the end works out to while none
is typed. City walks keep their arrivals per station. The logic lives in
one module both day plans run on.
2026-10-20 20:52:30 +02:00
Maurice c38eb7ebf0 feat(tours): estimate the walking time of imported tours
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.
2026-10-20 20:52:30 +02:00
Maurice e653733390 fix(notifications): a little more room for the unread count, with a test 2026-10-20 20:52:23 +02:00
Maurice 2655e510fc fix(notifications): give the unread count room in its badge 2026-10-20 20:48:33 +02:00
Maurice 904eb31e62 fix(tours): even the tour detail's rows and footer
The times fill their row when there are two, the trip days are boxes in
the look of the measures with the date under the day, the footer's
collection button carries its label like the others and the copy button
says Copy, and the name field keeps its distance to a save button as large
as the close button.
2026-10-20 20:48:33 +02:00
Maurice 76ec302526 feat(tours): kept tours are rows of a collection's list, on the phone too
A tour kept in a collection was a card in a box of its own above the list,
and its button lost its look to the collections page's button reset. It is
now a row of the list like a saved place: its grade or type tile, its name,
type and the trip it came from, its measures, and Add to a trip, with
removing dimmed beside it. The list's search narrows the tours too. The
phone shows the same rows and takes a tour into a trip from a sheet, on the
same hooks as the desktop.
2026-10-20 20:48:33 +02:00
Maurice 810feba413 fix(tours): open the tour dialogs in the middle of the screen
The walk from a Google Maps link, the take-over from another trip and the
other new tour dialogs sat at the top of the screen; they now open centred
like the planner's own dialogs.
2026-10-20 20:15:05 +02:00
Maurice 4009588078 fix(tours): give the latest stage date a starting value
Sonar flagged the reduce without an initial value; every stage has a date at
that point, so the empty start never wins.
2026-10-20 19:27:58 +02:00
Maurice ef583ede18 fix(notifications): name each wanderer hint text in a key of its own
The three texts of a wanderer hint were picked in a nested conditional, which
the client's key check cannot read, so it counted them as unused.
2026-10-20 19:16:20 +02:00
Maurice 9bc543a387 chore(tours): keep the client ratchets where they were
Snowshoe is no longer exported, useRouteCalculation takes the stage stops
type through dayRoutePlan, and tourPlannerFacts and useTourRoute hand the
saved duration, the ski check and the planned key to small helpers.
2026-10-20 19:07:13 +02:00
Maurice 7291555e95 test(tours): follow the ski tour changes in the tests
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.
2026-10-20 19:07:12 +02:00
Maurice 57f0e2f263 fix(tours): give the tour entities back their generated form
The format run rewrote two files that check:entities holds byte for byte
to the generator's output; .prettierignore excludes them for that reason.
2026-10-20 19:07:12 +02:00
Maurice de043cfb58 fix(tours): keep the get_tour_plan description one string
An apostrophe in the ski sentence ended the quoted description early, so the
tours MCP file no longer parsed and the server could not boot.
2026-10-20 18:53:40 +02:00
Maurice 69df85bfa2 chore(tours): format the ski and winter files 2026-10-20 18:41:55 +02:00
Maurice be6702f3ab fix(tours): keep the shipped ski migrations and follow the ski tour through
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.
2026-10-20 18:40:48 +02:00
Maurice 164a3633ac feat(tours): plan ski and winter tours, snowshoeing and winter hiking among them
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.
2026-10-20 18:40:48 +02:00
Maurice 204ee2ef86 chore(ci): give the server test typecheck room, format new client files
The server's src and tests together are past 570,000 lines and the test
typecheck needs about 4.3 GB, over Node's default heap; it now gets 8 GB like
the test jobs. Forty new client files are formatted, and the react-hooks/refs
baseline drops by the one warning a split hook removed.
2026-10-20 18:39:58 +02:00
Maurice baba2de344 refactor(planner): hand the day plan its tours as one bundle from the page hook
Which places are tours, their planned times and where their stages run
travel together to the desktop and the phone day plan. useTripPlannerPage
now puts them together as dayPlanTours, and the page passes that bundle
instead of naming the three props at both places.
2026-10-20 18:26:56 +02:00
Maurice 49dd2db040 refactor(planner): project a day's tours by assignment in one place
The day route plan and the day plan sidebar both built the map of a day's
stops projected onto their tours, each stage on its own stretch, by hand.
projectedById in dayTourProjection builds it for both, and the sidebar's
drive runs reuse the route plan's point type instead of spelling it out
twice.
2026-10-20 18:26:00 +02:00
Maurice 49e306ea48 refactor(map): report the GL viewport from a module of its own
reportGlViewport says what a GL map shows, its bounds and its zoom on the
raster tile scale, and has its own test for that scale. MapViewGL only
wires it to moveend and idle, and its test checks that wiring. The GL map
tests put the zoom back to its default before each test, as they do for
every other value a test overrides, instead of each test restoring it.
2026-10-20 18:23:50 +02:00
Maurice 1d7c8a63e2 refactor(tours): hold the planner's routing choices in a hook of their own
The hike's limit and preferences, the bike profile and the run profile were
four states with four refs kept in step by hand in every handler that set
them. useRoutingChoices holds them as one value with one ref, so a history
step, an undo, a new draft and an opened tour each lay them in with one
call, and useTourPlanner is back under the size limit.
2026-10-20 18:21:46 +02:00
Maurice fb208a88e2 test(tours): read the MCP fields and stage edits the way the other tests do
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.
2026-10-20 18:19:00 +02:00
Maurice 5ae8ac8e70 test(tours): type the tours tests against the current contracts
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.
2026-10-20 18:05:41 +02:00
Maurice 20bd384feb fix(tours): send a ski import's technique only when one is chosen
The classification request may clear the technique with null; the GPX
import fields take it or leave it out, so the null never reaches them.
2026-10-20 18:05:40 +02:00
Maurice 58302dc9e4 fix(tours): keep the planner page hook's view model its only object return
The map focus helpers split out of useTripPlannerPage returned object
literals ahead of the hook's own return, which the B4A wiring test reads as
the view model. They move to tourMapFocus.ts beside the hook.
2026-10-20 18:05:40 +02:00
Maurice b460cfd9a7 refactor(tours): move non-component exports out of component files and split long hooks
Clears the client lint ratchet: helpers move to sibling .ts files for react-refresh, complex render paths get small helpers, floating promises are voided, the stage rail lets a stale request go during render, and the collections page reads the active list's tours and rights in ActiveCollectionTours. StageNumber is no longer exported.
2026-10-20 17:51:28 +02:00
Maurice 2b8d2e790f chore(shared): format the tours and wanderer locale and spec files 2026-10-20 17:51:27 +02:00
Maurice 2fc9b1991a test(tours): fix client test expectations and mocks
The type order puts run before kayak, the import button reads Import GPX or KML, and the mocks gain what the components now read: routeRun, packing items and links, the map's zoom, the translation provider and the compare store's map flag.
2026-10-20 17:51:27 +02:00
Maurice 4d81180fc1 test(tours): bring the server tests in line with the tours board
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.
2026-10-20 17:51:27 +02:00
Maurice 0f600d04f5 fix(tours): import the walk icon, keep one stop menu open and count only stage ends a change lost
The activity section used Footprints without importing it. Opening one stop's menu now closes any other. lostStageEnds no longer counts a stop the change kept without its mark as lost twice over. The wanderer watch switch keeps the same tree once the connection is known, so it is not remounted and keeps its focus.
2026-10-20 17:51:26 +02:00
Maurice b53257deaf fix(tours): answer a new tour with its activities and type the change save's outcome
createTour now answers activities: [] as the detail read does, and the change save's transaction declares the shape it returns so the strict typecheck can narrow it.
2026-10-20 17:51:26 +02:00
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