PAD-05B: a click on an obstacle on the map or on the route band opens its card in the look of the via ferrata card: the kind with its glyph, the name, how it stands to the route (on it at a distance along it, near it with the link marked unclear, or only in the area), the paddling rule its tags give with canoe read before boat, a condition as written, height, grade, hours and fees, the source with the date of the data and the OpenStreetMap link. A missing rule reads as unknown, never as allowed, and a portage way recorded as private says so. Objects only in the area show from zoom 13 on, on Leaflet and GL alike; on GL the lines only near the route are dashed as on Leaflet, one click handler picks the marker over the line, and a picked marker no longer flickers. The route band marks the obstacles on and near the route by their share of the measured line. The obstacle notice also names an answer cut at its limit.
PAD-05A: the admin area gets a Paddle water data card under the addons, on desktop and phone: a search in Geofabrik's list with each region's path, the regions with their state (waiting, reading with its progress, ready with objects and data date, failed with the reason), reading again and removing after a confirmation. The list follows a read without reloading. Region changes write audit rows.
Map colours of paddle objects and portages come from theme tokens read at draw time, as the via ferratas do; the ferrata layer now shares that reader. The rail takes the planner page's parts as one bag, the map hit tests of the tour layers are one helper, and the unused noWaterData string is gone from every locale.
PAD-03 portages and locks on planned tours and recorded tracks, PAD-07 paddle time with portage and lock stops, PAD-08 wind forecast for the start, PAD-05A regional obstacle import from Geofabrik extracts with admin endpoints and a monthly refresh, PAD-05B first cut of the obstacle layer on Leaflet, GL and 3D. Work in progress: checklist (PAD-06), check bench (PAD-09), admin UI, translations and tests follow.
- Paddle as a plannable tour type: one boat (kayak, canoe, SUP, other), river or
lake, a pace that a boat change never overwrites; time is distance over pace
- Hand-drawn legs beside routed ones, drawn dotted and labelled as not routed,
kept through save, reopen and edits elsewhere
- Reverse a tour and close it back to its start, each one undo step
- GPX import shows a preview first (line, measures, duplicates, type) and only
the confirmation stores the tour
- Base maps and overlays per tour type in the planner: hike keeps topo and the
via ferrata layer, paddle gets the OpenSeaMap seamark overlay
- P2C-02: tours record their origin (planned, GPX with file name, wanderer)
instead of reading it off their shape; the detail answers a provenance
contract and a trip copy keeps a tour's import record
- MF-03: a wanderer snapshot's source can be checked on purpose, read only,
with a confirmed reimport as a new snapshot
- PR1-18: every way of putting a tour on a day shares one set of rules
- PR1-07/08: each person can hide tours from the trip map; the list shows
each tour's line colour and the picked tour stands out on both maps
- GPX files drop onto the map or a day; the import lands on that day
- wanderer connection per user in Integrations; the workspace offers the
user's own trails only, behind an admin switch under the Tours addon
- wanderer difficulty is stored on the SAC scale (T1 to T3), backfilled
- a GPX or wanderer import can be planned as an editable tour, either in
place (the recorded track is kept and can be restored) or as a new tour
that remembers the import it came from
- rails, rows and detail dialog follow the plan sidebar's cards and badges
- admin addon shelves with more than one row fold, collapsed by default
- reverse-geocoded waypoint names are cached on the device
The tours map gets a 3D mode on the same map surface between the side panels:
a "3D" switch in the layer pill drapes the route over open Terrarium elevation
tiles (AWS Open Data), colours it by grade, plays a flyover tied to the
elevation profile in the rail, and keeps planning working by clicking and
dragging waypoints on the terrain. The corridor around the route is preloaded
before the flyover so the landscape never loads in front of the camera.
GPX imports no longer get a made-up hiking difficulty: max_hiking_difficulty
becomes nullable (new migration, waypoints kept), the import writes null and
the client shows no T badge for it. Drawn tours still default to T2.
Info hints in the planner show their tooltip only, and the discard
confirmation reads "Discard changes".
This commit completes the reviewed Tours follow-up scope after the Tours MVP was merged into dev.
Included feature blocks:
- RC-01: identify Tours in PLAN day rows and show compact Tour facts;
- RC-06: search for a city or address in TOUR-PLANNER without hidden writes;
- RC-07: reorder Tour waypoints in the left sidebar;
- RC-08: drag Tour waypoints on Leaflet, MapLibre and Mapbox with stable viewport, history and routing behavior;
- RS-01: persist Tour descriptions and HTTPS information links with local provider classification;
- RS-02: persist breaks/additional time and calculated or manually adjusted planned Tour duration without changing scheduling semantics.
The implementation preserves:
- ordinary Place and Legacy Track behavior;
- Tours-off fallback behavior;
- existing GPX/import behavior;
- Tour assignment and deletion behavior;
- calculated routing duration as a separate value;
- existing Tour provenance fields;
- keyboard/list alternatives for waypoint editing.
Not included:
- PR1-09 trip-wide Tour summary;
- Wanderer integration;
- GPX import changes;
- new Tour types;
- Tour-to-Day drag-and-drop;
- further roadmap or Post-Release features.
Validation:
- focused RC-01/RC-06/RC-07/RC-08/RS-01/RS-02 tests passed;
- affected shared/server/client typechecks passed;
- migration and generated DB type checks passed;
- scoped ESLint passed;
- staged diff checks passed;
- owner manual tests passed for all six feature blocks.
The base-layer pill sits in the map's bottom-left corner on purpose. The
place inspector, a tour's detail and the day panel are centred between the
sidebars and were assumed never to reach that corner, but with both
sidebars open in a 1280 window they do, and the pill covered "Remove from
Day" and Edit. These cards now keep their left edge clear of the pill: a
narrow map moves the card right of it, a wide map keeps it centred as
before. The pill itself does not move, and the phone sheets are unchanged.
The Bookings tab showed "Book" at every width, which read as cut off. The
short form now only shows where the bar is tight; from xl up the tab shows
its full name.
Refs #2604
A GPX or KML import stores the track's first point as the place's position,
so the day route treated it as a single stop there and routed from the
track start to the next stop, a second road running alongside the track.
Any assigned place with a usable track now gets the gap a Tour already had:
the route runs up to the track start and continues from the track end, on
the desktop map and day list, the mobile timeline, the trip overview and the
PDF map, which now draws the track as a line. A place without a usable track
stays a plain stop and never shows as an invalid Tour.
Refs #2600
A chosen alternative or a hand-bent drive is stored as vias, and only the
road trip rail routed through them. The day route on the map, its
connectors in the day list and on the phone, and the overview of all days
asked the router for the bare stops, so they drew and measured the default
road again.
They now thread the same vias between two stops that follow each other in
the stored list, with the anchor counted the way the rail counts it, and
fold the answer back to one leg per stop pair. Only while the road trip
addon is on; avoid settings and the routing engine stay with the rail.
Refs #2580
MapViewAuto took props: any and the Leaflet MapView destructured any, so a prop
one renderer learned and the other never heard of went unnoticed.
components/Map/mapViewContract.ts holds MapViewProps, derived from the GL
renderer's props and what the callers pass; MapView and the GL bindings in
glLazy declare satisfies ComponentType against it, and the GL renderer extends
it with its engine. The file imports types only, so check:gl-split is not
affected. Types only, no runtime change.
The types show two known gaps, recorded in the contract rather than papered
over: onMapReady is called by the GL renderer only, and callers pass pins
without trip_id (assignment and collection places) while both renderers type
their pins as Place; MapViewAuto bridges the latter with one commented cast.
MapView and MapViewGL each held the same photo loading effect: read the
cached thumbs, fetch the missing ones, listen for their arrival and batch
them into one animation frame. It now lives in useMarkerThumbs, which
also owns the thumb state and the place photos switch, and both maps
call it where the effect used to be.
The effect keeps its keys (the picture sources and the switch). It reads
the places through useEffectEvent, which drops the old
exhaustive-deps suppression in MapViewGL and the warning in MapView.
ReservationOverlay and reservationsMapbox each carried the booking type
list, the icon and arc table, the time zone aware duration and the loop
that turns a booking into waypoints, arcs and labels. All of that now
lives in transportHops.ts; each renderer keeps only its drawing.
The one difference stays a parameter: Leaflet asks for arcs that cross
the antimeridian to be copied 360 degrees over, the GL map does not
because it repeats features across world copies itself. Leaflet still
adds its transit segments on top of the shared hop.
calculateSegments and the cached legs call turned each OSRM leg into the
same connector record line by line. That record now comes from
osrmLegSegment; the legs call keeps adding its durationText on top, so
both answers stay exactly as they were.
Arabic runs the app right to left, but margins, paddings, insets, text
alignment, borders and corner radii were written for the left side, so
icons sat against the wrong edge of their labels, indents and badges
pointed the wrong way and amount columns aligned left. They now use the
logical forms (ms-, ps-, start-, text-start, marginInlineStart,
insetInlineEnd and their CSS equivalents), which are the very same sides
in a left to right language. Geometry stays physical on purpose: maps,
positions measured from the pointer or a bounding box, time axes,
sliders, transforms and print output.
Amounts drawn as several spans keep their order in a right to left row,
the time picker keeps hours before minutes, and two switches whose thumb
slid out of its track in Arabic now move the right way.
lint:rtl counts what is still physical per file against a baseline that
may only shrink (1512 uses in 370 files before, 319 in 113 now), and CI
runs it, so new code reaches for the logical forms.
The phone shell was chosen by width alone, and most phones are wider than
767px in landscape, so turning one swapped the whole route tree for the
desktop planner and an open sheet, its input and the map position were
lost. The phone query now also covers a touch screen under 500px tall,
which no tablet is. It lives in one place: the shell, the planner and todo
hooks, the warnings banner, dialog focus and the stylesheets read it, and
six components that measured the window once on mount now follow it.
The public instance allows about one request a second per client, and the
Tours elevation questions went out right behind the route request with no
pause, so they could be refused with a 429 and leave the tour without the
elevation it needs to be saved. Every request to that host now waits for
its turn, across callers; an operator's own Valhalla is not paced.
MapTogglePill always set aria-pressed, so the phone map's Filters control read
as a toggle although it opens a sheet. An opensDialog option drops aria-pressed
for aria-haspopup="dialog"; the other map toggles are unchanged.
On a phone the only way to narrow the trip's pins was to leave the map for
the places list, and the rating floor could not be set at all. The phone map
now has a round "Filters" control at the foot of its right-hand stack, badged
with how many filters are on, opening a sheet with the pool (all / unplanned /
planned / tracks), the minimum rating (the desktop list's floors), the trip's
categories including "no category", and a reset. It is hidden on the road trip
stage, which draws its own pins.
The places browser's filter panel gains the same rating floor; its category
rows move into MPlacesFilterControls so the panel and the sheet share one
copy. Everything writes the trip store, so list and map stay in step.
MapTogglePill takes an optional count badge.
Small requests from the discussions, each built end to end: planner and
map (cycling mode, date first headings, clear a day, stay times per day,
off-route stops, map lock, compact unplanned markers, pending stays,
cable car, transit quick picks), places (location filter, category from
OSM, open in maps, geo: link, image from an attachment, enrichment on
file import, places near a pin, place name language, Amap first in
China), lists and costs (packed counter, inherited sharing, Markdown
to-dos, payment notes, bookings CSV, my plan PDF), sharing (unplanned
places, travel only, without photos), journey (photo zoom, drafts,
photo order, trip suggestion, day jump), atlas (region counter, region
bucket list, places per country), vacay half company holidays, studio
copy and paste plus the book's own layouts, and RESTORE_FROM_BACKUP for
a first start restore.
Sonar counts a promise that is neither awaited nor caught as a reliability bug, and it reads every such call in a touched file as new. Each of these already reports its own failure (toast, rollback or the sheet's catch), so they are marked as fire and forget rather than changed.
The overview holds its camera frame across content edits and only publishes a
fresh one on activation or on a trip switch. Activation is covered; the trip
switch branch was not, so a regression there would leave the map framed on
the previous trip. The new case keeps days and stops identical and changes
only the trip id, so the switch alone has to produce the new frame.
The Plan tab was the last trip tab still in the old design. It now reads
like Bookings, Costs, Lists, Files and Collab, without losing a function:
- Day plan: each day is a card with a head band (day tile with weather,
stay and rental pills, a "+" menu for place, stay, transport, transit
and note), rows with "..." menus, white badges and a white route bar.
- Places column: add, search and filters in three rows, a show dropdown,
category and rating filters, and a selection bar at the foot.
- Place inspector, day details and the stay editor on the shared dialog
frame; the place form in the editor look with the name in the head band.
- Clicking a booking or transport anywhere on the plan opens the booking
detail popup first; Edit leads to the editor. The popup is one host
shared with the Bookings and Transports tabs.
- Files and Collab dialogs, notes, polls, links and chat moved to the same
frame; shared DialogShell and dialogParts hold the building blocks.
- The map hover card shows the place photo and follows the theme.
Every new and reworded string is translated into all locales, and a few
older English leftovers (help center, cost split, import hints) with them.
Phone screens keep their own look.
On a day that opens with a flight, where the traveller also put the
hotel on the day as a stop of their own, the phone timeline started the
day with a hotel row and the drive from that stop to the next place,
above the flight, and then showed the same drive again in its own spot.
hotelLegsForDay guessed the bookends from coordinates: the first
segment leaving the hotel's spot was the morning leg. The route had
correctly drawn no morning leg on that day, but the stop on the
hotel's spot starts a leg there too, so that hop was taken for it.
The route calculation now tags the two legs it draws from and to the
accommodation, and the timeline reads that tag instead. The hotel end
of a tagged leg still has to be the day's own stay, because the
calculation keeps the previous day's legs until the new day has
routed. The stop to stop connectors leave tagged legs alone, so one
leg can no longer show up in both places.
Since drag-to-day (#891) a place pin swallows mousedown so the map holds
still while a drag starts. Leaflet only clears its "the map was panned"
flag on a mousedown it sees, so after one drag of the map it took every
later click on a pin for the end of that pan and dropped it. Pins stayed
dead until something else on the map was pressed, which is why zooming
out and clicking a cluster brought them back. Trip planner and
collections map were both affected; the GL renderers never were.
Pressing a draggable pin now clears that flag itself by switching the
map's pan handler off and on again, which is what the map would have
done had it seen the press. The click keeps Leaflet's normal route, so
it still closes an open popup on the map and still reaches listeners on
the page, such as an open context menu.
The new test file drives a real Leaflet map, since the mocked
react-leaflet has no pan state to go stale.
A new manifest capability poiCategories (up to 4, icon from an allow-list,
strict #rrggbb color) and the hook poiCategoryProvider.getPois behind the
hook:poi-category-provider permission. GET /api/plugin-pois in the plugin
contributions module answers them in the /api/maps/pois shape, with
search_plugin_pois as the MCP twin. plugin: ids no longer count as Google
place ids. SDK 1.8.0 carries the types, validation and docs.
On the map, one hook merges the built-in categories with the ones active
plugins declare, and the pill shows the plugin chips after a divider on
desktop and phone, scrolling sideways instead of shrinking a chip below
its touch target. A plugin chip searches through a repo that refuses
offline and parses the answer strictly. Its pins, on Leaflet and GL, use
the category's own icon and color, both checked again before they reach
any markup, and the hover card lists the plugin's details as escaped
text. A plugin POI can be added as a place like any other. The admin
panels on desktop and phone show a chip for the new permission and the
categories a plugin would add, from one shared module.
Addresses discussion #1781.
On a day a flight, train, ferry, cruise or bus leaves and lands, tonight's stay was seated
by its check-in time. A hotel in Munich checked into from 15:00 stood ahead of a 15:15 flight
out of Hamburg, so the drive went from the Munich hotel to Hamburg airport and reported the
flight as nine hours late. A check-in is the earliest arrival, not an appointment: such a
stay now waits behind the landing when it lies nearer the arrival than the departure, and
its check-in only holds it when the ride lands before the room is ready. A time typed on the
stop keeps its seat.
With the stays at both ends of a day switched on:
- a day with nothing but the next stay's check-in and a ride booked across it gets no drive
from one hotel to the other, the same rule the day plan follows since #2476;
- a stay is never joined by road to a terminal more than 2000 km away. The check moved to
shared/roadtrip so Days and the road trip read one copy.
A departure terminal's pin is dated: reaching it more than half a day late no longer moves
the flight to the next day without a word, it is a late warning with the full minutes.
A check-in evening also carries the stay's check-in time, as a label only.
calculate_roadtrip runs the same code and its description says all of the above.
With the Unplanned filter on, a click on a stop in the day list moved the
map while a click on the stay chip did nothing: the map only went to a
selected place it showed as a pin, and the GL map also to one of the open
day's stops, which a stay is neither. Both renderers now look the target
up the same way, the pin first, then the day's stop, then the selected
place itself, so the map goes to the stay whatever filter hides its pin.