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
Replace inline DB queries in getTripSummary with calls to existing
service functions: listDays, listAccommodations, listBudgetItems,
listPackingItems, listReservations, listCollabNotes, getTripOwner,
and listMembers.
Budget and packing stats are now derived from the service results
instead of separate COUNT/SUM queries.
Replace all db.prepare() calls in mcp/index.ts, mcp/resources.ts, and
mcp/tools.ts with calls to the service layer. Add missing service functions:
- authService: isDemoUser, verifyMcpToken, verifyJwtToken
- adminService: isAddonEnabled
- atlasService: listVisitedCountries
- tripService: getTripSummary, listTrips with null archived param
Also fix getAssignmentWithPlace and formatAssignmentWithPlace to expose
place_id, assignment_time, and assignment_end_time at the top level, and
fix updateDay to correctly handle null title for clearing.
Add comprehensive unit and integration test suite for the MCP layer (821 tests all passing).
Dateless days are now reassigned to the new date range instead of being
deleted and recreated. This keeps all assignments, notes, bookings and
other day content intact when a user adds start/end dates to a trip
that was created without them.
- 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
Now that trips always default to a start+7 day window when no dates
are provided, the test expectation of null dates and zero dated days
is no longer valid.
canAccessUserPhoto was using .get() which only returned the first matching
trip, causing access to be incorrectly denied when a photo was shared across
multiple trips and the requester was a member of a non-first trip.
Adds optional check_in and check_out fields to create_reservation and
link_hotel_accommodation so MCP clients can set accommodation times,
matching the existing REST API behaviour.
Closes#363
- No dates → tomorrow to tomorrow+7d
- Start only → end = start+7d
- End only → start = end-7d
- Both provided → unchanged
fix(ci): include client/package-lock.json in version bump commit
Trip members viewing another member's shared photo were getting a 404
because the proxy endpoints always used the requesting user's Immich
credentials instead of the photo owner's. The ?userId= query param the
client already sent was silently ignored.
- Add canAccessUserPhoto() to verify the asset is shared and the
requesting user is a trip member before allowing cross-user proxying
- Pass optional ownerUserId through proxyThumbnail, proxyOriginal, and
getAssetInfo so credentials are fetched for the correct user
- Enforce shared=1 check so unshared photos remain inaccessible
- toggleCompanyHoliday now calls loadStats() so the entitlement sidebar
updates immediately when a vacation day is converted to a company holiday
- deleteYear now deletes vacay_user_years rows for the removed year,
preventing stale entitlement data from persisting and re-appearing
when the year is re-created
- deleteYear recalculates carry-over for year+1 when year N is deleted,
using the new actual previous year as the source
- removeYear store action now calls loadStats() so the sidebar reflects
the recalculated carry-over without requiring a page refresh
- Add prev-year button (+[<] 2026 [>]+) so users can add years going
backwards after deleting a past year; add vacay.addPrevYear i18n key
to all 13 supported languages
Closes#371
When a user enters an http:// Immich URL that redirects to https://,
the test succeeded (GET follows redirects fine) but subsequent POST
requests (e.g. photo search) broke due to method downgrade on 301/302.
Now testConnection() checks resp.url against the input URL after a
successful fetch. If the only difference is http→https on the same
host and port, it returns a canonicalUrl so the frontend can update
the input field before the user saves — ensuring the correct URL is
stored.
- Fix attachment URLs to use /api/trips/:id/files/:id/download instead
of /uploads/files/... which was unconditionally blocked with 401
- Use getAuthUrl() with ephemeral tokens for displaying attachments and
opening them in a new tab (images, PDFs, documents)
- Replace htmlFor/id label pattern with ref.current.click() for the
file picker button in NoteFormModal — fixes file not being added to
pending list on first note creation
- Add integration tests COLLAB-028 to COLLAB-031 covering URL format,
listing URLs, ephemeral token download, and unauthenticated 401
* add test suite, mostly covers integration testing, tests are only backend side
* workflow runs the correct script
* workflow runs the correct script
* workflow runs the correct script
* unit tests incoming
* Fix multer silent rejections and error handler info leak
- Revert cb(null, false) to cb(new Error(...)) in auth.ts, collab.ts,
and files.ts so invalid uploads return an error instead of silently
dropping the file
- Error handler in app.ts now always returns 500 / "Internal server
error" instead of forwarding err.message to the client
* Use statusCode consistently for multer errors and error handler
- Error handler in app.ts reads err.statusCode to forward the correct
HTTP status while keeping the response body generic
New POST /api/trips/:id/copy endpoint that deep copies all trip
planning data (days, places, assignments, reservations, budget,
packing, accommodations, day notes) with proper FK remapping
inside a transaction. Skips files, collab data, and members.
Copy button on all dashboard card types (spotlight, grid, list,
archived) gated by trip_create permission. Translations for all
12 languages.
Also adds reminder_days to Trip interface (removes as-any casts).
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>
Fixes#306 — OIDC scopes were hardcoded to 'openid email profile',
causing OIDC_ADMIN_CLAIM-based role mapping to fail when the required
scope (e.g. 'groups') wasn't requested. The new OIDC_SCOPE variable
defaults to 'openid email profile groups' so group-based admin mapping
works out of the box. Variable is now documented in README, docker-compose,
.env.example, and the Helm chart values.
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>
Adds COOKIE_SECURE (fixes login loop on plain-HTTP setups) and the previously
undocumented OIDC_DISCOVERY_URL to .env.example, docker-compose.yml, README.md,
chart/values.yaml, chart/templates/configmap.yaml, and chart/README.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Auth middleware now tags its 401s with code: AUTH_REQUIRED so the
client interceptor only redirects to /login on genuine session failures,
not on upstream API errors
- Fix /albums and album sync routes using raw encrypted API key instead
of getImmichCredentials() (which decrypts it), causing Immich to reject
requests with 401
- Add toast error notifications for all Immich operations in MemoriesPanel
that previously swallowed errors silently
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
db was already imported as addonDb; the extra db named import was
unnecessary. Updated the one stray db.prepare call at line 155 to use
addonDb consistently.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LoginPage now uses getApiErrorMessage() instead of err.message so
backend validation errors (e.g. "Password must be at least 8 characters")
are displayed instead of the generic "Request failed with status code 400"
- Add missing db import in server/src/index.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>