v4.2.0 - #2161
Merged
Merged
Conversation
… coordinates alone Three places answered "is this a place the trip already has?", not two: PlacesService.findDuplicatePlace and isPlaceDuplicate got the shared strategy list in #2130; CollectionsService.findDuplicateCollectionPlace was missed and kept its own hand-rolled copy of the pre-#2130 order. Unlike findDuplicatePlace, this one had no isPlaceDuplicate() guard in front of it — savePlace calls it directly — so the coordinate fallback for a NAMED candidate was live, not latent: two distinct places sharing an address (a restaurant and the bar upstairs) would report each other as duplicates the moment their coordinates round to the same ~11 m box, regardless of name. It also never read google_place_id/google_ftid/osm_id at all, even though every collection_places row stores them, so a place renamed since it was saved could be saved again instead of being recognised by its provider id. Now walks placeMatchStrategies() from @trek/shared, same as findMatchingPlaceId: provider id, then name, then coordinates only when there is no name. savePlace passes the provider id fields through since they are already on the request body; the two bulk call sites (importablePlaces, saveFromTripPlaces) are unchanged, since their source rows don't carry provider ids to pass. Closes #2137
savePlace was not the only caller of findDuplicateCollectionPlace. The bulk copy already carries google_place_id/google_ftid/osm_id into the row it writes but asked without them, and the import picker never selected them at all, so a renamed place was recognised on one path and not on the other two. The picker answering differently from the import is the drift the method exists to prevent: a row shown as new would come back refused. COLLECTIONS-SVC-102 and -103 cover the bulk copy and the picker.
…map cannot take the planner down Opening a trip on 4.1.0 could land on the error boundary with "Map has no maxZoom specified", and so could the map settings tab, which left no screen from which to pick a different basemap. Leaflet answers getMaxZoom() from the map options first and otherwise from a layer that brought one, and only a GridLayer ever contributes via its beforeAdd hook. A vector basemap is a GL canvas on a plain L.Layer, so it contributes nothing, and MapContainer set no ceiling of its own. MarkerClusterGroup.onAdd refuses an infinite one by throwing, before any clustering, so an empty trip hit it too. That path opened when the default basemap became a MapLibre style. It reaches further than an operator choosing OpenFreeMap: resolveTileUrl sends a keyless CARTO template to the app default, which is now a vector style, so every instance still carrying the old CARTO basemap fell into it on upgrade. The ceiling belongs on the map rather than on the base layer, because it has to hold whichever of the three branches renders. 19 matches what the raster and satellite layers already carried, so nothing changes for the maps that worked. SharedTripPage gets the same treatment: it has no cluster to throw, but it draws the same vector basemap and its fitBounds asks for a ceiling. mapZoomCeiling.test.ts pins Leaflet's own semantics against the real library rather than a mock, since a mocked react-leaflet would have kept passing through all of this.
… draws The map settings fields hold what the user is editing rather than what useTileUrl already resolved, so the preview received a bare template with no key on it. resolveTileUrl then read it as a keyless CARTO url and did what it is meant to do with one: fall back to the app default. The preview drew OpenFreeMap while the fields said CARTO, whether or not a key was saved. Same reason the tab crashed for anyone on a CARTO basemap even after entering a key, which is the loop that made this hard to escape: the key is entered on the page the fallback had taken down. FE-COMP-MAP-033b pins the template the preview is handed, with and without a key.
…w too Same shape as the user-facing map tab, and reported alongside it: the field holds what the admin is editing, so the preview resolved a keyless CARTO template and drew the app default instead of the basemap being set.
… too The third preview with the same shape, and the one that matters most while the crash is live: the phone layout is where someone would go to change the basemap when the desktop tab will not open.
…ong bar Closes #2136 A stay was always exported as an all-day block across its whole range, and the check-in and check-out markers came on top of it. When the stay records both ends of its clock the markers already say everything the block does, and the block is the half nobody can act on: it takes a week of calendar to repeat what two one-hour events state precisely. Same shape as the parking and rental windows in #2068. Only where the markers actually stand in for the booking, though. They are emitted once per stay and titled from its lowest-id reservation, so a second room on the same stay would otherwise be left with nothing at all. A stay that knows only one end keeps its block too, since nothing else carries the other end's date. CAL-025b covers the drop, -025c the one-sided stay, -025d the second room.
The fourth preview with the same shape, missed when the other three were fixed. Sonar pointed at it: the desktop and phone admin pages are near-identical, so the duplication report named the file the change had not reached.
Contributor
…ring (#2155) tags and pros_cons are JSON held in TEXT columns. Three read paths decoded them by hand and createEntry and updateEntry did not, so an edit answered with the raw row: tags came back as '["beach","sunset"]', the store spread it into an entry the client types as string[], and the journey page threw "tags.map is not a function" behind an error boundary. Any edit was enough to arm it. The update path writes JSON.stringify(val) with no length check, so even clearing an entry's tags stores '[]' and answers with that string. Neither compiler could see it: JourneyEntry describes the row, tags and all, and both sides read the same field name while only one of them was right. So the decoded shape gets a type of its own and one decoder, which is what turned up the third case - the early return for an update that changes nothing handed back a raw row too. Fixes #2085
* fix(sdk): grade docs/screenshot.png exactly, like the registry gate * docs(wiki): document plugin MCP tools and the exact screenshot gate * test(sdk): cover the preflight screenshot probe
* chore: bump version to 4.1.0 [skip ci] * fix(collections): stop findDuplicateCollectionPlace merging places by coordinates alone Three places answered "is this a place the trip already has?", not two: PlacesService.findDuplicatePlace and isPlaceDuplicate got the shared strategy list in #2130; CollectionsService.findDuplicateCollectionPlace was missed and kept its own hand-rolled copy of the pre-#2130 order. Unlike findDuplicatePlace, this one had no isPlaceDuplicate() guard in front of it — savePlace calls it directly — so the coordinate fallback for a NAMED candidate was live, not latent: two distinct places sharing an address (a restaurant and the bar upstairs) would report each other as duplicates the moment their coordinates round to the same ~11 m box, regardless of name. It also never read google_place_id/google_ftid/osm_id at all, even though every collection_places row stores them, so a place renamed since it was saved could be saved again instead of being recognised by its provider id. Now walks placeMatchStrategies() from @trek/shared, same as findMatchingPlaceId: provider id, then name, then coordinates only when there is no name. savePlace passes the provider id fields through since they are already on the request body; the two bulk call sites (importablePlaces, saveFromTripPlaces) are unchanged, since their source rows don't carry provider ids to pass. Closes #2137 * fix(collections): ask the same question at all three call sites savePlace was not the only caller of findDuplicateCollectionPlace. The bulk copy already carries google_place_id/google_ftid/osm_id into the row it writes but asked without them, and the import picker never selected them at all, so a renamed place was recognised on one path and not on the other two. The picker answering differently from the import is the drift the method exists to prevent: a row shown as new would come back refused. COLLECTIONS-SVC-102 and -103 cover the bulk copy and the picker. * fix(map): give the Leaflet map its own zoom ceiling, so a vector basemap cannot take the planner down Opening a trip on 4.1.0 could land on the error boundary with "Map has no maxZoom specified", and so could the map settings tab, which left no screen from which to pick a different basemap. Leaflet answers getMaxZoom() from the map options first and otherwise from a layer that brought one, and only a GridLayer ever contributes via its beforeAdd hook. A vector basemap is a GL canvas on a plain L.Layer, so it contributes nothing, and MapContainer set no ceiling of its own. MarkerClusterGroup.onAdd refuses an infinite one by throwing, before any clustering, so an empty trip hit it too. That path opened when the default basemap became a MapLibre style. It reaches further than an operator choosing OpenFreeMap: resolveTileUrl sends a keyless CARTO template to the app default, which is now a vector style, so every instance still carrying the old CARTO basemap fell into it on upgrade. The ceiling belongs on the map rather than on the base layer, because it has to hold whichever of the three branches renders. 19 matches what the raster and satellite layers already carried, so nothing changes for the maps that worked. SharedTripPage gets the same treatment: it has no cluster to throw, but it draws the same vector basemap and its fitBounds asks for a ceiling. mapZoomCeiling.test.ts pins Leaflet's own semantics against the real library rather than a mock, since a mocked react-leaflet would have kept passing through all of this. * fix(map): put the CARTO key back on the template the settings preview draws The map settings fields hold what the user is editing rather than what useTileUrl already resolved, so the preview received a bare template with no key on it. resolveTileUrl then read it as a keyless CARTO url and did what it is meant to do with one: fall back to the app default. The preview drew OpenFreeMap while the fields said CARTO, whether or not a key was saved. Same reason the tab crashed for anyone on a CARTO basemap even after entering a key, which is the loop that made this hard to escape: the key is entered on the page the fallback had taken down. FE-COMP-MAP-033b pins the template the preview is handed, with and without a key. * fix(admin): put the CARTO key back on the default-settings map preview too Same shape as the user-facing map tab, and reported alongside it: the field holds what the admin is editing, so the preview resolved a keyless CARTO template and drew the app default instead of the basemap being set. * fix(mobile): put the CARTO key back on the phone map settings preview too The third preview with the same shape, and the one that matters most while the crash is live: the phone layout is where someone would go to change the basemap when the desktop tab will not open. * fix(calendar): a fully timed stay is its two hand-overs, not a week-long bar Closes #2136 A stay was always exported as an all-day block across its whole range, and the check-in and check-out markers came on top of it. When the stay records both ends of its clock the markers already say everything the block does, and the block is the half nobody can act on: it takes a week of calendar to repeat what two one-hour events state precisely. Same shape as the parking and rental windows in #2068. Only where the markers actually stand in for the booking, though. They are emitted once per stay and titled from its lowest-id reservation, so a second room on the same stay would otherwise be left with nothing at all. A stay that knows only one end keeps its block too, since nothing else carries the other end's date. CAL-025b covers the drop, -025c the one-sided stay, -025d the second room. * fix(mobile): the phone admin map preview needs the CARTO key too The fourth preview with the same shape, missed when the other three were fixed. Sonar pointed at it: the desktop and phone admin pages are near-identical, so the duplication report named the file the change had not reached. * chore: bump version to 4.1.1 [skip ci] * feat(plugins): admin editor for instance-wide plugin settings The scope:'instance' settings a plugin declares had API endpoints but no UI. - shared: plugins.schema.ts — the settings-field descriptor and the admin instance-config GET/PUT wire contracts, imported by both sides - server: GET /api/admin/plugins/:id/config now returns the declared fields alongside the (masked) values; PUT re-spawns an ACTIVE plugin (its child reads config once, at init) and reports it via 'restarted'; the admin list carries instanceSettingsCount so the UI gates its menu item without a fetch - client: 'Instance settings' row action opening a form modal (desktop) / MSheet (phone) in both admin shells — checkbox/select/secret rendering as in the user settings form; an untouched secret mask is never sent back - i18n: three new admin.plugins.* keys in every locale * refactor(plugins): move the admin body contracts to @trek/shared The remaining plugin DTO schemas (install, link, activate, uninstall, retrust, update, egress-hosts, user-settings update, route) now live in shared/src/plugins/plugins.schema.ts with their inferred types, alongside the instance-config contracts. The journey-contract doctrine notes (#1842) moved with them — each body stays deliberately loose so the handlers keep the rejections and fallbacks they already own, and new spec tests pin that looseness so a well-meant tightening breaks a test that explains itself. plugins.dto.ts is now thin createZodDto wrappers over the shared schemas (same class names, no controller changes), and the client's pluginInstall types its options and body against PluginInstallRequest. * fix(admin): offer Allowed hosts only to plugins that declared operatorEgress The row's egress chip was already gated on the declaration, but the kebab menu (desktop) and row action sheet (phone) offered Allowed hosts on every installed plugin — inviting the admin to widen egress for plugins that never asked for it. Both entries now render only when the plugin declares operatorEgress; the dialog's unsupported-notice fallback stays as the truth-teller for stale list data. * test(admin): close the Sonar gaps on the instance-settings form The PR gate flagged new-code coverage (76.4%) and duplication (10.9%). - The two identical 39-line handler blocks were logic, not markup — they move into one shared useInstanceSettings hook; each shell keeps its own markup (the deliberate desktop/phone divergence stays) - New tests per shell cover the untested branches: every field type renders and round-trips into the save payload (select, checkbox/MToggle, number, hint, no-label key fallback), a failed fetch is a toast, a rejected save shows the server reason and keeps the edits, and the dialog/sheet closes without saving - INS-009 covers the controller's no-body '|| {}' fallback --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: michael-bohr <263459669+michael-bohr@users.noreply.github.com> Co-authored-by: Maurice <mauriceboe@icloud.com>
Closes #2153 Bucket-list marker tooltips rendered as a single nowrap line, so long notes overflowed off both edges of the viewport instead of wrapping. Wrap and cap the tooltip's size, add a scrollbar for notes that still exceed it (only shown when content actually overflows, and scoped to this tooltip so short region/country tooltips are unaffected), and make the tooltip interactive with a delayed close so the pointer can reach it to scroll instead of Leaflet closing it the instant it leaves the marker's tiny hit area. Also compute the tooltip's direction and offset from the marker's on-screen position before it opens, so it flips below the marker and stays clear of the left/right edges when there's no room to render it centered above. The positioning/scroll math is pure and covered by unit tests in atlasModel.test.ts. Updated the L.marker mocks in AtlasPage.test.tsx and useAtlas.test.tsx with on/off/getTooltip/closeTooltip so the new marker event wiring doesn't crash under test.
The store boots from localStorage, but 'app_language' is only written on an explicit in-app choice; a language that lives in the account's server settings never reaches the device. A PWA cold start with no network then runs in English even though every online session ran in the user's language, the same stranding #1618 fixed for currency and units. loadSettings now mirrors the fetched language into its own key ('app_language_server'), and the boot chain falls back to it between the explicit choice and 'en'. A separate key on purpose: 'app_language' means an explicit choice, and the login page's detection chain must keep running for users who never made one.
Closes #2151 The desktop Costs view mixes recorded settle-up payments into the day-grouped expense ledger, but the mobile screen only ever fetched balances/flows and silently dropped the settlements array the server already returns — a recorded payment cleared the balance but then left no trace anywhere on mobile.
It has no trip tab and no page of its own — it pulls places from an external service, which is what the integration group is for. The type only drives grouping in the admin UI (and the global nav check), so existing installs just see the tile move; migration appended for them, seed fixed for fresh ones.
The three stacked sections become one grid of cards per group, with the type shown on the tile itself. Bag tracking, the collab features and the photo providers hang off their parent addon as compact shelf rows instead of full-width siblings, and each group header counts how many of its addons are on. Along the way: - naver_list_import, airtrail and llm_parsing get catalog entries, so their names and descriptions translate like everyone else - dark detection moves into a shared useIsDark hook that watches the .dark class instead of re-deriving settings + matchMedia locally - the ToggleSwitch knob uses --accent-text so a light accent in dark mode no longer renders white-on-white
…MCP and plugin RPC (#2154)
…wallet downloads (#2165)
Leaflet's wheel handler on the map container cancels the native scroll, so the wheel zoomed the world under the note; the tooltip stops scroll and click propagation now. Hovering back to the marker no longer closes the tooltip mid-way, the flip threshold matches the real tooltip height, and a touch swipe scrolls the note instead of panning the map.
…olocation message (#2095) The phone toast was a single truncated pill, which cut the denied message exactly before the settings hint it exists for; long messages wrap up to four lines now. An insecure context is reported as its own error instead of masquerading as a blocked device permission.
) Between 768 and 1023px the view-controls row is hidden and the provider and upload buttons had moved into it, leaving no way to add photos; the gallery actions render below the floating bar there now. The mobile journey settings sheet gets the linked-trip tracks switch the desktop dialog got, and the request gate and the fitBounds behaviour are pinned by tests.
Deleting a bag over MCP moves every figure, the items fall into the unassigned pile, yet it sent no totals ping; it pings the room now like REST and the plugin RPC do. The client refetches totals after a room re-join and when coming back online, through the same coalescing window. The plugin SDK types learn the create fields the routes accept since #2154, on both hand-kept declarations.
… iOS (#2175, #2176) The split and receipt fields rejected the third decimal a KWD or BHD amount seeds, on desktop and in the mobile sheet, which share the currency-aware guard now. Signed amounts get a sign toggle on the numeric input because the iOS decimal pad has no minus key, and the under/over hint no longer swaps direction for a negative total.
…nt (#2146) The mirror of the account language stayed in localStorage past logout, so the next account on the device booted in the previous user's language forever. It is cleared on logout and rewritten on login, and the rate-limit message follows the same order the boot language does.
… config (#2147) Without any configuration getAppUrl() invents http://localhost as the relying party, and the UI advertised passkeys that every click answered with not configured. A localhost RP only counts as configured now when the operator declared one. The origin check also binds to the expected origins again instead of accepting every subdomain of the RP id; that widening never shipped in a release.
…ttons (#2158) Same trap the task-list fix hardened: the press animation shrinks the container mid-click and the release lands next to the target. The collections rows, the journey gallery tiles and the shared vacay calendar cards carry the established no-press attribute now.
…f trusting itself (#2156) Disabling Journey cascades the photo providers off on the server, but the panel kept its local rows, so after off and on the providers showed active while being disabled. The desktop and the mobile panel re-read the list after a journey toggle; the write itself still rolls back a single row on failure.
…footnotes (#2177) rehype-sanitize silently dropped anything that parsed as an unknown tag, eating text people had saved long before the sanitizer arrived; disallowed markup renders as literal text now. GFM footnotes pointed at a doubly prefixed anchor and never landed. One shared plugin set drives the polls, the notes, the note card and both mobile tabs.
…ialog (#2170, #2159) The instance-settings save button paired bg-accent with a hard text-white and vanished in dark mode; it uses the accent pair token now. A failed activate() after saving no longer reports success over a dead plugin: the config stays, the response says what happened. The allowed-hosts dialog scrolls like the ones e70d739 fixed, and the discover cards get the press-scale hardening from #2158, on desktop and on the phone panel.
…ars the dock (#2104) On wider phones the dashboard filter pill stretched across the leftover width instead of wrapping its chips. The collections list switcher could not scroll, so its lower entries and the new-list button hid behind the dock.
…2167) failed was set once and never cleared, so a single hiccup kept the badge on its error dash while later fetches quietly delivered data. It resets with every new anchor.
…oop (#2157) Without stay times the bookend leg drew unless a carrier or a 2000 km edge stop disproved it, which is exactly the reported car trip: home five hundred kilometres away, routed back to the hotel after check-out. The disproof threshold is a day-trip distance now, and the route optimizer anchors through the same rule on desktop and mobile instead of unconditionally, so drawing and optimizing agree.
The farthest-stop reduce gets its explicit seed (the guard above already refuses an empty list, the seed makes that local), and the toast pill moves into its own component so the host stays below the complexity ceiling.
The list held its manual order, so an item due tomorrow sat behind one created earlier but due later. The sort-by section gains a due-date toggle next to the priority one, on desktop and in the phone tab: nearest deadline first, undated tasks after all dated ones, ties keep the manual order. One sort at a time, and manual drag-reorder pauses while either is active, exactly like the priority sort.
* feat(shared): scope plugin action descriptors * feat(plugin-sdk): instance-scoped settings actions * feat(plugins): run instance-scoped actions from the admin settings dialog * feat(admin): instance-scoped plugin actions in the settings dialog * fix(plugins): surface instance actions without instance fields and harden the action gate * docs(plugin-sdk): note that hosts before 4.2.0 ignore action scope * fix(admin): open the dialog for instance actions, explain inactive 404s, guard the save window * docs(wiki): plugin pages for instance actions and the admin panel changes
…2207) Prompt arguments cross the wire as strings, but the three trip prompts declared tripId as z.number(), so every prompts/get failed with -32602 "expected number, received string", MCP Inspector included. The id is now parsed from the string through a shared tripIdPromptArg (still an integer in the safe range), PromptOptions only admits string-input schemas so the mistake cannot come back unnoticed, and a prompt declared with an empty argsSchema no longer demands an empty arguments object from the client. The prompt tests run through a real in-memory MCP client instead of calling the callbacks directly.
…he day (#2210) The hotel chips next to the day pill shared its target, so tapping a stay opened the day sheet. A chip now carries its stay and opens it the way the stay card in the day sheet does: the accommodation editor for members who may edit days, otherwise the hotel's place, and the day sheet only when neither applies. Closing the editor returns to the timeline it was opened from instead of surfacing the day sheet, and the chip's accessible name says whether it is a check-in, a check-out or an ongoing stay.
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.5 to 3.1.7. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](fastify/fast-uri@v3.1.5...v3.1.7) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.7 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> (cherry picked from commit 56f7e18)
…stops off The content browser gets a filter over the journey's pictures: all of them, the ones no entry holds, or one entry at a time, with the day and the place under each name. An entry card in the Entries tab reaches its own pictures the same way. Pictures can now come in without leaving Studio, into the entry the filter names or into the gallery, from the panel or dropped straight onto the page, and a photo element can fill its page or the whole spread in one press. Stops can be taken out of the arithmetic. A journal entry carries a stats_excluded flag; an entry that has it is off the route, out of the distance, out of the countries and not a step, while staying in the journal. Studio lists every stop in the travel panel with a switch, the entry editor carries the same switch on desktop and phone, and the entry card says when a day is off the route.
…t lands in The stops list was the longest thing in the travel panel by a distance: a fortnight is fourteen rows, and it pushed the maps and the country tiles below the fold on every journey. It is a folding section now, closed to begin with, with the count of what still counts in its head, so the closed section answers the question anyone opens it for. The rows are one line each, the day loses its weekday, the country appears only on a journey with more than one, and the switch is a mark rather than a word, which is what lets the place name fit. A picture uploaded onto an entry also goes into the gallery, because that is where the row lives. The entry upload only answers with the entry's view of it, so the store derives the gallery row it did not get and the content browser shows a new picture without the journey being fetched again.
Three things Sonar was right about. The content panel had grown a filter, a menu and an upload into one function of thirty-two branches: the filtering is four pure rules in photoFilter.ts, the row that drives it is its own component, and what is left in the panel is the grid and the drop zone. The gallery rows behind an entry upload move out of the store's setter into a named function, since five levels of nesting is a place bugs hide. And the ids for placed elements come from getRandomValues rather than Math.random, in one module instead of four copies of the same line: two editors minting the same id for two elements is one of them losing their work. The new paths carry tests now, which is what the gate was really asking for: the filter's rules, the ids, the upload helper, the store's derived gallery rows, the canvas file drop, and the shell's upload, drop and stop switch.
… test is impatient (#2196) The #2196 timeouts were applied to one shared transport, so the 30-second inactivity bound landed on the password-reset and notification mails as well as on the admin's test send. That bound starts counting after the greeting, which means it bounds the transfer: a relay that scans the message can sit on DATA for minutes, and a reset mail dropped for that reason fails where nobody can see it. Nodemailer's ten minutes are back for a real send, the connect and greeting cuts stay, and the test send keeps the 30 seconds it needs to answer inside the client's 40-second budget. The source comment already described this split; only the value disagreed.
…t already has (#2216, #2217) Editing a booking on the phone erased the stop it was linked to. The sheet seeded its assignment from the create-for-a-stop flag, which is null on an edit, and saved that straight back over a link the user had made on the desktop; the server writes the field whenever the key is present. It now seeds from the booking being edited, and the picker the desktop dialog has always had sits in the sheet too, so a stop can be seen, changed and cleared from a phone. The option list moved to its own module rather than being copied. Two more of the same shape, found on the way. The endpoint set was sent as an empty list on every save, and the server swaps the whole set when that key is present, so editing a transit booking dropped its stations; neither form edits endpoints, so an edit no longer sends the key at all. And the sheet's Files block listed only what was picked in this session, so a file uploaded from the desktop was invisible on the phone. It lists what is attached now, resolved the way the desktop resolves it. A place also shows the files of the bookings that sit on it, on the phone and on the desktop: a confirmation lives on the booking, and the place card was the one screen that could not reach it.
#2218) In an installed app, opening a maps target in a new context left an empty window: the platform switches to the maps application before that context paints anything, and coming back lands on a blank page the user has to dismiss before TREK is usable again. The handover now happens from the page the user is already on when TREK runs as an installed app, which is where there is no tab strip to close a stray window from. A browser tab still opens a tab, because there a link should.
|
Release notes link the banner from the repo rather than from a comment attachment, so it keeps working when the notes are edited later.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Fixes
Modal for plugin update (re consent, ..) is now scrolable (Closes [bug] plugin update ui not scrollable #2159)
Automated transit routes no longer flood the map after re-entering a trip with the route toggle on (Closes [BUG] Automatic Train Transport routes are always shown when routes are enabled #2019)
Calendar subscription: accommodation check-in/check-out render as one-hour slots instead of 0-minute events; both times set drops the all-day block, and the markers then carry the booking's confirmation, notes and location themselves (Closes [BUG] calendar subscription: accommodation - same approach for all timed multi-day events #2136)
Passkey registration works on instances without APP_URL — origins outside the relying-party scope fail fast with an actionable error, TLS-proxy scheme/port mismatches heal on register and login; instances without any usable configuration stop advertising passkeys the options step would refuse (Closes [BUG] Unable to register passkeys #2147)
Transit journeys added via the transit search can be fully edited on desktop too — travelers, costs, files, booking code and status via the new "Edit details" handoff (Closes [BUG] Edit of automatic transport inconsistent between mobile and desctop #2148)
Recorded settlement payments show up in the mobile Costs tab, day-grouped like on desktop (Closes [BUG] Settlement payments never appear in mobile Costs tab #2151)
Atlas: bucket-list marker tooltips wrap, scroll and stay on-screen instead of overflowing the viewport (Closes [BUG] Atlas: bucket-list marker tooltip overflows the viewport with long notes #2153)
POST /packing accepts weight_grams, bag_id and quantity (and bag weight limits) instead of silently dropping them — same contract on REST, MCP and plugin RPC; foreign/dead bag references return 400 (Closes [BUG] POST /api/trips/:tripId/packing silently drops weight_grams, bag_id and quantity #2154)
Day routing no longer starts at a hotel before check-in or routes back after check-out when stay times are missing (Closes [BUG] Routing takes me back to Hotel on Day's end after Checking Out and Starts from not checked in Hotel #2157)
Task-list checkboxes work again on desktop: the press-scale animation no longer swallows the click; same hardening on planner rows, dashboard cards, collections rows, journey gallery tiles and shared vacay calendar cards (Closes [BUG] Filling checkboxes in the task list is broken on desktop views #2158)
Day-assignment notes written through MCP/API are visible and editable everywhere: inspector, sidebar, edit forms, mobile sheets and the PDF export; new PUT /assignments/:id/notes and update_assignment_notes MCP tool (Closes [BUG] Day assignment notes written through MCP/API are invisible in the UI #2163)
GPX/ICS/feed/wallet downloads no longer 500 on non-ASCII trip or place names — RFC 6266/5987 filenames keep the original characters (Closes [BUG] GPX export returns 500 when trip/place name contains non-ASCII characters (e.g. Japanese) — Content-Disposition header ERR_INVALID_CHAR #2165)
Weather anchors to the selected day's own location (with a bookend-hotel fallback), names the forecast location, revalidates the PWA cache hourly and serves the last five days from the forecast API instead of the lagging archive; forecast cache is language-aware (Closes Weather Report does not match [BUG] #2167)
Costs: edit fields show amounts with two decimals again, and three-decimal currencies (KWD, BHD) stay typable in the split and receipt fields (Closes [BUG] Costs - two decimal places are not always visible #2175)
Costs: negative amounts work end-to-end for partial reimbursements — input (with a sign toggle, since the iOS decimal pad has no minus key), split math, balances, MCP (Closes [BUG] Costs - Negative amounts not possible e.g. for partial reimbursement #2176)
Offline maps: the OpenStreetMap DE and Stadia presets are now actually stored offline (they matched no service-worker cache rule and their hosts were missing from the CSP allow-list), the prefetch counts only tiles it really fetched, and it covers the low zooms and the full opening view of a multi-city trip — on both the raster and the default vector basemap (Closes [BUG] Offline mode - does not store all the needed map tiles offline #2180)
Studio: all seven text fonts really render — the canvas and print renderer used a stale three-font map, and five of the families were declared but never imported, so picks fell back to Poppins, Georgia and friends; the faces now load with the Studio chunk (Closes [BUG] Studio: Not all fonts work on text elements #2183)
Packing: a shared bag now weighs what everyone put in it. Totals are summed on the server over every item, so other members' personal items count toward the total and the weight limit; only integer totals cross the wire, item writes ping the room on REST, MCP and plugin RPC alike, and offline the surfaces sum what they can see (Closes [BUG] Lists - you cannot see the right total weight of bags from your buddies #2191)
docker stopno longer ends in SIGKILL after ten seconds: WebSocket clients get a clean 1001 "going away", idle keep-alive sockets are dropped, the database closes on every exit path and the fallback exit fires inside Docker's grace period; the sequence lives in src/shutdown.ts and is tested (Closes [BUG] Container restarts every hour #2193)Journey maps no longer draw every linked trip's GPX tracks unasked. They are an owner-only Journey setting now ("Show all trip GPX tracks"), off by default and gating the request itself, not just the rendering; tracks also no longer zoom the map out past the entries (Closes [BUG] All planner GPX tracks are shown within the journey map #2194)
Admin: "Send test email" logs every outcome and answers with the classified SMTP cause (auth rejected, DNS, refused or filtered port, the 465/587 TLS mismatch, certificate) instead of a generic failure over an empty log; the test send bounds every SMTP phase so the verdict arrives inside the client's budget while a real notification keeps nodemailer's inactivity window, and the password field no longer swallows a replacement into its stored-value mask (Closes [BUG] Mail server settings fail to send test email with no log #2196)
Journey gallery is chronological now: capture time when the photo carries one, else the timestamp of the stop it belongs to, else upload time; on desktop, mobile and shared journeys (Closes [BUG?] Journey Gallery sorting #2200)
A place in the plan tab shows every booking linked to it, not just the first: desktop plan tab, place inspector, mobile timeline and mobile place sheet (Closes [BUG] Multiple Bookings Linked to Single Day Assignment #2201)
MCP prompts (Trip Summary, Budget Overview, Packing List) work again from every client: their trip id was declared as a number while prompt arguments always arrive as strings, so
prompts/getfailed with -32602 before the handler ran; numeric prompt arguments are parsed from strings now, the registry refuses number schemas at compile time, and a prompt without arguments no longer demands an empty arguments object (Closes [BUG] Prompts not working, cause of MCP error -32602 #2207)Mobile: tapping the hotel chip in a day's header opens the stay (the editor, or the hotel's place for members without day edit rights) instead of the day sheet, and closing it returns to the timeline (Closes [BUG] (Mobile) Selecting a Lodging item in Travel View Opens Day Overview, Not the Reservation #2210)
Collections map on mobile fills the viewport like the trip and journal maps instead of a fixed-height card (discussion Inconsistent map sizing #2104)
Geolocation failures show a localized message instead of a hover tooltip nobody sees on a phone, with a settings hint when access is blocked (discussion Unable to access location on PWA on iOS #2095)
Mobile dashboard on narrow phones: the filter row no longer widens the layout viewport, which pushed the top bar and the bottom dock off the right edge under Android's forced zoom (reported on a Galaxy S26 Ultra); body additionally guards against sideways overflow on phones
Android launcher icon: dedicated maskable icons with a proper safe zone, so the glyph no longer fills the whole tile edge to edge (installed PWAs pick it up after a reinstall)
Journey: editing an entry returns its tags decoded as a list instead of the raw JSON string (Closes fix(journey): answer an entry edit with tags decoded, not the JSON st… #2155)
Boot in the account's language when offline (fix: boot in the account's language when offline #2146)
Guarded the post-drag click swallow against a torn-down document (test flake)
Editing a booking on a phone no longer erases the stop it was linked to, and the day-assignment picker the desktop dialog has is in the phone sheet too, so a stop can be seen, changed and cleared there (Closes [BUG] "Link to day assignment" not working on mobile layout #2216)
Editing a transit booking no longer drops its stations: neither form edits endpoints, so an edit stops sending an empty endpoint set the server would write through
The phone's booking sheet lists the files already attached to that booking instead of only this session's picks, and a place shows the files of the bookings that sit on it, on desktop and on the phone (Closes [BUG] Files Do Not Appear in Mobile "Edit Reservation" Window (AND not showing in the Place Window) #2217)
Handing a place to a maps application from an installed app no longer leaves a blank window behind (Closes [BUG] (Mobile Web & PWA) Blank Webpage Remains After Returning from External Mapping Application #2218)
Collab notes: markdown written by another member is sanitized before it renders and note links are hardened, on the desktop notes panel, the notes card and the mobile notes tab
Plugin SDK:
trek-plugin checkgradesdocs/screenshot.pngon disk the way the registry gate does, instead of passing on any README image link, so a plugin no longer goes green in preflight and red in CI; the plugin MCP tools and the gate are documented in the wiki (Fix/sdk screenshot gate parity #2164)Dependencies:
fast-urimoves to 3.1.7, which closes six advisories rated high (host confusion, request forgery and authority injection through crafted URLs); it reaches the image through the MCP SDK's JSON schema validatorFeatures
Todo list sorts by due date: a toggle next to the priority sort, on desktop and the phone tab; nearest deadline first, undated tasks last, drag-reorder pauses while a sort is active (Closes [BUG] Todo list items are not sorted based on due date but on creation date #2205)
Admin addons page rebuilt as a tile grid with sub-shelves; photo providers now require the Journey addon (enable is blocked without it, disabling Journey cascades) (feat(admin): rebuild the addons page as a tile grid with sub-shelves #2156)
Polls support safe Markdown, multiline questions and fully wrapping long options on desktop and mobile (Closes Polls: support Markdown, multiline questions, and wrapped long options #2177)
Journey gallery: connected photo providers (Immich/Synology) sit next to the Upload button in the page header
Instance-wide plugin settings editor in the admin UI (feat(plugins): instance-wide settings editor in the admin UI #2170)
Plugins can declare instance-scoped settings actions, which admins run as buttons in the plugin's instance-settings dialog; existing actions stay on the user settings tab (Feat/plugin instance actions #2209)
Plugin settings fields honour their declared default: pre-filled in the admin form, the desktop settings tab and the mobile settings screen, resolved at runtime for the plugin itself, and accepted as satisfying a required field; an empty required field is refused on both save paths (Fix/sdk default #2199)
Studio: the content browser filters the journey's pictures by entry, by the ones no entry holds or by what was just uploaded, and combines with the search box; pictures upload from the panel or drop straight onto the page, with Fill page and Fill spread on photo elements; and a stop can be switched off the route, so the entry stays in the journal but leaves the distance, the countries and the map alone (discussion [Improvements/FR] TREK Studio #2064)