feat(spaces): in-space album management + shared-space-album scope helper - #752
Merged
Conversation
Deeds67
force-pushed
the
space-albums-onto-main
branch
from
July 10, 2026 08:38
a117997 to
230b1db
Compare
Deeds67
added a commit
that referenced
this pull request
Jul 16, 2026
…lper (#752) Rebase of the 282-commit space-albums-onto-main branch onto v3.0.3 main, squashed to a single commit (net feature diff of 7c12f74..tip re-applied onto the force-pushed main). Reconciled the branch's #755 stack-in-space work against main's landed #782: - server addAssets/removeAssets: use main's #782 expandStackAssetIds (visibility-restricted, not owner-scoped) for stack atomicity, while keeping the branch's AssetShare permission (required now that album-linking makes the AssetRead space arm exploitable) and the branch's multi-path-aware face retention (getAssetIdsWithoutOtherSpacePath — keep faces for assets still reachable via a linked album/library). - mobile space timelines: combined the branch's album joins/showInTimeline with the stack collapse. - web space timeline: kept the branch's shell/snippet structure and re-added main's #782 withStacked badge prop. - docs: kept main's #782 Stacked Photos section. The branch's own getOwnedStackSiblingIds/getStackSiblingIdsInSpace repo helpers are now unused by the service (superseded by StackRepository.getStackedAssetIds) but retained with their direct repo test — optional follow-up cleanup.
Deeds67
force-pushed
the
space-albums-onto-main
branch
from
July 16, 2026 07:53
d84a451 to
acc4656
Compare
Deeds67
added a commit
that referenced
this pull request
Jul 16, 2026
…lper (#752) Rebase of the 282-commit space-albums-onto-main branch onto v3.0.3 main, squashed to a single commit (net feature diff of 7c12f74..tip re-applied onto the force-pushed main). Reconciled the branch's #755 stack-in-space work against main's landed #782: - server addAssets/removeAssets: use main's #782 expandStackAssetIds (visibility-restricted, not owner-scoped) for stack atomicity, while keeping the branch's AssetShare permission (required now that album-linking makes the AssetRead space arm exploitable) and the branch's multi-path-aware face retention (getAssetIdsWithoutOtherSpacePath — keep faces for assets still reachable via a linked album/library). - mobile space timelines: combined the branch's album joins/showInTimeline with the stack collapse. - web space timeline: kept the branch's shell/snippet structure and re-added main's #782 withStacked badge prop. - docs: kept main's #782 Stacked Photos section. The branch's own getOwnedStackSiblingIds/getStackSiblingIdsInSpace repo helpers are now unused by the service (superseded by StackRepository.getStackedAssetIds) but retained with their direct repo test — optional follow-up cleanup.
Deeds67
force-pushed
the
space-albums-onto-main
branch
from
July 16, 2026 08:15
acc4656 to
82dd112
Compare
Deeds67
added a commit
that referenced
this pull request
Jul 16, 2026
…B1 / consistency sweep / S5 / S7) Resolves the confirmed launch blocker and the read-vs-machinery asymmetry surfaced by the multi-agent audit of #752. The read/timeline layer already unions BOTH linked-album arms (album_asset OR the cross-owner album_space_asset contribution, #764) via spaceAlbumAssetExists; retention, count, reconcile, and thumbnail-eligibility joined only album_asset. This teaches them the same union so a contributed asset is treated consistently everywhere it is visible. - B1 (blocker) — face retention: getAssetIdsWithoutOtherSpacePath / getAlbumAssetIdsWithoutOtherSpacePath route the album arm through spaceAlbumAssetExists (both arms). Removing a direct add (or unlinking) no longer sweeps the space person for an asset still reachable via a cross-owner contribution while it remains visible in the timeline. New medium test: contributed-path retention. - S1 / S6 (consistency sweep): the reconcile pager (getAssetIdsInSpacePage) now enumerates album + contributed assets so the face-projection fallback can (re)cover them; getAssetCount / getRecentAssets / getLastAssetAddedAt / isAssetInSpace now include contributions (count/cover/ thumbnail eligibility agree with the timeline). New medium test: count + pager. - S5 — remove-from-space: removeAssets bounds stack-atomic expansion to DIRECT members (getDirectAssetIds), so removing an album-projected asset can never drag a directly-added stack sibling out of the space; it now returns exactly the ids it removed, and the web RemoveFromSpaceAction reflects the real result (no false-success toast / optimistic hide of album-only assets). New medium test: album-only removal does not remove a direct stack sibling. - S7 — rebase cleanup: deleted the dead getOwnedStackSiblingIds / getStackSiblingIdsInSpace repo helpers (superseded by StackRepository.getStackedAssetIds) and their dead medium spec (the live path is covered by shared-space-stacks.spec.ts + the new S5 test); updated the scope guard (registered the contributed helper, swapped the allowlist entry). - Regenerated SQL + OpenAPI (spec, TS SDK, Dart client) for the removeAssets return type and repo changes. Deferred to triage (design decisions, not in this change): S2 (real-time projection of contributed faces), S3 (mobile sync spaceId correlation + revocation), S4 (space-editor album-write RBAC scoping).
Deeds67
added a commit
that referenced
this pull request
Jul 16, 2026
…B1 / consistency sweep / S5 / S7) Resolves the confirmed launch blocker and the read-vs-machinery asymmetry surfaced by the multi-agent audit of #752. The read/timeline layer already unions BOTH linked-album arms (album_asset OR the cross-owner album_space_asset contribution, #764) via spaceAlbumAssetExists; retention, count, reconcile, and thumbnail-eligibility joined only album_asset. This teaches them the same union so a contributed asset is treated consistently everywhere it is visible. - B1 (blocker) — face retention: getAssetIdsWithoutOtherSpacePath / getAlbumAssetIdsWithoutOtherSpacePath route the album arm through spaceAlbumAssetExists (both arms). Removing a direct add (or unlinking) no longer sweeps the space person for an asset still reachable via a cross-owner contribution while it remains visible in the timeline. New medium test: contributed-path retention. - S1 / S6 (consistency sweep): the reconcile pager (getAssetIdsInSpacePage) now enumerates album + contributed assets so the face-projection fallback can (re)cover them; getAssetCount / getRecentAssets / getLastAssetAddedAt / isAssetInSpace now include contributions (count/cover/ thumbnail eligibility agree with the timeline). New medium test: count + pager. - S5 — remove-from-space: removeAssets bounds stack-atomic expansion to DIRECT members (getDirectAssetIds), so removing an album-projected asset can never drag a directly-added stack sibling out of the space; it now returns exactly the ids it removed, and the web RemoveFromSpaceAction reflects the real result (no false-success toast / optimistic hide of album-only assets). New medium test: album-only removal does not remove a direct stack sibling. - S7 — rebase cleanup: deleted the dead getOwnedStackSiblingIds / getStackSiblingIdsInSpace repo helpers (superseded by StackRepository.getStackedAssetIds) and their dead medium spec (the live path is covered by shared-space-stacks.spec.ts + the new S5 test); updated the scope guard (registered the contributed helper, swapped the allowlist entry). - Regenerated SQL + OpenAPI (spec, TS SDK, Dart client) for the removeAssets return type and repo changes. Deferred to triage (design decisions, not in this change): S2 (real-time projection of contributed faces), S3 (mobile sync spaceId correlation + revocation), S4 (space-editor album-write RBAC scoping).
Deeds67
force-pushed
the
space-albums-onto-main
branch
from
July 16, 2026 10:54
09dfeb5 to
fb709e4
Compare
Deeds67
added a commit
that referenced
this pull request
Jul 16, 2026
…B1 / consistency sweep / S5 / S7) Resolves the confirmed launch blocker and the read-vs-machinery asymmetry surfaced by the multi-agent audit of #752. The read/timeline layer already unions BOTH linked-album arms (album_asset OR the cross-owner album_space_asset contribution, #764) via spaceAlbumAssetExists; retention, count, reconcile, and thumbnail-eligibility joined only album_asset. This teaches them the same union so a contributed asset is treated consistently everywhere it is visible. - B1 (blocker) — face retention: getAssetIdsWithoutOtherSpacePath / getAlbumAssetIdsWithoutOtherSpacePath route the album arm through spaceAlbumAssetExists (both arms). Removing a direct add (or unlinking) no longer sweeps the space person for an asset still reachable via a cross-owner contribution while it remains visible in the timeline. New medium test: contributed-path retention. - S1 / S6 (consistency sweep): the reconcile pager (getAssetIdsInSpacePage) now enumerates album + contributed assets so the face-projection fallback can (re)cover them; getAssetCount / getRecentAssets / getLastAssetAddedAt / isAssetInSpace now include contributions (count/cover/ thumbnail eligibility agree with the timeline). New medium test: count + pager. - S5 — remove-from-space: removeAssets bounds stack-atomic expansion to DIRECT members (getDirectAssetIds), so removing an album-projected asset can never drag a directly-added stack sibling out of the space; it now returns exactly the ids it removed, and the web RemoveFromSpaceAction reflects the real result (no false-success toast / optimistic hide of album-only assets). New medium test: album-only removal does not remove a direct stack sibling. - S7 — rebase cleanup: deleted the dead getOwnedStackSiblingIds / getStackSiblingIdsInSpace repo helpers (superseded by StackRepository.getStackedAssetIds) and their dead medium spec (the live path is covered by shared-space-stacks.spec.ts + the new S5 test); updated the scope guard (registered the contributed helper, swapped the allowlist entry). - Regenerated SQL + OpenAPI (spec, TS SDK, Dart client) for the removeAssets return type and repo changes. Deferred to triage (design decisions, not in this change): S2 (real-time projection of contributed faces), S3 (mobile sync spaceId correlation + revocation), S4 (space-editor album-write RBAC scoping).
Deeds67
force-pushed
the
space-albums-onto-main
branch
from
July 16, 2026 11:03
fb709e4 to
f33c828
Compare
Deeds67
added a commit
that referenced
this pull request
Jul 16, 2026
…B1 / consistency sweep / S5 / S7) Resolves the confirmed launch blocker and the read-vs-machinery asymmetry surfaced by the multi-agent audit of #752. The read/timeline layer already unions BOTH linked-album arms (album_asset OR the cross-owner album_space_asset contribution, #764) via spaceAlbumAssetExists; retention, count, reconcile, and thumbnail-eligibility joined only album_asset. This teaches them the same union so a contributed asset is treated consistently everywhere it is visible. - B1 (blocker) — face retention: getAssetIdsWithoutOtherSpacePath / getAlbumAssetIdsWithoutOtherSpacePath route the album arm through spaceAlbumAssetExists (both arms). Removing a direct add (or unlinking) no longer sweeps the space person for an asset still reachable via a cross-owner contribution while it remains visible in the timeline. New medium test: contributed-path retention. - S1 / S6 (consistency sweep): the reconcile pager (getAssetIdsInSpacePage) now enumerates album + contributed assets so the face-projection fallback can (re)cover them; getAssetCount / getRecentAssets / getLastAssetAddedAt / isAssetInSpace now include contributions (count/cover/ thumbnail eligibility agree with the timeline). New medium test: count + pager. - S5 — remove-from-space: removeAssets bounds stack-atomic expansion to DIRECT members (getDirectAssetIds), so removing an album-projected asset can never drag a directly-added stack sibling out of the space; it now returns exactly the ids it removed, and the web RemoveFromSpaceAction reflects the real result (no false-success toast / optimistic hide of album-only assets). New medium test: album-only removal does not remove a direct stack sibling. - S7 — rebase cleanup: deleted the dead getOwnedStackSiblingIds / getStackSiblingIdsInSpace repo helpers (superseded by StackRepository.getStackedAssetIds) and their dead medium spec (the live path is covered by shared-space-stacks.spec.ts + the new S5 test); updated the scope guard (registered the contributed helper, swapped the allowlist entry). - Regenerated SQL + OpenAPI (spec, TS SDK, Dart client) for the removeAssets return type and repo changes. Deferred to triage (design decisions, not in this change): S2 (real-time projection of contributed faces), S3 (mobile sync spaceId correlation + revocation), S4 (space-editor album-write RBAC scoping).
Deeds67
force-pushed
the
space-albums-onto-main
branch
from
July 16, 2026 11:21
f33c828 to
fdd8705
Compare
Deeds67
added a commit
that referenced
this pull request
Jul 16, 2026
Deeds67
added a commit
that referenced
this pull request
Jul 17, 2026
…on-bar z-index (#752) Removing an album-projected asset from a space is a no-op (it's present via a linked album, not a direct member), yet the web toasted "removed 0 assets" as a success. Add a member-gated endpoint that resolves which linked album(s) project the blocked assets, and show a clear message naming the album ("... is in the album X, which is linked to this space; remove it from the album instead"); only toast success for assets actually removed. Also move the space-album selection control bar outside UserPageLayout's z-0 stacking context (matching recently-added/favorites/etc.) so it no longer renders behind the timeline scrubber and the asset grid.
Deeds67
added a commit
that referenced
this pull request
Jul 17, 2026
…lper (#752) Rebase of the 282-commit space-albums-onto-main branch onto v3.0.3 main, squashed to a single commit (net feature diff of 7c12f74..tip re-applied onto the force-pushed main). Reconciled the branch's #755 stack-in-space work against main's landed #782: - server addAssets/removeAssets: use main's #782 expandStackAssetIds (visibility-restricted, not owner-scoped) for stack atomicity, while keeping the branch's AssetShare permission (required now that album-linking makes the AssetRead space arm exploitable) and the branch's multi-path-aware face retention (getAssetIdsWithoutOtherSpacePath — keep faces for assets still reachable via a linked album/library). - mobile space timelines: combined the branch's album joins/showInTimeline with the stack collapse. - web space timeline: kept the branch's shell/snippet structure and re-added main's #782 withStacked badge prop. - docs: kept main's #782 Stacked Photos section. The branch's own getOwnedStackSiblingIds/getStackSiblingIdsInSpace repo helpers are now unused by the service (superseded by StackRepository.getStackedAssetIds) but retained with their direct repo test — optional follow-up cleanup.
Deeds67
force-pushed
the
space-albums-onto-main
branch
from
July 17, 2026 16:51
ac2bade to
0acbabc
Compare
Deeds67
added a commit
that referenced
this pull request
Jul 17, 2026
…B1 / consistency sweep / S5 / S7) Resolves the confirmed launch blocker and the read-vs-machinery asymmetry surfaced by the multi-agent audit of #752. The read/timeline layer already unions BOTH linked-album arms (album_asset OR the cross-owner album_space_asset contribution, #764) via spaceAlbumAssetExists; retention, count, reconcile, and thumbnail-eligibility joined only album_asset. This teaches them the same union so a contributed asset is treated consistently everywhere it is visible. - B1 (blocker) — face retention: getAssetIdsWithoutOtherSpacePath / getAlbumAssetIdsWithoutOtherSpacePath route the album arm through spaceAlbumAssetExists (both arms). Removing a direct add (or unlinking) no longer sweeps the space person for an asset still reachable via a cross-owner contribution while it remains visible in the timeline. New medium test: contributed-path retention. - S1 / S6 (consistency sweep): the reconcile pager (getAssetIdsInSpacePage) now enumerates album + contributed assets so the face-projection fallback can (re)cover them; getAssetCount / getRecentAssets / getLastAssetAddedAt / isAssetInSpace now include contributions (count/cover/ thumbnail eligibility agree with the timeline). New medium test: count + pager. - S5 — remove-from-space: removeAssets bounds stack-atomic expansion to DIRECT members (getDirectAssetIds), so removing an album-projected asset can never drag a directly-added stack sibling out of the space; it now returns exactly the ids it removed, and the web RemoveFromSpaceAction reflects the real result (no false-success toast / optimistic hide of album-only assets). New medium test: album-only removal does not remove a direct stack sibling. - S7 — rebase cleanup: deleted the dead getOwnedStackSiblingIds / getStackSiblingIdsInSpace repo helpers (superseded by StackRepository.getStackedAssetIds) and their dead medium spec (the live path is covered by shared-space-stacks.spec.ts + the new S5 test); updated the scope guard (registered the contributed helper, swapped the allowlist entry). - Regenerated SQL + OpenAPI (spec, TS SDK, Dart client) for the removeAssets return type and repo changes. Deferred to triage (design decisions, not in this change): S2 (real-time projection of contributed faces), S3 (mobile sync spaceId correlation + revocation), S4 (space-editor album-write RBAC scoping).
Deeds67
added a commit
that referenced
this pull request
Jul 17, 2026
…on-bar z-index (#752) Removing an album-projected asset from a space is a no-op (it's present via a linked album, not a direct member), yet the web toasted "removed 0 assets" as a success. Add a member-gated endpoint that resolves which linked album(s) project the blocked assets, and show a clear message naming the album ("... is in the album X, which is linked to this space; remove it from the album instead"); only toast success for assets actually removed. Also move the space-album selection control bar outside UserPageLayout's z-0 stacking context (matching recently-added/favorites/etc.) so it no longer renders behind the timeline scrubber and the asset grid.
#752) Replace the collapsed filter panel's vertical icon rail with a single filter button in the timeline header. Collapsed, the panel now renders nothing so the photo grid reclaims the width; the header button (shared FilterToggleButton, with an active-filter dot) reopens it. Applied consistently across photos, album detail, space timeline, the space-album page, and the map — where the collapsed panel used to float over the map's own controls, the toggle now sits next to the "Map" title. Also: give each filter section more breathing room above its content, and turn the panel's collapse chevron into a close (x) icon. Backward-compatible via a new opt-in externalToggle prop, so the map's mobile drawer keeps the built-in button.
…hug the edge The People/Places/Recently-added sections sat directly in UserPageLayout's p-2 content, leaving them too close to the side. Add px-2 md:px-4 to each section.
…Toggle mode Collapsing an externalToggle panel unmounted it, so the transition-[width] had nothing to animate. Keep the shell mounted at w-0 instead (space still reclaimed, content inert while hidden) so the width transition glides open/close again.
…edge The sidebar's item container had pe-6 but no leading padding, leaving the nav items and active pills flush against the left edge. Add ps-2.
…#752) The space "Create album" flow dropped you into a nameless empty album with no way to title or describe it, and the list never refreshed its name/count afterward. Render the regular album page's editable AlbumTitle + description (owner-gated) as the space album's timeline header, auto-delete an abandoned empty album on navigate-away, and re-fetch the linked-album list on mount so names/counts stay current. Moves AlbumTitle/AlbumDescription to $lib/components/album-page so both pages share them.
…abandon (#752) The "linked album" activity captured the album name at link time — empty for the create-then-name flow and stale after a rename. Resolve the current name live from the stored album id, and drop link/unlink entries whose album no longer exists (an abandoned create-flow album auto-deleted on navigate-away). Also allowlist getLinkedAlbumsContainingAssets in the space-visibility guard (it returns album id/name only, no asset content) — a pre-existing gap surfaced by the guard spec.
Round the hover/active nav background on both sides (not just the right, per @immich/ui's rounded-e-full) and add more left inset so the pills sit off the edge.
… no scroll jump Widen the nav pills toward the edges (ps-2/pe-0). Reserve the scrollbar gutter so expanding Spaces/Albums no longer shrinks the pills when the thin scrollbar appears. And drop the expand/collapse caret's own hover background so hovering it highlights the parent item's pill instead.
The recent-spaces/albums sub-items carried 40px horizontal padding on both sides (px-10); the right 40px was dead space that truncated names early. Keep the ps-10 left indent but drop the right padding to pe-4, freeing ~24px of text.
After rebasing onto main's #733 merge-policy rewrite, the in-space merge now runs main's cross-owner authorizer AND records a PersonMerge activity (the fork's feature). #733's delegation tests didn't mock logActivity, and the strict auto-mock fails on any unmocked repository call — so mock it for those cases.
- svelte-check: coerce the bindable `collapsed` (boolean | undefined) to a boolean when persisting; the `=== undefined` init keeps the seed-from-storage path. - a11y/e2e: the collapse button used aria-label "Close", colliding with the album cover-selection "Close" (strict-mode violation in album.e2e). Label it "Collapse". - map e2e: the map filter panel now uses externalToggle, so collapsing surfaces the header FilterToggleButton instead of the removed collapsed-icon-strip.
…n redesign The collapsed filter panel is now an externalToggle header button (filter-toggle-btn) instead of the inline collapsed-icon-strip. Update the collapse/expand and badge-dot assertions accordingly: collapsing surfaces the header button, expanding clicks it, and the single active dot replaces the per-icon strip badges.
The /photos header-button redesign lets the collapsed filter panel reclaim its width (w-12 → w-0), shifting the timeline's total scroll height by a hair. With ~300 months in the fixture, a medium month's scrubber segment is only ~1.4px tall, so dragging to its centre resolves — under browser pixel hit-testing — to the target or an immediately adjacent month. That deterministically nudged the "2011-8" drag onto its neighbour "2011-9" (1px away), failing the exact-match assertion. Accept a landing within one month of the target: that is the achievable resolution of a sub-pixel scrubber segment, and it stays immune to future sub-pixel layout shifts while still catching gross mis-navigation (wrong direction, off-by-many, no scroll at all).
In month/year grouping the timeline renders large representative cards that hugged the memories carousel. Pad the carousel wrapper (day view untouched) so its measured height shifts the cards down for clear breathing room.
…ping At the very top the memories strip occupies the space above the first card, so getTimelineTopVisibleAnchor found no card and fell back to the first one — the grouping change then scrolled down to it, flashing the memories out of view. Return undefined when scrolled above the first card so the top position is kept.
The representative card showed its big centred title whenever it had no cover asset id — including the brief window before covers finish loading — so the label flashed in the centre then flicked to the bottom-left overlay. Treat the loading window as a neutral placeholder and reserve the centred title for a genuine image-load failure.
… collapse Switching All→Months/Years let the outgoing day thumbnails play their scale-out exit animation because updateOptions() only suspends transitions from a post-render effect — after the view swap had already created the exit transitions. Suspend transitions from an $effect.pre keyed on the grouping so the swap is instant; updateOptions() still clears the flag after the reload.
An external-library asset reachable through a space album — owned (album_asset) or a cross-owner contribution (album_space_asset) — was granted to space members even when offline, because only the shared_space_library arm gated `isOffline`. It is masked today (an offline transition also sets deletedAt, which the album arms check), but it leaned on that cross-module coupling. Add the isOffline guard to both album arms in checkSpaceAccess/checkSpaceAccessForSpace for parity, and add medium coverage for external-library assets in a space album (reachable online, blocked offline, contribution cascades away on asset delete).
…counts The Members-page per-member count (getContributionCounts) only counted the direct shared_space_asset pool, so photos a member brought in via linked albums, linked libraries, or cross-owner contributions were dropped — a member showed far fewer photos than they had contributed (e.g. 1,355 of 7,815). Mirror getAssetCount's four sources, attributing each asset to whoever performed the action (addedById) and UNION-deduping across paths. Adds medium coverage for album/library/ contribution attribution, off-timeline exclusion, and dedup.
New docs page covering linking/creating albums in a space, cross-owner contributions, per-album timeline control, and the roles/permissions table, plus a blurb and links from the Shared Spaces page.
Deeds67
force-pushed
the
space-albums-onto-main
branch
from
July 19, 2026 09:45
e45c8e3 to
5886917
Compare
* docs(plan): space albums launch fixes implementation plan (#752 follow-up) * fix(web): report remove-from-album results truthfully, prune only removed assets (#752) * fix(web): keep the space-album picker open and the grid untouched when adding photos fails (#752) * fix(web): make the collapsed filter panel reopenable on mobile viewports (#752) * fix(spaces): filter dead-album activity in SQL so feed pagination never dead-ends (#752) * fix(spaces): sweep faces of contribution-only assets on unlink/departure/album delete (#752) * fix(spaces): refresh surviving album grants on member re-join so absence-window contributions sync (#752) * fix(spaces): regenerate SQL query docs to match make sql output (#752) * fix(i18n): translate the new album/activity keys for fr/de/nl/pl/es/it (#752)
This was referenced Jul 22, 2026
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.
Space Albums for Shared Spaces (#752)
Adds first-class album support to Shared Spaces — link existing albums (or create new ones) into a space, browse and organise them in a dedicated Albums tab, and let members contribute across owners — all under the space's role-based access model. Along the way it also lands cross-owner contribution plumbing, a round of timeline/grouping UI polish, and several RBAC-hardening fixes.
High-level summary — the PR is large; see the commit history for the blow-by-blow.
Space albums
album_space_asset, distinct from owner-addedalbum_asset). Contributions render in the album grid and counts, are view/download-accessible to members, sync to mobile, and are tombstoned on unlink / re-delivered on re-link.showInTimeline) controls whether a linked album's photos appear in the aggregated space timeline — complementing the existing per-member timeline preference.shared-space-album-scopehelper routes space-album scoping through ~9 repositories (access / asset / search / timeline / map / memory / view / person-face / face-identity), replacing duplicated inline SQL — proven equivalent by real-DB characterization tests. Closes several soft-delete face-retention holes and enforcesalbum_asset/album_space_assetexclusivity.RBAC hardening
isOfflineparity on the space-album asset-access arms — offline external-library assets reached through a space album are now blocked for members, matching the library arm.Timeline & UI polish
/photosfilter panel collapses into a header filter button, letting the grid reclaim the width; Google-Photos-style sidebar nav pills.Also folded in
Testing
TDD throughout — server unit + real-DB medium tests (including the scope-equivalence characterization spec, the
isOfflineaccess parity, and per-source member counts), the full web component suite, and Playwright e2e journeys (search / sort / group / view / create / link / viewer gating). Regenerated SQL / OpenAPI / Dart clients.