* fix(places): match imports on the provider id so renamed places are not duplicated Re-importing a Google Maps list duplicated every place the user had renamed. buildDedupSet only ever collected names and, for unnamed places, coordinates, and isPlaceDuplicate returns on the name before it reaches the coordinate fallback, so a renamed place was findable by neither. The ids were stored all along, and even backfilled onto matched places, they were simply never read. Match on google_place_id / google_ftid / osm_id first, keeping name and coordinates in their old roles below it. Widening the coordinate check instead would have merged the restaurant and the bar in the same building. The collection copy carried a hand-mirrored port of the same predicate; it now uses the shared helpers and gets the fix with them. Closes #1550 * feat(calendar): put accommodations into the subscribed feed as all-day stays A hotel keeps its dates on the linked stay, not on the reservation: the booking form writes reservation_time = NULL for type 'hotel', and day_accommodations carries start day, end day and the check-in/out clock. The feed only ever read the reservation, so a stay showed up once on the arrival day at best, and not at all when it came from the booking form. Join the stay in and emit the reservation as an all-day range over every night, which also keeps it correct when days are reordered later — the dates now come from the day rows rather than from the copy stamped at creation time. Check-in and check-out become their own timed events when the stay records a clock, because an all-day event cannot carry the time you actually need reminding of. The check-in window, when set, becomes that event's DTEND. Closes #1586 * fix(oauth): treat a concurrent refresh as a race, not as a stolen token Refresh rotation is a race by construction. Several MCP clients share one refresh token, both post it within the same second, and the loser was read as a replay: chain revoked, MCP sessions torn down, browser window asking to log in again. That is the daily re-authentication people were seeing, not a short lifetime — access tokens last an hour and refresh tokens roll for thirty days. A revoked refresh token now only counts as a replay once it is outside a 30-second grace window, and only when the rotation that revoked it left a successor still alive. Inside the window the caller gets its own pair branched off the same parent, so both clients walk away with a working token. Explicit revocation leaves no live successor and a chain killed by a real replay has none either, so neither can slip through the window. Closes #1007 * feat(collections): mark a place visited from the trip it belongs to A place saved in several lists had to be opened in each of them to say you had been there, which is the wrong way round: you find out you have been somewhere while looking at the trip, not while browsing the library. The save picker now shows each list's own status and cycles it in place, with one action for "visited, wherever this is saved". The planner's selection bar and the mobile bulk toolbar carry the same thing for a whole selection — select all, one tap, done. Matching happens on the server with the signals findMembership already uses plus the source link a place saved from this trip carries, so a copy renamed inside a list is still found. Lists you may only read are skipped rather than refused; a shared list you cannot edit should not stop you marking your own. Closes #1469 * feat(journey): jump to the top or back to the last entry in a long journal Everything that adds to a journal sits at the top — the entry button, the gallery upload — and where you left off sits at the bottom, so adding one photo to a long journey meant scrolling the whole way twice. Two buttons float over the middle of the feed, clear of the Add Entry column on one side and the reorder arrows on the other, each appearing only when there is more than a screenful to travel in that direction. They live in a zero-height sticky box so they take no layout space and never push the timeline around. While in the header, the status pill also picks up the height of the round buttons beside it. Closes #1088 * fix(packing): give the item name the row back on phones An item name had roughly a third of the row to itself: a spelled-out sharing sentence, an empty weight column and two round action buttons took the rest, so anything longer than a word was cut off. Sharing is now the icon it always was next to a label rather than a sentence in the layout, an unset weight prints nothing instead of a dash, and edit/delete sit behind one overflow button that opens the same inline menu the bag picker already uses. The to-do list needed none of this — it wraps its chips onto a second line already. Closes #1525 * docs(wiki): cover the six community changes - Calendar feeds: accommodations as an all-day range plus check-in/check-out - Collections: setting the visited status from a trip, and how places are matched - Journey: the jump buttons on a long journal - Packing: what the phone row keeps and what moved behind the overflow button - Places: re-importing a list no longer duplicates renamed places - MCP setup: the rotation race no longer ends an OAuth session * test(collections): assert the stored status instead of a private method tests/ sits outside the build tsconfig, so reaching into getPlaceById only showed up in the dedicated typecheck step. Reading the row is the better assertion anyway — it pins what was persisted, not what a getter returns.
7.5 KiB
Collections
Collections is a personal, server-wide library of saved places that lives outside of any single trip. Keep multiple named lists of places you have discovered — a "Norway road trip" wishlist, "Best coffee in Lisbon", "Someday" — each with a want-to-go / visited status, and share a list with other users.
Admin: enable Collections in Admin-Addons.
What Collections is
A place you save to a trip only exists inside that trip. Collections is the opposite: a place library that belongs to you, independent of any trip, so a spot you find while planning one trip is still there for the next one. Places are copied into and out of trips (never linked), so editing a saved place never changes a trip and vice versa.
Accessing Collections
When the admin has enabled the addon, a Collections entry appears in the main navigation (and a bottom-tab on mobile). Opening it shows your lists on the left, the active list's places in the middle, and a map on the right.
The dashboard also gains a Collections widget that surfaces your lists as compact badges. Each user can hide that widget from their own dashboard under Display Settings without affecting anyone else.
Lists
- Create a list from the "New list" action in the rail. A list has a name, a colour, an optional cover image (upload your own or search Unsplash), a description and a set of links.
- Edit or delete a list from the Edit button in the list's hero (owner only). Deleting a list removes its saved places.
- All saved is a built-in view that unions the places from every list you own or co-own, so you can search and act across your whole library at once.
Place status
Every saved place carries a status you can cycle with one tap:
- Idea — something you noted but haven't committed to.
- Want to go — on the shortlist.
- Visited — been there.
Status is a Collections concept and is not carried into a trip when you copy a place.
Setting it from a trip
You usually find out you have been somewhere while looking at the trip, not while browsing the library, so the status can be set from there too:
- The Save to list dialog on a trip place shows every list that already holds it, each with its own status pill. Tap a pill to cycle that list's status, or use Visited everywhere to mark all of them at once.
- The places panel has a mark visited action in its selection bar (and in the bulk toolbar on phones). Select the places — Select all covers the whole trip — and every saved copy of them is marked visited.
Matching is by the place's provider id, its coordinates, or the link a place saved out of that trip already carries, so a copy you renamed inside a list is still found. Lists shared with you read-only are left alone.
Categories
Places can be assigned a category from the same admin-defined set used across TREK (see Admin: Categories). Category colours and icons show on the place avatar, the place detail and the list rows, and you can filter a list by category.
Adding places
- Search and add — the "+" action opens a search; pick a result and set the name, category, status, a markdown description and links before saving, all in one step.
- Save from a trip — the place inspector and the trip place context menu both offer Save to collection, which toggles that place in or out of each of your lists.
- Bulk-add from a trip — in the trip place list, enter select mode, tick several places, and use the Save to collection action in the selection bar to copy them all into a chosen list at once. Duplicates (by name or coordinates) are skipped automatically.
- Import a whole trip — the download button in the list's filter row (and the second button on an empty list) opens Import from a trip: pick one of your trips, then tick the places you want. Places you already saved are greyed out and cannot be picked twice, and the ones no day of that trip holds start out selected, since those are the plans a trip left behind. Only new hides everything already on the list, and the counter on the import button always shows what is about to be added.
Place detail
Clicking a saved place opens a detail sheet showing a cover photo (fetched automatically when the place has none of its own), its category, its labels, a live status control, a markdown description, and links. Editing the place also lets you assign its labels. From there you can edit the place, copy it into a trip, or remove it from the list.
To set your own cover instead of the auto-fetched one, use the camera button on the detail sheet's cover to upload an image (JPG, PNG, GIF or WebP, up to 20 MB); the remove button next to it clears the custom image and restores the automatic default.
Filtering and bulk actions
Above the places sit compact filters — by status, by category, and by label — plus a Select toggle. In select mode you can:
- Select all the currently filtered places.
- Assign label — add one or more of the list's labels to every selected place at once.
- Copy to trip — copies the selected places into any of your trips (carrying their name, description, category, notes, price, coordinates, photo and tags).
- Move or Duplicate the selection into another of your lists.
- Delete the selection.
Custom labels
Each list can define its own labels — for example Berlin, Hamburg and Ostsee inside a "Germany 2026" list — to group its saved places beyond the shared category set. Labels belong to the one list they're created in and are shared with everyone on it.
- Manage — a label manager (reachable from the filter bar) lets you create, rename, recolour and delete labels.
- Assign — set a place's labels from its detail sheet, or add labels to many places at once with Assign label in the selection bar.
- Filter — pick one or more labels in the filter bar to narrow both the place list and the map to places carrying any of them.
Managing and assigning labels needs edit rights (Editor or Admin); filtering by label is available to every member, including Viewers. Moving a place to another list drops its labels, since labels belong to the source list.
Sharing a list (fusion)
Lists are private by default. The owner can share a list by inviting other users, similar to Vacay fusion. Invited users see the list once they accept, and changes sync live over websocket.
Member roles
When sharing, the owner assigns each member a permission role, and can change it at any time:
| Role | Can do |
|---|---|
| Viewer | View the list and copy its places into their own trips, and filter by label — no changes to the list. |
| Editor (default) | Add new places and edit existing ones, and manage + assign the list's labels. |
| Admin | Everything an editor can, plus delete places. |
The owner always has full control. The owner can also remove a member, and a member can leave a shared list themselves. Permissions are enforced on the server, so a role can only ever do what it is allowed to.

