Skip to content

feat: client-side persistence of the slideshow queue and history#664

Open
Zaida-3dO wants to merge 1 commit into
immichFrame:mainfrom
Zaida-3dO:feature/client-persist-asset-queue
Open

feat: client-side persistence of the slideshow queue and history#664
Zaida-3dO wants to merge 1 commit into
immichFrame:mainfrom
Zaida-3dO:feature/client-persist-asset-queue

Conversation

@Zaida-3dO

@Zaida-3dO Zaida-3dO commented Jul 3, 2026

Copy link
Copy Markdown

Summary

Adds an opt-in setting that lets the web client persist its slideshow state to the browser's localStorage, so a refresh — or the frame's built-in error-reload (window.location.reload()) — resumes exactly where it left off instead of re-fetching a fresh random batch.

  • ClientPersistAssets — persists the pending asset queue, the currently-displayed asset(s), and the "previous" history so back-navigation survives a reload.

Defaults to false (fully backward compatible).

Server session ID

Persisted asset IDs are only meaningful to the server instance that handed them out — the server's asset-routing tracker (BloomFilter) is rebuilt on restart, after which it can no longer resolve those IDs. To handle this, the server now generates a session ID at startup and exposes it in the client config; when the client sees it change, it clears its persisted assets before restoring.

Alternative considered

Instead of storing the queue on the client, we could have the server keep an asset history and asset queue per connected client. This would also have the added benefit of potentially allowing the server to dedupe across asset batches if a user wanted to. However, this would require a way to uniquely identify clients across restarts and I can't think of a clean way to do that.

Summary by CodeRabbit

  • New Features

    • Added an option to keep the current asset queue and browsing state after refreshes.
    • The app now tracks a server session ID to detect restarts and safely reset saved client state.
  • Bug Fixes

    • Improved resume behavior so saved assets, history, and currently displayed items restore consistently.
    • Prevents stale saved state from being reused after the server restarts.
  • Documentation

    • Updated configuration examples and docs to include the new persistence setting.

Persist the asset backlog, history, and currently-displayed assets to the
browser's localStorage so a refresh (or the frame's error-reload) resumes in
place instead of re-fetching a fresh batch.

- Add a ClientPersistAssets general setting (V2 and legacy V1 config) that
  gates the persisted stores
- Add a server session ID generated at startup, exposed via the client config;
  clients clear their persisted assets when it changes, because the server's
  asset-routing tracker (BloomFilter) resets on restart and can no longer
  resolve stale persisted assets
- Weave persist/restore into the v3 slideshow engine (home-page.svelte)
- Persisted reads fall back to the default when the stored value is missing or corrupt
- Update config-loader test resources for the new setting
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a ClientPersistAssets server-side configuration flag propagated through settings interfaces, JSON/YAML config, and ClientSettingsDto. Introduces a ServerSession singleton exposing a startup session id, wired into ConfigController. The web client persists and restores its asset backlog/history/displaying queue in localStorage, detecting server restarts via session id comparison.

Changes

Client Persist Assets feature

Layer / File(s) Summary
Server settings contract
ImmichFrame.Core/Interfaces/IServerSettings.cs, ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs, ImmichFrame.WebApi/Models/ServerSettings.cs, ImmichFrame.WebApi.Tests/Resources/*, docker/Settings.example.*, docs/docs/getting-started/configuration.md
Adds ClientPersistAssets boolean (default false) to IGeneralSettings, ServerSettingsV1/adapter, GeneralSettings, test fixtures, example configs, and documentation.
Server session tracking
ImmichFrame.WebApi/Helpers/ServerSession.cs, ImmichFrame.WebApi/Program.cs
New ServerSession class generates a startup-unique SessionId, registered as a DI singleton.
DTO and controller wiring
ImmichFrame.WebApi/Models/ClientSettingsDto.cs, ImmichFrame.WebApi/Controllers/ConfigController.cs, immichFrame.Web/src/lib/immichFrameApi.ts
ClientSettingsDto gains ClientPersistAssets/ServerSessionId; FromGeneralSettings accepts a session id parameter; ConfigController injects ServerSession and passes its id; TypeScript typings updated to match.
Client persistence stores
immichFrame.Web/src/lib/stores/persist.store.ts
Adds loadPersistedArray, persistArrayStore, clearPersistedStore, and exported stores for session id, backlog, history, and displaying assets.
Home page persistence/restore flow
immichFrame.Web/src/lib/components/home-page/home-page.svelte
Persists backlog/history/displaying assets during transitions via a unified showAssets path; on mount detects server restarts by comparing session ids, clearing or restoring persisted queue state accordingly.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: JW-CH

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding client-side persistence for the slideshow queue and history.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
ImmichFrame.WebApi/Helpers/ServerSession.cs (1)

8-11: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Session id is per-process, not per-cluster.

SessionId is generated once per process instance. In a horizontally-scaled deployment without sticky routing, each replica would hand out a different id, causing clients to treat every request as a "server restart" and repeatedly clear persisted state. Given ImmichFrame's typical single-instance self-hosted deployment, this is a low-probability edge case, but worth a doc note if multi-instance/HA deployments are ever supported.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ImmichFrame.WebApi/Helpers/ServerSession.cs` around lines 8 - 11, The
ServerSession.SessionId value is generated per process, so in multi-replica
deployments it can change across requests and look like a restart. Update the
ServerSession class or its usage to document that SessionId is process-scoped,
not cluster-scoped, and note the limitation wherever the session identity is
exposed or relied on so future HA/multi-instance support doesn’t assume a stable
cluster-wide id.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@immichFrame.Web/src/lib/stores/persist.store.ts`:
- Around line 36-44: Wrap the localStorage write in persistArrayStore’s
subscribe callback in a try/catch so quota failures don’t escape synchronously.
Update persistArrayStore to catch errors from localStorage.setItem, and handle
them gracefully (for example by logging or ignoring) while keeping
assetBacklogStore, assetHistoryStore, and displayingAssetsStore transitions
alive.

---

Nitpick comments:
In `@ImmichFrame.WebApi/Helpers/ServerSession.cs`:
- Around line 8-11: The ServerSession.SessionId value is generated per process,
so in multi-replica deployments it can change across requests and look like a
restart. Update the ServerSession class or its usage to document that SessionId
is process-scoped, not cluster-scoped, and note the limitation wherever the
session identity is exposed or relied on so future HA/multi-instance support
doesn’t assume a stable cluster-wide id.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3fe47f3b-3cfe-4b5e-951b-81f16e58be19

📥 Commits

Reviewing files that changed from the base of the PR and between 682fdc4 and f1f278d.

📒 Files selected for processing (16)
  • ImmichFrame.Core/Interfaces/IServerSettings.cs
  • ImmichFrame.WebApi.Tests/Resources/TestV1.json
  • ImmichFrame.WebApi.Tests/Resources/TestV2.json
  • ImmichFrame.WebApi.Tests/Resources/TestV2.yml
  • ImmichFrame.WebApi/Controllers/ConfigController.cs
  • ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs
  • ImmichFrame.WebApi/Helpers/ServerSession.cs
  • ImmichFrame.WebApi/Models/ClientSettingsDto.cs
  • ImmichFrame.WebApi/Models/ServerSettings.cs
  • ImmichFrame.WebApi/Program.cs
  • docker/Settings.example.json
  • docker/Settings.example.yml
  • docs/docs/getting-started/configuration.md
  • immichFrame.Web/src/lib/components/home-page/home-page.svelte
  • immichFrame.Web/src/lib/immichFrameApi.ts
  • immichFrame.Web/src/lib/stores/persist.store.ts

Comment on lines +36 to +44
function persistArrayStore<T>(key: string, defaultValue: T[]) {
const store = writable(loadPersistedArray(key, defaultValue));

store.subscribe((value) => {
localStorage?.setItem(key, JSON.stringify(value));
});

return store;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does localStorage.setItem throw QuotaExceededError when storage limit is exceeded?

💡 Result:

Yes, localStorage.setItem throws a QuotaExceededError when the storage limit is exceeded [1][2][3]. According to the HTML Standard and Web Storage API specifications, if a browser cannot set a new value—either because the storage quota has been reached or because storage has been disabled for the site—it must throw a QuotaExceededError [2][3][4]. To handle this, it is recommended to always wrap calls to localStorage.setItem in a try...catch block [1][5][6]. When implementing error handling, note the following: - In modern browsers, this error is typically represented as a DOMException with the name QuotaExceededError [7]. - Some older browser implementations or specific conditions (such as private browsing mode) may exhibit variations in how this error is surfaced or named (e.g., NS_ERROR_DOM_QUOTA_REACHED in older Firefox versions) [8][9]. - Because localStorage might be unavailable even if the feature check succeeds (e.g., due to user settings or private browsing restrictions), a robust practice is to test for storage availability by attempting to set and then remove a test item before use [10].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file and related callers/usages.
git ls-files | rg 'immichFrame\.Web/src/lib/stores/persist\.store\.ts|immichFrame\.Web/src/lib/stores|persistHistory|persistBacklog|getNextAssets|handleDone|AssetResponseDto'

echo '--- persist.store.ts ---'
cat -n immichFrame.Web/src/lib/stores/persist.store.ts

echo '--- search for persistArrayStore usage ---'
rg -n "persistArrayStore|persistHistory|persistBacklog|localStorage\.?\.setItem|AssetResponseDto" immichFrame.Web/src -A 4 -B 4

echo '--- any explicit storage quota handling in repo ---'
rg -n "QuotaExceededError|quota|localStorage" immichFrame.Web/src -A 2 -B 2

Repository: immichFrame/ImmichFrame

Length of output: 31658


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- AssetResponseDto shape ---'
sed -n '90,170p' immichFrame.Web/src/lib/immichFrameApi.ts

echo '--- home-page persistence flow ---'
sed -n '140,305p' immichFrame.Web/src/lib/components/home-page/home-page.svelte

Repository: immichFrame/ImmichFrame

Length of output: 6809


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "handleDone\\(" immichFrame.Web/src/lib/components/home-page/home-page.svelte -A 3 -B 3

Repository: immichFrame/ImmichFrame

Length of output: 1408


Wrap the localStorage.setItem write in a try/catch.
assetBacklogStore, assetHistoryStore, and displayingAssetsStore persist full AssetResponseDto[] payloads, so a large queue can hit the localStorage quota and throw synchronously inside subscribe(). That rejection will escape persistBacklog()/persistHistory() and abort the transition flow instead of degrading gracefully.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@immichFrame.Web/src/lib/stores/persist.store.ts` around lines 36 - 44, Wrap
the localStorage write in persistArrayStore’s subscribe callback in a try/catch
so quota failures don’t escape synchronously. Update persistArrayStore to catch
errors from localStorage.setItem, and handle them gracefully (for example by
logging or ignoring) while keeping assetBacklogStore, assetHistoryStore, and
displayingAssetsStore transitions alive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant