Commit Graph

270 Commits

Author SHA1 Message Date
Maurice 87441f269d fix(studio): lay out a book as long as its journey, with no page limit
A book held at most 150 spreads. The auto layout cut everything past that
without a word, so a long journey's book ended about three quarters of the
way through, with its last page and back cover gone, and adding, copying or
applying a designed page to a full book was refused.

The spread cap is gone from the book schema, the page operations, the
design composer and its apply, the assistant's book card and the design tool
contracts. A design's own page budget (pages.max) still bounds what it lays
out. The save route's 8 MB body still bounds a book's size, which at two to
five kilobytes an auto laid page is a few thousand pages.

The templated design digests move because their counter ids shift by two;
the pages, their order and their elements are unchanged.
2026-10-28 12:06:50 +01:00
Maurice 2d398568da fix(assistant): no status request while neither AI addon is on
Every page of an install with the assistant and AI parsing off asked /api/ai/status and logged the 404 the addon guard answers. The store asks only while an addon opens the route and the install is not managed, and one hook on the desktop window and both phone screens asks again when the assistant or AI parsing is switched.
2026-10-25 09:48:37 +01:00
Maurice 4b3340546f feat(assistant): the journey page opens the journal's chat on desktop and phone
On /journey/:id the assistant window and the phone's assistant screen now
open the journey's conversation, the one TREK Studio holds too, instead of
the home chat; the Studio route keeps its own column. A turn sent from a
chat the Studio does not host names the journal page, a hosted one the
Studio, so the server offers the right tools. A journal card from the home
chat opens that conversation in place on the desktop and the journal page
on the phone, and the empty chat asks about the journal rather than the
book.
2026-10-24 22:54:43 +02:00
Maurice 9695e269e3 fix(studio): shapes named in every language, the align target kept across selections, up and down in a tab grid 2026-10-24 11:07:16 +02:00
Maurice a0554c2346 fix: four reported bugs that dev still had
- Dashboard: a browser that names no time zone (Chrome on Windows for a zone
  it cannot map) crashed the world clocks and the whole dashboard. One helper
  now reads the browser zone and every reader picks its fallback; a stored
  zone the browser does not know is left out instead of throwing (#2694).
- Phone: a leg of a multi-leg train or flight showed only its route, and its
  sheet showed the first leg's seat. The day plan subtitle is one helper for
  desktop and phone now, so a leg names its own train or flight, platform and
  seat, translated instead of the hardcoded "Gl."/"Sitz"; the sheet opened
  from a leg shows that leg's number, seat, platform and times (#2701). The
  PDF export translates platform and seat too.
- Journey list: the year badge named the year a journey was written, not the
  years of its trips; a trip over New Year shows both (#2685).
- A place's description and notes are markdown in the planner but printed as
  raw syntax in the PDF export and on a shared trip. Both render it now through
  the app's sanitized markdown pipeline, raw HTML stays text (#2692).

Closes #2694
Closes #2701
Closes #2685
Closes #2692
2026-10-24 10:39:35 +02:00
Maurice e2c00ba18b feat(studio): a full page says so instead of swallowing the click 2026-10-24 10:07:59 +02:00
Maurice 00018959c6 fix(studio): a full page takes no new element, the opacity slider commits a move only, a theme draft survives saves
Every add in the Studio (a sticker, a shape, an icon, a text, a frame, a line,
a travel element, a photo from the content panel or a drop), a text style and
a grid of frames appended without looking at the page's cap of 90 elements,
which paste and duplicate already kept. The save then failed with a 400 and
every later save of the book failed the same way. Each of them now leaves a
full page as it is and selects nothing, as a paste does.

The opacity slider committed on every key let go on it, so the Tab that moved
the focus there wrote the value it was last moved to, an empty undo step at
best and another colour's opacity at worst. It commits once after it moved,
on the key or the pointer let go or when the focus leaves.

The Theme panel threw away colours, faces and motifs being tried whenever the
book came back from a save by another editor or the assistant, because the
same theme read anew is a new object. The draft now follows the book's theme
only when the theme itself changed.
2026-10-24 04:19:25 +00:00
Maurice 84e18e4954 fix(studio): paste style, the page background, layouts and fill page keep the engine's rules
Paste style wrote the copied fields straight onto the element, although a
pasted look is meant to be the style operation the assistant sends. It now
runs the engine's own restyle (bookElementStyled): a colour or face pasted by
hand takes the element off the theme, so the next theme applied no longer
brings the old colour back; a plain fill replaces the paint of the shape it
lands on instead of staying hidden under it; and a line takes another line's
look without its points, which are its course, not its look.

A background picked by hand in the inspector kept the page's link to the
theme, so the next theme applied set the page back to the theme's colour. The
picker and the background operation now paint through one helper that takes
the link away (withSpreadPaintByHand). Giving one page's background to every
page carries that page's link along, or takes the others' away when it was
painted by hand.

A built-in layout, a book's own layout and laying a page out again from its
entry ignored locks in the Studio: a locked shape was dropped and a locked
photo poured into a slot unlocked, while the assistant's layout operation left
both where they were. All four now lay a page out around its locked elements
through one function (layoutAroundLocks), which the engine's layout operation
uses too.

Fill page and fill spread kept the photo in its group, so the background
picture was selected and dragged with its caption and listed under the group
at the front. It leaves its group as the engine's fill does, and a group left
with one member goes.
2026-10-24 04:06:27 +00:00
Maurice 75d0593198 feat(studio): text effects, fit to frame and 24 lockups in a Text styles shelf
- A text can be cased (capitals, lower case, small caps), placed at the top,
  middle or foot of its frame, outlined, given a hard shadow or a marker band,
  bent on an arc or round a circle, filled with a gallery photo or left
  hollow, and shrunk to fit its frame. Every effect prints as vector: copies
  and strokes for HTML type, textPath and a clip over an <image> for SVG.
- Fitting is solved when the text is edited, in the Studio and in the edit
  engine, and stored; the chosen size is kept as the ceiling.
- Text sizes reach 400 pt and tracking 1.5 em.
- The Elements panel opens on a Text tab: the plain styles and 24 lockups
  (titles, badges and stamps, chapter marks, quotes, captions, places), each
  drawn with the book's own title and placed as one group.
- The assistant sets every effect through style and add, places a lockup with
  add_lockup, reads the effects in describe_book_page and gets two new
  findings (curve_overflow, effect_heavy) beside photo-fill and band checks.
- Book format revision 6.
2026-10-23 22:08:13 +00:00
Maurice 82b8eb1067 feat(studio): book themes with palette, font pairing and motif set 2026-10-23 22:08:13 +00:00
Maurice 5d334d7ec8 feat(assistant): the assistant opens outside a trip, as a home chat about all trips
A third scope, home, next to trip and journey: threads that name neither a
trip nor a journey (the CHECK now only forbids both), reached under
/api/assistant by their owner only. A home turn reads the user's trips
through list_trips into a short card (under way, next, later, behind,
undated), offers every read (a trip's on the id the model names, checked by
the tool with the user's rights) and only the writes that take no trip or
journey (atlas, vacation planner, collections, tour library, settings), all
held as change cards. open_trip_chat answers with a card that goes on in
that trip's own chat with the question waiting in its composer.

On the desktop the window opens on the home chat outside a trip; its head
lists the trips to switch into and a switched trip chat has the way back.
On the phone the home chat opens full screen from the dashboard head and the
dock's More list, and the card opens the trip with its chat. Both run on
useAssistantHome.
2026-10-23 22:00:06 +02:00
Maurice cd60d4d859 feat(tours): assistant takes found places into saved tours and route files from the chat
P6: insert_tour_stop and remove_tour_stop take a place into a saved hike or
city walk as one stop and out again, each the other's undo, with change
cards in the chat. The planner's insertion rule (poiInsertion) moved to
shared so the planner and the server place a stop the same way. "+ Tour" on
a found place holds it as a stop of the saved tour open in the planner.

P7: GPX, KML and KMZ files attached in the chat are read into their tracks
and each is held as a change that takes it in as a hike through
import_tour_gpx, undoable per track; the GPX text stays on the held call.
2026-10-23 22:00:05 +02:00
Maurice d8213bfa42 feat(studio): groups, a layers panel, marquee, nudge, multi-resize, align and paste style
Elements can be grouped on a page and picked, moved, resized, aligned and
restacked as one. A layers panel lists the page front to back with lock,
visibility, rename and drag to restack. The sheet gains marquee selection,
arrow-key nudge, one box that resizes a multi-selection with Shift holding
the proportions, and the design-tool shortcuts. The inspector lines a
selection up and spaces it out, and copies and pastes a style.

The assistant gets the same through the group, ungroup, name and hide
operations, and describe_book_page lists the groups. The format gains the
optional group, name and hidden fields and a spread's group names, so
BOOK_SCHEMA_REV goes to 2.
2026-10-23 21:07:13 +02:00
Maurice d07b8ba56e feat(assistant): web search through the provider, and cards with a way back for the smaller tour writes
- Web search: ChatGPT (Responses web_search), Anthropic (web_search server
  tool) and OpenRouter (web plugin) search the web themselves when the admin
  switch allows it (on as it ships). A search shows as a step ("Searching the
  web for ..."), the cited pages as source chips under the answer. Anthropic
  answers that searched are replayed with their blocks (new provider state
  adapter); the search step never goes back to the model as a call. The
  prompt says when to search and that chosen places still go through
  search_place.
- Tour writes: assign_tour_track (undo: take that assignment off),
  unassign_tour_track, set_tour_activity_excluded (undo: the other switch),
  link_tour_packing and unlink_tour_packing (undo from the link before, which
  both answers now name) get change cards and their ways back.
2026-10-23 19:13:51 +02:00
Maurice d1b8cb20a3 feat(assistant): found places open the place form, notes kept on its own, city walk stops checked off
- Found places: the strip shows only when the answer names one of them, the
  named ones first. A click on a card, or "Add to trip", opens the planner's
  place form filled in with the place on the day in focus, its Google details
  (photos, hours, description) loading beside it; "+ Day" and the day grid
  are gone.
- Trip notes: remember_for_trip runs at once without a card. The assistant
  keeps lasting facts the user mentions (who travels, needs, likes, budget,
  pace) on its own and offers the unsure ones as a follow-up; the chat shows
  "Noted for this trip: ..." with the notes a tap away, and reads them again
  so the dialog never saves over the new note.
- City walks: set_city_walk_visit checks a station off on its day (visited,
  skipped, or the mark taken back), with day_edit as in the day plan, a
  change card naming the station and its walk, and an undo that restores the
  status before while the station is as the change left it.
- Card heads are a step smaller (32px tile, smaller title and count), and the
  dot beside "Working on it" in the header is gone.
2026-10-23 18:21:54 +02:00
Maurice 9f6f62c6ae feat(assistant): a proposed route shows on the Tours tab's map too, and its ways leave the map once one is planned
- The Tours tab's map draws the routes the chat's cards show and follows the
  assistant's camera, as the plan's map does; "On map" there stays on it and
  only a tab without a map switches to the plan's.
- Once one of an answer's ways is taken into the trip, the answer draws none of
  them: the taken one stands as its change, a discarded take brings them back.
2026-10-23 15:20:44 +02:00
Maurice 30382dc366 feat(studio): a book saved by a newer Studio opens read-only instead of losing what it cannot read
- A book carries schemaRev. The reader that meets a newer one opens the book
  read-only with a reload banner and saves nothing, where it used to drop the
  elements it could not read on the next save.
- The server refuses a save, a restore or a tool edit that would write an older
  revision over a newer book or one newer than itself (409 book_schema_newer),
  on REST and through every book tool alike.
- normalizeBookDocument moves to book-normalize.ts.
- Print waits for photos eagerly and up to 15 s for an export of more than 40
  photos, and skips images without a source.
2026-10-23 14:15:01 +02:00
Maurice 8afc20e1ab feat(assistant): planning a tour from the chat is one tap and opens it in Tours
- "Plan this" decides the take it holds at once instead of waiting below for a
  second confirmation, says it is being planned, and once the tour is added the
  desktop opens it in the Tours tab to be edited.
- The stops run as a timeline: each walking time sits on the line between two
  stops, read with the stop it leads to, so no time reads as a stop's own.
- An answer about a tour leaves out the strip of places its searches went
  through (hotels, a club in another state).
- "On map" from another tab of the planner brings the map back first, and a
  map that mounts right after a request still carries it out.
2026-10-23 13:11:25 +02:00
Maurice d6b15efa63 feat(assistant): the model offers the follow-up questions that fit the conversation
A turn may end on suggest_follow_ups: the server answers the call itself, keeps
its questions as data-trek-follow-ups and the chat shows them as chips under
the answer. Without them the chips fall back to what the answer holds, now by
the kind of change: a tour asks about the tour, the day plan's questions only
follow a change to the day plan, and an answer that asks the user something
offers no "tell me more".
2026-10-23 13:10:55 +02:00
Maurice 0095f2105a fix(assistant): design and page cards keep the newest choice and draw every page they touch
An answer or a refusal no longer overwrites a newer page choice, and Apply
waits for every page selection the chat has sent, also after the card was
closed. A delete of several pages draws each page it takes, the receipt opens
the first page the design wrote through the same rule the strip uses, and the
vacay group's choices wait while a change runs.
2026-10-23 01:45:16 +00:00
Maurice 9e40092507 fix(studio): the assistant column draws the page the server applied, and reads the user's role
A page preview draws the spread the server applied with the journey's photos,
texts and figures instead of applying the operations again without them. The
Studio's welcome, privacy banner and header fit a journey chat: a viewer is
told they can only look, the banner speaks of the book, and the trip notes
button stays in the trip chat. Escape closes the header menu before it stops a
reply, a single page renders at the full long edge, the status says a model
reads pages only while its provider has not refused an image, and pointing at
a page in a design card outlines it in the Studio.
2026-10-23 01:45:16 +00:00
Maurice 6c43304205 fix(ci): book frames typed for every compile mode, promise checks and the store's length
The shared book engine takes the schema's own frame type, so the client's
non-strict compile accepts a zod frame; promise lookups compare against
undefined, the render wait keeps its timer const, the notification store
moves its read and unread update into one helper, and the registry test
counts the two book packages.
2026-10-22 21:51:44 +00:00
Maurice 09ffc7d946 feat(studio): the assistant sits beside the book and draws the page it looks at
A chat column in the Studio with its own privacy notice, a live page snapshot
through modern-screenshot that leaves out photos of other people and foreign
map tiles, and flushes before every turn and apply.
2026-10-22 21:34:15 +00:00
Maurice 51ed038374 feat(assistant): a chat can be about a journey and its book
Threads carry a journey scope next to the trip scope, with journey rights
(outsider 404, viewers without write tools, addon off), its own routes and a
book context, and the client keys chats by trip or journey.
2026-10-22 21:34:15 +00:00
Maurice fd01a090fc test(assistant): shared card tests, wiki rows and ratchets after the card packages 2026-10-22 21:34:15 +00:00
Maurice a8ff98b660 feat(assistant): vacation calendar cards with the days left 2026-10-22 21:34:15 +00:00
Maurice 59313dfaff feat(assistant): hike, walk and route cards with their lines on the map, and "Plan this" 2026-10-22 21:34:14 +00:00
Maurice 8547006742 feat(studio): the open editor takes saves made elsewhere and keeps its own undo 2026-10-22 21:34:14 +00:00
Maurice 18b8e4af61 fix(ci): parse errors, two leaky tests and the lint ratchets after the parity round 2026-10-22 21:34:14 +00:00
Maurice 8365d77c1d feat(client): open views follow member, settings, tag, atlas and journey changes made elsewhere 2026-10-22 21:31:23 +00:00
Maurice 3967ca2790 feat(assistant): a card framework every addon package plugs into 2026-10-22 21:30:39 +00:00
Maurice d2a2728cde feat(studio): the book engine in shared: layouts, grids, page ops and findings 2026-10-22 21:30:39 +00:00
Maurice 56f26b5efd fix(planner): a stay booked elsewhere shows without a reload
The server sends accommodation:created and accommodation:updated to the
whole trip, the assistant's writes included, but the client listed both
as ignored and reloaded stays only after a delete. A stay the assistant,
a fellow traveller, a plugin or a booking import added therefore showed
in the sidebar, on the map and in the day panel only after a reload.

Both events now refresh the stays like the deletes do, in the planner
(desktop and phone, which share its hook) and in the day panel. The
refresh is bundled per burst, since one assistant step can apply many
changes, a newer answer always wins over an older one, and a stay being
edited in the day panel that was deleted or moved elsewhere closes its
editor instead of letting the form overwrite another stay.

A browser test books a stay from a second context and sees it appear in
the open planner, on the desktop and on the phone.
2026-10-22 21:30:39 +00:00
Maurice c37d3f773e fix(assistant): readable cards for any tool, and the CI round
- A card for a tool without one of its own names what it works on: the place
  of an assignment, the day, the note, the booking; its arguments sit under
  the labels the other cards use, a new order reads as the names in order.
  28 frequent tools carry their own action name in every language.
- The server's own steps without a card still refuse, as before.
- Tests: the rate limit is asked one request after another, the mention list
  closes on the caret move a browser reports, the place factory hands its
  category over as a plain id, eleven files formatted.
2026-10-22 21:30:37 +00:00
Maurice d24db65cb8 fix(assistant): green CI round, trip notes the assistant can keep, calmer change cards
- the assistant keeps facts for a trip (remember_for_trip) as a change card,
  applied into the user's trip notes; existing expenses can be changed
- change card fields as a few icon chips with the rest folded under Details,
  the day band marks only what changes, the week grid as compact tiles
- no map button on receipts of things that have no place on the map
- tests follow the composer's accessible name, the receipt's status icon and
  the GL map's container; the hanging stream helper honours an early abort
- Postgres probe: qualified columns in the upsert updates; entities restored
  to the generator's output; strict ratchet reads the SDK client as untyped
2026-10-22 21:30:26 +00:00
Maurice 123e27643d feat(assistant): one AI tile for assistant, parsing and MCP, a planner-aware chat
The three AI addons share one tile in the integration column; a dialog holds
the connection, the assistant's limits and AI parsing, which now reads with
the assistant's connection unless an install keeps its own. Existing parsing
configs stay where they are and keep winning.

The chat sees the map on screen, offers next questions under an answer,
shows its live step in the window head, previews waiting changes inside the
day list, checks whether a day still works, folds a multi-day plan into a
week grid, keeps per-trip notes and can estimate costs. An empty or cut-off
answer gets one push instead of ending silently, and the assistant's own
step and size limits are raised.
2026-10-22 21:30:26 +00:00
Maurice b64da26651 feat(assistant): trip assistant chat with your own AI connection
Adds an in-app assistant that reads and plans the open trip. Users connect
their own provider (Sign in with ChatGPT, or an Anthropic, OpenAI, Gemini or
OpenRouter key) or use the one the admin set up, including a custom
OpenAI-compatible server. It is an addon and only shows while a connection
is usable.

The chat runs on the existing MCP tools, pinned to the trip. Every change is
held as a change card the user applies or discards, and applied changes can
be undone. On the desktop it is a floating window that can be moved and
resized; on the phone it is fullscreen. Search results show as place cards
that can go onto a day, weather lookups as a strip, proposed places as
ghost markers on the map with a before/after switch, and an answer can be
shared into the trip chat. Booking files can be dropped in and are read
into change cards.
2026-10-22 21:30:12 +00:00
Maurice 31859651d7 feat(help): rewrite the help center for v5 with a Tours section, new pictures in every language, and fix the tour planner bugs it surfaced
Every help screen checked against the v5 redesign. Tours is documented for
the first time, as a screen of its own with twelve screens under it, and
notifications get help of their own. All pictures are taken again on a
realistic demo trip, with short loops where a still cannot show the motion.
The English is plain and every language is written naturally rather than
word for word.

The switcher shows two levels at most, a screen's overview lists its
sub-screens as cards above its guides, each wiki page is linked once, and
the help sidebar is wider.

Fixes found along the way: Save to Collection keeps a tour's route, a track
dropped on the places column opens the Tours import, tour Share on desktop,
the unsaved tour draft lives on the account, a route click no longer adds
an end point, Trip places leaves the trip's tours out, Escape closes a
stage's day menu, confirms before destructive deletes, DWD warnings sharing
one id kept apart, a booking's date moves with it, and smaller fixes across
notifications, bookings, the day plan, stays, Atlas, Collections, Vacay and
the admin screens.
2026-10-22 22:29:35 +02:00
Maurice 94c369b913 feat(journey): prepare the recorded days of a journey's trips in the background, switched on per journey, with each day's state
Journey Settings get "Prepare recorded days automatically" under GPX Tracks,
off by default and set by the owner. With it on, the Dawarich background
check also reads the linked trips of the journey for the owner and every
editor with a connection of their own, within the same 400 day reach and
windows as the trip sync, and only for new and changed stays: a trip the
person travels on is not asked twice, and the stays of a trip they are not
on are filed under no trip. Nothing is written into the journey; the stays
wait at their day, and a stay taken over or left out is never offered again.

Under the switch the caller sees when their stays were last fetched, or why
not (no connection, no dated trip, background check off, the reason of the
last failure beside the state still shown), with a "Fetch now" that the
switch also runs when it is turned on. A day of the trips that is not final
yet carries a dot on its take-over button, on the phone in the stay sheet,
and the tooltip says whether it is still running, was fetched before it
ended, was not fetched yet or shows an older state after a failed refresh.
The day review says so for today too.

Server: migration 046180 adds journeys.auto_prepare_recordings and
journey_recording_prep (the last attempt, last success and last error per
journey and person). JourneyRecordingPrepService in the journey domain lists
the prepared trips and keeps the status; DawarichSyncService walks those
trips in the same loop as the person's own and records each journey's
outcome. GET /api/journeys/:id/recordings/prep answers the caller's status
under a @trek/shared contract, the MCP tool get_journey_recording_prep reads
the same, and update_journey takes auto_prepare_recordings like the PUT
contract. The column stays out of the plugin output. All 27 locales carry
the strings; the Journey, Dawarich and MCP tool wiki pages describe it.
2026-10-21 17:39:33 +02:00
Maurice 2377a63a05 fix(tours): offer the place's undo only while its insert is the last history step, and explain the add button with a tooltip 2026-10-21 11:14:39 +02:00
Maurice 377619abd1 fix(tours): keep the place card open while its position is picked, with slots, a map preview and undo 2026-10-21 11:08:46 +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 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 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 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 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 0331d9950f feat(tours): plan rides on horseback from signposted horse routes
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.
2026-10-20 15:11:08 +02:00
Maurice 51bfb5abd1 feat(tours): plan a trail run on forest tracks and natural paths
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.
2026-10-20 15:05:51 +02:00