Claude.ai's start-auth flow POSTs to the registration_endpoint advertised
in the discovery document, but no public handler existed at /oauth/register
(only /api/oauth/register with browser cookie auth). This caused a
start_error redirect immediately on every connect attempt.
- Add POST /oauth/register to oauthPublicRouter following RFC 7591
- Make oauth_clients.user_id nullable via a raw (no-transaction) migration
so anonymous DCR clients can be created without a user context
- Update migration runner to support { raw: () => void } migrations for
DDL that requires PRAGMA foreign_keys = OFF outside a transaction
- Update createOAuthClient to accept userId: number | null with a global
cap (500) for anonymous DCR clients in place of the per-user limit
Adds an OAuth 2.1 public client registration flow so MCP clients can
self-register via a user-facing consent page instead of requiring manual
setup in Settings.
Server:
- DB migration adds `is_public` and `created_via` columns to oauth_clients
- New GET /api/oauth/register/validate — validates DCR params, returns
requested scopes; unauthenticated callers get loginRequired flag
- New POST /api/oauth/register — creates a public client, saves consent,
and redirects with client_id (cookie auth required)
- `authenticateClient` / `refreshTokens` skip secret check for public
clients (PKCE provides the security guarantee)
- `createOAuthClient` accepts options for isPublic/createdVia; public
clients store an opaque secret hash instead of a usable secret
- `rotateOAuthClientSecret` blocked on public clients
- `isValidRedirectUri` extracted as a shared helper
- Discovery metadata now advertises registration_endpoint and auth method
`none`; token/revoke endpoints no longer require client_secret for
public clients
Client:
- New OAuthRegisterPage (/oauth/register) — loading → optional
login-required gate → scope selection → done states
- New ScopeGroupPicker component — collapsible groups, indeterminate
checkboxes, select-all per group or globally
- oauthApi.register.{validate,submit} added to api/client.ts
- apiClient exported so it can be reused outside api/client.ts
- IntegrationsTab tests fixed for new collapsible section structure
- collab_notes fallback changed from undefined to [] in MCP trip tools
OAuth 2.1 authentication for MCP:
- Add OAuth 2.1 authorization server with PKCE support (routes/oauth.ts)
- Add OAuth service for client CRUD, auth-code flow, and token management (services/oauthService.ts)
- Add typed scope definitions and enforcement helpers (mcp/scopes.ts)
- Add OAuth consent UI page (OAuthAuthorizePage.tsx)
- Add client-side scope labels and descriptions (api/oauthScopes.ts)
- Integrate OAuth token auth into MCP handler alongside existing static tokens
- All OAuth endpoints gated on `mcp` addon
Addon gating across MCP tools, resources, and prompts:
- Add typed ADDON_IDS constant (server/src/addons.ts) replacing all string literals
- Gate budget tools and resources (trip-budget, per-person, settlement) on `budget` addon
- Gate packing tools and resources (trip-packing, trip-packing-bags, trip-todos) on `packing` addon
- Gate todos tools on `packing` addon (mirrors web UI Lists tab behavior)
- Expand atlas gate to cover full tool body (bucket-list + country tools no longer leak)
- Expand collab gate to cover full tool body (collab notes no longer leak)
- Gate packing-list and budget-overview MCP prompts on their respective addons
- Gate get_trip_summary sections per addon; blank packing/budget/collab_notes/todos when disabled
- Remove trip-files resource and files field from get_trip_summary
- Replace all isAddonEnabled('literal') calls with ADDON_IDS constants
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add reordering support for budget categories and line items within
categories. Changes persist via new DB table (budget_category_order)
and existing sort_order column. Live sync via WebSocket budget:reordered
event. Use Map instead of plain objects for category grouping to
preserve insertion order with numeric category names.
Reordering places on one day of a multi-day reservation no longer
affects the order on other days. Transport positions are now stored
per-day in a new reservation_day_positions table instead of a single
global day_plan_position on the reservation.
- Add quantity field to packing items (persisted, visible per item)
- Bags are now renamable (click to edit in sidebar)
- Bags support multiple user assignments with avatar display
- New packing_bag_members table for multi-user bag ownership
- Save current packing list as reusable template
- Add bag members API endpoint (PUT /bags/:bagId/members)
- Migration 74: quantity on packing_items, user_id on packing_bags, packing_bag_members table
- Link budget items to reservations via reservation_id column
- Update budget entry when reservation price changes (not create duplicate)
- Delete budget entry when reservation price is cleared
- Sync price back to reservation when edited in budget panel
- Lock budget item name when linked to a reservation
- Add migration 73 for reservation_id on budget_items
- migrations.ts: keep dev's migrations 69 (place_regions) + 70 (visited_regions), renumber our notification_channel_preferences migration to 71 and drop-old-table to 72
- translations: use dev values for existing keys, add notification system keys unique to this branch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SSRF: guard sendWebhook() with checkSsrf() + createPinnedAgent() to block
requests to loopback, link-local, private network, and cloud metadata endpoints
- XSS: escape subject, body, and ctaHref in buildEmailHtml() via escapeHtml()
to prevent HTML injection through user-controlled params (actor, preview, etc.)
- Encrypt webhook URLs at rest: apply maybe_encrypt_api_key on save
(settingsService for user URLs, authService for admin URL) and decrypt_api_key
on read in getUserWebhookUrl() / getAdminWebhookUrl()
- Log failed channel dispatches: inspect Promise.allSettled() results and log
rejections via logError instead of silently dropping them
- Log admin webhook failures: replace fire-and-forget .catch(() => {}) with
.catch(err => logError(...)) and await the call
- Migration 69: guard against missing notification_preferences table on fresh installs
- Migration 70: drop the now-unused notification_preferences table
- Refactor: extract applyUserChannelPrefs() helper to deduplicate
setPreferences / setAdminPreferences logic
- Tests: add SEC-016 (XSS, 5 cases) and SEC-017 (SSRF, 6 cases) test suites;
mock ssrfGuard in notificationService tests
- Add visited_regions table migration
- Mark/unmark region endpoints with auto-mark parent country
- Unmark country cascades to its regions; unmark last region cascades to country
- Region modal with mark/unmark flow and bucket list shortcut
- Viewport-based lazy loading of region GeoJSON at zoom >= 6
- i18n: add atlas.markRegionVisitedHint and atlas.confirmUnmarkRegion across all 13 locales
- Zoom >= 5 shows visited regions (states/provinces/departments) colored on the map
- Server resolves places to regions via Nominatim reverse geocoding (zoom=8)
- Supports all ISO levels: lvl4 (states), lvl5 (provinces), lvl6 (departments)
- Handles city-states (Berlin, Vienna, Hamburg) via city/county fallback
- Fuzzy name matching between Nominatim and GeoJSON for cross-format compatibility
- 10m admin_1 GeoJSON loaded server-side (cached), filtered per country
- Region colors match their parent country color
- Custom DOM tooltip (ref-based, no re-renders on hover)
- Country layer dims to 35% opacity when regions visible
- place_regions DB table caches resolved regions permanently
- Rate-limited Nominatim calls (1 req/sec) with progressive resolution
Introduces a fully featured notification system with three delivery
channels (in-app, email, webhook), normalized per-user/per-event/
per-channel preferences, admin-scoped notifications, scheduled trip
reminders and version update alerts.
- New notificationService.send() as the single orchestration entry point
- In-app notifications with simple/boolean/navigate types and WebSocket push
- Per-user preference matrix with normalized notification_channel_preferences table
- Admin notification preferences stored globally in app_settings
- Migration 69 normalizes legacy notification_preferences table
- Scheduler hooks for daily trip reminders and version checks
- DevNotificationsPanel for testing in dev mode
- All new tests passing, covering dispatch, preferences, migration, boolean
responses, resilience, and full API integration (NSVC, NPREF, INOTIF,
MIGR, VNOTIF, NROUTE series)
- Previous tests passing
- New todo_items DB table with priority, due date, description, user assignment
- Full CRUD API with WebSocket real-time sync
- 3-column UI: sidebar filters (All, My Tasks, Overdue, Done, by Priority),
task list with inline badges, and detail/create pane
- Apple-inspired design with custom dropdowns, date picker, priority system (P1-P3)
- Mobile responsive: icon-only sidebar, bottom-sheet modals for detail/create
- Lists tab with sub-tabs (Packing List + To-Do), persisted selection
- Addon renamed from "Packing List" to "Lists"
- i18n keys for all 13 languages
- UI polish: notification colors use system theme, mobile navbar cleanup,
settings page responsive buttons
When unlinking an Immich album, photos synced from that album are now
deleted. A new `album_link_id` FK column on `trip_photos` tracks the
source album link at sync time; `deleteAlbumLink` deletes matching
photos before removing the link. Individually-added photos are
unaffected. The client now refreshes the photo grid after unlinking.
Adds integration tests IMMICH-020 through IMMICH-024.
Closes#398
Introduces a full in-app notification system with three types (simple,
boolean with server-side callbacks, navigate), three scopes (user, trip,
admin), fan-out persistence per recipient, and real-time push via
WebSocket. Includes a notification bell in the navbar, dropdown, dedicated
/notifications page, and a dev-only admin tab for testing all notification
variants.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allow the first-boot admin account to be configured via ADMIN_EMAIL and
ADMIN_PASSWORD environment variables. If both are set the account is created
with those credentials; otherwise the existing random-password fallback is
used. Documented across .env.example, docker-compose.yml, Helm chart
(values.yaml, secret.yaml, deployment.yaml), and CLAUDE.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Link Immich albums to trips — photos sync automatically
- Album picker shows all user's Immich albums
- Linked albums displayed as chips with sync/unlink buttons
- Auto-sync on link: fetches all album photos and adds to trip
- Manual re-sync button for each linked album
- DB migration: trip_album_links table
fix: shared Immich photos visible to other trip members
- Thumbnail/original proxy now uses photo owner's Immich credentials
when userId query param is provided, fixing 404 for shared photos
- i18n: album keys for all 12 languages
- New expense_date column on budget items (DB migration #42)
- Date column in budget table with custom date picker
- CSV export button with BOM, semicolon separator, localized dates,
currency in header, per-person/day calculations
- CustomDatePicker compact/borderless modes for inline table use
- i18n keys for all 12 languages
Per-user Immich API keys were stored as plaintext in the users table,
giving any attacker with DB read access full control over each user's
Immich photo server. Keys are now encrypted on write with
maybe_encrypt_api_key() and decrypted at the point of use via a shared
getImmichCredentials() helper. A new migration (index 66) back-fills
encryption for any existing plaintext values on startup.
The smtp_pass setting was stored as plaintext in app_settings, exposing
SMTP credentials to anyone with database read access. Apply the same
encrypt_api_key/decrypt_api_key pattern already used for OIDC client
secrets and API keys. A new migration transparently re-encrypts any
existing plaintext value on startup; decrypt_api_key handles legacy
plaintext gracefully so in-flight reads remain safe during upgrade.
The oidc_client_secret was written to app_settings as plaintext,
unlike Maps and OpenWeather API keys which are protected with
apiKeyCrypto. An attacker with read access to the SQLite file
(e.g. via a backup download) could obtain the secret and
impersonate the application with the identity provider.
- Encrypt on write in PUT /api/admin/oidc via maybe_encrypt_api_key
- Decrypt on read in GET /api/admin/oidc and in getOidcConfig()
(oidc.ts) before passing the secret to the OIDC client library
- Add a startup migration that encrypts any existing plaintext value
already present in the database
getPlaceWithTags, canAccessTrip, and isOwner were calling _db! directly,
bypassing the Proxy that guards against null-dereference during a backup
restore. When the restore handler briefly sets _db = null, any concurrent
request hitting these helpers would crash with an unhandled TypeError
instead of receiving a clean 503-style error.
Replace all four _db! accesses with the exported db proxy so the guard
("Database connection is not available") fires consistently.
Previously, the migration runner called each migration function directly with no transaction wrapping and updated schema_version only after all pending migrations had run. A mid-migration failure (e.g. disk full after ALTER TABLE but before CREATE INDEX) would leave the schema in a partially-applied state with no rollback path. On the next restart the broken migration would be skipped — because schema_version had not advanced — but only if the failure was noticed at all.
~43 catch {} blocks silently discarded every error, including non-idempotency errors such as disk-full or corruption, making it impossible to know a migration had failed.
Changes:
- Each migration now runs inside db.transaction(); better-sqlite3 rolls back automatically on throw.
- schema_version is updated after every individual migration succeeds, so a failure does not cause already-applied migrations to re-run.
- A migration that throws after rollback logs FATAL and calls process.exit(1), refusing to start with a broken schema.
- All catch {} blocks on ALTER TABLE ADD COLUMN re-throw any error that is not "duplicate column name", so only the expected idempotency case is swallowed.
- Genuinely optional steps (INSERT OR IGNORE, UPDATE data-copy, DROP TABLE IF EXISTS) now log a warning instead of discarding the error entirely.
- Add configurable trip reminder days (1, 3, 9 or custom up to 30) settable by trip owner
- Grant administrators full access to edit, archive, delete, view and list all trips
- Show trip owner email in audit logs and docker logs when admin edits/deletes another user's trip
- Show target user email in audit logs when admin edits or deletes a user account
- Use email instead of username in all notifications (Discord/Slack/email) to avoid ambiguity
- Grey out notification event toggles when no SMTP/webhook is configured
- Grey out trip reminder selector when notifications are disabled
- Skip local admin account creation when OIDC_ONLY=true with OIDC configured
- Conditional scheduler logging: show disabled reason or active reminder count
- Log per-owner reminder creation/update in docker logs
- Demote 401/403 HTTP errors to DEBUG log level to reduce noise
- Hide edit/archive/delete buttons for non-owner invited users on trip cards
- Fix literal "0" rendering on trip cards from SQLite numeric is_owner field
- Add missing translation keys across all 14 language files
Made-with: Cursor
- Add centralized notification service with webhook (Discord/Slack) and
email (SMTP) support, triggered for trip invites, booking changes,
collab messages, and trip reminders
- Webhook sends one message per event (group channel); email sends
individually per trip member, excluding the actor
- Discord invite notifications now include the invited user's name
- Add LOG_LEVEL env var (info/debug) controlling console and file output
- INFO logs show user email, action, and IP for audit events; errors
for HTTP requests
- DEBUG logs show every request with full body/query (passwords redacted),
audit details, notification params, and webhook payloads
- Add persistent trek.log file logging with 10MB rotation (5 files)
in /app/data/logs/
- Color-coded log levels in Docker console output
- Timestamps without timezone name (user sets TZ via Docker)
- Add Test Webhook and Save buttons to admin notification settings
- Move notification event toggles to admin panel
- Add daily trip reminder scheduler (9 AM, timezone-aware)
- Wire up booking create/update/delete and collab message notifications
- Add i18n keys for notification UI across all 13 languages
Made-with: Cursor
Keep MFA backup codes visible after enabling MFA by avoiding protected-route unmount during user reload (`loadUser({ silent: true })`) and restoring pending backup codes from sessionStorage until the user explicitly dismisses them.
Audit logging for admin actions, backups, auth events.
New AuditLogPanel in Admin tab with pagination.
Dockerfile security: run as non-root user.
i18n keys for all 9 languages.
Thanks @fgbona for the implementation!
Share links:
- Generate a public link in the trip share modal
- Choose what to share: Map & Plan, Bookings, Packing, Budget, Chat
- Permissions enforced server-side
- Delete link to revoke access instantly
Shared trip page (/shared/:token):
- Read-only view with TREK logo, cover image, trip details
- Tabbed navigation with Lucide icons (responsive on mobile)
- Interactive map with auto-fit bounds per day
- Day plan, Bookings, Packing, Budget, Chat views
- Language picker, TREK branding footer
Technical:
- share_tokens DB table with per-field permissions
- Public GET /shared/:token endpoint (no auth)
- Two-column share modal (max-w-5xl)
Email Notifications:
- SMTP configuration in Admin > Settings (host, port, user, pass, from)
- App URL setting for email CTA links
- Webhook URL support (Discord, Slack, custom)
- Test email button with SMTP validation
- Beautiful HTML email template with TREK logo, slogan, red heart footer
- All notification texts translated in 8 languages (en/de/fr/es/nl/ru/zh/ar)
- Emails sent in each user's language preference
Notification Events:
- Trip invitation (member added)
- Booking created (new reservation)
- Vacay fusion invite
- Photos shared (Immich)
- Collab chat message
- Packing list category assignment
User Notification Preferences:
- Per-user toggle for each event type in Settings
- Addon-aware: Vacay/Collab/Photos toggles hidden when addon disabled
- Webhook opt-in per user
ICS Calendar Export:
- Download button next to PDF in day plan header
- Exports trip dates + all reservations with details
- Compatible with Google Calendar, Apple Calendar, Outlook
Technical:
- Nodemailer for SMTP
- notification_preferences DB table with per-event columns
- GET/PUT /auth/app-settings for admin config persistence
- POST /notifications/test-smtp for validation
- Dynamic imports for non-blocking notification sends
- Bucket list now supports POIs (not just countries): add any place
with auto-search via Google Places / Nominatim
- Optional target date (month/year) via CustomSelect dropdowns
- New target_date field on bucket_list table (DB migration)
- Server PUT route supports updating all fields
- Country bucket modal: date dropdowns default to empty
- CustomSelect: auto-opens upward when near bottom of viewport
- Search results open upward in the bucket add form
- i18n keys for DE and EN
- Click member avatars on budget items to mark who paid (green = paid)
- Multiple green chips = those people split the payment equally
- Settlement dropdown in the total budget card shows optimized payment
flows (who owes whom how much) and net balances per person
- Info tooltip explains how the feature works
- New server endpoint GET /budget/settlement calculates net balances
and minimized payment flows using a greedy algorithm
- Merged category legend: amount + percentage in one row
- i18n keys added for DE and EN
Transport reservations (flights, trains, buses, cars, cruises) now appear
directly in the day plan timeline based on their reservation date/time.
- Transport cards display inline with places and notes, sorted by time
- Click to open detail modal with all booking data and linked files
- Persistent positioning via new day_plan_position field on reservations
- Free drag & drop: places can be moved between/around transport entries
- Arrow reorder works on the full visual list including transports
- Timed places show confirmation popup when reorder breaks chronology
- Custom delete confirmation popup for reservations
- DB migration adds day_plan_position column to reservations table
- New batch endpoint PUT /reservations/positions for position updates
- i18n keys added for DE and EN
Audit log
- Add audit_log table (migration + schema) with index on created_at.
- Add auditLog service (writeAudit, getClientIp) and record events for backups
(create, restore, upload-restore, delete, auto-settings), admin actions
(users, OIDC, invites, system update, demo baseline, bag tracking, packing
template delete, addons), and auth (app settings, MFA enable/disable).
- Add GET /api/admin/audit-log with pagination; fix invite insert row id lookup.
- Add AuditLogPanel and Admin tab; adminApi.auditLog.
- Add admin.tabs.audit and admin.audit.* strings in all locale files.
Note: Rebase feature branches so new DB migrations stay after existing ones
(e.g. file_links) when merging upstream.
Files can now be linked to multiple bookings and places simultaneously
via a new file_links junction table. Booking modal includes a file picker
to link existing uploads. Unlinking removes the association without
deleting the file.
- Immich connection per user (Settings → Immich URL + API Key)
- Photos addon (admin-toggleable, trip tab)
- Manual photo selection from Immich library (date filter + all photos)
- Photo sharing with consent popup, per-photo privacy toggle
- Lightbox with liquid glass EXIF info panel (camera, lens, location, settings)
- Location filter + date sort in gallery
- WebSocket live sync when photos are added/removed/shared
- Proxy endpoints for thumbnails and originals with token auth
Atlas:
- Click any country to mark as visited or add to bucket list
- Bucket list with country flags, planned month/year, horizontal layout
- Confirm popup with two options (mark visited / bucket list)
- Full A2/A3 country code mapping for all countries
Trip creation:
- Drag & drop cover image support
- Add travel buddies via CustomSelect dropdown when creating a trip
- Manual date entry via double-click on date picker (supports DD.MM.YYYY, ISO, etc.)
Admins can create one-time registration links (1–5× or unlimited uses)
with optional expiry (1d–14d or never). Recipients can register even
when public registration is disabled. Atomic usage counting prevents
race conditions, all endpoints are rate-limited.
- Add reservation_end_time field (DB migration, API, UI)
- Split reservation form: separate date, start time, end time, status fields
- Fix DateTimePicker forcing 00:00 when no time selected
- Show end time across all reservation displays
- Link-to-assignment and date on same row (50/50 layout)
- Assignment search now shows day headers for filtered results
- Auto-fill date when selecting a day assignment
- Route segments: single OSRM request instead of N separate calls (~6s → ~1s)
- Route labels visible from zoom level 12 (was 16)
- Fix stale route labels after place deletion (useEffect triggers recalc)
- AbortController cancels outdated route calculations
## 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
- New budget_item_members junction table (migration 27)
- Assign trip members to budget items via avatar chips in Persons column
- Per-person split auto-calculated from assigned member count
- Per-person summary integrated into total budget card
- Member chips rendered via portal dropdown (no overflow clipping)
- Mobile: larger touch-friendly chips (30px) under item name
- Desktop: compact chips (20px) in Persons column
- Custom NOMAD-style tooltips on chips
- WebSocket live sync for all member operations
- Fix invite button text color in dark mode
- Widen budget layout to 1800px max-width
- Shorten "Per Person/Day" column header
- Times are now per-assignment instead of per-place, so the same place
on different days can have different times
- Migration 26 adds assignment_time/assignment_end_time columns
- New endpoint PUT /assignments/:id/time for updating assignment times
- Time picker removed from place creation (only shown when editing)
- End-before-start validation disables save button
- Time collision warning shows overlapping activities on the same day
- Fix participant avatars using avatar_url instead of avatar filename
- Rename "Add Place" to "Add Place/Activity" (DE + EN)
- Improve README update instructions with docker inspect tip
Migration 25 inserted the collab addon before seeding ran, causing
the COUNT check to find 1 row and skip all default addons. Switch to
INSERT OR IGNORE so every default addon is created regardless.
- Add /api/health endpoint (returns 200 OK without auth)
- Update docker-compose healthcheck to use /api/health
- Admin: configurable allowed file types
- Budget categories can now be renamed (inline edit)
- Place inspector: opening hours + files side by side on desktop
- Address clamped to 2 lines, coordinates hidden on mobile
- Category icon-only on mobile, rating hidden on mobile
- Time validation: "10" becomes "10:00"
- Hotel picker: separate save button, edit opens full popup
- Day header background improved for dark mode
- Notes: 150 char limit with counter, textarea input
- Files grid: full width when no opening hours
- Various responsive fixes
- Assignment participants: toggle who joins each activity
- Chips with hover-to-remove (strikethrough effect)
- Add button with dropdown for available members
- Avatars in day plan sidebar
- Side-by-side with reservation in place inspector
- Right-click context menus for places, notes in day plan + places list
- Budget categories can now be renamed (pencil icon inline edit)
- Admin: configurable allowed file types (stored in app_settings)
- File manager shows allowed types dynamically
- Hotel picker: select place + save button (no auto-close)
- Edit pencil opens full hotel popup with all options
- Place inspector: opening hours + files side by side on desktop
- Address clamped to 2 lines, coordinates hidden on mobile
- Category shows icon only on mobile
- Rating hidden on mobile in place inspector
- Time validation: "10" becomes "10:00"
- Climate weather: full hourly data from archive API
- CustomSelect: grouped headers support (isHeader)
- Various responsive fixes
BREAKING: Reservations have been completely rebuilt. Existing place-level
reservations are no longer used. All reservations must be re-created via
the Bookings tab. Your trips, places, and other data are unaffected.
Reservation System (rebuilt from scratch):
- Reservations now link to specific day assignments instead of places
- Same place on different days can have independent reservations
- New assignment picker in booking modal (grouped by day, searchable)
- Removed day/place dropdowns from booking form
- Reservation badges in day plan sidebar with type-specific icons
- Reservation details in place inspector (only for selected assignment)
- Reservation summary in day detail panel
Day Detail Panel (new):
- Opens on day click in the sidebar
- Detailed weather: hourly forecast, precipitation, wind, sunrise/sunset
- Historical climate averages for dates beyond 16 days
- Accommodation management with check-in/check-out, confirmation number
- Hotel assignment across multiple days with day range picker
- Reservation overview for the day
Places:
- Places can now be assigned to the same day multiple times
- Start time + end time fields (replaces single time field)
- Map badges show multiple position numbers (e.g. "1 · 4")
- Route optimization fixed for duplicate places
- File attachments during place editing (not just creation)
- Cover image upload during trip creation (not just editing)
- Paste support (Ctrl+V) for images in trip, place, and file forms
Internationalization:
- 200+ hardcoded German strings translated to i18n (EN + DE)
- Server error messages in English
- Category seeds in English for new installations
- All planner, register, photo, packing components translated
UI/UX:
- Auto dark mode (follows system preference, configurable in settings)
- Navbar toggle switches light/dark (overrides auto)
- Sidebar minimize buttons z-index fixed
- Transport mode selector removed from day plan
- CustomSelect supports grouped headers (isHeader option)
- Optimistic updates for day notes (instant feedback)
- Booking cards redesigned with type-colored headers and structured details
Weather:
- Wind speed in mph when using Fahrenheit setting
- Weather description language matches app language
Admin:
- Weather info panel replaces OpenWeatherMap key input
- "Recommended" badge styling updated
- Add version check against GitHub releases in admin dashboard
- Show amber banner when a newer version is available
- One-click update: git pull + npm install + auto-restart
- Confirmation dialog with backup recommendation and data safety info
- Dark mode support for update banner
- Fix fresh DB migration: initial schema now includes all columns
- i18n: English + German translations for all update UI
- Fix backup restore: try/finally ensures DB always reopens after closeDb
- Fix EBUSY on uploads during restore (in-place overwrite instead of rmSync)
- Add DB proxy null guard for clearer errors during restore window
- Add red warning modal before backup restore (DE/EN, dark mode support)
- JWT secret: empty docker-compose default so auto-generation kicks in
- OIDC: pass token via URL fragment instead of query param (no server logs)
- Block SVG uploads on photos, files and covers (stored XSS prevention)
- Add helmet for security headers (HSTS, X-Frame, nosniff, etc.)
- Explicit express.json body size limit (100kb)
- Fix XSS in Leaflet map markers (escape image_url in HTML)
- Remove verbose WebSocket debug logging from client
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
Features:
- Single Sign-On (OIDC) — login with Google, Apple, Authentik, Keycloak
- OpenStreetMap place search as free fallback when no Google API key
- Change password in user settings
- Delete own account (with last-admin protection)
- Last login column in admin user management
- SSO badge and provider info in user settings
- Google API key "Recommended" badge in admin panel
Improvements:
- API keys load correctly after page reload
- Validate auto-saves keys before testing
- Time format respects 12h/24h setting everywhere
- Dark mode fixes for popups and backup buttons
- Admin stats: removed photos, 4-column layout
- Profile picture upload button on avatar overlay
- TravelStats duplicate key fix
- Backup panel dark mode support
DEMO_MODE=true enables: auto-seeded admin + demo user, 3 example trips
(Tokyo, Barcelona, Wien), hourly reset of demo user data, one-click
demo login, visible banner with feature info. Zero behavior change
when DEMO_MODE is not set.
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