Commit Graph

335 Commits

Author SHA1 Message Date
Maurice 0f44d7d264 feat(journey): combined map+timeline view on mobile (Polarsteps-style)
Merge the separate Timeline and Map tabs into a single fullscreen
combined view on mobile (<1024px). A Leaflet map fills the background
while a horizontal snap-scroll carousel of entry cards sits at the
bottom. Scrolling the carousel auto-focuses the corresponding map
marker; tapping a marker scrolls to the card. Tapping a card opens
a new fullscreen entry view with edit/delete actions.

- New: MobileMapTimeline, MobileEntryCard, MobileEntryView components
- New: useIsMobile hook (matchMedia < 1024px)
- JourneyMap: fullScreen + paddingBottom props, focusMarker guard
- Desktop layout completely unchanged
- Public share page gets the same combined view (read-only)
- Fix: entry editor now portaled to body (iOS stacking context)
- Fix: pros/cons dark mode input backgrounds
- Fix: mood button borders in dark mode
- Fix: location icon color (neutral instead of green/indigo)
2026-04-16 23:37:09 +02:00
Maurice df075630fb feat(system-notices): add personal thank-you notice for v3.0.0
Personal note from the creator shown as the first page in the 3.0
upgrade modal. Includes community links (Discord, Ko-fi) and a
special shout-out to jubnl. Modal UX improved: users must click
through all pages before dismissing, wider layout, enhanced
markdown rendering with styled links, signature, and HR separator.
i18n coverage across all 15 languages.
2026-04-16 22:25:03 +02:00
jubnl bdb6b01765 fix(synology): paginate all three album sources past 100 albums and tighten targetUserId type
- Extract _fetchAllSynologyAlbums helper that loops until the source is
  exhausted; listSynologyAlbums now uses it for personal, shared-out,
  and shared-with-me instead of a hard-capped single request of 100
- Make getSynologyAssetInfo targetUserId required (number, not number|undefined)
  to match every call site and eliminate an implicit any at the _requestSynologyApi
  boundary
2026-04-16 20:54:35 +02:00
jubnl 6eb3ab38fb fix(ui): hide scrollbars on mobile, keep styled bars on desktop
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.
2026-04-16 16:42:36 +02:00
jubnl 84574020f2 fix(journey): increase PDF preview button touch targets for mobile
Raises button min-height to 44px and bumps padding/font-size to meet Apple HIG
minimum touch-target guidelines on iOS PWA. Fixes #680.
2026-04-16 15:55:20 +02:00
jubnl 1b7ea2c87d fix(journey): replace window.open with srcdoc iframe overlay for PDF preview
Rewrites downloadJourneyBookPDF to render the preview in an in-page srcdoc
iframe overlay instead of calling window.open(), which Safari iOS PWA blocks
in async callbacks. Matches the existing TripPDF pattern. Fixes #679.
2026-04-16 15:54:07 +02:00
jubnl 5e8602c50a fix(system-notices): fix FE-SN-BANNER-004 to reflect highest-priority-first array order 2026-04-16 15:08:52 +02:00
Julien G. d80bbd5bed Merge branch 'feat/system-notices' into dev 2026-04-16 14:38:14 +02:00
jubnl 293506217e feat(notices): add system notice infrastructure
Server-side notice registry with per-user condition evaluation (firstLogin,
existingUserBeforeVersion, addonEnabled, dateWindow, role, custom).
Notices are sorted by priority then severity, filtered against dismissals
stored in a new user_notice_dismissals table, and served via
GET /api/system-notices/active + POST /api/system-notices/:id/dismiss.

Client renders notices through a host component that partitions by
display type (modal / banner / toast). The modal renderer supports
multi-page pagination with directional slide transitions, keyboard
navigation, and correct dismiss-all semantics on CTA / X / ESC.
Dismissals are optimistic with a single background retry.

Includes 3.0.0 upgrade notices (v3-photos, v3-journey, v3-features),
onboarding welcome modal, and full i18n coverage across 15 languages.
The /journey route is addon-gated on both client and server.

Also includes: unit + integration test suites, registry integrity test
that validates action CTA IDs against client source, and technical
documentation in docs/system-notices.md.
2026-04-16 14:36:33 +02:00
Maurice 9739542a3a Merge pull request #672 from mauriceboe/feature/uncategorized-filter
feat: add uncategorized filter to category dropdown and more
2026-04-16 00:34:28 +02:00
Maurice 9f3a88223d fix: update ReservationModal test for check-in time range fields
Use getAllByText for check-in labels since both "Check-in" and
"Check-in until" now match the /Check-in/i pattern.
2026-04-16 00:29:25 +02:00
Maurice 409a63633c feat: support check-in time ranges for hotel accommodations
- Add check_in_end column to day_accommodations (Migration 102)
- Server: create/update accommodation accepts check_in_end
- Bidirectional sync: check_in_end synced between accommodation
  and linked reservation metadata (check_in_end_time)
- DayDetailPanel: shows check-in range (e.g. "14:00 – 22:00"),
  new "Until" time picker in hotel form
- ReservationModal: new check-in-until field for hotel bookings
- ReservationsPanel: displays check-in range in metadata cells
- i18n: checkInUntil keys in all 15 languages

Closes #366
2026-04-16 00:23:00 +02:00
Maurice 125436fa87 fix: correct test matchers for list import and reservations
- PlacesSidebar: match "List Import" (actual i18n value) not "Import List"
- ReservationsPanel: use unique titles to avoid matching filter buttons
2026-04-16 00:12:06 +02:00
Maurice 975846c236 fix: update tests for naver always-on and reservations redesign
- Remove server test for naver addon disabled (addon check removed)
- Update PlacesSidebar tests: "Google List" → "Import List" (both
  providers always shown)
- Update ReservationsPanel tests: status is always a span (no toggle),
  remove click-to-toggle test, update summary test
2026-04-16 00:04:14 +02:00
Maurice 7befb7d555 feat: enable naver list import by default, remove addon toggle
- Remove addon check from naver import endpoint
- Naver import always available alongside Google list import
- Migration 101: auto-enable naver_list_import for existing installs
- Remove unused isAddonEnabled import from places route
- Remove unused useAddonStore import from PlacesSidebar
2026-04-15 23:57:09 +02:00
Maurice 099255761c feat: collab sub-feature toggles and provider icons
- Add admin toggles for individual collab sections (Chat, Notes,
  Polls, What's Next) stored in app_settings
- CollabPanel adapts layout dynamically: chat always fixed 380px,
  remaining panels share space equally
- Mobile: disabled tabs are hidden
- Add Immich and Synology Photos SVG icons to photo provider toggles
- Add Luggage icon to bag tracking sub-toggle
- API: GET/PUT /admin/collab-features endpoints
- i18n: all 15 languages updated

Closes #604
2026-04-15 23:53:16 +02:00
Maurice c8fc21b8bd fix: reservations panel mobile responsiveness
- Hide type filter pills on mobile (< md breakpoint)
- Move add button right-aligned on mobile
- Separate booking code into its own row below date/time
- Hide weekday in date on mobile for space
- Reduce padding on mobile
2026-04-15 23:26:49 +02:00
Maurice 9186b8c850 feat: redesign reservations panel with unified toolbar and responsive grid
- Unified toolbar with title, type filter pills (with count badges),
  and add button in one row
- Cards redesigned: labeled fields in rounded boxes, status/type in
  header, edit/delete actions right-aligned
- Responsive grid with max 3 columns, auto-filling full width
- Type filters persist in sessionStorage per trip
- Widen reservations tab container to match other tabs (1800px)
2026-04-15 23:21:51 +02:00
Maurice e38c5fed44 feat: add uncategorized filter option to category dropdown
Add a "No Category" option to the category filter dropdown in the
places sidebar, allowing users to filter for places without an
assigned category. The filter is synced with the map view.

Closes #607
2026-04-15 22:54:23 +02:00
jubnl 618b1b8697 feat(admin): add map preview and auto-save to default user settings tab 2026-04-15 22:41:33 +02:00
jubnl e45a0efce3 feat(admin): add admin-configurable default user settings
Allow admins to set instance-wide defaults for temperature unit, color
mode, time format, route calculation, blur booking codes, and map tile
URL via a new Admin > User Defaults tab. Defaults are stored in
app_settings (prefixed default_user_setting_*) and applied at read time
as a fallback — user's own explicit values always take priority.
Translations added for all 16 supported languages.
2026-04-15 22:31:41 +02:00
jubnl 9e8d101d63 fix(ntfy): improve admin ntfy UX and add clear token button
- Add missing admin.ntfy.hint translation key in all 15 languages
- Add admin ntfy server hint clarifying it is the default for users
- Expose admin_ntfy_server via PreferencesMatrix so user settings
  placeholder reflects the admin-configured default
- Add clear token button to admin ntfy panel (same pattern as user settings)
- Extract common.clear from settings.ntfyUrl.clearToken across all 15 languages
2026-04-15 20:23:31 +02:00
jubnl bfe84b3016 feat(notifications): add ntfy as a first-class notification channel
Adds ntfy.sh (and self-hosted instances) as a new push notification
channel with full parity to the existing webhook channel.

- Backend: NtfyConfig type, getUserNtfyConfig, getAdminNtfyConfig,
  resolveNtfyUrl, sendNtfy (header-based API with Title/Priority/Tags/
  Click headers), testNtfy, NTFY_EVENT_META (priority + emoji tags per
  event), SSRF guard via existing checkSsrf + createPinnedDispatcher
- notificationPreferencesService: ntfy added to NotifChannel union,
  IMPLEMENTED_COMBOS, getActiveChannels parser, getAvailableChannels,
  ADMIN_GLOBAL_CHANNELS, and AvailableChannels interface
- notificationService: per-user ntfy dispatch after webhook block;
  admin-scoped ntfy via getAdminGlobalPref for version_available events
- Routes: POST /api/notifications/test-ntfy with saved-token fallback
- authService: admin_ntfy_server/topic/token in ADMIN_SETTINGS_KEYS,
  masked + encrypted on read/write
- settingsService: ntfy_token added to ENCRYPTED_SETTING_KEYS
- Frontend: ntfy topic/server/token inputs + Save/Test/Clear buttons in
  NotificationsTab; admin Ntfy panel in AdminPage; testNtfy API method
- i18n: full English strings; English placeholders in 14 other locales
- Tests: resolveNtfyUrl, sendNtfy, dispatch integration, UI tests,
  MSW handler for test-ntfy endpoint
2026-04-15 13:59:25 +02:00
Julien G. f349e567f8 Merge pull request #665 from mauriceboe/feat/indonesian-translation
Feat/indonesian translation
2026-04-15 08:17:55 +02:00
jubnl ff434f4515 fix: discord links in tests 2026-04-15 08:12:22 +02:00
Julien G. 326f9c0823 Merge pull request #664 from mauriceboe/main
Align dev
2026-04-15 07:38:11 +02:00
jubnl 5023406717 Update discord link to a permanent link 2026-04-15 07:33:26 +02:00
jubnl 191d59166c Merge remote-tracking branch 'origin/dev' into feat/indonesian-translation 2026-04-15 06:28:35 +02:00
jubnl 875c91e5ff feat(places): unified file import modal with drag-and-drop and deduplication
- Replace separate GPX and KML/KMZ import buttons with a single "Import
  file" modal accepting all three formats, with a drag-and-drop drop zone
- Support dragging files directly onto the Places sidebar panel; overlay
  appears on hover and pre-loads the file into the modal on drop
- Fix [object Object] description bug in KML imports caused by
  fast-xml-parser returning mixed-content nodes as objects; add stopNodes
  config and object guard in asTrimmedString
- Fix CDATA sections leaking into descriptions (e.g. "text.]]>") by
  unwrapping CDATA markers before tag stripping
- Add import deduplication across all import paths (GPX, KML/KMZ, Google
  list, Naver list): reimporting skips places already in the trip by name
  (case-insensitive) or by coordinates (within ~11 m tolerance), with
  intra-batch dedup so duplicate placemarks within the same file are
  also collapsed
- Fix KML route returning 400 "No valid Placemarks found" when all
  placemarks were valid but deduplicated; 400 now only fires when the
  file contains zero placemarks
- Show a warning toast "All places were already in the trip" instead of
  a misleading success toast when a reimport produces zero new places
  (GPX, KML/KMZ, Google list, Naver list)
- Add 8 new i18n keys across all 14 locales; remove 11 keys made unused
  by the modal consolidation
2026-04-15 06:07:26 +02:00
jubnl a1a7795945 Merge PR #488: KMZ/KML place import
Resolves conflicts with Naver list import (PR #662) — kept both unified
list-import dialog and new KMZ/KML dialog. Dropped duplicate react-dom
import and unused CustomSelect import from PlacesSidebar.
2026-04-15 05:09:45 +02:00
jubnl 9789c51d4f fix(naver-import): address PR #495 review issues
- SSRF: validate user-supplied URLs with checkSsrf() before fetch in
  both importNaverList and importGoogleList; upgrade naver.me substring
  check to exact hostname comparison to prevent bypass
- i18n: add missing places.importNaverList key to de.ts and es.ts
- migration: switch Naver addon seed to INSERT OR IGNORE to preserve
  admin customizations on re-runs; restore budget_category_order
  CREATE TABLE to its original formatting
- route: remove redundant cast after type-narrowing guard in naver-list handler
- component: hoist provider ternary above try/catch in handleListImport
- tests: add four new Naver import cases (502, empty list, no-coords,
  canonical URL skipping redirect fetch)
2026-04-15 04:48:39 +02:00
jubnl 4362406e74 Merge remote-tracking branch 'refs/remotes/pull/495' into feat/naver-support 2026-04-15 04:38:50 +02:00
jubnl 607498cabe fix(search-autocomplete): address PR #542 review issues
- Fix race condition: AbortController cancels in-flight autocomplete
  requests on each keystroke; stale responses no longer overwrite fresh ones
- Remove acTrigger state hack; onFocus calls fetchSuggestions directly
- Cap autocomplete input at 200 chars server-side (400 on violation)
- Filter Nominatim suggestions with empty osm_id segments
- Revert getPlaceDetails OSM branch from unconditional parallel fetch to
  conditional serial: Nominatim called only when Overpass lacks coords/address
- Wire places.loadingDetails i18n key to Loader2 spinner via aria-label/role
- Add tests: MAPS-017, MAPS-040c, MAPS-093, FE-MAPS-004
2026-04-15 04:16:56 +02:00
jubnl 35321076cf Merge branch 'review/pr-542' into feat/search-autocomplete 2026-04-15 04:02:08 +02:00
jubnl ee3966d6c8 fix(vacay): allow vacation on public holidays and add today marker
Removes the client-side guard that blocked toggling vacation entries on
public holiday dates, so users who work on holidays can still book leave.
Also adds a filled blue circle on today's date in the Vacay calendar for
quick orientation.

Closes #651
2026-04-15 02:38:50 +02:00
jubnl 0104ecfee8 fix(mobile): prevent bottom nav from clipping scrollable content and dialogs
- 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)
2026-04-15 02:16:57 +02:00
jubnl be71425bb7 fix(pwa): account for safe-area-inset-top in OfflineBanner so it's visible on iOS PWA 2026-04-15 00:09:25 +02:00
jubnl b194e8317d feat(pwa): implement real offline mode with IndexedDB sync
Add genuine offline read/write capability for trips:

- Dexie IndexedDB schema (trips, places, packing, todo, budget,
  reservations, files, mutationQueue, syncMeta, blobCache)
- Repo layer for all domains: offline reads from Dexie, writes
  optimistically to Dexie and enqueue mutations for later replay
- Mutation queue with UUID idempotency keys (X-Idempotency-Key),
  FIFO flush, temp-ID reconciliation on 2xx, fail-and-continue on 4xx
- Trip sync manager: caches all trips with end_date >= today or null,
  auto-evicts 7d after end_date, fetches bundle endpoint in one request
- Map tile prefetcher: bbox from place coords, zooms 10-16, 50MB cap,
  warms SW cache via fetch
- Sync triggers: network online → flush + syncAll; WS reconnect →
  flush only (rate-limiter safe); visibilitychange/30s → flush only
- WS remoteEventHandler writes through to Dexie on every event
- Server idempotency middleware + idempotency_keys table (migration 100,
  24h TTL nightly cleanup)
- GET /api/trips/:id/bundle endpoint for efficient single-request sync
- OfflineBanner component: amber (offline) / blue (syncing) / hidden
- OfflineTab in Settings: cached trip list, re-sync and clear actions
- usePendingMutations hook for per-item pending indicators

Closes #505 #541
2026-04-14 23:04:25 +02:00
jubnl 8c7567faf3 fix(pwa): fix offline session redirect and file download auth (#505 #541)
**#541 — File downloads broken in PWA standalone mode**
Replace getAuthUrl + window.open pattern with blob-based fetch using
credentials:include. The old approach minted a 60s single-use ephemeral
token then called window.open, which handed the URL to the system browser
on Android/iOS — losing the PWA cookie jar and producing "invalid or
expired token". The new approach fetches the file directly inside the
PWA WebView as a blob URL, so no auth handoff occurs.

New helper client/src/utils/fileDownload.ts with downloadFile and openFile.
Updated FileManager, ReservationsPanel, ReservationModal, PlaceInspector,
CollabNotes.

Security hardening in fileDownload.ts:
- assertRelativeUrl() guard prevents credentials being sent to external hosts
- openFile() checks blob.type against a safe-inline allowlist; HTML, SVG and
  other script-capable MIME types are forced to download instead of being
  opened inline, preventing same-origin XSS via blob URLs
- resp.ok check covers all non-2xx responses, not just 401

**#505 — PWA offline session lost on reload**
Wrap authStore with Zustand persist middleware, serializing only
{user, isAuthenticated} to localStorage key trek_auth_snapshot.
maps_api_key is intentionally excluded from the snapshot.

On cold start with no network: persist hydrates isAuthenticated:true,
App.tsx clears isLoading and calls loadUser({silent:true}), ProtectedRoute
renders the dashboard immediately. The network error from loadUser leaves
isAuthenticated intact so no login redirect occurs.

On 401 or logout: store state is cleared, persist writes
{isAuthenticated:false} — stale snapshot does not grant offline access
after session expiry.
2026-04-14 21:48:25 +02:00
Maurice fc6430d5ad Fix AddonManager test for provider sub-toggles under Journey addon
- Add journey addon to mock data so providers render under it
- Update toggle count assertion (journey + 2 providers = 3)
2026-04-14 20:47:30 +02:00
Maurice 5ea4095beb Fix content divider placed above paragraph instead of below (#624)
- Change divider from line-prefix action to insert action at cursor position
- Divider now inserts after the cursor with proper spacing
2026-04-14 20:31:47 +02:00
Maurice e695e0f62d Move memories providers under Journey addon in admin settings (#629)
- Remove memories providers from trip addons section
- Show Immich/Synology as sub-items under the Journey global addon
- Same pattern as bag tracking under packing list
2026-04-14 20:27:44 +02:00
Maurice 00e96baf0e Fix Stadia Maps 401 on journey and atlas maps (#640)
- Add referrerPolicy to JourneyMap TileLayer (matching trip planner behavior)
- Add referrerPolicy to AtlasPage TileLayer (same issue)
- Stadia Maps requires the referrer header for domain validation
2026-04-14 20:21:57 +02:00
jubnl f60e611577 fix(places): fix notes type and display in inspector
Add missing notes (and other fields) to client Place type so the field
is correctly typed when hydrating the edit form. Fix PlaceInspector to
show description and notes as separate blocks so notes are no longer
hidden when a place also has a description.
2026-04-14 15:50:59 +02:00
jubnl 5b99efce06 fix(places): add notes textarea to place edit form (#596)
Notes field was writable via MCP but had no UI input in PlaceFormModal.
2026-04-14 15:38:39 +02:00
Julien G. eb8ec8d793 Merge pull request #637 from mauriceboe/fix/595-pdf-non-transport-reservations
fix(pdf): render restaurant/event/tour/other reservations in trip PDF
2026-04-14 15:33:10 +02:00
jubnl 137ae27cb8 fix(pdf): render restaurant/event/tour/other reservations in trip PDF
Resolves #595. The PDF builder filtered reservations through a transport-only
allow-list, silently dropping all non-transport types. Replace the allow-list
with a single hotel exclusion (hotel is already covered by the accommodations
block) so every other reservation type now appears in the daily itinerary.

Add per-type icon and accent colour matching the existing ReservationsPanel
palette, and per-type subtitle builders (party size, venue, operator) plus a
generic location line for future use.
2026-04-14 15:27:25 +02:00
Maurice d3eab7d973 Fix journey map OSM warning (#627) and sidebar re-render on tab switch (#610)
- Enable attributionControl and add OSM attribution to JourneyMap TileLayer
- Memoize sidebar map entries array to prevent unnecessary map rebuilds
- Use stable callback reference for onMarkerClick
2026-04-14 15:24:29 +02:00
jubnl 0a408c21ac fix(tests): restore native AbortController for undici fetch compatibility
jsdom replaces globalThis.AbortController with its own implementation;
Node.js undici-based fetch validates signals via instanceof against the
native AbortSignal, causing fetch to throw before MSW could intercept.

Fix via custom Vitest environment (tests/environment/jsdom-native-abort.ts)
that captures native AbortController/AbortSignal before jsdom patches them
and restores them after jsdom setup.

Also updates JournalBody test 004 to match component behaviour (headings
rendered as <p>) and removes debug console.log statements.
2026-04-14 15:08:55 +02:00
Maurice c60332dcf1 fix(journey): normalize headings and fix setext hr in story text
- Render h1/h2/h3 as plain paragraphs — journal stories are plain
  text, not structured documents
- Preprocess text to insert blank line before --- and === so they
  become horizontal rules instead of setext headings
2026-04-13 20:46:20 +02:00