feat(immich): dubbelen-review iteraties 1-4 — vergelijk-view, batch-cleanup, quarantaine #68

Merged
janpeter merged 1 commit from feat/immich-dubbelen-review-iteraties into main 2026-07-31 15:52:26 +02:00
Owner

Immich Dubbelen — review + iteraties 1-4

Volledige verbetering van het Immich Dubbelen-scherm op basis van de diepgaande review in docs/reviews/2026-07-31-immich-dubbelen-scherm-review.md (met uitvoeringsstatus per iteratie).

Iteratie 1 — quick win

  • Media-route: lookup via genormaliseerde ImmichAsset (dekt duplicate-assets zonder persoonskoppeling) met person-join fallback
  • variant=thumbnail serveert echte Immich-thumbnails met lokaal fallback
  • Overzicht: thumbnails, resolutie/grootte/datum per asset, video-badge, Identiek/Near-duplicate badge (checksum), blocked-badge fix

Iteratie 2 — vergelijk-view

  • Nieuwe pagina /immich/dubbelen/[groupId]: grote previews (variant=preview, Immich size=preview), criteria-matrix (resolutie, grootte, datum, pad, personen, checksum) met winnaar-marks, vorige/volgende-navigatie
  • Keeper-override: preview én execute accepteren keeperImmichAssetId, server-side gevalideerd (unknown_keeper_override, keeper_override_without_local_asset); preview levert per-asset detail + suggestedKeepImmichAssetId

Iteratie 3 — beslis-model, batch, quarantaine

  • Migratie 20260731120000: decision/decided_at/decision_keeper_immich_asset_id op ImmichDuplicateGroup (overleeft re-sync) + JobType duplicate_cleanup_batch
  • POST /api/immich/duplicates/decisions (gevalideerde batch-beslissingen) en POST /api/immich/duplicates/execute-batch (job-queue)
  • runDuplicateCleanupBatchJob: queued groepen sequentieel met voortgang in Jobs-UI; geblokkeerde/falende groepen terug naar pending zonder de batch te stoppen
  • Quarantaine: losers naar <media-root>/.quarantine/<run-id>/ + manifest.json i.p.v. direct fs.unlink (fail-safe bij bestaand doel; scanner slaat dot-mappen over)

Iteratie 4 — metrics, filters, historie

  • Besparingsmetrics per groep en totaal (duplicateGroupSavingsBytes, hergebruikt keeper-algoritme)
  • Statusfilters met tellingen + paginering (50/pagina) i.p.v. vaste take: 200
  • Run-historie /immich/dubbelen/runs met per-item status en retry-knop

Verificatie

  • npm test: 355/355 groen (+43 nieuwe tests t.o.v. main)
  • npm run build: groen (incl. alle nieuwe routes)
  • npx eslint: 0 errors (2 bekende <img>-warnings conform bestaand patroon)

Deploy-notities

  • Bevat DB-migratie: na merge op max2 npm run check-db && npm run db:deploy (postgres enum + nieuwe kolommen) vóór of tijdens de redeploy
  • Deploy loopt via de ops-agent flow redeploy_media_organizer; deploy-taak voor max2 staat klaar in de s4m-queue
## Immich Dubbelen — review + iteraties 1-4 Volledige verbetering van het Immich Dubbelen-scherm op basis van de diepgaande review in `docs/reviews/2026-07-31-immich-dubbelen-scherm-review.md` (met uitvoeringsstatus per iteratie). ### Iteratie 1 — quick win - Media-route: lookup via genormaliseerde `ImmichAsset` (dekt duplicate-assets zonder persoonskoppeling) met person-join fallback - `variant=thumbnail` serveert echte Immich-thumbnails met lokaal fallback - Overzicht: thumbnails, resolutie/grootte/datum per asset, video-badge, Identiek/Near-duplicate badge (checksum), `blocked`-badge fix ### Iteratie 2 — vergelijk-view - Nieuwe pagina `/immich/dubbelen/[groupId]`: grote previews (`variant=preview`, Immich `size=preview`), criteria-matrix (resolutie, grootte, datum, pad, personen, checksum) met winnaar-marks, vorige/volgende-navigatie - Keeper-override: preview én execute accepteren `keeperImmichAssetId`, server-side gevalideerd (`unknown_keeper_override`, `keeper_override_without_local_asset`); preview levert per-asset detail + `suggestedKeepImmichAssetId` ### Iteratie 3 — beslis-model, batch, quarantaine - Migratie `20260731120000`: `decision`/`decided_at`/`decision_keeper_immich_asset_id` op `ImmichDuplicateGroup` (overleeft re-sync) + JobType `duplicate_cleanup_batch` - `POST /api/immich/duplicates/decisions` (gevalideerde batch-beslissingen) en `POST /api/immich/duplicates/execute-batch` (job-queue) - `runDuplicateCleanupBatchJob`: queued groepen sequentieel met voortgang in Jobs-UI; geblokkeerde/falende groepen terug naar `pending` zonder de batch te stoppen - **Quarantaine**: losers naar `<media-root>/.quarantine/<run-id>/` + `manifest.json` i.p.v. direct `fs.unlink` (fail-safe bij bestaand doel; scanner slaat dot-mappen over) ### Iteratie 4 — metrics, filters, historie - Besparingsmetrics per groep en totaal (`duplicateGroupSavingsBytes`, hergebruikt keeper-algoritme) - Statusfilters met tellingen + paginering (50/pagina) i.p.v. vaste `take: 200` - Run-historie `/immich/dubbelen/runs` met per-item status en retry-knop ### Verificatie - `npm test`: 355/355 groen (+43 nieuwe tests t.o.v. main) - `npm run build`: groen (incl. alle nieuwe routes) - `npx eslint`: 0 errors (2 bekende `<img>`-warnings conform bestaand patroon) ### Deploy-notities - Bevat DB-migratie: na merge op max2 `npm run check-db && npm run db:deploy` (postgres enum + nieuwe kolommen) vóór of tijdens de redeploy - Deploy loopt via de ops-agent flow `redeploy_media_organizer`; deploy-taak voor max2 staat klaar in de s4m-queue
feat(immich): dubbelen-review iteraties 1-4 — vergelijk-view, batch-cleanup, quarantaine
All checks were successful
CI / docker-build (pull_request) Successful in 1m28s
041a67e34e
- media-route: lookup via genormaliseerde ImmichAsset + Immich thumbnail/preview-varianten
- overzicht: thumbnails, metadata, checksum-badges, besparingsmetrics, statusfilters, paginering
- vergelijk-view /immich/dubbelen/[groupId]: side-by-side previews, criteria-matrix, keeper-override
- beslis-model (migratie): decision overleeft re-sync; decisions API
- batch-cleanup als duplicate_cleanup_batch-job met voortgang en per-groep herstel
- quarantaine (<media-root>/.quarantine/<run>/) + manifest i.p.v. direct unlink
- run-historie /immich/dubbelen/runs met per-item status en retry
- 355 tests groen, next build groen, review-doc in docs/reviews
s4m-codex-reviewer left a comment

Verdict: REQUEST_CHANGES

Geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden.

Findings

  • HIGHsrc/lib/duplicates/execute.ts:395: quarantine-manifest wordt pas na performDuplicateCleanupSideEffects geschreven. Als moveToQuarantine() voor één loser slaagt en een latere loser/Immich scan/andere side effect faalt, eindigt de run als error zonder manifest voor de al verplaatste bestanden. Daardoor is de nieuwe quarantaine-laag niet betrouwbaar herstelbaar en kan een retry het probleem verergeren omdat alleen de later verplaatste bestanden in het manifest terechtkomen. Schrijf het manifest atomair mee zodra bestanden verplaatst worden, of gebruik een per-file append/update met fail-safe handling voordat een run fout kan terugkeren.

Opmerkingen

De PR volgt verder de richting van de bestaande productstandaarden: protected routes hercontroleren server-side, de batch loopt via de job-queue, en de UI gebruikt semantische componentvarianten/tokens. De testdekking is breed, maar mist specifiek het partial-failure scenario waarin quarantaine al bestanden heeft verplaatst voordat de run faalt.

# Verdict: REQUEST_CHANGES Geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden. ## Findings - **HIGH** — `src/lib/duplicates/execute.ts:395`: quarantine-manifest wordt pas na `performDuplicateCleanupSideEffects` geschreven. Als `moveToQuarantine()` voor één loser slaagt en een latere loser/Immich scan/andere side effect faalt, eindigt de run als error zonder manifest voor de al verplaatste bestanden. Daardoor is de nieuwe quarantaine-laag niet betrouwbaar herstelbaar en kan een retry het probleem verergeren omdat alleen de later verplaatste bestanden in het manifest terechtkomen. Schrijf het manifest atomair mee zodra bestanden verplaatst worden, of gebruik een per-file append/update met fail-safe handling voordat een run fout kan terugkeren. ## Opmerkingen De PR volgt verder de richting van de bestaande productstandaarden: protected routes hercontroleren server-side, de batch loopt via de job-queue, en de UI gebruikt semantische componentvarianten/tokens. De testdekking is breed, maar mist specifiek het partial-failure scenario waarin quarantaine al bestanden heeft verplaatst voordat de run faalt.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
janpeter/Media-Organizer!68
No description provided.