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>
useCallback captured tripStore at creation time (dep: [routeCalcEnabled]).
If assignments were empty on first render (trip still loading), the callback
would permanently see empty assignments and call setRoute(null) whenever
invoked — e.g. when clicking a place triggers onSelectDay → updateRouteForDay.
Fix: store tripStore in a ref updated on every render so the callback always
reads the latest assignments without needing to be recreated.
Implements a full undo history system for the Plan screen.
New hook: usePlannerHistory (client/src/hooks/usePlannerHistory.ts)
- Maintains a LIFO stack (up to 30 entries) of reversible actions
- Exposes pushUndo(label, fn), undo(), canUndo, lastActionLabel
Tracked actions:
- Assign place to day (undo: remove the assignment)
- Remove place from day (undo: re-assign at original position)
- Reorder places within a day (undo: restore previous order)
- Move place to a different day (undo: move back)
- Optimize route (undo: restore original order)
- Lock / unlock place (undo: toggle back)
- Delete place (undo: recreate place + restore all day assignments)
- Add place (undo: delete it)
- Import from GPX (undo: delete all imported places)
- Import from Google Maps list (undo: delete all imported places)
UI: Undo button (Undo2 icon) in DayPlanSidebar header. PDF, ICS and
Undo buttons all use custom instant hover tooltips instead of native
title attributes.
A toast notification confirms each undo action.
Translations: undo.* keys added to all 12 language files.
Store & re-render optimization:
- TripPlannerPage uses selective Zustand selectors instead of full store
- placesSlice only updates affected days on place update/delete
- Route calculation only reacts to selected day's assignments
- DayPlanSidebar uses stable action refs instead of full store
Map marker performance:
- Shared photoService for PlaceAvatar and MapView (single cache, no duplicate requests)
- Client-side base64 thumbnail generation via canvas (CORS-safe for Wikimedia)
- Map markers use base64 data URL <img> tags for smooth zoom (no external image decode)
- Sidebar uses same base64 thumbnails with IntersectionObserver for visible-first loading
- Icon cache prevents duplicate L.divIcon creation
- MarkerClusterGroup with animate:false and optimized chunk settings
- Photo fetch deduplication and batched state updates
Server optimizations:
- Wikimedia image size reduced to 400px (from 600px)
- Photo cache: 5min TTL for errors (was 12h), prevents stale 404 caching
- Removed unused image-proxy endpoint
UX improvements:
- Splash screen with plane animation during initial photo preload
- Markdown rendering in DayPlanSidebar place descriptions
- Missing i18n keys added, all 12 languages synced to 1376 keys