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.
The "Unplanned" filter button in PlacesSidebar only filtered the place
list but not the map. Propagate the filter state to TripPlannerPage so
mapPlaces excludes planned places when the filter is active.
Handle European number formats (e.g. 1.150,32) by detecting the last
separator as decimal and stripping thousand separators. Applied to
budget inline edit cells, add item row, and reservation price field.
Fixes#498
Adds a dedicated download button (blob-based, works on iOS WebApp)
to file cards, file preview modal, and image lightbox. Previously
only "open in tab" was available which doesn't work for non-browser
file types like .gpx on iOS.
Fixes#462
- Notifications: map raw avatar filename to /uploads/avatars/ URL in
getNotifications, createNotification broadcasts, and respond handler
- Admin listUsers: include avatar field in SELECT and map to avatar_url
- Admin page: render actual avatar image instead of initial letter only
- Budget loadItemMembers: map avatar to avatar_url (fixed in prior commit)
Fixes#507
Adds a collapse/expand toggle to the day detail panel header.
Collapsed state persists across day switches. Clicking the header
or the chevron button toggles between compact header-only view
and the full detail panel.
Closes#457
Three distinct bugs caused infinite OIDC redirect loops:
1. After logout, navigating to /login with no signal to suppress the
auto-redirect caused the login page to immediately re-trigger the
OIDC flow. Fixed by passing `{ state: { noRedirect: true } }` via
React Router's navigation state (not URL params, which were fragile
due to async cleanup timing) from all logout call sites.
2. On the OIDC callback page (/login?oidc_code=...), App.tsx's
mount-level loadUser() fired concurrently with the LoginPage's
exchange fetch. The App-level call had no cookie yet and got a 401,
which (if it resolved after the successful exchange loadUser()) would
overwrite isAuthenticated back to false. Fixed by skipping loadUser()
in App.tsx when the initial path is /login.
3. React 18 StrictMode double-invokes useEffect. The first run called
window.history.replaceState to clean the oidc_code from the URL
before starting the async exchange, so the second run saw no
oidc_code and fell through to the getAppConfig auto-redirect, firing
window.location.href = '/api/auth/oidc/login' before the exchange
could complete. Fixed by adding a useRef guard to prevent
double-execution and moving replaceState into the fetch callbacks so
the URL is only cleaned after the exchange resolves.
Also adds login.oidcLoggedOut translation key in all 14 languages to
show "You have been logged out" instead of the generic OIDC-only
message when landing on /login after an intentional logout.
Closes#491
- Exclude place-assigned reservations from timeline to prevent duplicate display
- Use selected day's date instead of today when entering time without date
- Pass day_id when updating reservations, not only when creating
- Clear reservation_time fields when switching booking type to hotel (#459)
- Parse date-only reservation_end_time correctly on edit (#455)
- Show end date on booking cards for date-only values (#455)
- Add auth token to file download links in bookings (#454)
- Account for timezone offsets in flight time validation (#456)
Bidirectional substring matching in isVisitedFeature caused unrelated
regions to be highlighted as visited (e.g. selecting Nordrhein-Westfalen
also marked Nord France due to "nord" being a substring match).
Replace the fuzzy loop with an additional exact check against the Natural
Earth name_en property to cover English-vs-native name mismatches.
Also fix Nominatim field priority to prefer state over county so
reverse-geocoded places resolve to the correct admin-1 level.
Adds integration tests ATLAS-009 through ATLAS-011 covering mark/unmark
region endpoints and user isolation.
Fixes#446
Timed places now auto-sort chronologically when a time is set.
Untimed places can be freely dragged between timed items.
Transports are inserted by time with per-day position override.
Fixes regression from multi-day spanning PR that removed timed/untimed split.
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.
If the budget category stored in reservation metadata was deleted, the
form would re-submit it on next save, resurrecting the deleted category.
Now validates against live budget items on form init and falls back to
auto-generation when the stored category is gone.
Closes#442
If a user's last visited tab belongs to an addon that gets disabled while
they are away, re-opening the trip now resets the active tab to 'plan'
instead of rendering the inaccessible addon page.
Closes#441
The navigateTo function was clearing lightboxInfo without re-fetching it,
causing the EXIF sidebar to disappear and nav button placement to break.
Mirrors the fetch logic already present in the thumbnail click handler.
Fixes#439
- 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
- Add TREK description and "Made with heart" text to About tab (all 13 languages)
- Add Report Bug, Feature Request and Wiki cards to About tab and Admin GitHub panel
- Version shown as inline badge
- Show day count input in trip form when no start/end date is set
- Backend accepts day_count param for create and update
- Remove forced date assignment for dateless trips (was always setting tomorrow + 7)
- Fix off-by-one: single-date fallback now creates 7 days instead of 8
- Add dayCount/dayCountHint translations for all 13 languages
Encrypted webhook URLs are no longer returned to the frontend. Both user
and admin webhook fields now show '••••••••' as a placeholder when a URL
is already saved, and the sentinel value is skipped on save/test so the
stored secret is never exposed or accidentally overwritten.
Replaces the 2-column masonry layout with a horizontal pill tab bar
matching the admin page pattern. Extracts all sections into self-contained
components under components/Settings/ and reduces SettingsPage.tsx from
1554 lines to 93. Adds i18n tab label keys across all 13 language files.
- 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>
- 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