The biggest release TREK has ever had
TREK gets a phone, and a book. A purpose-built mobile interface for everything under 768px, and TREK Studio: a photo book designer that turns a journey into something you can actually print. Plus a new front door and a map you can search. Vacay learns half days, comp days and a leave year that does not have to start in January. Places learn what they look like before you save them. Your files learn to live somewhere other than the box TREK runs on. And underneath all of it, the server finished a migration that had been running for months, and about a hundred and fifty reported bugs went with it.
Nineteen people wrote this one, across 23 languages.
TREK goes mobile
by @mauriceboe · #1577
Phones no longer get the desktop layout with the corners filed off. Everything under 768px renders inside a purpose-built mobile shell — a glass bottom dock, its own sheet layer, its own toast presenter. Tablets and desktops (768px and up) are completely untouched.
The mobile screens sit on the same data hooks and stores as the desktop ones, so live WebSocket sync, offline persistence, permissions and undo behave identically. This is a new interface, not a second app.
Screens
- Rewritten for phones: Dashboard, Trip planner, Vacay, Atlas, Journey (list and detail), Collections, Notifications, Settings and Admin.
- Mobile Settings splits into Display, Appearance, Map, Notifications, Integrations, Plugins, Offline, Account and About behind a section-switcher pill.
- Mobile Admin gets native panels for users, permissions, addons, plugins, categories, packing templates, backups, the audit log, MCP tokens, GitHub and notification settings.
The trip planner on a phone
A full-screen takeover with its own in-trip dock: a Travel / Plan / Places segment, a day-chip rail, a plan timeline, a full-screen map that stays warm underneath the timeline, and a places browser. Transports, Bookings, Costs, Lists (packing plus to-dos), Files and Collab (chat, notes, polls) open as full overlays, alongside any plugin tabs.
Native sheets instead of desktop modals
Creating and editing no longer falls back to a shrunken desktop dialog. New sheets cover:
- Reservations and bookings — type pills, dates and times, confirmation code, attachments, "create expense"
- Transports — manual multi-leg flights and trains with add-stop, plus the automated transit search
- Expenses — equal / custom / ticket splits, multi-currency with conversion, single or multiple payers
- Places, day notes, accommodations, packing items, tasks, imports and exports, save-to-collection
The component kit
MSheet (centred card, bottom-docked panel and left drawer variants, in glass, bar-glass or opaque), MBottomNav, MGlassBar, MSegmented, MToggle, MChip, MFab, MListRow, MProgress, MStatusDot, MDropdownPanel, MIconBtn and MToastHost.
The mascot
Empty states now star an animated TREK mascot — the logo itself is the character — with 17 scenes (dashboard, bookings, transport, costs, packing, tasks, files, notes, polls, chat, journey, collections, atlas, notifications, guide, search, idle) and moods such as sleepy or confused.
It came back to the desktop too, through a shared empty-state component used on roughly 16 surfaces. Opening a trip on desktop now plays a mascot travel splash — pack, hit the road, load photos, drop the pin — on a frosted card, replacing the old GIF. It freezes under reduced-motion.
Make the phone yours
Settings → Appearance gains a Mobile card with two editors:
- Bottom-nav customizer — move nav items between "in the bar" and "under More" and reorder them, with a live preview of the dock. Dashboard is pinned first; the bar holds Dashboard plus two more, because "More" takes the fourth slot.
- Dashboard order — reorder the phone dashboard blocks (trips list, currency, collections, timezones, upcoming reservations). Disabled widgets carry a "Hidden" badge; the spotlight hero stays pinned at the top.
Both live per account inside the existing appearance blob. Empty means the current automatic behaviour, so nothing changes until you touch them — no server or database change was needed.
Translations
Eight new namespaces (mobileTrip, mobileJourney, mobileVacay, mobileAtlas, mobileNav, mobileAdmin, mobileSettings, mobileCollections) in full key parity across all 23 locales — the mobile UI is translated everywhere from day one.
TREK Studio (Beta)
by @mauriceboe · #1973
The Journey add-on could turn a trip into a PDF. One layout, no way to change it, and the second half of that sentence was the whole problem: people asked for a book, and a book is something you arrange.
Studio is the same button in the same place, opening a photo book designer. It replaces the old Journey export rather than sitting beside it, and the PDF now comes out of the thing you can see.
It lays the book out, when you ask it to
A new book starts empty, because a book nobody has seen yet is not a book they can judge. Auto layout builds it in one go — a cover, spreads for the entries, a back cover — or rebuilds just the spread you are on. Which arrangement an entry gets depends on what that entry actually has. A panorama gets the full width with the caption set over it. A day with six photographs gets a real arrangement rather than a grid of six equal squares. A day with no photographs gets a quiet text page instead of empty frames. Six hand-drawn spread templates are tried and the one that fits the entry wins; thirteen more sit in the Layouts panel for when you would rather choose yourself.
The pages are built out of areas the way a printed travel book is, so you get coloured bands down the outer edge, panels carrying the country and the date, statistics pages. Some of those arrangements are not code at all: a spread drawn by hand in Studio can be read back as a template, stored as fractions of the page, and used by the auto layout whenever an entry has the material it asks for.
Then it gets out of the way
What the auto layout produces is an ordinary document. There is no locked "auto mode" and nothing re-runs behind your back. Every element is as editable as one you placed yourself: move, resize, rotate from any corner, layer, lock, duplicate.
It snaps to the page edges, the page centres, the safe area and both sides of the gutter, and to the edges and centres of every other element, drawing the guide so you can see why it stopped there. Undo treats a drag as one step rather than four hundred. Text is typed on the page itself, in its own typeface at its own size, because the point of editing there is watching the line endings while you write.
It knows it is a travel book
Alongside 64 vector shapes, 31 photo grids and the frame styles (polaroid, film, taped, white border, drop shadow) there are elements made out of your trip:
- Route maps that follow the roads you actually drove, from the routed geometry the planner already stores, rather than joining your stops with a ruler. Drawn as vector outlines, over real map tiles, or cut to the shape of the country so the picture is the country, with the page showing through around the coastline.
- Country outlines from real borders, one per country, placed and sized like any other element.
Every Studio string has a key in all 23 languages, but it is the newest surface in the app: about a hundred of them per locale still read in English outside German, and translations land as they come in.
- Statistics: distance, days, steps, photographs, countries, places and the furthest point reached.
- Marks: dates, day counters, coordinates, flags, weather, mood, altitude.
- Photographs can be cut to any of the 64 shapes and cropped by dragging the point that decides what stays in frame.
It saves itself, and more than one of you can be in it
The book autosaves. Two people saving at once is decided inside a single statement rather than by a read-then-write race, and whoever loses is offered both sides instead of being told off.
Everyone in a journey can open the same book at the same time and see each other's names and pointers moving across the page, so planning a book together works the way planning a trip together already does.
The export is the thing you were looking at
The same renderer that draws the editor draws the PDF, so what comes out is what you saw. Single pages in reading order for a print shop, or spreads the way the book opens for reading. Bleed and crop marks are a toggle. Trim size is yours, from a 21cm square to A4 landscape to whatever your vendor binds.
Any spread can also be downloaded as a file and imported into somebody else's book, so a good page can leave the journey it was made in. Measurements travel as fractions of the page, so a spread drawn on a square lays out correctly in a landscape book. Photographs stay behind: the file carries the design, not your pictures.
Studio is desktop only, and so is the PDF now. Designing a book asks for room to work. Everything else about a journey is unchanged on a phone. It ships marked Beta because a book designer earns its stripes on real books rather than on test data.
A new front door
by @mauriceboe · #1697
The sign-in screen showed six plane silhouettes drifting across a gradient and a grid of eight feature tiles. Neither said anything about TREK that the app itself does not say better.
It is now a dot map of the world with routes lighting up between cities — a route leaves, a light travels the curve, the destination lands, the arc retires, another starts. The feature tiles are gone. What is left is the mark, the tagline and one line of copy.
The sign-in animation picks the same map up and escalates it: eleven routes departing in a cascade, the logo landing as the network completes, then a soft hand-off into the app instead of a cut. Same component, same 2.6-second budget as the old takeoff.
The screen is unauthenticated, so it cannot call the Atlas endpoint the rest of the app uses for country geometry. The map is the same admin-0 bundle sampled onto a 300×150 grid and delta-encoded in base36: 15 KB and no request, instead of a 28 MB fetch the screen is not allowed to make yet. It renders once into an offscreen canvas and only the live arcs are drawn per frame. Reduced motion gets a still map.
Also from #1697: the mobile Vacay person card stopped packing used, carried-over, comp and remaining days into one row of nine-pixel numbers. Only the balance is left, as a pill tinted by how much of it there is, and the rest is a tap away.
Desktop Facelift: Vacay & Journey
by @mauriceboe · #1577
The two youngest areas of TREK were still wearing the old chrome. Both got a liquid-glass pass — visuals only, with the same features, the same store and the same API behind them.
Vacay
- A new light and dark palette drives every surface: month cards on glass with softened person-day fills, greyed-out weekend and settings-blocked days, company holidays still amber, a today ring and trip dots.
- Every month is padded to six week-rows, so all twelve cards share one height instead of jittering between 5- and 6-row months.
- The sidebar becomes Year / People / Legend / Entitlement cards, with "You" and "Pending" pill badges on the people list.
- The settings dialog widens into two columns, grouping calendar rules on one side and holiday options on the other.
- Toggling a vacation day or a company holiday now paints instantly and rolls back if the server rejects it.
Journey
- The dashboard gets a blur-mask magazine hero and a wider four-up grid of journey cards with cover images and title/subtitle overlays. The old top toolbar is gone.
- The detail page is centred and widened: a frosted hero, a glass stat pill, round controls, a glass timeline/gallery switch with a labelled add-entry / upload pill, and rounder day headers, entry cards and suggestion cards.
- The entry editor becomes a two-column dialog — title, photos and the growing story on the left; date and location, pros and cons, mood and weather on the right, with pill pickers.
- Settings moves to two columns with Vacay-style contributor role badges, and share controls gain a delete action next to copy.
- Two functional changes came with it: the create dialog now accepts a subtitle, and the dashboard always features a hero — the currently-running journey if there is one, otherwise the most recent, labelled "Active" or "Latest" accordingly.
Vacay
Four releases' worth of Vacay in one.
Half vacation days — #552
by @mauriceboe · #1631
- A Half day switch in the calendar toolbar, desktop and mobile. A modifier, not a third mode: turn it on and a click logs 0.5. Clicking with the other setting converts in place; clicking with the same one clears.
- Half days keep the person's colour and carry a small orange corner dot, mirroring the blue trip dot. The tooltip spells out per person whether a day is half or full.
- Entitlement sums fractions instead of counting rows, so Used / Left show the real total —
12.5 / 30. Carry-over follows the same sum. - Stored as a
fractioncolumn defaulting to1, so every entry logged before this release stays a full day.
Comp / flex days and a configurable vacation year — #1074, #737
by @mauriceboe · #1679
- Comp / flex days. A second leave type for time taken back from overtime or flextime. It deducts nothing from the entitlement, so used, remaining and carry-over stay correct. Independent of the half-day toggle, so a half comp day works out of the box.
- A comp day is drawn as a diagonal hatch in the person's colour instead of a solid fill — including the two-, three- and four-person split shapes, where each person's segment stays solid or hatched on its own. Desktop and mobile.
- Comp days are counted separately beside the entitlement tiles. The server had been counting them all along; nothing displayed the figure.
- The toolbar buttons now show the marker they actually place — the orange dot for half days, a hatched disc in the selected person's colour for comp — instead of a ½ glyph and a clock that appear nowhere on the calendar.
- Vacation year, per user: Calendar (1 Jan – 31 Dec, the default), Fiscal (a start month and day, e.g. 1 Jul – 30 Jun), or Hire date (the anniversary of your start date).
- Entitlement, used days and carry-over are counted over that period, and both grids roll over with it — a July start shows Jul–Jun, and mobile month navigation stays inside the period instead of falling out past December. Carry-over on a shifted year reads "from previous period" rather than naming a calendar year.
- In a fused plan the leave year stays personal: the grid and its data follow whoever is looking, while each member's numbers are counted over their own period and each stat row is labelled with the window it was counted over.
- Deleting a year is window-aware — personal entries per author over that author's period, company holidays only over the range every member shares, so one person cannot reach into another's live period.
- The entitlement card folds away by clicking its header, remembered per browser.
- Known limitation: with a start day past the 1st (the UK's 6 April, a mid-month hire date) the twelve cards stay month-aligned while counting is day-exact, so a handful of days at each end sit on the wrong card.
School holiday overlays — #971
- A School holidays toggle in Vacay settings with its own list of calendars, kept separate from public holidays. Purely visual: they never consume entitlement and never create entries.
- A school-holiday day gets a thin coloured band along the bottom of the cell plus a soft background wash, so it stays distinct from a public holiday (which tints the whole cell) and does not recolour the day number. Several calendars on one day split the band into equal segments.
- Hovering folds school breaks into the same tooltip as your logged days, under a divider.
- Data from OpenHolidays, with an explicit whitelist of 16 validated countries — AT, BE, CH, CZ, DE, EE, ES, FR, IE, IT, NL, PL, PT, RS, SI, SK. Germany, Austria, France and Spain resolve by subdivision, the Netherlands and Belgium by regional group. Sweden is deliberately not offered yet, because its coverage did not look reliable enough during validation.
Read-only calendar sharing — #444, #667
by @mauriceboe · #1637
- A Shared Calendars card in the sidebar (a share sheet on mobile) lets you show your calendar to another TREK user without merging plans. No acceptance step; either side can end it at any time.
- Shared calendars overlay the grid as a coloured ring around the sharer's days off — vacation days, half days and their company holidays — clearly different from the filled cells of your own plan. An eye toggle hides one without removing the share.
- View-only by design: no write path accepts a share, and both people keep their own settings, allowances and company holidays. Company holidays expose dates only, never the note text other plan members wrote.
- A share follows the person, not the plan: it survives fusing and dissolving, and lies dormant while you are fused with that person, then resumes.
- Being shared with is its own notification, in the app and through whatever channel you have wired up, with its own row in the notification preferences so it can be muted on its own.
- If the sharer's colour collides with one already in your calendar, the overlay remaps to a free preset.
Places
See the place before you save it — #1430
by @mauriceboe · #1867
Place search gave you a name and an address, and that was all you had to decide with. A thumbnail "tells nothing for a place you never saw before".
The desktop add-place dialog now carries a Place details column. Pick a search result and it fills in on its own: pictures of the place and a description, each credited to its source. No extra dialog and no extra click — if you already know the place, ignore the column and save exactly as before.
- Clicking a picture makes it the place's thumbnail, everywhere: list, map marker, itinerary, PDF export, shared trips.
- Free sources are the normal case, not the fallback. Pictures from Wikimedia Commons, found through a four-step ladder — the Wikidata item first, then the lead image of the article the place is tagged with, then its Commons category — falling back to a coordinate search only when the rungs above come up short. Descriptions come from the OSM
descriptiontag or that same tagged article, resolved from the tag and never guessed from the name, because a confident description of somewhere else is worse than none. - Places no encyclopaedia describes get OpenStreetMap facts as chips instead of an empty column — cuisine, wheelchair access, outdoor seating, takeaway, delivery, diets, a menu link, internet access — with the opening hours set out below them as a real week rather than crushed into a chip.
- With a Google key, up to three Google Places photos head the picture strip, because its pictures are of the business itself. Its editorial summary is the other way round: that is only asked for once the OSM
descriptiontag and the tagged article have both come up empty. - Place Enrichment is an admin switch, under Admin → Settings → API Keys, on by default. An operator who would rather TREK called nobody can turn the whole column off.
- Every thumbnail is credited with its licence and a link to the source page, and the credit is stored with the cached file so it stays visible in the inspector afterwards. Handing someone a strip of CC BY-SA images to choose from makes that non-optional.
- Pictures are copied into TREK's own photo cache, never hotlinked — no viewer's address reaches Google or Wikimedia, and no expiring provider URL ends up on a place.
Export a trip as GPX — #1442
by @mauriceboe · #1907
TREK has read GPX for a long time and never written it, which left the format one-way. The day-plan toolbar exports GPX beside PDF and ICS now: places without geometry become waypoints (carrying their category as <sym>), imported tracks come back out as tracks with their elevation intact, and each day becomes a route in plan order. Straight onto a handheld or into Organic Maps.
Open a place in the app you actually drive with — #919
by @mauriceboe · #1885
Google Maps keeps the precise link it always had — ftid, then place id, then details URL, coordinates only as a last resort, so it still lands inside the mall rather than on its roof. Waze, Apple Maps and CoMaps join it and the OpenStreetMap link moved in beside them, so the list is five, four of them on Android where Apple Maps is withheld. Waze opens with navigation armed; the others open on the place. They stay hidden for a place TREK has no position for.
CoMaps (@subdee, #1932) goes further than a single pin: a whole day's route can be handed over to it, which is the case the offline-navigation crowd actually asked for.
Permanently closed places stay out of search — #1341
by @mauriceboe · #1887
businessStatus was in Google's answer all along and TREK never asked for it. Now it does, and CLOSED_PERMANENTLY drops out of the results.
Collaborative place ratings — #1435
by @mauriceboe · #1644
- Every trip member gives a place their own 1–5 stars; the place shows the group's average. A tooltip lists who voted and with how many, with avatars.
- The places sidebar gains a star button that filters by a minimum rating, with an average badge per row. It shows on a trip that has no categories yet, which is exactly where somebody is collecting places to rate (#1885).
- Saved places in Collections can be rated the same way, and the collection filter row gets a minimum-rating filter.
- Ratings survive copying in both directions without leaking: copying from a collection into a trip brings only the votes of people who are also on that trip.
- New MCP surface for Collections — around 25 tools covering list and place CRUD, status, ratings, labels, sharing and copy-to-trip, behind two new scopes (
collections:read,collections:write), plus arate_placetool. - Voting deliberately does not bump the place's
updated_at— rating something does not make it look edited.
Custom place images — #1136
by @mauriceboe · #1641
- Click a place's thumbnail to upload your own photo. It becomes that place's image everywhere: the places list, map markers, the itinerary, the PDF export and shared trips. A remove button restores the automatic default.
- HEIC is converted to JPEG automatically; JPG, PNG, GIF and WebP up to 20 MB.
- Uploads are cleaned up on replace, remove and delete, and reference-counted across trip places and collection places — a photo shared by save-to-collection is only removed once nothing points at it.
Planned filter and hotel-transfer routing — #1297
by @mauriceboe · #1666
- A Planned filter next to All and Unplanned, in the places sidebar and on the map, showing what you have already dropped onto a day. On the map it shows the whole plan regardless of which days are collapsed.
- A day that is nothing but a hotel-to-hotel transfer now offers its route tools — Route, Google Maps, optimize and the transport-mode picker — where before it offered none, even though the map already drew the leg.
Re-importing a Google Maps list stops duplicating renamed places — #1550
by @mauriceboe · #1915
Rename a place, re-import the list, get a twin. The duplicate check only ever compared names, and fell back to coordinates for places with no name at all — so a renamed place matched neither. The provider ids were stored on every imported place the whole time and simply never read. They are read now, and first.
Collections
Import a whole trip's places into a list
by @mauriceboe · #1903
Coming home from two weeks with a dozen places worth keeping meant a dozen round trips through a menu. A list now offers Import from a trip: pick a trip, tick the places, done. Places already in the list are greyed out with a reason, and the ones no day ever held — precisely the ones a finished trip leaves behind — are pre-selected.
Mark a place visited from the trip it belongs to — #1469
by @mauriceboe · #1915
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 carries the same for a whole selection — select all, one tap, done. A copy renamed inside a list is still found.
The plus adds a place
by @mauriceboe · #1933
On a phone, the dock's + adds a place when you are looking at a collection, instead of offering to create a trip.
Coordinates and A–Z — #1640
by @mauriceboe · #1647
Editable address, latitude and longitude in the add-place modal and the detail sheet, so a place can be entered from coordinates alone when search finds nothing. Pasting a lat, lng pair splits it across both fields. A sort control toggles between the saved order and alphabetical.
Shared trips
The map on a share link shows the plan — #1955
by @mauriceboe · #1965
A link handed to somebody who is not on the trip drew a scatter of pins. It now draws the plan: numbered day stops, a connecting line and a day picker. Markers keep their category colour outside a day selection, and the map stops refitting on every change, which used to throw away the pan and zoom of whoever was reading it.
Atlas
Visited is not the same as planned — #1048
by @mauriceboe · #1771
The atlas counted every trip as a visit, so a country you had merely booked a flight to was painted exactly like one you came back from. Visited and planned are separate classes now, with their own colours and their own counts. Planned countries are off the map until you ask for them, through a switch above the globe that only appears once you have planned something and remembers where you left it; the headline number counts what you have visited, with the planned count beside it.
Wishlist countries get their own look
by @NtsCiccio · #1893
Countries on the bucket list but not yet visited are drawn with a diagonal hatch instead of disappearing into the grey of everywhere you have never been. Country colours became deterministic, derived from the ISO code rather than from visit order — marking one new country no longer reshuffles the colours of every other. A remove action came with it.
Search the map by place, and count cities that are cities
by @mauriceboe · #1908
The search box only knew country names, so typing "Milan" found nothing and marking Lombardy meant knowing which region Milan sits in. It now searches places too, flies to the result and offers to mark the region it lands in.
The city counter was counting shops and streets — "Family Mart", "BNP Paribas", a bus stop — because it read the first useful-looking part of an address. It now reads from the administrative end instead, skipping the country, anything with digits and the region it already resolved.
Journey
Photos remember where and when they were taken (#1614)
by @mauriceboe · #1972
Three columns on every photo, filled from wherever the truth actually is: Immich albums carry their coordinates through, Synology is asked for its GPS block, and local uploads have their EXIF read on the server. Whichever source answers first wins, so a later, emptier answer cannot blank what is already known.
The photos are then drawn on the journey map as thumbnails, collapsing into a count where they cluster, sitting under the entry pins because the itinerary is the point of the map and the photos are context. A photo's location follows the same switch the entry coordinates follow, so turning the map off on a shared journey does not publish the very places you withheld.
One thing worth knowing before someone files a bug: the client converts HEIC before upload and that conversion drops GPS, so an iPhone photo often arrives already stripped.
A shared journey can read newest first (#1614)
by @mauriceboe · #1972
A journey shared while the trip is still running is read like a blog. The order is set per share link, the reader can flip it, and it is off by default so an already-published link does not reorder itself under its readers. Only the reading order changes: day colours and stop numbers stay chronological.
One numbering on the public map (#1614)
by @mauriceboe · #1972
The marker showed the stop's position within its day while the heading showed the day number: same colour, different meaning, and on mobile the heading is not rendered at all, so the number had no key. Both count across the whole journey now, so every number appears exactly once.
Entry time, at last (#1614)
by @mauriceboe · #1967
The time was stored, sent, and shown in the timeline, on the map, in the PDF and on the public share. The desktop editor simply never rendered an input for it, so a draft carried whatever time it happened to be created at and there was no way to correct it.
Weather for the hour, not the day (#1614)
by @mauriceboe · #1972
An entry made at 14:30 wants the weather of that hour. The archive path only asked for daily figures, so it answered with a rainy morning and a bright afternoon averaged together.
Quick capture takes a note (#1614)
by @mauriceboe · #1972
The ask was "photos or quick notes" while travelling, and capture mode only did the first, so a moment worth catching without a picture had no way in short of opening the full editor.
Galleries with hundreds of photos scroll again (#1614)
by @mauriceboe · #1972
Every tile was in the DOM from the first paint. The gallery renders 60 and grows as you scroll. Deliberately not paginated over the wire: the gallery arrives inside the one payload the client, the MCP tool, the MCP resource and the PDF export all read, so cutting it there would silently shorten the book.
Smaller things (#1614)
by @mauriceboe · #1967
- The camera works on desktop and tablet, through its own input rather than a
captureattribute that would take the photo library away on a phone. - The photo picker's date headings read chronologically again after the switch to distance sorting.
- Providers are named properly instead of everything being called "Synology Photos".
- All six Open-Meteo calls carry a timeout, like every other outbound client in the codebase.
GPX tracks on the journey map — #1260
by @mauriceboe · #1911
The route you imported while planning was drawn in the planner and vanished in the journal. Nothing needed storing to fix that: a journey has no trip of its own, but every entry remembers the trip it came from, and a trip's routed geometry already lives on its places. The tracks are drawn in the colour they already have in the planner, with a white casing so they stay readable on satellite tiles, and deliberately different from the thin dashed line TREK draws between entries.
External photos inside the entry editor
by @SimMesg20 · #1632
- A third button next to Upload and From Gallery: External photos, opening an embedded Immich/Synology picker inside the editor rather than a separate dialog.
- It opens on a This day tab and immediately searches the library for the entry's own date. Trip Period, Date Range, All Photos and Albums are still there.
- With a pinned location, photos carrying GPS are ranked nearest first. Nothing is filtered out — distant and GPS-less photos stay available further down.
- Selections queue with the rest of the editor's unsaved changes. If attaching fails after the entry was created, pressing Save again retries against the same entry instead of creating a duplicate.
Mobile quick capture
- Adding an entry on mobile opens a short quick-capture sheet instead of the full editor: camera, gallery, date and time, location, weather.
- The sheet asks for the device location on open, reverse-geocodes it into a place name and looks up the current weather, preselecting the matching category. Everything it fills in stays editable; a denied lookup shows why and leaves the fields alone.
- Add details expands the same sheet into the full editor, keeping what has already been captured.
Use my current location
by @RenzoBeux · #1732
A one-tap button in the journey entry editor that pins the entry where you are standing.
Jumping around a long journal — #1088
by @mauriceboe · #1915
Everything that adds to a journal sits at the top; where you left off sits at the bottom. Adding one photo to a long journey meant scrolling the whole way twice. Two buttons now float over the feed, each appearing only when there is more than a screenful to travel in that direction.
Bookings, day plans & costs
Assign travelers to bookings — #1517
by @mauriceboe · #1676
- Every booking and transport can be assigned to one or more trip members or named guests, picked in the create/edit modal on desktop and the booking sheet on mobile.
- Assigned people show as avatar pills under a "Travelers" label. A traveler filter narrows the list to the people you pick — it is a multi-select, not one person at a time — and only appears once a trip has more than one member and something is actually assigned.
- Guests are treated exactly like members, so a named guest without an account can be assigned to a flight or a room.
A travel mode per leg — #1281
by @mauriceboe · #1651 · follow-up by @geoida · #1890
- Click the travel-time connector between two places to set that leg's mode: walking, driving, any route-provider plugin you have enabled — plus Use day default.
- Choices are persisted, not session-only. The whole-day Foot/Car picker sets a saved day default, and a leg follows it only while it has no mode of its own.
- The follow-up extends this to boundary legs: a leg whose origin is not a place at all — the walk from an arriving booking, the morning leg out of a hotel — now carries its own mode on the destination stop.
- A leg can also be planned as a public-transport journey from the same menu, with the connection search behind it (@geoida, #1910).
- Two MCP tools came with it,
set_leg_transport_modeandset_day_default_transport_mode, so an assistant can set what the menu sets (@geoida, #1929).
Parking as its own booking type — #1444
by @mauriceboe · #1642
Parking joins Hotel, Restaurant, Event, Tour and Other, with its own icon and blue colour everywhere reservations are drawn — panel, day detail, day-plan sidebar, mobile card, PDF export, dashboard preview. It joins the type filter, and its auto-created expense is filed under Transport. It draws no route line: parking is a booking, not a leg.
A multi-day parking booking shows on its first and last day rather than on every day in between, the way a hotel already did, and the distance connector between the legs stays where the middle days are hidden (#1958).
One export dialog
by @mauriceboe · #1933
PDF, ICS and GPX were three buttons on the day-plan toolbar, each with its own quiet failure mode. They are one dialog now, with each row reporting its own progress.
A booking reference per leg — #1943
by @mauriceboe · #1958
A multi-leg flight or train carried one confirmation code for the whole journey, which is not how airlines issue them. Each leg can hold its own now, and the PDF prints the one that belongs to the row.
Accommodations in the calendar feed — #1586
by @mauriceboe · #1915
A hotel keeps its dates on the linked stay, not on the reservation, and the feed only ever read the reservation — so a stay showed up once on the arrival day at best. It is now an all-day event over every night, taken from the day rows, so reordering days moves it too. Check-in and check-out become their own timed events when the stay records those times; a check-in window becomes that event's end.
A note on an expense — #1658
by @mauriceboe · #1888
budget_items.note had existed for a long time, but the costs UI stored its itemized receipt in the same field — so an expense split by receipt could never carry a note, and saving an expense anywhere else silently wiped one typed in the budget table. The receipt moved to its own column and the note became a real field.
Fuel and Parking are their own expense categories
by @MikeDabrowski · #1996
Petrol on a road trip had nowhere sensible to go, so it landed in Transport or in Other. Fuel and Parking join the fixed list, with the labels translated into all 23 languages. Parking also closes a gap the parking booking type left behind in #1642: a parking booking's auto-created expense used to be filed under Transport, because there was no Parking category to send it to. It goes to Parking now, and a manually typed "parking" lands in the same place. Expenses already recorded are not moved.
Satellite tiles on the trip map — #993
A corner button toggles the trip map between street and satellite. Tokenless ESRI World Imagery, so it needs no configuration, and the choice is remembered per user across trips and reloads — offline included. It sits on the default map; an instance running the GL renderers keeps their own imagery styles.
Packing: weights that survive a copy, and a bag limit — #207
by @mauriceboe · #1773, #1774
createItem never accepted a weight, so every "copy to my list" arrived empty and the reported workflow — one person curates, everyone copies — meant re-entering every weight by hand. A copy now keeps its weight, and keeps the bag only when nobody owns it or the copier is a member of it, so nothing lands in someone else's luggage. Bags gained a weight limit, and shared items stopped counting towards the recipient's total.
Mobile packing rows get their name back — #1525
by @mauriceboe · #1915
A spelled-out sharing sentence, an empty weight column and two round action buttons left the item name about a third of the row. Sharing is an icon again, an unset weight prints nothing, and edit/delete moved behind one ⋯ button — which is exactly what the request asked for.
Notes
Rich formatting and colours — #1629
by @mauriceboe · #1889
Notes have rendered Markdown for a long time. What was never there was any way to find that out — the only editor was a bare textarea, so unless you already knew the syntax, notes were plain text as far as you could tell.
- Both note fields and the place description get a formatting bar: bold, italic, strikethrough, code, link, bulleted and numbered list, quote. It edits Markdown rather than replacing it with a rich-text model, so the stored value stays plain text and the PDF export, the plugin API, the MCP tools and every note written so far keep reading it unchanged.
- Links open in their own tab, with
rel="noopener noreferrer nofollow"— notes are content other trip members wrote, and withoutnoopenerthe opened page gets a handle back on this one. - Day notes take a colour from a short shared palette, so the picker, the card and the PDF cannot drift apart. Seven hues plus "no colour", which stays the default.
Start where you actually are
Open TREK on your active trip, on the tab you pick
by @mauriceboe · #1845
Opening TREK on the road took three clicks and three loads before you could type an expense. Settings → General → Startup now offers a start page (Dashboard, or Active trip) and a start tab, so the app can open straight into Costs of the trip you are on.
The redirect sits on /, which is what a shortcut, a bookmark and the installed PWA all launch — /dashboard keeps working as itself. Deep links work on their own too: /trips/42?tab=finanzplan.
Jump to today — #1567
by @mauriceboe · #1891
The mobile shell has seeded the active day with today since it was built; the desktop day plan never did, so the same running trip behaved differently depending on the screen. It now selects today, expands it and scrolls it into view — once, only while nothing else is selected, and only when today is inside the trip. The date check reads the local clock, because 08:00 in Tokyo is still yesterday in UTC.
Your files do not have to live on the box — #373
Everything TREK stores has always been a path on the disk the server runs on. That is fine until the disk is the thing you are worried about.
Storage is now eight content categories — trip documents, journey photos, cover images, profile pictures, place images, the Google photo cache, the TREK photo cache and backups — and a set of backends they can be pointed at. Out of the box every category resolves to the same local directories as before, so an install that changes nothing behaves exactly as it did.
- An S3-compatible driver takes AWS S3, Cloudflare R2, Backblaze B2, Garage and MinIO. Endpoint, bucket, credentials, and optionally a region, a key prefix, retries and a timeout. Any category can be assigned to it.
- The local driver became relocatable: the built-in backends can be pointed at another directory or mount from the admin panel, with per-category prefixes. The spool sits on the destination volume, so committing an upload is an atomic rename rather than a copy across devices.
- A mirror backend writes to its primary and then to one or more replicas. The primary stays the source of truth and a replica failure never fails the request; it lands in a Health strip and, on the first failure, in an admin notification, debounced to one summary per backend per hour. That is the off-box backup ask, and it is allowed on any category, not only on backups.
- Sync now is for a target you added after the fact: it copies across whatever the primary already holds and the target does not. Objects already there at the same size are skipped, one bad object does not stop the run, and you can cancel it. One sync at a time.
- Admin → Storage holds all of it: backends and their usage, the category map, mirrors folded into their primary, a health strip, a connection test, and an inline warning that reassigning a populated category does not move what is already in it. The phone shell has the same screen.
- Backups are created, listed, downloaded, restored and deleted through the storage layer, so a backup can live on S3 or be mirrored there.
- Credentials are encrypted at rest and only ever rendered to the browser as a mask. Saving a backend with credentials requires
ENCRYPTION_KEYto be set in the environment — the key TREK otherwise derives for itself is not accepted here, because a stored S3 secret should not be recoverable from the same material that signs your sessions. - A
storage-config.jsonin the data directory is imported exactly once, on the first boot with no stored configuration, for people who provision instances rather than click through them.
Two limits worth knowing before you move a category: served media proxies through the server and does not support HTTP Range, and replica writes happen inside the request, so a slow target slows every upload on that backend.
For admins and operators
Place search keys belong to the instance — #1939
by @mauriceboe · #1958
The Google Places and Unsplash keys were stored per user, on whichever admin pasted them in. Every other member of the instance got a 403 from place search, which reads as a broken install rather than as a configuration choice. They are instance configuration now, an upgrade migration promotes an existing single-holder key into the instance row, and saving them writes an audit entry naming the fields that changed. The key-rotation script learned about them, and about users.unsplash_api_key, which it had never covered.
Also for admins
- Two ways to supply the Places credential from outside the database (#1946) —
PLACES_API_KEYtakes it from the environment instead of the per-user key columns, andPLACES_API_BASEroutes the traffic through an endpoint you host yourself. - Outbound proxy support (@Cynosure159, #1922) — the official image sets
NODE_USE_ENV_PROXY=1, soHTTP_PROXY,HTTPS_PROXYandNO_PROXYwork for every outbound request TREK makes. - An admin can clear another account's TOTP through a new
DELETE /api/admin/users/:id/mfa(#1946). - Admin sections are deep-linkable with
?tab=on/admin(#1947). - The source-code link moved from the About tab onto the version in the sidebar footer.
For plugin authors
- Surfaces, insets and the mobile palette (@mauriceboe, #1886) — a plugin never learned which of six host surfaces it was mounted in, how much chrome floats above and below it, or that the phone has a second token family entirely.
trek:contextnow carries aviewportfield with all of it, so a plugin can finally aim at the target it is drawn into. - Session storage (@geracobo, #1707) — plugin iframes run with an opaque origin and cannot touch
sessionStorage.window.trek.sessionbridges it, scoped per user and plugin or additionally per trip, with hard limits so a plugin cannot eat the host's storage. dayTintProvider(@RenzoBeux, #1743) — a hook that colours the day itself, so a plugin modelling a trip as legs or phases can make day 12 look different from day 40 while you scroll a long itinerary. Bounded by day count rather than by a 60-item cap.mapLayerProvider(@mauriceboe, #1577) — a plugin could contribute markers and had no way to show a computed route, a reachable-range corridor or a zone. A granted plugin now returns polylines, polygons and metric circles and the host draws them on both renderers, under the core routes. Declarative only: coordinates range-checked, styling clamped to a four-tone palette, and budgets of 4 layers, 150 features and 8,000 vertices per plugin.routeProvider(@mauriceboe, #1577) — the route toggle offered Driving and Walking, both OSRM. A plugin can now declare up to three profiles incapabilities.routeProfiles— an EV profile that plans charging stops, say — and they appear beside the built-ins in the per-leg picker. Only the chosen provider is invoked, with a 20-second timeout because it may call an external solver, and anything malformed falls back to straight lines exactly like an OSRM outage.dayScheduleProvider(@mauriceboe, #1577) — a plugin can add rows like "35 min charging at this stop" or "45 min security before this flight" to the day plan, drawn by the host under the place or booking they anchor to, with their minutes folded into the day's total. It is the first hook whose output feeds a number the reader already trusts, so minutes are clamped to 1–1440, day ids are checked against the trip's own days and each provider is capped at 60 items.geolocation:read(@mauriceboe, #1577) — plugin frames run with an opaque origin, sonavigator.geolocationwas simply unavailable inside them. A bridge permission lets the host read the position and post plain coordinates into the frame. The browser's own prompt still applies, the grant is re-checked on every fix so revoking it stops a live stream immediately, and nothing reaches the TREK server.- The SDK ships all of it typed: the three provider interfaces, the
RouteProfileCapabilityshape, manifest validation that mirrors the server, and a dev preview that answers the geolocation bridge with a fixed position so a plugin can be built without a real prompt. - Blur booking codes on the plugin context (@geracobo, #1703).
- The whole plugin RPC surface moved onto decorators (#1853–#1860), so a method is registered at spawn only if the plugin holds the permission that unlocks it — registration is authorization.
Under the hood
The client stopped shipping what you did not open
Every one of the twenty pages was a static import, so anyone who opened the dashboard downloaded the planner, the journal, the atlas and the vacation planner too.
| before | after | delta | |
|---|---|---|---|
| Entry chunk | 7,987 kB | 219 kB | −97% |
| Map, mapbox users | 2,828 kB | 1,798 kB | −1.03 MB |
| Map, maplibre users | 2,828 kB | 1,028 kB | −1.80 MB |
The gzip figure was last taken at #1817, where the entry chunk was 581 kB raw and 177 kB gzipped; the two cuts after that were measured in raw bytes.
Both GL engines were being bundled into one chunk and everyone downloaded both to run one of them (#1819). Page chunks (#1812), planner tab panels (#1816), the PDF export and the transport modal (#1821) all load on demand; React and the core libraries got their own chunks (#1818); the viewport branch is now picked at the route instead of inside the tree (#1817).
The page stops going white
#1805 — there were zero error boundaries in the client and no window.onerror or unhandledrejection handler either. One throw during render unmounted the entire tree: white page, no navigation, reload or nothing. That is also the prerequisite for the code splitting above — without it, splitting trades bundle size for blank screens whenever a chunk 404s after a deploy.
The server finished its migration
The strangler migration that had been running since NestJS was introduced is done. Legacy src/services/ is deleted, every domain is a DI module, every cron is Nest-owned, /ws is a Nest gateway with its own adapter, and zero bridge files remain.
It took two strands. The PRs between #1823 and #1880 fold weather, places, journey, memories, notifications, auth, admin, oauth, webauthn, backups and the rest into the container, put the MCP and plugin surfaces on decorators, and give every trip-scoped controller the same TripAccessGuard + permission shape. Alongside them @jubnl moved auth, oidc, passkeys, collections, atlas, transit, places, notifications and admin onto proper DI, retired src/scheduler.ts, took the plugin jobs off node-cron and deleted the last bridge under the MCP mount.
One consequence is worth knowing if you drive the API yourself: on every controller that took the shared TripAccessGuard, the trip 404 and the permission 403 are now decided before the body is validated, where they used to be decided after, so a malformed body from a caller who has no access answers 404 or 403 rather than 400. The places create, update and import routes, the trip-member writes and the three upload routes keep the old order on purpose.
Authentication flipped from opt-in to default-deny: routes must declare themselves public, and a boot-time ratchet refuses any public route that is not on the allow-list.
And the client caught up too
React 19 migration finished (#1826), Zustand 5 (#1825), react-router 7.18.2 (#1804), and a proper MCP Nest wrapper for dependency injection (@jubnl, #1708).
Security
Several of these were reported to us privately from outside the project. Thank you.
- Packing items are authorized against the item, not just the trip (GHSA-vh2h-288v-ggch) — a packing item is Common, Personal or Shared with named recipients, and the list honoured that, but update, delete, clone, contributor and sharing all resolved the item by trip id alone. Trip membership plus
packing_editwas enough to read, edit, delete, clone or re-share another member's Personal item, and the update path answered with the enriched row, handing back the name, category, quantity, weight, owner and recipient list of an item the caller was never shown. The rule now covers the MCP tools and the plugin RPC surface too. Reported privately by Chichi (@vickyzer027-hash). — @mauriceboe, #1941 - A packing template no longer captures other people's private items —
saveAsTemplateread every row in the trip regardless of visibility, so a template silently carried the names and categories of everyone's Personal items wherever it was reused. It takes the Common list plus the actor's own items now. — @mauriceboe, #1941 - Copying a trip keeps packing privacy — the copy re-inserted every packing row without
is_privateorowner_id, so another member's Personal item reappeared in the copy as a Common item visible to everyone. — @mauriceboe, #1941 - A private packing item edited over MCP stays off the other members' screens (#1976) — restricted rows were broadcast to every socket in the trip, because the MCP broadcast helper had no argument for who may see the change. Asking an assistant to tick something off your own list pushed that row onto everyone else's screen, and their client cached it. Reported by @ssadras. — @mauriceboe, #1985
- Share tokens now require
share_manage(#1883) — the calendar-feed token and the share-link routes accepted trip membership as authorization for minting a bearer URL. Membership lets someone read the trip while they are signed in; it does not let them mint a link that keeps working after they are removed. AGETthat never called its own access check used to answer200to a stranger where404belonged. - The feed token stopped riding along in trip payloads (#1883) —
TRIP_SELECTreadst.*, so any member could read the calendar-feed token straight out ofGET /api/trips/:id, theget_trip_summaryMCP tool or thetrips.getByIdplugin RPC, which made whatever the token route enforced decoration. - User ids in write bodies are scoped to the trip (#1952) — holding
packing_editsays you may edit this trip, not that a user id you name in the body belongs to it. Packing assignees, item recipients, to-do assignees, day participants and place tags stored whatever id arrived, and every one of those paths ends by re-selecting through a join on users, so an unrelated account came back out with a display name and an avatar attached — in the response, and for several paths in the WebSocket fan-out to the whole room. Guests stay assignable everywhere a member is. - Budget payers and split members are scoped to the trip (#1952) — the check asked whether a user exists at all, not whether they are on this trip, so any account on the instance could be named as a payer or dragged into a split by anyone holding
budget_edit. Settlement parties are refused outright rather than filtered, because a settlement names exactly two people and cannot silently drop one. - Unshared trip photos stopped reaching journey galleries (#1614) — a photo a member left unshared is correctly denied on the trip path, but linking that trip to a journey copied every photo row into the journey gallery, where every contributor and, with an open gallery, every anonymous visitor of the share link could see it. #1967 filtered the copy; #1972 removed the copy itself.
- Linking a trip to a journey checks the journey too (#1614) — the REST route verified the caller could reach the trip and never that they could reach the journey, so any signed-in user could link a trip of their own into a stranger's journey and seed entries and photos there. The MCP tool had always checked; REST was the half that did not, and the check moved into the service so both are covered. — @mauriceboe, #1967
- Stored API keys are no longer read back in the clear (#1946) —
GET /api/auth/me/settingswas the only endpoint that decrypted a stored credential back to the caller. Right when the admin pasted the key in, wrong when the operator supplied it. The three keys are withheld from that response. - An admin password reset now ends that account's sessions (#1946) — an admin sets somebody's password for one reason, and until now every cookie an intruder already held kept working: the single action taken to lock them out was the one action that did not. It bumps
password_versionand revokes the account's MCP tokens. - OIDC provider calls pass through the SSRF guard (#1946) — the token, userinfo and JWKS URLs come out of the discovery document, so they are chosen by the identity provider rather than by an admin. All four calls are guarded now, and the token exchange follows no redirect at all, since one would hand
client_secretto a second host. Loopback and LAN stay allowed, so Authentik onlocalhost:9000keeps working. - A free-form AI endpoint is admin-controlled only (#1772) — any account could name the address the server calls, and the guard in front of it deliberately allows loopback and LAN so a self-hosted Ollama works. Reasonable for whoever runs the instance, not for an arbitrary account that could aim a request from inside that network and read the response. The endpoint field is gone from user settings, including an admin's own, and the resolver reads the address from the instance defaults. — @mauriceboe, #1923
- The link-preview endpoint requires access to the trip in the URL — it drives the server-side fetcher, and until this release any authenticated user could reach it against any trip id. — @jubnl
- Untrusted values reaching marker HTML (#1820) — both map renderers built marker HTML as a string.
place.image_urlis settable by any member with edit rights and was only checked withstartsWith('/uploads/'), which is not the same as being safe to interpolate.category.colorsat one line above it and also reaches the public share page, so anything that is not#rgbor#rrggbbnow falls back to a default: escaping alone would have left a working CSS value behind, andurl(https://…)in a background is a tracking pixel. - Reservation day-position writes are scoped to the trip (#1951) — the per-day branch of the position update did not scope its statement, unlike the legacy branch three lines below it, so a caller could move a reservation belonging to a trip they are not on. It is scoped now, which also turns a stale id into a quiet no-op rather than a 500, and a migration clears the ordering rows whose reservation and day disagree about which trip they belong to.
- Place image and website URLs are validated (#1951) — on REST, plugin RPC and MCP alike, and values written before the check existed are sanitised on read.
- The Leaflet journey map escapes entry titles and track names (#1951) before they become tooltip markup, which its GL twin already did.
- Inherited secrets are masked (#1649) so an admin default never reaches a browser in clear text.
- Share links hardened — transactional writes, TTL renewal on update, and shared queries scoped to the link. — @jubnl
- react-router moved to 7.18.2 (#1804) — an open redirect that has no patch on the 6.x line, across 41 call sites. 7.18 rather than 7.17, because 7.0 through 7.17 carry an unauthenticated route-matching DoS of their own.
- undici moved to 7.29.0 (#1780) — five advisories on the HTTP client behind every geocoding, weather, photo-provider and extraction request. The declared range was tightened at the same time, so an install cannot silently resolve back to a vulnerable build.
- dompurify to 3.4.12, and four transitive server packages lifted in the lockfile (#1963) — six advisories the dependency gate had flagged as high.
Bug fixes
Around a hundred and fifty of them. These are the ones worth naming, grouped by where you would meet them; where somebody filed it, the issue number is on the line.
Trips, days and the planner
- Booking dates can no longer fall outside the trip (#1662) — only Accommodation was confined; the rest used an unbounded picker. — @subdee, #1678
- Blank trip page from a malformed transport time (#1620) — a broken time crashed the map's duration calculation mid-render, and the white page came back on every reload. — @subdee, #1671
- An empty reservation event no longer makes a trip permanently unopenable (#1979) — the accommodation cascade sends
reservation:createdandreservation:updatedwith no payload as a "go look" ping, and the applier pushedundefinedinto the list, where it stuck in that browser through every reload. The handler refetches the authoritative list instead, so a hotel booked by one member now appears for the others without a reload. — @mauriceboe, #1986 - Deleting an accommodation unlinks every booking pointing at it (#1869) —
reservations.accommodation_idcarries no foreign key and the service assumed at most one booking per stay, so deleting a stay unlinked the first booking and left the second pointing at a row that no longer existed. — @mauriceboe, #1923 - Selecting a day frames the route without sliding the first marker off the map (#1982) — the fit already reserved room for the day panel and then panned on top of it, so a north-south day pushed its own first stop out of view. — @mauriceboe, #1990
- The centred tab pill no longer runs under the logo or the user menu (#1983) — the pill was absolutely positioned with no relationship to what sits beside it, while its width grows with every enabled addon and page plugin. It gives way and becomes scrollable now, so every tab stays reachable. — @mauriceboe, #1991, after @xthephreakx, #1747
- Dragging a place into the day plan works on tablets (#1616) —
dragDisabledswitched off on any coarse pointer, which only ever hit tablets, and a finger cannot start a native HTML5 drag on Android at all. A long press of 320 ms arms a bridge that replays a real drag sequence, the pane scrolls when you hold a place near its edge, and the day-reorder popup opted in separately because it portals out of the sidebar. — @mauriceboe, #1744 - A place can be dragged off the map marker straight onto a day (#891) — @mauriceboe, #1891
- Closing a place in the planner no longer blanks the whole app — the file-upload callback was declared below the
if (!place) return nullbail-out, so closing the inspector, deleting the open place or switching trips changed the hook order mid-render. — @mauriceboe, #1800 - Malformed reservation metadata renders a row without its subtitle instead of crashing the day-plan sidebar. One booking with broken metadata from an importer, an MCP client or a hand edit used to take the page with it. — @mauriceboe, #1737
- A day stranded by a weekend-config change can be cleared — @subdee, #1912
- A trip with no dates shows up in the Planned tab (#1706) — it returned null rather than 'past' and fell out of every tab, appearing nowhere at all. "No trips yet" now shows only when there really are none. — @mauriceboe, #1710
- A trip the hero fell back to stays in the grid (#1710), the mobile dashboard trip sheet offers the currency (#1701).
- Trip card controls are always visible — edit, duplicate, archive and delete sat at opacity 0 until the card was hovered, over a white translucent fill, and two people in a row reported that they could not find how to rename or unarchive a trip. An archived card shows a restore icon, which mobile already did. — @mauriceboe, #1951
- Duplicating a trip remaps its cross-links and copies the budget split rows, which used to come out unlinked and unsplit. — @jubnl
- A check-in day's route no longer starts at the hotel (#1597) — @jubnl, #1607
Money
- An uneven expense split no longer corrupts the total (#1964) — 163.21 across two people is 81.61 and 81.60, both exact, but the server discarded the total the client sent and re-derived it by adding the parts as doubles, storing 163.20999999999998. Settlement moved to integer cents, unticked receipt lines are excluded, and the 0.01 epsilon is gone. A linked booking gets a cent-clean price, one stamped wrong before this heals on the next save, and both expense forms clean the value they seed rather than writing it straight back. — @mauriceboe, #1987, #1923
- Expense amounts use the trip currency's decimal separator (#1624) — @subdee, #1645
- A picked payer is saved when nobody splits the expense — @subdee, #1785, with the mobile twin in #1737
- New trips default to your own currency setting — @subdee, #1790
- An expense can be linked to a place, the way a booking already could, with the same Costs block on the place form. Deleting the place deletes the expense with it. — @mauriceboe, #1917
Import and extraction
- Anthropic imports stop reporting "no reservations found" (#1968) — the model sometimes serialises its tool input as a JSON-encoded string, which the array check discarded with nothing logged, so the same document imported fine on one attempt and not on the next. Ollama and the other OpenAI-compatible providers go through the same normaliser now. — @mauriceboe, #1988
- Imported train, bus and rental-car pick-up and drop-off points survive the save (#1969) — endpoints without coordinates are correctly dropped by the database, and the lookup meant to fill them in lived in a method no client calls. An endpoint that still cannot be located stays on the item and is named in a warning rather than vanishing, and import warnings reach you even when items were found. — @mauriceboe, #1993
- Google Maps list imports get a free reverse lookup so addresses stop arriving empty, and a share link keeps its feature id when it lives in the URL path blob. — @mauriceboe, #1965
- Booking import tolerates non-strict JSON from Gemini (#1638) — @fbnlrz, #1648
- Leg dates prefill from the endpoint's local date — @Xre0uS, #1686
- A failed booking import can be retried with AI parsing — @Xre0uS, #1687
- OpenAI imports retry with
max_completion_tokens— @subdee, #1779 - A stale base URL no longer hijacks the Anthropic endpoint — @subdee, #1777
- AirTrail 3.12.0 flights import their seat and their reason again (#1931) — 3.12.0 renamed the flight passenger list with no alias, so nothing was found, and because a pull rebuilds metadata from the AirTrail answer, seats already stored were wiped. They heal on the next sync. A seat edited in TREK reaches AirTrail again too, where it was silently dropped on the way out. — @mauriceboe, #1938
- Multi-leg AirTrail imports no longer claim they were removed (#1646) — @fbnlrz, #1650
- An
.emlfile is decoded as MIME before extraction (#1724), and the Naver list import is size-capped like the Google one. — @mauriceboe, #1741; @jubnl
Calendar feed and PDF
- Flights in the calendar feed keep the trip's timezones (#1453) — the export read
reservation_timefirst, which every transport carries, and derived a zone from a linked place a flight does not have, so departures went out as floating times. The formatter also appended seconds regardless of input, so an imported10:00:00came out too long to attach a TZID to. — @subdee, #1874; the seconds half @mauriceboe, in #1919 - Car rentals emit Pickup and Drop-off as their own timed events — @michael-bohr, #1919
- A date-only booking spans to its end date (#1869). — @mauriceboe, #1923
- The PDF export prints each day in the order the planner shows it (#1978) — every day was ordered by one global position seeded from whichever day rendered first, so on any other day of a span it pointed at the wrong slot. A booking linked to a place assignment is printed once rather than twice, and a multi-leg journey prints one row per leg at its own time. — @mauriceboe, #1992
- The page break between days is optional (#1292, #1471) — plus a stay card no longer splits across two sheets and a day header no longer strands at the foot of a page. — @mauriceboe, #1745
- A trip exported straight after opening it keeps its icons — the icon renderer loaded asynchronously and two guards returned an empty string until it was ready. — @mauriceboe, #1820
- A GIF trip cover prints — uploads accept GIF, the renderer's extension check did not. — @mauriceboe, #1867
- Calendar and cost exports download in Firefox and Safari again — every one of them revoked the blob URL in the same tick as the click, from an anchor never inserted into the document. Chrome grabbed the blob synchronously; the other two aborted. — @mauriceboe, #1737
Places and the map
- Map POI pins named in your language (#1655) — @subdee, #1672
- Opening hours are evaluated in the place's own timezone (#1680), the 24h time setting is honoured (#1725), the photo endpoint stops answering 404 for photo-less places (#1727), retired OSM tile subdomains are gone (#1733, rewritten on read and write so an instance that already stored one is fixed too), transit prefers
displayNamefor the line (#1715), and OSM place ids stop being sent to Google Places (#1727), where they could only ever return a paid400. — @mauriceboe, #1741 - A place that has no photo is not re-asked every five minutes (#1727) — a permanent absence and a failed provider call shared one short TTL. — @mauriceboe, #1741
- A place search is billed as one session — the Google autocomplete calls carried no session token, so every keystroke was its own billable request. A details lookup on an instance with no Google key answers empty instead of erroring. — @mauriceboe, #1942
- Picking a second search result no longer keeps the first place's website and phone — the form merged a pick with
result.x || prev.x, which cannot tell "the user typed this" from "the last search result wrote this". — @mauriceboe, #1867 - Places in the day plan show their photo again (#1136) — @mauriceboe, #1641
- A place tooltip stops sticking to the screen (#1404) — the whole marker layer was torn down and rebuilt on any render at all, including the one the tooltip itself caused, so the marker went away from under the cursor and the mouse never left it. — @mauriceboe, #1737
- A street-level lookup is no longer answered with a cached country — atlas asks at zoom 3 and 8, the map at 18, and a coordinate-only cache key could not tell them apart. All of it now goes through one throttled, cached client, so TREK stops ignoring the OSM rate limit (#576). — @mauriceboe, #1876
- A saved place's address can be corrected after it is added to a collection, and the add-place dialog keeps its selection, sticks its search row and says something when there are no results. — @mauriceboe, #1923
- GPX tracks get their own colour (#776) so several imported routes stay apart, with a Track colour row in the inspector to override it. Two silent losses came out with it: duplicating a trip kept its tracks (the copy went through a column list that omitted the geometry) and undoing a deleted track restores the track, not a bare point. — @mauriceboe, #1722
- A republished map style takes effect on the next load (#1924) — the service worker's caching rule matched every URL under the host, style documents included. — @mauriceboe, #1923
- The map layer switcher moved clear of the sidebar edge, and a day chip on the phone map reduces it to that day, which is the declutter the desktop sidebar has always had. — @mauriceboe, #1933, #1965
Atlas
- Airport layovers no longer mark the connecting country as visited (#1535) — the pairing is guarded so rental cars, cancellations and return flights still count theirs. — @mauriceboe, #1958
- Travel statistics count only the flights you are actually on (#1966) — on a shared trip everybody's bookings counted for everybody. A booking with nobody assigned still counts for every member, so an install that never used traveller assignment sees the same numbers as before. — @mauriceboe, #1994
- Great Britain has its counties and boroughs back — it shipped four regions where it should have had 216, so a place in London lit nothing up. A migration reconciles pre-3.1.0 Natural Earth codes by name and clears the cached rows so English places re-derive their real county. — @mauriceboe, #1995
- The region layer is bounded to what is on screen (#1950), with an evicting cache, and the Leaflet renderers are created once per map instead of leaking one per rebuild. — @mauriceboe, #1958
- Unvisited country names are translated — @subdee, #1798
- The atlas panel stops clipping its own search results — @subdee, #1913
- The bucket list refuses an entry it already holds, and the sparkline stopped painting over its own numbers (#1787). — @mauriceboe, #1923, #1884
Vacay, packing and lists
- Vacay opens on the year you are in, not on the highest one in the plan — adding 2030 to plan ahead made 2030 the year every later visit landed on. A shifted leave year is honoured, and a live update no longer pulls the grid off the year you are reading. — @mauriceboe, #2056
- Weekend blocking is enforced in the service, not only in the interface. — @jubnl
- Today's ring is drawn around the day cell instead of replacing it, the add-calendar dialog says which region is required and clears it when the country changes, and tapping a day on a phone opens the month editor rather than the dock's plus creating a trip. — @mauriceboe, #1923
- Applying a packing template on mobile lands in the list you are looking at — the call omitted the visibility argument, so items went to the common list while the toggle sat on personal, and they appeared to vanish. — @mauriceboe, #1737
- The "My" to-do filter stops listing tasks belonging to nobody — with no resolved user id the comparison matched every unassigned item, so the list openly disagreed with the count badge below it. — @mauriceboe, #1737
- Packing weights survive a copy, bags gained a limit, and the mobile row got its name back — see Packing above. — @mauriceboe, #1773, #1774, #1915
- Every single-item packing path authorizes against the item — see Security. — @mauriceboe, #1941
Journey and Studio
- Photos and shared journey assets stop 404ing on any install whose uploads path contains a dot segment — the response resolved against the rewritten request URL, and the 404 was cached for a day. — @mauriceboe, #1973
- Journey entry thumbnails render in a shared journey on mobile — a shared journey delivers photos carrying
idwhere the private one carriesphoto_id. — @mauriceboe, #1737 - Public journey photos from a non-Immich backend stopped 404ing inside the Synology id parser (#584). — @mauriceboe, #1876
- A failed photo save in the phone entry sheet no longer duplicates the entry on retry, and the phone editor offers the connected photo providers. — @mauriceboe, #1923
- An Immich URL with a leading slash resolves. — @jubnl
- Clicking inside the journey Link-Trip dialog stops closing the settings dialog behind it. — @mauriceboe, #1737
Notifications, auth and sessions
- Umlauts in ntfy notification titles (#1615) — non-ASCII arrived as
?; now RFC 2047-encoded. — @subdee, #1621 - Settings reload after a failed start (#1618) — settings loaded once at login; a failed request left the session silently running on defaults. — @subdee, #1626
- Signed-in users are redirected away from /login and /register — @subdee, #1822
- An empty user setting falls back to the admin default (#1634) — a system-wide Mapbox token only ever reached brand-new accounts. — @fbnlrz, #1649
- Sessions renew themselves past half the token lifetime, remember-me survives a password change, and SSO honours it too (#1927). — @jubnl
- OAuth sessions stop expiring daily (#1007) — rotation is a race by construction: several MCP clients share one refresh token, both post it in the same second, and the loser was read as a stolen token, which revoked the chain and popped a login window. A revoked token now only counts as a replay outside a 30-second grace window. — @mauriceboe, #1915
- Public endpoints answer signed-in users again — MFA enforcement decided on two hard-coded path lists, so every public endpoint added after those lists were written answered 403 to a signed-in user who had not passed MFA, while answering a stranger perfectly well. — @mauriceboe, #1873
- A malformed WebSocket frame can no longer take the server down (#1576) —
wssurfaces protocol violations and reserved close codes as an error event on the socket, and unhandled, Node rethrows it. — @mauriceboe, #1879 Refereris sent on the Google Maps key validation request — @beards, #1862- Itinerary edits made through an MCP assistant stop breaking every other member's open trip — the MCP WebSocket payloads had drifted from the REST shapes the client is written against, so a moved assignment vanished, a reordered day emptied itself remotely and a deleted day never disappeared. — @jubnl
Interface and the phone
- Switching language reports a failure instead of silently doing nothing — the locale import had no catch, so after a deploy that removed the chunk the interface stayed on the previous language with nothing on screen to say why. — @mauriceboe, #1805
- The map tile prefetch no longer blocks the interface after login — it dispatched a whole bounding box in one synchronous loop, up to 12,288 tiles per trip. — @mauriceboe, #1704
- A batch upload on mobile no longer stops at the first failure — one rejection aborted the loop and the toast still counted the whole batch. — @mauriceboe, #1737
- A failed day reorder on mobile rolls back rather than leaving the timeline showing a plan the server does not have. — @mauriceboe, #1737
- A booking with dates but no times saves again on mobile — both sides were padded to midnight and compared, so entering an end date made the form insist the end preceded the start. — @mauriceboe, #1737
- Link previews in collaborative notes are cached per trip, not globally by URL, so one trip's preview stops appearing in another. — @mauriceboe, #1737
- Desktop polls can be multiple-choice — the form posted a field the server does not read, so every poll created from the desktop panel was single-choice whatever the toggle said, and the badge never appeared. — @jubnl
- On phones the document scrolls again on the flow screens (#1809), so iOS Safari collapses its address bar there. The trip planner, the journal, the atlas and Vacay keep their own full-height scrollers and still will not. — @mauriceboe, #1923
- Mode-agnostic German departure translation — @bauerj, #1765
- Turkish Vacay translations rewritten where machine translation had left artefacts. — @34ezz623-create, #1940
- The shared trip's day header stops squeezing its title into a 37-pixel column (#1955) — @mauriceboe, #1965
- The empty collaboration chat state fills the panel, label chips truncate instead of overflowing, and the reservation time gets its own chip. — @mauriceboe, #1933
- The phone plan stops showing an UP NEXT that is already behind you — the card kept pointing at this morning's first stop hours after it had started, and at day one of a trip that ended last month. Seeding the day also skips to the next day still ahead when today falls into a gap in the trip's dates. — @mauriceboe, #2057, picking up @cyzbcf-beep's #2055
Operators
- Scheduled backups snapshot the database instead of archiving it live (#1675) — the nightly job kept its own drifted copy of the archive logic and never got the fix the manual export had. It calls the same code now, so a scheduled backup carries the at-rest encryption key and the plugin data and is restorable onto another install for the first time. The SQLite journal mode became configurable with it. — @mauriceboe, #1742, #1827
- The journey and places upload directories are pre-created, which fixes
EACCESon a bind-mounted uploads volume (#1762). — @jubnl - An invalid environment value aborts startup instead of being ignored, and reminder settings take effect on the next tick rather than needing a restart. — @jubnl
- The admin plugin detail view survives a registry manifest that omits permissions, egress or settings, and shows a plugin's widget slot and the planner tabs it replaces before you install it. — @mauriceboe, #1737
- MCP tool calls land in the admin audit log. — @jubnl
- The admin panel, the client and the search agree on whether a Google key exists (#1939) — three code paths decided that independently, so Google search could be advertised as available while every request failed. — @mauriceboe, #1958
- A plugin creating, editing or deleting a place reaches open sessions live (#1705) — the same missing-broadcast gap as
itinerary.unassign. — @mauriceboe, #1741 - The offline bundle's packing items are scoped to the viewer (#858) — @jubnl
- Trip days are generated in UTC, so a DST shift cannot drop or repeat one. — @michael-bohr, #1928
zh-TWresolves as a default language. — @jubnl
Contributors
Nineteen people shipped this release. Thank you.
@mauriceboe · @subdee · @fbnlrz · @jubnl · @geracobo · @Xre0uS · @RenzoBeux · @xthephreakx · @geoida · @elmocito · @beards · @bauerj · @SoonYu97 · @SimMesg20 · @NtsCiccio · @michael-bohr · @Cynosure159 · @34ezz623-create · @MikeDabrowski
And to Chichi (@vickyzer027-hash), who reported the packing-list authorization hole privately rather than publicly.
Upgrading
docker pull mauriceboe/trek:4.0.0
docker compose up -dTwenty migrations run automatically on startup, taking the schema from 175 to 195. No manual steps. Vacation entries logged before 4.0.0 stay full vacation days, and every plan resolves to the calendar year until you choose otherwise.
A few things do change on an existing install, and it is better to read them here than to file them as bugs:
- Your Atlas numbers may drop, and that is the fix. A country you had only booked a flight to was counted as visited (#1048), an airport you changed planes in counted as a country (#1535), and on a shared trip everybody's flights counted for everybody (#1966). All three are corrected. Countries you have only planned are no longer drawn on the map until you turn them on, through a switch above the globe. And Great Britain goes from four regions to 216, so a place in London finally lights something up: a migration reconciles the old region codes and clears the cached rows so English places re-derive their county.
- Four migrations touch data you already have. One clears the day-ordering rows whose reservation and day disagree about which trip they belong to — the bookings themselves are not touched (#1951). One promotes an existing Google Places or Unsplash key from the admin who pasted it into the instance row (#1958). One moves every itemized receipt out of the expense note column into its own, which is why an expense note beginning with
TICKETJSON:disappears: it was never a note (#1658). One is the GB reconciliation above. - A personal AI endpoint moves to the admin. The free-form endpoint field is gone from user settings; a self-hosted Ollama is configured once, by whoever runs the instance (#1772).
- The startup is stricter. An invalid environment value now aborts the boot instead of being ignored: a malformed
BACKUP_UPLOAD_LIMIT_MBorMCP_RATE_LIMITstops the server with a named error rather than quietly falling back to a default. - Minting a share link or a calendar feed now needs
share_manage. Trip membership was enough before, so a member who used to hand out a feed URL will get a 403 until you give them the permission (#1883). - The Journey PDF is a desktop export now. The book comes out of Studio, and Studio needs room to work, so the export button is gone on a phone. Everything else about a journey is unchanged there (#1973).
- If you drive the API yourself: on the trip-scoped routes a
404for the trip and a403for the permission are now decided before the body is validated, so a malformed body from a caller without access answers 404 or 403 rather than 400. And a packing item you cannot see answers 404 everywhere now: where the sharing route used to answer 403, and where update, delete, clone and the contributor routes used to answer 200 with the item attached.
Worth a look after upgrading:
- Open TREK on your phone. It is a different app now.
- Settings → Appearance → Mobile to arrange your bottom bar and dashboard.
- Settings → General → Startup if you would rather land on the trip you are actually on.
- Vacay settings if your leave year is not January to December, or if you want school holidays on the grid.
- Add a place and watch the details column fill itself in.
- Export a trip as GPX and take it on the handheld.
- Admin → Storage if you would rather your uploads and backups lived on S3, or were mirrored there.

