Commit Graph

1394 Commits

Author SHA1 Message Date
Maurice e6fe14cac2 fix(pwa): use the self-contained app icon for the favicon so it shows on dark tabs (#1328)
icon-dark.svg is a black logo on a transparent background and is invisible on a dark browser tab strip (e.g. Edge dark mode). Point the favicon at icon.svg, which carries its own dark gradient background and reads on both light and dark chrome; icon-dark.svg keeps its in-app light-mode use.
2026-06-27 20:14:52 +02:00
Maurice 2a8caf6e7d fix(share): translate the day label on the public share page (#1296)
Untitled days on the public share page rendered as a hardcoded English 'Day N' instead of the dayplan.dayN key used everywhere else, so they stayed English regardless of the viewer's language. The key is already translated in every locale.
2026-06-27 20:14:52 +02:00
Maurice 005e0c109d fix(maps): make Overpass endpoints configurable and harden the POI search (#1309)
Builds on @Hardik-369's instance-specific User-Agent idea and reworks the rest
of the #1309 fix:

- keep the unique User-Agent (buildUserAgent) — a shared UA gets the public
  Overpass mirrors to rate-limit harder; it appends the configured instance
  URL and is applied to every Nominatim/Overpass/Wikimedia call
- add OVERPASS_URL so an operator behind locked-down egress (e.g. a Kubernetes
  cluster) can point the explore search at an internal/self-hosted Overpass
  instance instead of the public mirrors
- keep the per-endpoint timeout default at 12s but make it tunable via
  OVERPASS_TIMEOUT_MS for slow self-hosted instances; non-positive/invalid
  values fall back to the default rather than 502-ing every search at a 0ms cap
- log each endpoint's failure reason before the 502 so blocked egress is
  diagnosable instead of a bare "Overpass request failed"

Adds unit tests for the User-Agent, endpoint and timeout resolution plus the
all-mirrors-down path, and documents the two new env vars in .env.example, the
wiki and the Helm chart.
2026-06-27 20:14:52 +02:00
Hardik-369 e54ea2f17d fix: memoize User-Agent and prevent localhost leak; bump timeout to 30s
- Memoize USER_AGENT via IIFE so it's computed once, not per-request
- Only append instance URL when APP_URL or ALLOWED_ORIGINS is explicitly
  configured; skip the getAppUrl() localhost fallback
- Bump OVERPASS_TIMEOUT_MS to 30000 (above the observed 25.7s TTFB)
2026-06-27 20:14:52 +02:00
Hardik-369 544a76d2da fix(maps): increase Overpass timeout and add instance-specific User-Agent
The POI search endpoint (/api/maps/pois) returned 502 errors because:

1. OVERPASS_TIMEOUT_MS (12s) was shorter than mirror response times
   (kumi.systems takes ~25.7s to first byte). Increased to 25s to match
   the [timeout:20] query timeout.

2. The static User-Agent string was indistinguishable between instances,
   making rate-limiting and throttling more likely. The new userAgent()
   function appends the instance's APP_URL so each deployment identifies
   itself uniquely, following Overpass API best practices.
2026-06-27 20:14:52 +02:00
Maurice a5ba246cb8 test(i18n): account for the Swedish locale in SUPPORTED_LANGUAGES
The Swedish translation added 'sv' as the 21st language but left the
FE-COMP-I18N-009 length assertion at 20, so the full client suite went
red on this branch. Bump the count to 21 and add an 'sv' sample.
2026-06-27 20:14:52 +02:00
Maurice 0b2780ead2 test: make the Google Maps ftid path honest + cover the URL helper
The Places API googleMapsUri is a cid-style URL with no ftid, so the
search/getPlaceDetails fixtures had stored a fabricated ftid. Switch them
to real cid URLs and assert google_ftid is null — the precise
query_place_id link still fixes the wrong-spot bug — and document the
behaviour on googleFtidFromMapsUrl.

- add a direct googleFtidFromMapsUrl test: extracts a real /place ftid,
  returns null for a cid URL, rejects malformed/hostile values
- add placeGoogleMaps.test.ts covering the whole fallback chain
  (ftid -> place_id -> details URL -> coords) and the hostile-ftid rejection
- PlaceInspector: use a freshly-fetched ftid when the place hasn't stored one
2026-06-27 20:14:52 +02:00
Azalea 91fcaa50f6 Use Google Maps feature IDs for place map links 2026-06-27 20:14:52 +02:00
Azalea 9669642c62 feat(maps): add MapLibre OpenFreeMap support (#1317)
Adds MapLibre GL with OpenFreeMap as a tokenless third map provider
alongside Leaflet and Mapbox: a provider abstraction with style presets,
CSP + service-worker entries for tiles.openfreemap.org, and the
map_provider allow-list entry. Mapbox-only APIs stay gated behind the
mapbox provider, and existing Mapbox/Leaflet users are unaffected.

Maintainer review follow-ups folded in: the new map-settings strings are
translated across all locales; the GL engine is lazy-loaded so
Leaflet-only installs don't download it; MapLibre gets its own
maplibre_style slot so switching providers no longer overwrites a custom
Mapbox style; and the MapLibre render path plus the OpenFreeMap
style-guards are covered by tests.
2026-06-27 20:14:52 +02:00
Maurice 7531badbe8 i18n(sv): add the settings.distance key (Avståndsenhet)
Keeps the Swedish locale in parity with the rest after the distance-unit
setting (#1300) landed on the release branch.
2026-06-27 20:14:52 +02:00
Andreas Olsson 424018fc66 feat: swedis translation 2026-06-27 20:14:52 +02:00
Maurice 9d8af4b357 i18n(nl): fix doubled "In" typo in packing.importTitle
"InInpaklijst importeren" → "Inpaklijst importeren".
2026-06-27 20:14:52 +02:00
eindpunt 5b3f77f11d Added new Dutch translations and some corrections 2026-06-27 20:14:52 +02:00
Azalea e04cf85bef feat(planner): seek places sidebar on map selection 2026-06-27 20:14:52 +02:00
Maurice 3d65bb0c12 fix: address review feedback on the distance unit setting
- server: allow distance_unit as an admin default (+ value validation) so the
  Admin "Default User Settings" toggle persists instead of returning 400
- i18n: add settings.distance to all 20 locales and translate the labels
  through t() instead of hardcoding "Distance"
- route legs: include the unit in the OSRM cache key and recompute on a unit
  switch, so map and sidebar distances refresh and never mix units
- keep wind speed tied to the temperature unit — a distance setting must not
  silently flip existing Fahrenheit users from mph to km/h
- restore the sub-1km metres reading for metric, convert GPX elevation to feet
  for imperial, and format distances with a '.' decimal in every locale
- add units.test.ts
2026-06-27 20:14:52 +02:00
Matt Van Horn 94dca8cad7 feat: add distance unit (metric/imperial) display setting
Mirrors the existing temperature_unit pattern. Adds distance_unit to Settings,
a Display settings control, admin default, and a formatDistance helper applied
at distance render sites. Backward compatible (default metric). Closes #1300.
2026-06-27 20:14:52 +02:00
Maurice b1145e7e0a fix(map): drop the hotel leg to/from a transport endpoint on arrival days (#1321)
On the first day of a trip the morning hotel is only a check-in fallback
— you arrive from home, you didn't sleep there — so bookending the route
from that hotel to the flight/train departure point drew a phantom
hotel → departure leg, both on the map and in the day sidebar. The same
backwards leg showed up on a multi-day transport's arrival day, and its
mirror departure → hotel on an evening departure.

getDayBookendHotels now also reports whether the morning hotel is one you
actually slept in and whether you sleep in the evening hotel tonight. The
map and sidebar only draw a hotel↔transport bookend when that holds; a
hotel↔place leg is always kept, so the home-base loop and onward-travel
legs are unaffected. The optimizer keeps using the hotel values as before.
2026-06-27 20:14:52 +02:00
Sheroy Cooper 382ec37142 docs: dedupe development environment guide (#1320) 2026-06-26 16:10:46 +02:00
jubnl 92e3ebb4d5 chore(wiki): ensure correctness for kitinerary installation 2026-06-25 08:41:44 +02:00
jubnl 49fb2fded2 chore(wiki): make sure that all environement variables are properly documented 2026-06-24 14:03:39 +02:00
github-actions[bot] 4cd4c9c8d8 chore: bump version to 3.1.2 [skip ci] v3.1.2 2026-06-23 19:24:13 +00:00
jubnl 6cc8908f87 fix(tests): memory leak 2026-06-23 21:23:39 +02:00
Maurice 68f48bc070 ci: give client test workers 8 GB heap (no coverage) to fix worker OOM (#1258) 2026-06-23 21:23:39 +02:00
Maurice 76d8abb44d ci: run client tests without coverage to avoid the v8 report OOM (#1258) 2026-06-23 21:23:39 +02:00
Maurice 91c350c946 ci: raise client coverage heap to 12 GB for the v8 report phase (#1258) 2026-06-23 21:23:39 +02:00
Maurice 1e4a9a95c2 ci: raise Node heap for the client coverage run to fix OOM (#1258) 2026-06-23 21:23:39 +02:00
Maurice fe54f45d62 fix(map): draw the route line to and from the day's accommodation (#1275)
The map route ran first-activity to last-activity only, while the sidebar
already showed the hotel-to-first-stop and last-stop-to-hotel legs with
their drive times. Feed the day's accommodation bookends into the map
route too, reusing the same getDayBookendHotels lookup and the
"optimize from accommodation" gate, so the drawn line starts and ends at
the hotel, including single-activity and transfer days.
2026-06-23 21:23:39 +02:00
Maurice b36c9931b3 fix(costs): allow recording an expense with no split or payer (#1286)
Adding an expense required at least one participant, so a cost you only
want to record — e.g. a booking paid on-site later — could not be saved
without splitting it. Drop the participant requirement: with nobody
selected the expense saves as a recorded total, counted in the trip
total and shown as Unfinished, and kept out of settlements until
who-paid is filled in. The shared schema and server already supported
this case.
2026-06-23 21:23:39 +02:00
Maurice c1fe1d2d6a fix(packing): keep a custom category when its last item is removed (#1289)
Removing the only item of a user-created category deleted the whole
category. Turn that row back into the existing ... placeholder in
place instead, so the category keeps its position and colour; adding an
item reuses the placeholder slot. Deleting the placeholder (or the
category menu) still removes an empty category.
2026-06-23 21:23:39 +02:00
Maurice ebbbf91d60 fix(dashboard): show an error instead of a blank trip list when the server is unreachable (#1283)
When the backend or identity provider was unreachable, a returning user with a
persisted session landed on the dashboard with an empty trip grid and no error.
That looks identical to a logged-in user who simply has no trips, so people
assumed their data had been lost.

Three client-side layers were quietly swallowing the failure: the auth check
only cleared state on a 401, so a 5xx or a network error left the stale session
in place and kept rendering the protected route; the offline-first trip repo
turned a failed fetch into the empty cache without throwing; and the dashboard
had neither an error nor an empty state, so a blank grid meant both "outage" and
"no trips".

The auth check now tells genuine offline (keep serving the cache silently, the
PWA happy path) apart from a server outage while online (keep the session but
flag it). The dashboard shows a reassuring "couldn't reach the server, your
trips are safe" banner with a retry, and a real zero-trip account finally gets a
proper empty state so the two cases never look alike. New strings added across
all locales.
2026-06-23 21:23:39 +02:00
Maurice 328d1c9468 fix(auth): keep the last admin when OIDC claims would demote it (#1274)
On OIDC-only instances the bootstrap admin (first SSO user) rarely carries the configured admin claim, so a forced re-login — e.g. after a JWT-secret rotation — re-derived its role purely from claims and demoted it to user, locking the instance out with no recovery. The OIDC login role sync now skips a downgrade that would strip the last remaining admin, and the admin user-update endpoint guards the same case.
2026-06-23 21:23:39 +02:00
Maurice 48ebdff2d5 feat(planner): bring back the Google Maps route export button (#1255)
The day-plan route bar lost its Open in Google Maps action in the 3.1.0 redesign. A small button with the Google logo (monochrome, theme-aware) now sits next to the Route toggle and opens the day stops, in planned order, as a Google Maps directions link in a new tab.
2026-06-23 21:23:39 +02:00
Maurice 457a42b229 fix(admin): show non-Docker update steps when not running in Docker (#1269)
The "How to Update" modal always rendered Docker commands and claimed the instance runs in Docker, even on bare-metal / LXC installs like Proxmox Community Scripts. It now branches on the is_docker flag the backend already returns: non-Docker installs get a generic "re-run your install method" note plus a link to the update guide. Docker stays the default when the flag is absent, so existing installs are unaffected.
2026-06-23 21:23:39 +02:00
Alejandro Pinar Ruiz 7df5956920 feat(helm): add annotations support for PVCs (#1270)
Co-authored-by: Maurice <mauriceboe@icloud.com>
2026-06-23 21:23:39 +02:00
Maurice 0d50d5d7c3 fix(atlas): give the country-GeoJSON fetch a longer timeout (#1254)
The gzipped admin0 GeoJSON is still a few MB, so behind a slow reverse proxy or Cloudflare Tunnel it could exceed the global 8s axios timeout and abort, leaving the map with no countries. It now gets a 30s per-request timeout, matching the existing /maps/pois exception.
2026-06-23 21:23:39 +02:00
Maurice 4a3aa478c6 fix(dashboard): add a text-shadow so spotlight and card titles stay legible (#1267)
When no trip is ongoing the spotlight falls back to a trip gradient, and several of those are light enough that the white title vanished in light mode. A subtle text-shadow on the hero title and trip-card names keeps them readable without affecting dark covers or dark mode.
2026-06-23 21:23:39 +02:00
Maurice abee2fc088 fix(costs): move the unfinished marker to the category icon on mobile (#1266)
A long expense title pushed the "Unfinished" pill into the price on narrow screens. On mobile the status now shows as a small marker on the category icon, freeing the title and price row; desktop keeps the labelled pill.
2026-06-23 21:23:39 +02:00
Maurice e40465ba1f test(days): bump over-long-time assertion to the new 250 limit (#1252)
Follow-up to raising the day-note time cap to 250: the unit test still sent 151 chars expecting a 400, which now passes validation and fell through to an unmocked service call.
2026-06-23 21:23:39 +02:00
Maurice 8dab26fe7b fix(chart): allow setting storageClassName on PVCs (#1261)
The PVC templates rendered no storageClassName and values exposed no key, so clusters without a default StorageClass (or needing a specific class) couldn't install. Add persistence.{data,uploads}.storageClassName, omitted when empty so the default class is still used.
2026-06-23 21:23:39 +02:00
Maurice 7459067b2e fix(pdf): show photos for OSM places in the trip PDF (#1130)
The PDF photo pre-fetch only fired for places with a google_place_id, so OSM/Nominatim places (osm_id only) fell back to category icons even though they show photos in-app. Recover osm_id from the full places pool (the assignment projection drops it) and key the photo off google_place_id || osm_id || coords, matching the UI.
2026-06-23 21:23:39 +02:00
Maurice a2c552f04d fix(days): align note time limit to 250 and keep toasts above modal blur (#1252)
The day-note 'time' field capped at 150 server-side while the dialog and shared schema allow 250, so 151-250 char notes 400'd with a confusing 'time must be 150...' message. Raise the controller and MCP limits to 250. Also lift the toast container above modal overlays so the error toast isn't rendered behind the modal's backdrop blur.
2026-06-23 21:23:39 +02:00
Maurice 27762458e6 fix(dashboard): count archived trips in travel stats (#1264)
The trips/days widgets filtered out archived trips while places, countries and flight distance did not, so archiving a trip zeroed only those two. Drop the is_archived filter so all stats stay consistent.
2026-06-23 21:23:39 +02:00
Maurice adbe15abc4 fix(security): allow same-origin PDF previews under CSP (#1253)
Firefox/Chrome enforce object-src, so object-src 'none' blocked the inline <object> PDF preview (worked only in Safari). Relax to 'self' for same-origin file previews.
2026-06-23 21:23:39 +02:00
Maurice 982b99f0f6 chore: add ca_profile.xml for Unraid Community Apps submission 2026-06-23 21:23:39 +02:00
Neil Soult 6a797a39ae fix(atlas): gzip-compress responses so large country GeoJSON loads behind reverse proxies (#1262)
The admin-0 country GeoJSON served at /api/addons/atlas/countries/geo is
~30 MB uncompressed. With no compression in the request pipeline the
transfer aborts (~8s, net::ERR_FAILED despite a 200) behind reverse
proxies / Cloudflare Tunnel, so the Atlas map never colours visited
countries. LAN is unaffected.

Add the `compression` middleware to the shared applyGlobalMiddleware
pipeline (gzip brings ~30 MB down to ~4 MB). text/event-stream is
excluded so the /mcp StreamableHTTP (SSE) transport is not buffered.

Adds BOOT-008 asserting content-encoding: gzip on the geo endpoint.

Fixes #1254

Co-authored-by: pai <pai@stabpablo.eu>
2026-06-23 21:23:39 +02:00
jubnl d2cd317070 chore: dockerignore spec.ts files 2026-06-23 21:23:39 +02:00
jubnl 6ab6d79494 fix(budget): scale category bars relative to top category 2026-06-23 21:23:39 +02:00
jubnl d35972db39 fix(budget): accept comma decimal separator in expense amounts
The expense Total Amount, per-person "Who paid", and settlement amount
inputs used type="number" with a bare parseFloat. On desktop the number
input normalized comma→dot for free, but mobile keyboards drop the comma
before onChange fires, so parseFloat("39,99") silently became 39.

Switch the three inputs to type="text" inputMode="decimal" and normalize
comma→dot in their onChange handlers, matching the pattern already used
by the other budget inputs (BudgetPanelInlineEditCell, BudgetPanelAddItemRow,
DashboardPage). Both comma and dot now work on every device.

Closes #1256
2026-06-23 21:23:39 +02:00
github-actions[bot] 438d4fc400 chore: bump version to 3.1.1 [skip ci] v3.1.1 2026-06-18 18:14:04 +00:00
jubnl d152f9d02b v3.1.1 bug fixes (#1228)
* fix(shared-view): render each leg of multi-leg flights correctly

The read-only shared view showed the overall trip start/end airports and
the first leg's flight number on every leg of a multi-leg flight. The Day
Plan already expands legs (each carries __leg), but the renderer ignored it
and read flat top-level metadata; the Bookings tab had the same bug.

- Day Plan: use __leg for per-leg airline/flight number/route, plus dep-arr time
- Bookings tab: list each leg via getFlightLegs()
- unique React keys for multi-leg rows

Closes #1219

* feat(pdf): add legs to pdf export

* fix(demo): skip first-run admin seed in demo mode

When DEMO_MODE is on, the demo seeder creates its own admin (admin@trek.app,
username "admin") right after the generic seeds run. The first-run admin
bootstrap was grabbing username "admin" first, so the demo seeder hit the
UNIQUE(username) constraint and aborted before the demo user was ever created
- which surfaced as a 500 "Demo user not found" on demo-login. Skip the
generic admin bootstrap when demo mode owns the admin account.

* fix(docker): ship the encryption-key migration script in the image

The production image only copied server/dist, so the documented rotation
command `node --import tsx scripts/migrate-encryption.ts` failed inside the
container with a module-not-found error - the raw .ts was never present. The
script runs via tsx straight from source and only pulls node builtins plus
better-sqlite3 (both prod deps), so copying the single file into
/app/server/scripts is enough to make the rotation work again.

* fix(vacay): keep the mode toolbar above the mobile bottom nav

The floating Vacation/Company toolbar was pinned at bottom-3 with z-30, so on
mobile it landed in the same band as the fixed bottom nav (z-60) and got hidden
behind it - and could scroll out of reach entirely. Pin it above the nav with
the shared --bottom-nav-h variable (0px on desktop, so nothing changes there)
and reserve matching space below the calendar grid so it never gets swallowed.

* fix(dashboard): show the correct reservation date regardless of timezone

The upcoming-reservations widget built the date with new Date(reservation_time)
.toISOString(), which reinterprets the stored naive local time as UTC and can
roll the displayed day forward in non-UTC timezones (e.g. a 23:30 reservation
showing the next day). Read the date and time straight from the stored string
parts via splitReservationDateTime, and format the time with the shared
formatTime helper so it also honours the user's 12h/24h preference.

* fix(atlas): cursor-following tooltips and removing countries from search

Two related Atlas fixes:

- Country tooltips were bound with sticky:false, which anchors them at the
  feature's bounds centre. For countries with overseas territories (e.g.
  France) that centre sits far out in the ocean, so the tooltip popped up
  nowhere near the area being hovered. Make them sticky so they track the
  cursor.

- Selecting an already-visited country from the search bar always opened the
  "Mark / Bucket" dialog, with no way to remove it. Tiny countries like
  Vatican City or Singapore are hard to hit on the map, so search was the only
  way in. Mirror the map-click behaviour: a manually-marked country opens the
  Remove confirmation, a trip/place-backed one opens its detail.

* fix(oidc): keep dots in generated usernames

The OIDC username sanitizer stripped dots because they were missing from the
allowed character class, so a name claim like "first.last" became "firstlast".
Dots are valid usernames (the profile validator already allows
^[a-zA-Z0-9_.-]+$), so add the dot to the sanitizer.

* fix(collab): show poll option labels in the UI

The poll API formatted each option as { label, voters }, but the React poll
component renders opt.text - so every option button came out blank. Emit text
alongside label (kept for any other consumer) so options render again.

* feat(backup): make the upload size limit configurable

The restore upload was capped at a hard-coded 500 MB, so instances whose
backup archive (uploads/ included) grew past that got a 413 "File too large"
with no way to raise it. Add a BACKUP_UPLOAD_LIMIT_MB env var (default 500,
invalid values warn and fall back), documented in .env.example.

* feat(costs): create an expense from a booking, fix editing total-only items

Replace the inline price + budget-category fields in the Transport and
Reservation booking modals with a "Create expense" flow: the modal saves the
booking, then opens the full Costs editor prefilled (name + category mapped from
the booking type) and linked to the reservation. A booking with a linked expense
shows it inline with edit / remove.

Also fix the Costs editor so an expense with a recorded total but no payers
(transport-derived or pre-rework items) opens with its amount, lets you set the
currency, and saves - it previously showed 0 everywhere and could not be saved.
Legacy / localized categories now map to the fixed keys, and changing a booking's
type keeps its linked expense category in sync (unless it was manually set).

- shared: reservation_id on budget create, typeToCostCategory helper, i18n keys
- server: createBudgetItem stores reservation_id; keep total_price for payerless
  items; a booking update no longer wipes its linked expense and syncs the
  category on type change
- client: shared BookingCostsSection, exported ExpenseModal with prefill and an
  editable total, page-level save-then-open wiring

* test(reservations): align syncBudgetOnUpdate unit tests with no-wipe + type-sync

The service now leaves a linked expense alone when no budget entry is on the
payload (only an explicit total_price 0 deletes it) and syncs the category on a
booking type change. Update the unit tests accordingly - the old "price cleared"
case passed entry: undefined, which is now a no-op and left a mocked return
queued that leaked into the next test.

* fix(planner): keep a reservation on its day when edited (#1237)

Editing a booking forced its day_id to the globally selected day, which is null
when editing from the Book tab - so the booking lost its day and vanished from
the Plan. Preserve the reservation own day_id on edit instead.

* fix(planner): derive a booking day from its date when none is set (#1237)

The client always sends day_id on a reservation update, so the server only
derived it from reservation_time when the field was absent. A non-transport
booking saved without a selected day (Book tab) therefore got day_id null and
vanished from the Plan, even though its date matched a day. Derive the day from
reservation_time whenever day_id is null, mirroring create.

* fix(planner): let a booking's day follow its date when edited (#1237)

Preserving the old day_id on edit left a re-dated booking on its previous start
day while end_day_id followed the new date, so it spanned both. Stop sending
day_id from the edit modal entirely - the server derives both ends from the
booking's date (and keeps the current day when there is no date), so a re-dated
booking moves cleanly to the matching day.

* fix(atlas): keep the continent breakdown in sync on mark/unmark (#1225)

The optimistic mark/unmark updates bumped the country total but never the
per-continent counts, so the continent column froze until a full reload. Move
the country to continent map into @trek/shared (single source for server and
client) and adjust the matching continent count at every optimistic site: the
country confirm flow plus the choose / region mark and region unmark handlers.

* feat(admin): let admins set a default currency for new users

Adds a currency picker to Admin > User Defaults. Stored as the default_currency
user-default, so users who have not picked their own currency inherit it in
Costs.

* fix(atlas): give every sub-national region a distinct code (#1217)

geoBoundaries fills shapeISO with the bare country code for some countries (every
Spanish region got "ESP", every Chinese "CHN", also Chile/Oman), so marking one
region lit up the whole country. build-atlas-geo.mjs now keeps shapeISO only when
it is a real "XX-..." subdivision code and otherwise synthesizes a unique
per-country id from the region name. Regenerated admin1.geojson.gz: Spain/China/
Chile/Oman now carry distinct region codes (countries with real codes, e.g.
Germany, are unchanged).

* fix(dashboard): never crash on a malformed reservation date

A reservation with an invalid date blanked the whole My Trips page: the old
Upcoming widget did new Date(value).toISOString(), which throws "Invalid time
value" (fixed in #1222 by reading the string parts). Also guard splitDate so a
bad date renders a dash instead of "Invalid Date" or throwing.

* fix(airtrail): gate airtrail update behind a user setting, on airtrail update: rebuild payload from fresh data to prevent any data loss

* fix(airtrail): add back missing tests

* fix(costs): rework the cost panel UX wise and apply prettier on the shared package

* chore(prettier) prettier this file

* fix(airtrail): don't use cabin class as seat on import

When an AirTrail flight has a cabin class but no seat number, the mapper
fell back to the class for metadata.seat, so reservations showed e.g.
"economy" as the seat. Use only the seat number; leave the seat blank
otherwise. The class is still surfaced separately in the import picker.

Closes #1246

* fix(airtrail): import scheduled flight times instead of actual

AirTrail exposes both scheduled (departureScheduled/arrivalScheduled) and
actual (departure/arrival) times. TREK read the actual times, so a delayed or
early flight imported the wrong time for planning.

Read the scheduled times on import and on poll-sync (both go through
mapFlightToReservation); when a flight has no scheduled time, leave the clock
blank (date preserved) rather than fabricating 00:00 or falling back to actual.
The change-detection hash now tracks the scheduled values, so existing linked
reservations re-sync once on the next poll. The opt-in writeback mirrors the
read, pushing TREK edits to the scheduled fields so they round-trip.

* fix(planner): hydrate per-assignment times when editing a place from the pool

Times live per day-assignment, not on the pool place, so reopening a
place from the Places panel / inspector showed empty Start/End fields
(#1247). The editor now resolves a place's lone assignment when no day
is in context and hydrates the fields from it; ambiguous (0 or 2+ days)
edits hide the fields instead of showing non-persisting inputs.

* fix(mcp): make write tools return client-valid, hydrated entities

Audit of all write tools under server/src/mcp/tools (issue #1244 anchor).

S1 (broken):
- create_budget_item / create_budget_item_with_members now default the
  split to all trip members when member_ids omitted, so the entry passes
  the client save-gate instead of being member-less (#1244).
- create_transport / update_transport backfill lat/lng/timezone for
  code-only flight endpoints (NOT NULL columns) and return a clean error
  for unresolvable endpoints instead of crashing.

S2 (under-hydration): set_budget_item_members, create_journey,
create_journey_entry, create_packing_bag, bulk_import_packing and
update_vacay_plan now return the hydrated shape the matching read/REST
route returns; bulk_import widened to accept bag/weight_grams/checked.

S3 (parity): check_in_end added to accommodation tools; atlas
mark_region_visited echoes the client shape; update_journey_entry/
update_journey_preferences, set_bag_members, set_packing_category_assignees,
apply_packing_template return hydrated payloads; set_vacay_color echoes
the color.

Auth: save_packing_template now requires admin, matching the REST gate.

Also refactors server/src/config.ts (JWT-secret handling).

Adds getBudgetItem hydrated getter, exports EndpointInput, and MCP
regression tests (incl. new tools-transports and tools-journey suites).

* fix(mcp): fix ICS/maps/accommodation bugs, add settlement & template tools

Bugs:
- export_trip_ics: include flights that store times per-endpoint
  (local_date/local_time) instead of a top-level reservation_time
- resolve_maps_url: follow redirects for cid=/share links and fall back
  to parsing the page body, all SSRF-guarded
- link_hotel_accommodation: normalize accommodation_id (TEXT column) to an
  integer in the reservation read paths so it no longer returns "14.0"

Gaps:
- packing: save_packing_template returns the new template id; add
  list_packing_templates (read) and delete_packing_template (admin)
- budget: update_budget_item accepts payers/member_ids; clarify create/
  update/members descriptions to ask which members share the expense and
  who paid
- budget: add settlement tools — get_settlement_summary, list_settlements,
  create/update/delete_settlement (budget_edit, mirrors REST + WS events)

* chore: bump nodemailer

* chore: bump multer

---------

Co-authored-by: Maurice <mauriceboe@icloud.com>
2026-06-18 20:13:30 +02:00