Repository navigation
Conversation
Deeds67
force-pushed
the
feat/dissolve-person
branch
from
September 11, 2026 19:28
48c4688 to
a004c51
Compare
Deeds67
force-pushed
the
feat/dissolve-person
branch
2 times, most recently
from
September 19, 2026 21:56
ec3186f to
5ed8aa0
Compare
Deeds67
force-pushed
the
feat/dissolve-person
branch
from
September 27, 2026 20:00
5ed8aa0 to
ddc2a92
Compare
Deeds67
force-pushed
the
feat/dissolve-person
branch
from
October 8, 2026 20:54
ddc2a92 to
6faa909
Compare
Deeds67
force-pushed
the
feat/dissolve-person
branch
from
October 9, 2026 17:06
64bd6ad to
e9ecc0e
Compare
Deeds67
force-pushed
the
feat/dissolve-person
branch
from
October 15, 2026 06:57
e9ecc0e to
053af70
Compare
Deeds67
force-pushed
the
feat/dissolve-person
branch
from
October 28, 2026 13:42
5b0e81f to
be3b327
Compare
…sole Squashed from 32 commits when rebasing onto the v3.3.3 main (originals on backup/pr-1080-2026-10-15): - docs: design for dissolving a contaminated person - docs: close three gaps found reviewing the dissolve-person spec - feat(server): add the dissolve scope predicate - feat(server): clear the recognition watermark for a person's assets - test(server): prove clearFacesRecognizedAt honors the dissolve scope predicate - feat(server): add the dissolve write path, watermark before delete - test(server): pin the dissolve blast radius against a shared fixture - fix(server): capture the inserted shared_space id instead of reading it off the factory - fix(server): make the L6 pet-exclusion and L2 faceless-person guarantees actually break-tested - feat(server): count what a dissolve would touch, including what it cannot repair - fix(server): exclude pet siblings from sharedAssets and close counts test-discrimination gaps - feat(server): guard and orchestrate a person dissolve - test(server): close two dissolve-warning coverage gaps - test(server): make the metadata-import-on warning test discriminate on outcome - feat(server): expose the dissolve preview and apply endpoints - feat(web): surface per-person face contamination in the cleanup console - fix(server): make getPeopleHealth pagination stable under ties, fix i18n terms - feat(web): add the dissolve person dialog - fix(web): reach dissolve from the page discovery lands on - test(e2e): dissolve a metadata-contaminated person end to end - fix(server): require ownerId on people health, share the dissolve scope predicate - fix(server): make the dissolve preview's two dishonest warnings honest - test(server): pin the dissolve rollback, and reconcile the spec with what shipped - fix(faces): name the dissolve outcome schema so the Dart client compiles - fix(faces): never show one scope's counts under another, and align discovery with the dissolve - fix(faces): point the dissolve at person_group after the upstream repointing - chore: regenerate API clients and SQL queries for the v3.2.0 base - fix(rebase): carry the person-dissolve work onto the v3.2.2 server - fix(server): never delete a hand-drawn face box when dissolving a person - chore(open-api): regenerate for the dissolve handDrawn count - feat(web): say which hand-drawn faces a dissolve keeps and unassigns - docs: record that a dissolve never deletes hand-drawn face boxes
… dissolve person.type now lives in person_pet and asset_face.createdBy in asset_face_creator; FaceDissolve joins the gallery registry.
…s in the dissolve specs
Detected faces are hidden instead of unassigned or deleted, hiding is undoable, Space copies are cleaned up, and the outcome picker goes. Driven by issue #1228.
The service and DTO are updated in the next commit; this one does not typecheck on its own.
…atment The outcome and re-detect choices are gone. The service runs the write and the suggestion drain in one transaction, refuses while detection or recognition runs, and re-scans only photos whose imported faces were deleted. The web dialog still sends the old body until the next web commit.
…rs, and make restore honest - A face hidden before the dissolve keeps its Space links; unlink those too, so a Space person the preview said would go does go. - Recount surviving Space persons in the same transaction, and mark a repointed representative face as the system's choice. - The preview's hide count only includes faces a restore can bring back. - A restore reports faces that were hard-deleted since, and a second restore is refused under the record lock.
The dialog now lists hidden faces under "Can be undone" and, separately, the deleted imported faces, the removed person (with its name and birthday) and removed Space people under "Cannot be undone". The real-faces note only shows when faces will be hidden.
Deeds67
force-pushed
the
feat/dissolve-person
branch
from
October 28, 2026 20:40
938b58d to
d773c36
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a way to dissolve a person from the admin Face Cleanup console, and to undo it. Revised for issue #1228: "junk persons" built from small, blurry or non-face detections that face recognition chains together, and that came back after any cleanup.
What a dissolve does
The admin picks which faces: all, imported from metadata, detected, or without an embedding. Each face then gets the one treatment that keeps it from coming back:
asset_face.deletedAtset, person and identity links cleared. Recorded for restore.The person is removed once no face carries it. In Spaces, the dissolved faces are unlinked, Space people left empty are removed, survivors are recounted, and a survivor whose representative face was hidden gets a visible one.
Why hidden rather than unassigned or deleted: the non-forced recognition pass picks up every unassigned detected face and clusters it again with the same rules, and re-detecting the same photo finds the same boxes. A soft-deleted face is skipped by
getAllFaces,searchFaces, suggestions and Space matching, and a re-scan matches the tombstone instead of adding the box again.deletedAtis upstream's own soft-delete column, so stock Immich hides these faces too.Restore
Administration → Face cleanup → Dissolvedlists every dissolve with how many hidden faces can still come back. Restore un-hides them as unnamed faces and queues a non-forced recognition pass. Deleted imported faces cannot be restored, and a forced "Detect faces (All)" removes hidden faces too.Discovery
Unchanged from the first version: the Health tab ranks people by imported, manual and embedding-less faces. Junk clusters made of ordinary detected faces do not show there; the admin names the person on the People page and searches for it in the console.
Schema
Fork migration
1799100000000-AddFaceDissolveTablesaddsface_dissolveandface_dissolve_face. Three new FKs onto upstream keys are ingallery-upstream-fks.json;revert-to-immich.sqldrops both tables.down()drops them: the record has no upstream home, and the hidden faces stay hidden in upstream's column.Upstream files: registration lines only, +17 lines across
base.service.ts,test/utils.ts,test/medium.factory.ts,schema/gallery.tsandweb/src/lib/route.ts(grew). New server code lives underserver/src/gallery/.Testing
face-dissolve.sticks.spec.tsruns real recognition and detection after a dissolve: the faces stay unassigned, no person re-forms, and a neighbouring real person gains nothing. With the hide step reverted to a plain unassign, four of its five tests fail. The Space-matching test passes either way and only guards the hidden case.deletedAtand the people strip filters it, so no mobile change.Known limits
Design:
specs/2026-09-06-dissolve-person-design.md(Revision 2 at the top).