Repository navigation
Conversation
🧪 Release candidate buildLatest RC Images published
How to run this RCIn the directory containing your services:
immich-server:
image: ghcr.io/open-noodle/gallery-server:pr-1064-rc.17Then pull and restart: docker compose pull immich-server
docker compose up -dEach push publishes a new numbered tag, so update the image line to move to a newer RC. To roll back, point it at an earlier Before rolling back to an older image, undo the newer Gallery migrations with this RC image: https://docs.opennoodle.de/install/upgrading#rolling-back-to-an-earlier-release Previous builds (15)
5 older build(s) omitted. Their tags are still in GHCR until this PR closes. Last updated Wed, 28 Oct 2026 15:14:52 GMT. Every push while the |
4a31d23 to
1d3e9af
Compare
a248cb0 to
bf2b74c
Compare
bf2b74c to
0680f98
Compare
8a2baa6 to
8e21e1e
Compare
8e21e1e to
1675197
Compare
…viewer Squashed from 56 commits when rebasing onto the v3.3.3 main (originals on backup/pr-1064-2026-10-15): - docs: design family relationships as a graph with no family-tree object - feat(family): add the union, membership and access tables - fix(family): keep the union schema in step with its decorators - feat(family): add the family tree instance config block - feat(family): resolve effective family access from config and grant - feat(family): create, edit and delete unions with their validation rules - feat(family): deduplicate unions on their partner pair and start date - feat(family): derive relationship labels from the projected graph - fix(family): keep relationships intact across an identity merge - docs: correct the family-relationships spec against the codebase - feat(family): project the relationship graph onto what each viewer may see - feat(family): expose family relationships over the API - feat(family): derive relation labels over the API and add GET /family/me - feat(family): close five API gaps found by consuming UI slices - feat(family): extend familyRelationLabel to AssetResponseDto.people - feat(family): add the family tree admin settings section - feat(family): administer per-user family access grants - feat(family): show how each face relates to you in the asset viewer - feat(family): show a person's relationships on their person page - feat(family): add the family page with its cluster chips - feat(family): render the relationship graph around the viewer - feat(mobile): load family relationships from the server - feat(mobile): show a person's family on their person page - feat(mobile): show how each face relates to you in the asset viewer - fixup(mobile): render family relation labels via a person field, not a client-side join - fix(family): align the family UI with the generated client - feat(family): build the family graph by dragging people onto the canvas - feat(family): edit a union's status and dates from the canvas - feat(family): let a contributor actually edit the family canvas - test(family): prove the feature end to end - fix(family): accept UUID v7 identity ids in the DTO layer - fix(family): attach relation labels to the asset-viewer people strip - docs(family): document family relationships and guard the translations - style(family): format the i18n coverage spec - test(family): give the asset medium spec the config it now needs - fix(web): meet AA contrast on the page header description in dark mode - feat(family): let people start a family tree from an empty /family page - feat(family): draw the family canvas the way the mockup does - fix(family): make the canvas editable, legible and correctly connected - fix(family): join up the connectors and make the canvas's dead ends work - feat(family): open a person from their card, and stop clipping their label - test(family): reach the card before expecting its drop zones - fix(family): prune a union that removal has emptied out, and let one be deleted - fix(family): meet AA contrast on primary buttons in dark mode - chore: drop a scratch file committed by mistake - chore: drop a slice report that does not belong in the repo - fix(family): adapt to the v3.2.0 base's person_group and config changes - chore(api): regenerate the OpenAPI spec, TypeScript SDK and SQL docs - fix(mobile): finish the v3.2.0 migration in the family-relations strip - test(mobile): give the family fixtures the birthDate PersonResponseDto requires - fix(rebase): carry family relationships onto the v3.2.2 base - test(family): give the relations panel's person fixture the fields PersonResponseDto now requires - fix(family): satisfy the unicorn group-by and early-return lint rules in the family repository - test(family): pass the now-required onDelete prop in the union editor spec - fix(family): cover the family tables in the revert-to-immich guards and override cleanup - test(mobile): give the family API fixture the person fields PersonResponseDto now requires
Family's controller, repository and service join the gallery registry, its enum moves to src/gallery/enum.ts, its user foreign keys join the upstream FK registry, and person and space-asset access checks, media serving and person identity reads use main's fork seams.
Fork tables are listed in src/schema/gallery.ts on main, which is what the upstream-FK registry check reads.
Fork strings live in the i18n/gallery overlay on main.
ad8ce75 to
2f0182a
Compare
…ries family labels read
Gallery can now show how the people in your photos relate to you, not just who they are. You record two kinds of fact — unions (a partnership between people) and children (attached to a union, never to a single parent) — and every relative term is derived at read time: cousin, aunt, grandparent, step-parent, in-law, ex-wife. Nobody ever types the word "cousin".
There is deliberately no family-tree object. No document to own, name, share or delete. The graph is just a set of unions, and each viewer sees labels computed relative to themselves, so the same data reads as "your niece" for one person and "Anna's daughter" for another.
Design:
specs/2026-08-31-family-relationships-design-and-slices.md. User docs:docs/docs/features/family-relationships.md. Mockups:specs/mockups/2026-08-31-family-relationships.html.Access — off by default, two independent layers
Neither layer involves Shared Spaces.
familyTree.enabled+familyTree.defaultAccessin admin settingsfalse/nonefamily_accessrow overriding the instance defaultenabled: falseforcesnonefor everyone regardless of grants. A row withlevel: 'none'is a real override, so an admin can deny one user on an otherwise permissive instance.Admins get no implicit bypass. An admin who wants to read the graph grants themselves
viewlike anyone else, and that grant shows up in the same list as everybody else's. Silent admin visibility into family structure is exactly what this design exists to avoid.Write authority is the grant, not a space role. A space editor gains nothing from their space role; a
contributeuser may fix a union somebody else created, so a wrong relationship isn't stuck until its author gets around to it.createdByIdis kept for provenance.Visibility — the graph is projected per viewer
Access grants capability; content is scoped separately and automatically:
"Resolve" reuses the fork's existing profile resolution (your own
personrow, or ashared_space_personin a space you belong to) rather than re-deriving it. Hidden people don't resolve. Pets are rejected as participants at write time.Two rules in here are security requirements, not polish, and both are covered by medium tests:
identityId— leaking the id would let a client correlate the same hidden person across unions and across users, reconstructing the structure redaction is meant to withhold.deriveRelationLabeltakes an already-projected graph and never a repository, so it's hard to get this backwards.On a single-family instance everyone shares spaces, everyone resolves everyone, and this behaves indistinguishably from "granted users see everything". It only diverges on an instance with strangers on it — which is where it should.
Label derivation
Computed from the viewer's root — the identity they nominated as themselves, held in user preferences. Rules apply in order: no root → plain names; target is the root → "that's you"; a path exists → the relative term; no path → described via the nearest person the viewer can reach ("Pierre's sister"), which is what stops the feature feeling broken for someone marginal to the graph; beyond the supported degree → "relative", rather than attempting "third cousin twice removed".
Gender is never inferred — from a name, a photo, or anything else. Unset yields the neutral term. Ambiguity (someone who is both your cousin and your sister-in-law) resolves deterministically: shortest path, then a fixed precedence, then identity id, so it can't vary between requests. Union status drives ex-partner wording.
Surfaces
/familycanvas — the editing surface. Cluster chips, generation-banded layout, drag a person from the people rail onto a seat, edit a union's status and dates inline. Read-only forview, editable forcontribute, absent from the sidebar fornone.Schema
Four fork tables in
server/src/schema/migrations-gallery/1795000000000-AddFamilyRelationships.ts:family_union,family_union_partner,family_union_child,family_access. All four are inscripts/revert-to-immich.sqlwith the migration-row deletion, and thepartnerKeydeduplication carries the requiredmigration_overridesrow. Relationships survive an identity merge (identity-merge-propagation.service.ts), with a medium test proving it.Tests
family.service.spec.ts,family.controller.spec.ts,family-labels.spec.ts,family.dto.spec.ts, plus asset/person/config coverage.family-schema,family-write,family-visibility,family-identity-merge.e2e/src/specs/web/family.e2e-spec.ts.All nine required locales are translated in the same commits, guarded by
web/src/lib/i18n/family-i18n-coverage.spec.ts.https://claude.ai/code/session_011pxc3xiWnK74Fs6GAPns6r