Repository navigation
Conversation
defts
force-pushed
the
feat/space-badge
branch
from
October 28, 2026 07:35
c8a2093 to
0e3e657
Compare
A thumbnail whose photo is in one of your spaces gets a badge in its top-left corner: the Spaces icon in the space's colour, or white with a count when the photo is in several spaces. It counts every path into a space and stays off a space's own pages, the trash and the locked folder. Web asks a new POST /assets/spaces in batches and drops its spaces list when a space is edited, so a new colour shows without a reload. Mobile reads its local database and syncs once the server has taken an add, a removal or an edit, so the badge follows a change made in the app. Design: specs/2026-10-26-space-badge-design.md
defts
force-pushed
the
feat/space-badge
branch
from
October 31, 2026 21:31
0e3e657 to
59e3d43
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.
Summary
Nothing on a thumbnail tells you whether a photo is in a space. With a space shown on your timeline, your own photos, the ones you shared and the other members' photos all look the same in Photos, and finding out means opening each one.
This adds a small badge in the top-left corner of a thumbnail whose photo is in at least one of your spaces, on web and mobile. It is drawn like the thumbnail's other marks (favourite, stack): an icon on the photo, slightly see-through and with a thin shadow, so it hints without covering the photo.
Every path into a space counts (added to it, in a linked album, in a linked library), including spaces and albums you've hidden from your timeline: the photo is still in them and their members still see it. Only spaces you're a member of count, and hidden, locked and trashed photos are in none.
The badge stays off a space's own pages (photos, people, map, search), the trash, the locked folder, shared links and the viewer's stack strip. The selection check keeps the corner: the badge fades out while a photo is selected, and on web while you hover it.
Design doc:
specs/2026-10-26-space-badge-design.md.Screenshots
How
POST /assets/spacesinGalleryAssetController, next toPOST /assets/editable. It takes 1 to 1000 asset IDs and returns, for each asset the caller can read, the IDs of the caller's spaces that show it; assets in none are left out, and a shared-link session gets nothing.SharedSpaceRepository.getMemberSpaceIdsByAssetIdscrosses the requested assets with the caller's memberships and keeps the pairssharedWithVieweradmits with no album gate. The query runs first and the access check only on its hits, since most thumbnails are in no space.web/src/lib/gallery/space-badge/:SpaceBadge.svelte, mounted fromThumbnail.svelte, and a small cache that batches what the mounted thumbnails ask (50 ms, 500 IDs per request). Answers are kept and shown until a fresh one replaces them, so badges don't blink: space and album changes made in the app ask again about the photos they name, and moving to another page asks again about every mounted thumbnail. Colours come from the spaces list the sidebar already shares, loaded only once a photo is in a space. Editing a space now emits aSpaceUpdateevent, which drops that list like the other space events do, so a new colour shows without a reload. IconmdiAccountGroup, as the sidebar's Spaces entry.mobile/lib/gallery/space_badge.{provider,widget}.dart, inThumbnailTile's indicator stack so it fades with the tile's other marks. No server call: one Drift query per tile over the same three paths, joined to the user's memberships and watched on the space tables, so it follows sync and works offline. IconIcons.workspaces, as the Spaces tab. The Space person timeline now has the space origin, since every photo on it is in the space. A change the app makes to a space reached those tables only at the next sync, so a photo added from the timeline, or a new colour, showed late:SharedSpaceApiRepositorynow asks for a sync once the server has taken an add, a removal or an edit (queued behind a sync already running), below the action layer so every surface gets it, as linked albums already do.docs/docs/features/shared-spaces.md.Known limits
Verification
asset-spaces.medium.spec.ts): each path on its own, two paths into one space, hidden spaces, links and albums, a contribution through one of two spaces, a deleted album, another user's space, archived photos in, hidden, locked and trashed ones out even for an elevated owner, a removed member, a shared link. The RBAC oracle snapshots the new read per actor. Unit tests for the route (1 to 1000 IDs) and the service.updateSpaceDetailsemitsSpaceUpdateonly on success. The e2e timeline mocks answer the new route.ThumbnailTilefading it out while selected, and the repository asking for a sync after an add, a removal or an edit, not after a refusal.tsc,svelte-check,dart analyzeand the i18n check clean.server/src/queries/shared.space.repository.sql, the OpenAPI spec and the SDK regenerated.Upstream files: grew by 6 lines
Against the Immich v3.3.5 tag:
web/src/lib/components/assets/thumbnail/Thumbnail.svelte<SpaceBadge>mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dartSpaceBadgein the indicator stackweb/src/lib/managers/event-manager.svelte.tsSpaceUpdateeventweb/src/lib/stores/user.svelte.tsSpaceUpdatedrops the spaces list, as the other space events doThe generated
open-api/immich-openapi-specs.jsonandpackages/sdk/src/fetch-client.tsgain the route. Everything else is in the fork namespaces, the fork-ownedshared-space.repository.tsand its SQL,shared_space_timeline.repository.dart, tests, docs and the spec. No new strings, no migration.