Skip to content

Per-viewer Jellyfin user selection and isolated session state - #231

Merged
arishaig merged 1 commit into
mainfrom
feat/jellyfin-user-selection
Oct 28, 2026
Merged

arishaig merged 1 commit into
mainfrom
feat/jellyfin-user-selection

Conversation

@arishaig

Copy link
Copy Markdown
Owner

Summary

WatchBack followed the first playing Jellyfin session, and kept the manual episode, prefetch state, sync trigger/gate and last-sync status in single global slots. With several people using one instance they overwrote each other.

  • User selection: GET /api/watchstate/users lists Jellyfin users. When there are 2+, each browser is prompted to pick one (saved in localStorage as wb_watchUser; a header button switches). With 0–1 users nothing changes.
  • Session lookup: the Jellyfin provider follows only the chosen user's session, cached per user. If that user isn't playing, the result is idle rather than someone else's episode. With no choice it keeps the old first-playing behaviour (API/MCP callers).
  • Viewer isolation: a viewer is the chosen Jellyfin user, else the browser's random client id (WatchViewer.Key). Manual watch state, prefetch state, "sync now" trigger, sync gate and last-sync status are scoped to the viewer, so browsers only affect each other when they are the same Jellyfin user. Prefetch eviction also spares cache entries another viewer is on or has warmed.
  • /api/sync, /api/sync/stream, /api/sync/trigger, /api/watchstate/manual and /api/diagnostics/status accept userId / clientId query params. The ISyncService.SyncAsync signature gains watchUserId and clientId.

Type of change

  • New feature (non-breaking change that adds functionality)

Checklist

  • Branched off the latest main (main has not moved)
  • dotnet build passes with no new warnings
  • dotnet test and npm test pass (CI filter: 58 + 237 + 158 backend, 365 frontend)
  • Ran dotnet format on the changed files only
  • New user-facing strings added to .resx files (en + es)
  • Docs not updated

Notes for reviewers

  • Not verified against a real Jellyfin or in a browser. Unit and integration tests cover the filtering, the viewer isolation and the picker logic. The LiveIntegrationTests fail locally because they target a hard-coded unreachable Jellyfin address; that is unrelated.
  • Trade-off: the sync gate is now per viewer, so two different viewers on the same episode can fetch thoughts at the same moment instead of the second hitting a warm cache.
  • Left shared on purpose: the thought/ratings cache (keyed by episode, public data) and the persisted sync log table (the server's activity record). Making the log per viewer would need a migration.
  • /Users also returns disabled/hidden accounts, so the picker may list service accounts. No filter yet.
  • Per-viewer trigger/gate entries are small and aren't cleaned up when a viewer disconnects.
  • app.js / app.bundle.css are rebuilt; the ionicons churn from the Vite build was reverted.

🤖 Generated with Claude Code

WatchBack followed the first playing Jellyfin session and kept the manual
episode, prefetch state, sync trigger/gate and last-sync status in single
global slots, so concurrent viewers overwrote each other.

- List Jellyfin users (GET /api/watchstate/users) and prompt each browser
  to pick one when there are several; the choice is kept in localStorage.
- The Jellyfin provider follows only the chosen user's session (cached
  per user); with no choice it keeps the old first-playing behaviour.
- A viewer is the chosen user, else the browser's random client id
  (WatchViewer.Key). Manual watch state, prefetch state, sync trigger,
  sync gate and last-sync status are all scoped to the viewer, so browsers
  only affect each other when they are the same Jellyfin user.
- Prefetch eviction spares cache entries another viewer is on or warmed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@arishaig
arishaig merged commit bec85e9 into main Oct 28, 2026
9 checks passed
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.

2 participants