clusterTrips's gap check compared array-index adjacency between
DaySignal entries. Since `days` only contains dates with >=1 photo
(GROUP BY DATE(...) with no zero-photo rows), two trips separated by
months with no photos in between were array-adjacent (gapLen=0) and
got merged into one bogus trip. Compare actual elapsed calendar days
between the last and next away day instead.
The previous guard 'only set if formData.albumName is empty' lost races against the dialog mount cycle: in some orderings the formData starts empty, the effect fires while config.suggestedAlbumName is undefined, then later when the suggestion arrives the guard refuses to update because prev.albumName == ''. Net effect: the user clicks a trip, selects photos, opens Add to Album, and the Album Name field is empty.
Track the last applied suggestion via a ref and write the value whenever the suggestion changes to a new non-empty string. Doesn't overwrite mid-typing because the suggestion only flips when the user picks a different trip; doesn't overwrite when the user picks the same trip twice; works on mount-after-suggestion (which was the broken path).
The trip-assets endpoint was returning every orphan asset in the date range, so screenshots, photos taken at the airport coming back, or anything else timestamped during the trip days mixed in. The right panel ended up showing photos from multiple cities for one trip selection.
Pass the trip's dominant city through the context to the asset fetcher and add an optional 'city' filter on /api/albums/potential-trip-assets:
WHERE e.city = :city OR e.city IS NULL
NULL is included so we don't lose photos taken during the trip whose EXIF lacks a city tag (no GPS, screenshots edited later, older devices, etc.). When no city is provided (single-day Days view, or a trip whose city couldn't be resolved) the previous date-only behaviour is preserved.
Closes#267 (proposal accepted in spirit; happy to iterate on review).
Today the Potential Albums page groups orphan assets by single day, which is great for one-off events but tedious for trips: a 7-day Lisbon holiday shows up as 7 separate rows that the user has to merge by hand. This adds a sibling view that detects multi-day stretches of photos taken away from the user's "home" location and surfaces each as one row, with a sensible suggested name like `20180401 - Lisboa`.
What's in this PR:
Backend
- `src/helpers/trips.helper.ts` — pure TS clustering helper (haversine distance, home detection by mode-city + GPS centroid, linear-scan trip extraction with configurable gap tolerance and min-trip-days). Easy to unit-test.
- `src/pages/api/albums/potential-trips.ts` — per-day aggregation in PG (count + lat/lon centroid + MODE() city/country) → call helper → return `{ home, trips[] }`.
- `src/pages/api/albums/potential-trip-assets.ts` — orphan assets in a date range, with the same shape the existing single-day endpoint returns. Date format validated to keep the literal-interpolated SQL safe.
Frontend
- `src/components/albums/potential-albums/PotentialTrips.tsx` — list rows, click a trip to load its assets in the existing right panel.
- `src/components/albums/AlbumSelectorDialog.tsx` — accepts a new `defaultName` prop and pre-fills the "Create New" tab from `config.suggestedAlbumName`. User can still edit.
- `src/pages/albums/potential-albums.tsx` — Days/Trips Tabs at the top of the left panel. Trips tab passes `{startDate, endDate, suggestedAlbumName}` into the shared selection context; the existing `PotentialAlbumsAssets` component now fetches a date range when `endDate` is set.
- `src/contexts/PhotoSelectionContext.tsx` — adds `endDate` and `suggestedAlbumName` to the shared config.
Defaults match the issue:
- home radius 50 km
- min trip days 2
- gap tolerance 2 (transit days, days with no photos)
- name format `YYYYMMDD - {city}` (or `YYYYMMDD - Trip` if no city resolved)
Libraries with no GPS at all fall through to the existing Days view — no regression.
Open for the maintainer:
- Whether to also expose the three thresholds in Settings (left out here to keep the PR small; trivial follow-up).
- Whether the trips endpoint should cache its result per-user (some libraries are large).
This updates the container image to the official immich-power-tools repository; no other configuration changes were made.
Updated from previous (now deprecated) image references. Some files were still referencing the old image instance ´ghcr.io/varun-raj/immich-power-tools:latest´, updated them to ´ghcr.io/immich-power-tools/immich-power-tools:latest´ across the project (README.md, docker-compose.yml, and docker-compose-all-immich.yml).
- Show full condition details on IF/Switch nodes with person thumbnails
- Show album name and tag on action nodes
- Add dark mode support for React Flow controls via colorMode
- Center images in lightbox preview
- Fix leaflet marker icons for both Webpack and Turbopack bundlers
- Add import_api_key setting with auto-generate endpoint and settings UI
- Import job runner uses import API key when available, falls back to session
- Show ApiKeyGate empty state in both workflow and import pages when key missing
- Replace workflow API key warning popover with full gate overlay
- Redesign person cards: square avatar, hover action toolbar, cleaner layout
- Birthday field now uses popover with calendar + natural language input
- Fix autocomplete dropdown clipping inside overflow-hidden cards via portal
- People grid column slider persisted to localStorage (min 3, max 20)
Add delete with confirm dialog to asset lightbox (always visible, handles
deletion internally with local state update). Force dark theme on asset
info panel and fix text color inheritance. Add edge-to-edge image display
in lightbox by removing slide padding. Show open-in-immich link on hover
only. Add workflow page title breadcrumb. Add one-click workflow API key
generation that creates the key in Immich and saves it automatically.
Add beta badge to Workflows in the sidebar. Include settings page
improvements, workflow detail updates, profile info and sidebar
nav refinements, and validate-api-key endpoint.
- Implemented Nextcloud shared link detection and processing in the import workflow.
- Created a new processor for handling Nextcloud shared links, including asset downloading and uploading to Immich.
- Enhanced existing import functionality to support platform differentiation between Immich and Nextcloud.
- Added utility functions for parsing file names and guessing content types.
- Updated thumbnail and video endpoints to accommodate Nextcloud's API structure.
- Modified the import shared page to handle Nextcloud URLs and passwords.
- Refactored asset upload logic to streamline the process for both platforms.
Redesign import-shared with sticky import bar, dense gallery grid,
progress bar, and cleaner landing state. Add password-protected
shared link support - detects 401 "Password required" from Immich
and shows a password input. Also reduce asset grid spacing and
replace "Open in Immich" text with a link icon.
Reorganize the exif analytics page into labeled sections (Camera & Gear,
Location, Technical, Storage) with icon headers. Replace donut charts
with horizontal bar charts for multi-category EXIF distributions.
Improve heatmap with emerald color scale, day labels, and legend.
Add polished stat cards with icons and skeleton loading states.
- API returns node counts, total runs, and last run status per workflow
- List view with status icon, trigger badges, stats, and last run info
- Human-readable schedule labels (Every 6h, Every 30m)
- Last run shows status icon, asset count, and relative time
- Actions appear on hover for cleaner default state
- Improved empty state
- Add ownerId to settings table for per-user key-value storage
- Add settings KV API endpoint scoped by user
- Add workflow API key config in settings page
- Action executor uses per-user workflow API key, falls back to session auth
- Bootstrap cron scheduler on server start via instrumentation hook
- Re-register/unregister cron on workflow enable/disable/delete
- Downgrade node-cron to v3 for Turbopack compatibility
- Add structured console logs to workflow engine for all steps
- Enabled toggle saves immediately to server
- Schedule popover saves immediately to server
- Use react-hot-toast instead of shadcn toast in workflow editor
- Visual node-based workflow editor using React Flow
- Asset triggers: new asset, asset updated, all assets (with lookback buffer)
- Logic nodes: if/switch with combinable conditions (person, location, date, camera, EXIF, favorites, album membership)
- Action nodes: create album, add/remove from album, favorite, unfavorite, archive, tag
- Workflow-level triggers: manual run, cron schedule, webhook
- Debug mode: dry run with step-by-step logs and asset previews
- Run history with per-node asset tracking
- Run detail page with annotated read-only graph
- Processed asset tracking to prevent duplicate processing
- Multi-person picker with face thumbnails
- Album picker dropdown for actions
- JSON import/export for workflow sharing
- Workflow list page with create, delete, enable/disable
- Added ESLint configuration file with Next.js and Remotion settings.
- Removed experimental instrumentationHook from next.config.mjs.
- Updated package.json scripts to use ESLint directly and enabled turbopack for development.
- Bumped various dependencies including Next.js, React, and Radix UI components.
- Changed searchTimer initialization to use undefined for better clarity in multiple components.
- Move instrumentation.ts to src/ so Next.js picks it up (required when using src/ layout)
- Enable experimental.instrumentationHook in next.config.mjs (required in Next.js 14)
- Switch Dockerfile deps stage from npm to bun to match lockfile
- Explicitly copy @libsql and libsql into standalone runner — Next.js file tracing
misses dynamically required native modules (require(`@libsql/${target}`))
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>