* Start the Journey date picker week on Monday (#1078)
The Journey entry date picker started the week on Sunday (firstDow = getDay(), headers Su-first) while every other picker (CustomDateTimePicker, VacayCalendar) starts on Monday. Align it: Monday-first leading offset ((getDay()+6)%7) and Mo-first weekday headers.
* Fix Taiwan resolving to CN-TW in the Atlas country search (#1049)
natural-earth gives Taiwan ISO_A2='CN-TW' (a subdivision-style value) with ADM0_A3='TWN'. The dynamic A2_TO_A3 augmentation added 'CN-TW'->'TWN', which then overwrote the legitimate TWN->TW entry in the reverse map, so Taiwan's country option resolved to 'CN-TW' — unresolvable by Intl.DisplayNames (no name, broken flag, not searchable). Only augment A2_TO_A3 with real 2-letter codes.
* Drop empty leftover dateless days when a trip gets a shorter dated range (#1083)
generateDays kept all unused dateless placeholder days after switching to an explicit (shorter) date range, so day_count (COUNT(*) FROM days) stayed inflated. Delete the empty leftovers (no assignments/notes/accommodations) like the dateless path already does, while preserving any that still hold content. Adds TRIP-SVC-017.
* Render GPX and route overlays once the Mapbox style has loaded (#1036)
The GPX and route geojson effects ran before the map 'load' event had
attached their sources, so on the first paint they hit the early return
and never re-ran. Add mapReady to their dependencies so they fire again
the moment the sources exist.
* Convert HEIC trip and journey covers to JPEG before upload (#1085)
HEIC/HEIF covers coming straight off an iPhone could not be rendered in
the preview or stored as a usable image. Route both cover pickers through
normalizeImageFile, the same conversion the journal entry editor already
uses, so the file becomes a JPEG before it leaves the browser.
* Name GPX routes and tracks after their source file so multiple imports stick (#1054)
Unnamed routes and tracks all fell back to the same generic 'GPX Route' /
'GPX Track' label, so the name-based import dedup dropped every one after
the first - importing several files (or one file with several tracks) only
kept a single place. Derive the default name from the source filename with
an index suffix when a file holds more than one geometry, thread the
filename down through the controller, and let the import modal take more
than one file at a time. Adds PLACE-SVC-037/038.
* Namespace the modal backdrop class so content blockers stop hiding it (#1027)
Generic class names like .modal-backdrop sit on the cosmetic filter lists
that content blockers (1Blocker, EasyList Annoyances) ship, and get hidden
with display:none. The shared Modal - used by New Trip and Add Place -
carried that class, so Safari users running such a blocker saw the modal
silently fail to open with no error and no network request. Rename it to
.trek-modal-backdrop.
* Highlight GB regions by resolving England/Scotland/Wales/NI to finer admin-1 codes (#1067)
A zoom-8 reverse geocode of a UK place only resolves to the constituent
country (GB-ENG/SCT/WLS/NIR), but Natural Earth's admin-1 polygons for GB
are counties and boroughs (GB-LND, GB-MAN, GB-CON, ...). Those four codes
match no polygon, so places in England never highlighted in the Atlas
while CH/IT/NL/etc. worked. When a GB lookup lands on a constituent
country, re-resolve it at a finer zoom where Nominatim exposes the
county/borough code the polygons actually carry. Other countries keep the
exact zoom-8 behaviour. Adds ATLAS-UNIT-021.
* Surface the real place-search error instead of a generic toast (#1092)
When a place search or detail lookup fails, the backend already forwards the
upstream reason - including descriptive Google Places API messages such as
'Places API (New) has not been used in project ... or it is disabled'. The
planner discarded it and always showed 'Place search failed', so a key that
is mis-enabled, unbilled, or pointed at the legacy API instead of Places API
(New) looked like an unexplained silent failure. Show the server-provided
message when present, and stop the Atlas bucket-list search from swallowing
its error without a trace.
* Await the async cover normalization in the TripFormModal paste test (#1085)
handleCoverSelect now normalizes the pasted file before previewing it, so
URL.createObjectURL is called a microtask later. The assertion moves into
waitFor; a non-HEIC file still passes through unchanged.
* feat(planner): real road routes (OSRM) with travel-time connectors
Replace the straight-line "as the crow flies" route with real OSRM road
geometry (FOSSGIS routed-car/-foot) and an Apple-Maps style render
(blue casing under a lighter core) on both the Leaflet and Mapbox GL
maps. Routes are off by default and toggled per session, with a
driving/walking mode switch in the day footer.
Each day shows per-segment travel time/distance connectors between
places, computed from the OSRM legs and split at transport bookings.
Also redesigns the day header for visual consistency: vertical
number+weather capsule, name with a divider before the date, subtle
hotel/rental pills that stay on one line, and a hover-revealed 2x2
action square (edit / add transport / add note / collapse). Drops the
Google Maps button.
* test(planner): update route hook tests for calculateRouteWithLegs
* fix: hotel day-range clamping in ReservationModal + stale assignment_id on accommodation clear (issues #929, #934)
* ReservationModal hotel start/end pickers now use findIndex-based
positional clamping instead of raw ID arithmetic, matching the fix
applied to DayDetailPanel in 8e05ba7. Prevents inverted
start_day_id/end_day_id on trips with non-monotonic day IDs.
* Clearing accommodation_id on a hotel reservation now forces
assignment_id to null in the save payload, removing the stale
day-assignment link that had no UI path to clear.
* Migration: swaps inverted start_day_id/end_day_id pairs in
day_accommodations where start.day_number > end.day_number,
recovering existing corrupt rows from the pre-fix picker bug.
* Tests FE-PLANNER-RESMODAL-050/051/052 cover both fixes.
* fix: preserve line breaks and wrap long URLs in notes fields (#930)
Add remark-breaks to all reservation/place notes markdown renderers so
single newlines render as <br>, and add wordBreak/overflowWrap styles
so long unbroken URLs (e.g. booking.com tracking links) wrap correctly.
* fix: delete linked budget item when accommodation or reservation is deleted (#933)
Deleting an accommodation or reservation now removes any budget item
linked via reservation_id, preventing orphan entries in the Budget page.
Also fixes a pre-existing payload-shape bug where budget:deleted was
broadcast with {id} instead of {itemId}, breaking live updates for
collaborators when a reservation price was cleared.
Tests added: ACCOM-006, RESV-009b, BUDGET-004b.
* fix: restore scroll position in mobile Plan and Places sidebars on reopen (issue #932)
Both DayPlanSidebar and PlacesSidebar have their own internal scroll
containers (overflowY: auto). Scroll events don't bubble, so previous
attempts that tracked scrollTop on the outer portal div never fired.
Each sidebar now accepts initialScrollTop and onScrollTopChange props.
The internal scroll container saves its scrollTop via onScrollTopChange
on every scroll event, and restores it via useLayoutEffect on mount
(before the browser paints, so no visible flash).
TripPlannerPage holds the saved values in refs (mobilePlanScrollTopRef,
mobilePlacesScrollTopRef) and passes them through on each portal mount.
* fix(map): prevent auto zoom-out when opening/closing place inspector (issue #921)
Both Leaflet and Mapbox GL renderers now gate fitBounds strictly on fitKey
increments from the parent. Selecting or dismissing a place inspector changes
paddingOpts (via hasInspector) but no longer triggers a re-fit that zoomed
the map out to the full trip extent when no day was selected.
Also removes the zoom-12 visibility gate on Leaflet route info pills so they
render at all zoom levels when a route is active.
* fix: translate mobile bottom-nav tab labels (issue #931)
Replaced hardcoded English labels in BottomNav with t() lookups using the same translation keys as the desktop navbar (nav.myTrips, admin.addons.catalog.*.name).
The top bar still blocked the trip planner's top nav on mobile even
after #808's padding trick — nav layouts that position their own
sticky headers were ignoring the --offline-banner-h offset, and the
bar looked alarming for what is usually a 2s blip.
Redesign as a small floating pill anchored bottom-center, hovering
above the mobile bottom nav (calc(var(--bottom-nav-h) + 16px)). No
layout shift anywhere, nothing ever covers the nav, and the pill
looks like a passing status chip rather than an error banner.
Reverts the body padding-top / navbar top offset introduced in #808
since they're no longer needed with the pill positioning.
OfflineBanner was fixed at top:0 but the rest of the page had no
idea it was visible, so on mobile (and the desktop nav on wider
screens) the banner sat on top of the header content.
When the banner is visible it now sets --offline-banner-h on <html>;
body reserves that space via padding-top, and the desktop fixed
Navbar shifts its top by the same amount. When back online the var
is removed and everything snaps back.
- Mapbox GL provider alongside Leaflet for trip and journey maps (opt-in in
settings with token, style presets incl. 3D on satellite, quality mode,
experimental badge).
- GPS "blue dot" with heading cone on mobile; three-state FAB (off / show /
follow), geodesic accuracy circle, desktop-hidden since browser IP geo is
too coarse for navigation.
- Marker drift fix: outer wrap no longer carries inline position/transform,
so mapbox's translate keeps the pin pinned at every zoom and pitch.
- Journey map popup (mapbox-gl): Apple-Maps-style tooltip on marker
highlight/click showing entry title + location / date subline.
- Journey feed reorder: up/down controls to the left of each entry reorder
sort_order within a day. Server endpoint, optimistic store update, rollback
on failure.
- Journey entry editor: desktop modal now centers over the feed column only,
backdrop still blurs the whole page (map included).
- Scroll-sync guard on journey: marker click locks the sync so smooth-scroll
can't steer the highlight to a neighbouring entry mid-animation.
- Misc: map top-padding aligned with hero, live/synced badges replaced by a
compact back-button in the hero, skeleton entries no longer pollute the
journey map, journey detail no longer shows map on mobile path when
combined view is active.
Scrollbars on mobile caused layout shift (content pushed left).
Hidden via media query on mobile; desktop retains thin styled scrollbars.
Also removes inline scrollbarWidth override in DayPlanSidebar that bypassed the CSS rule.
- Add --bottom-nav-h CSS token (84px + safe-area on mobile, 0px on desktop)
to give all fixes a single source of truth for the nav height
- Apply token to JourneySettingsDialog (fixes#650) and PlacesSidebar
day-picker sheet so bottom-anchored sheets clear the nav bar
- Add paddingBottom to TripPlannerPage Bookings, Lists, and Budget tab
scroll containers so content can be scrolled past the nav
- Bump Modal z-index from z-50 to z-[200] so modals render above the
bottom nav (both share z-50 with nav winning by DOM order)
- Map tooltips now respect light/dark mode via CSS variables
- Journey creation inherits cover image from first selected trip
- Only day-assigned places are synced to journey (no unplanned places)
- Place count in trip picker reflects assigned places only
- Contributor avatars shown in journey detail page
- Suggestion banner button visible in dark mode (!important override)
- Dashboard list view uses correct trips array and status label
- 5-table schema (journeys, entries, photos, trips, contributors) with migrations 87-91
- Trip-to-Journey sync engine with skeleton entries and photo sync
- Full CRUD API for journeys, entries, photos with Immich/Synology integration
- Timeline, Gallery and Map views with entry editor (markdown, mood, weather, pros/cons)
- Journey frontpage with hero card, stats and trip suggestions
- Public share links with token-based access and photo proxy
- PDF photo book export (Polarsteps-inspired)
- Dashboard redesign: mobile greeting, live trip hero, quick actions, unified card design
- BottomNav profile sheet with settings/admin/logout
- DayPlan mobile inline place picker
- TripFormModal members management
- Vacay calendar trip date indicator dots
- Fix contributor photo access (403) for journey Immich/Synology photos
- Trip deletion cleanup for journey skeleton entries
- i18n: 231 new keys across all 14 languages (native translations, no fallbacks)
## Collab — Complete Redesign
- iMessage-style live chat with blue bubbles, grouped messages, date separators
- Emoji reactions via right-click (desktop) or double-tap (mobile)
- Twemoji (Apple-style) emoji picker with categories
- Link previews with OG image/title/description
- Soft-delete messages with "deleted a message" placeholder
- Message reactions with real-time WebSocket sync
- Chat timestamps respect 12h/24h setting and timezone
## Collab Notes
- Redesigned note cards with colored header bar (booking-card style)
- 2-column grid layout (desktop), 1-column (mobile)
- Category settings modal for managing categories with colors
- File/image attachments on notes with mini-preview thumbnails
- Website links with OG image preview on note cards
- File preview portal (lightbox for images, inline viewer for PDF/TXT)
- Note files appear in Files tab with "From Collab Notes" badge
- Pin highlighting with tinted background
- Author avatar chip in header bar with custom tooltip
## Collab Polls
- Complete rewrite — clean Apple-style poll cards
- Animated progress bars with vote percentages
- Blue check circles for own votes, voter avatars
- Create poll modal with multi-choice toggle
- Active/closed poll sections
- Custom tooltips on voter chips
## What's Next Widget
- New widget showing upcoming trip activities
- Time display with "until" separator
- Participant chips per activity
- Day grouping (Today, Tomorrow, dates)
- Respects 12h/24h and locale settings
## Route Travel Times
- Auto-calculated walking + driving times via OSRM (free, no API key)
- Floating badge on each route segment between places
- Walking person icon + car icon with times
- Hides when zoomed out (< zoom 16)
- Toggle in Settings > Display to enable/disable
## Other Improvements
- Collab addon enabled by default for new installations
- Coming Soon removed from Collab in admin settings
- Tab state persisted across page reloads (sessionStorage)
- Day sidebar expanded/collapsed state persisted
- File preview with extension badges (PDF, TXT, etc.) in Files tab
- Collab Notes filter tab in Files
- Reservations section in Day Detail view
- Dark mode fix for invite button text color
- Chat scroll hidden (no visible scrollbar)
- Mobile: tab icons removed for space, touch-friendly UI
- Fixed 6 backend data structure bugs in Collab (polls, chat, notes)
- Soft-delete for chat messages (persists in history)
- Message reactions table (migration 28)
- Note attachments via trip_files with note_id (migration 30)
## Database Migrations
- Migration 27: budget_item_members table
- Migration 28: collab_message_reactions table
- Migration 29: soft-delete column on collab_messages
- Migration 30: note_id on trip_files, website on collab_notes
Progressive Web App:
- Service worker with Workbox caching (map tiles, API, uploads, CDN)
- Web app manifest with standalone display mode
- Custom app icon with PNG generation from SVG
- Apple meta tags, dynamic theme-color for dark/light mode
- iOS safe area handling
New Branding:
- Custom NOMAD logo (icon + text variants for light/dark mode)
- Logo used in navbar, login page, demo banner, admin, PDF export
- MuseoModerno font for login tagline
- Plane takeoff animation on login
- Liquid glass hover effect on dashboard spotlight & widgets
- Brand images protected from save/copy/drag
- "made with NOMAD" footer on PDF exports
Bug Fixes:
- Fix mobile note reorder (missing tripId prop)
- Fix Atlas city counting (strip postal codes, normalize case)
- Fix Atlas country detection (add Japanese/Korean/Thai names)
- Fix PDF note positioning (use order_index instead of sort_order)
- Fix PDF note icons (render actual icon instead of hardcoded notepad)
- Fix file source badge overflow on mobile (text truncation)
- Fix navbar dropdown z-index overlap with mobile plan/places buttons
- Fix dashboard trip card hover contrast in dark mode
- Fix day header hover color matching place background in dark mode
- Shorten settings button labels on mobile
UI Improvements:
- Mobile navbar shows icon only, desktop shows full logo
- NOMAD version badge in profile dropdown
- Top padding before first item in day planner
- Improved drag & drop stability (larger drop zones, less flickering)
The biggest NOMAD update yet. Introduces a modular addon architecture and three major new features.
Addon System:
- Admin panel addon management with enable/disable toggles
- Trip addons (Packing List, Budget, Documents) dynamically show/hide in trip tabs
- Global addons appear in the main navigation for all users
Vacay — Vacation Day Planner (Global Addon):
- Monthly calendar view with international public holidays (100+ countries via Nager.Date API)
- Company holidays with auto-cleanup of conflicting entries
- User-based system: each NOMAD user is a person in the calendar
- Fusion system: invite other users to share a combined calendar with real-time WebSocket sync
- Vacation entitlement tracking with automatic carry-over to next year
- Full settings: block weekends, public holidays, company holidays, carry-over toggle
- Invite/accept/decline flow with forced confirmation modal
- Color management per user with collision detection on fusion
- Dissolve fusion with preserved entries
Atlas — Travel World Map (Global Addon):
- Fullscreen Leaflet world map with colored country polygons (GeoJSON)
- Glass-effect bottom panel with stats, continent breakdown, streak tracking
- Country tooltips with trip count, places visited, first/last visit dates
- Liquid glass hover effect on the stats panel
- Canvas renderer with tile preloading for maximum performance
- Responsive: mobile stats bars, no zoom controls on touch
Dashboard Widgets:
- Currency converter with 50 currencies, CustomSelect dropdowns, localStorage persistence
- Timezone widget with customizable city list, live updating clock
- Per-user toggle via settings button, bottom sheet on mobile
Admin Panel:
- Consistent dark mode across all tabs (CSS variable overrides)
- Online/offline status badges on user list via WebSocket
- Unified heading sizes and subtitles across all sections
- Responsive tab grid on mobile
Mobile Improvements:
- Vacay: slide-in sidebar drawer, floating toolbar, responsive calendar grid
- Atlas: top/bottom glass stat bars, no popups
- Trip Planner: fixed position content container prevents overscroll, portal-based sidebar buttons
- Dashboard: fixed viewport container, mobile widget bottom sheet
- Admin: responsive tab grid, compact buttons
- Global: overscroll-behavior fixes, modal scroll containment
Other:
- Trip tab labels: Planung→Karte, Packliste→Liste, Buchungen→Buchung (DE mobile)
- Reservation form responsive layout
- Backup panel responsive buttons
Real-Time Collaboration (WebSocket):
- WebSocket server with JWT auth and trip-based rooms
- Live sync for all CRUD operations (places, assignments, days, notes, budget, packing, reservations, files)
- Socket-based exclusion to prevent duplicate updates
- Auto-reconnect with exponential backoff
- Assignment move sync between days
Performance:
- 16 database indexes on all foreign key columns
- N+1 query fix in places, assignments and days endpoints
- Marker clustering (react-leaflet-cluster) with configurable radius
- List virtualization (react-window) for places sidebar
- useMemo for filtered places
- SQLite WAL mode + busy_timeout for concurrent writes
- Weather API: server-side cache (1h forecast, 15min current) + client sessionStorage
- Google Places photos: persisted to DB after first fetch
- Google Details: 3-tier cache (memory → sessionStorage → API)
Security:
- CORS auto-configuration (production: same-origin, dev: open)
- API keys removed from /auth/me response
- Admin-only endpoint for reading API keys
- Path traversal prevention in cover image deletion
- JWT secret persisted to file (survives restarts)
- Avatar upload file extension whitelist
- API key fallback: normal users use admin's key without exposure
- Case-insensitive email login
Dark Mode:
- Fixed hardcoded colors across PackingList, Budget, ReservationModal, ReservationsPanel
- Mobile map buttons and sidebar sheets respect dark mode
- Cluster markers always dark
UI/UX:
- Redesigned login page with animated planes, stars and feature cards
- Admin: create user functionality with CustomSelect
- Mobile: day-picker popup for assigning places to days
- Mobile: touch-friendly reorder buttons (32px targets)
- Mobile: responsive text (shorter labels on small screens)
- Packing list: index-based category colors
- i18n: translated date picker placeholder, fixed German labels
- Default map tile: CartoDB Light