The last SPA pre-work before deleting Blazor Libraries.razor (#91 phase b). Wires the already-shipped scanLibrary(id, deep) + scanCollections(family, id, deep) clients (F9 API, #235) into LibrariesScreen so the SPA reaches parity with Libraries.razor's four scan actions:
Action
Blazor
SPA (this PR)
Quick scan library
✅
✅ (already)
Deep scan library
✅
✅new — remote library rows only
Quick scan collections
✅
✅new — External Collections section
Deep scan collections
✅
✅new
How
Deep Scan Library: a ScanSearch button on each remote (Plex/Jellyfin/Emby) library row, threading deep through the existing optimistic-pending/scan-status-poll hook. Quick and deep share the per-library lock, so they gate on and reconcile through the same id set. Local libraries get no deep button (parity: Blazor shows it only for remote).
External Collections section: one row per remote source with sync-enabled libraries, quick + deep buttons calling scanCollections. Rows derive client-side from getMediaSources() — no new endpoint: GetAllMediaSourcesForApiHandler already filters each source's libraries to sync-enabled entries (ShouldIncludeLibrary = ShouldSyncItems for remote), so a remote source with a non-empty libraries list is exactlyGetExternalCollections's Libraries.Any(ShouldSyncItems) filter.
useCollectionsScan hook: collections scans have no scan-status poll surface (the endpoint is library-keyed; Blazor observed collections locks via in-process IEntityLocker events). So pending is optimistic + timeout-bounded — 409 "already scanning" is benign (stays disabled), 404/network surfaces the error. Follow-up #271 filed for a proper collections status surface.
Scope
Pure SPA change — no backend / no OpenAPI regen. The removal PR proper (rollback tag → surgical Blazor delete + catch-all fallback → auth cleanup → verify, with the mandatory cold adversarial pass) remains a separate focused follow-up; this PR only lands the capability so Libraries.razor can be deleted at parity.
Verification
web typecheck ✅, lint ✅ (0 warnings), full suite 674 tests pass✅ (8 new LibrariesScreen cases: deep-scan ?deep=true, remote-only deep button, collections row derivation incl. exclusions, quick/deep collections POST, 409-benign, 404-surfaces), production build ✅.
Live E2E on a running instance: to follow in review.
Docs
docs/blazor-route-parity.md §5 — SPA affordance marked DONE; removal PR's remaining pre-work is now just the cold adversarial pass.
## What
The last SPA pre-work before deleting Blazor `Libraries.razor` (#91 phase b). Wires the already-shipped `scanLibrary(id, deep)` + `scanCollections(family, id, deep)` clients (F9 API, #235) into `LibrariesScreen` so the SPA reaches parity with `Libraries.razor`'s **four** scan actions:
| Action | Blazor | SPA (this PR) |
|---|---|---|
| Quick scan library | ✅ | ✅ (already) |
| **Deep scan library** | ✅ | ✅ **new** — remote library rows only |
| **Quick scan collections** | ✅ | ✅ **new** — External Collections section |
| **Deep scan collections** | ✅ | ✅ **new** |
## How
- **Deep Scan Library**: a `ScanSearch` button on each remote (Plex/Jellyfin/Emby) library row, threading `deep` through the existing optimistic-pending/scan-status-poll hook. Quick and deep share the per-library lock, so they gate on and reconcile through the same id set. Local libraries get no deep button (parity: Blazor shows it only for remote).
- **External Collections section**: one row per remote source with sync-enabled libraries, quick + deep buttons calling `scanCollections`. **Rows derive client-side from `getMediaSources()`** — no new endpoint: `GetAllMediaSourcesForApiHandler` already filters each source's `libraries` to sync-enabled entries (`ShouldIncludeLibrary` = `ShouldSyncItems` for remote), so a remote source with a non-empty `libraries` list is *exactly* `GetExternalCollections`'s `Libraries.Any(ShouldSyncItems)` filter.
- **`useCollectionsScan` hook**: collections scans have **no** scan-status poll surface (the endpoint is library-keyed; Blazor observed collections locks via in-process `IEntityLocker` events). So pending is optimistic + timeout-bounded — 409 "already scanning" is benign (stays disabled), 404/network surfaces the error. Follow-up **#271** filed for a proper collections status surface.
## Scope
Pure SPA change — **no backend / no OpenAPI regen**. The removal PR proper (rollback tag → surgical Blazor delete + catch-all fallback → auth cleanup → verify, with the mandatory cold adversarial pass) remains a separate focused follow-up; this PR only lands the capability so `Libraries.razor` can be deleted at parity.
## Verification
- `web` typecheck ✅, lint ✅ (0 warnings), full suite **674 tests pass** ✅ (8 new LibrariesScreen cases: deep-scan `?deep=true`, remote-only deep button, collections row derivation incl. exclusions, quick/deep collections POST, 409-benign, 404-surfaces), production build ✅.
- Live E2E on a running instance: to follow in review.
## Docs
- `docs/blazor-route-parity.md` §5 — SPA affordance marked DONE; removal PR's remaining pre-work is now just the cold adversarial pass.
- `docs/decisions.md` — derive-vs-endpoint rationale + the optimistic-timeout decision (+ #271 follow-up).
Refs #91
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Last SPA pre-work before deleting Blazor Libraries.razor (#91 phase b):
wire the shipped scanLibrary(id, deep) + scanCollections(family, id, deep)
clients (F9 API, #235) into LibrariesScreen so the SPA reaches parity with
Libraries.razor's four scan actions.
- Deep Scan Library button on each remote (Plex/Jellyfin/Emby) library row,
threading `deep` through the existing optimistic-pending/poll hook (quick +
deep share the per-library lock).
- External Collections section (quick + deep per remote source). Rows derive
client-side from getMediaSources(): the media-sources API handler already
filters each source's `libraries` to sync-enabled entries, so a remote
source with a non-empty libraries list is exactly GetExternalCollections's
Libraries.Any(ShouldSyncItems) filter — no new endpoint.
- useCollectionsScan hook: collections scans have no scan-status poll surface
(the endpoint is library-keyed; Blazor observed collections locks via
in-process IEntityLocker events), so pending is optimistic + timeout-bounded
(409 benign, 404/network surfaces the error). Follow-up #271 for a proper
collections status surface.
Pure SPA change (no backend/OpenAPI). Docs: blazor-route-parity.md §5 (SPA
affordance DONE), decisions.md (derive-vs-endpoint + optimistic-timeout).
Refs #91
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
The last SPA pre-work before deleting Blazor
Libraries.razor(#91 phase b). Wires the already-shippedscanLibrary(id, deep)+scanCollections(family, id, deep)clients (F9 API, #235) intoLibrariesScreenso the SPA reaches parity withLibraries.razor's four scan actions:How
ScanSearchbutton on each remote (Plex/Jellyfin/Emby) library row, threadingdeepthrough the existing optimistic-pending/scan-status-poll hook. Quick and deep share the per-library lock, so they gate on and reconcile through the same id set. Local libraries get no deep button (parity: Blazor shows it only for remote).scanCollections. Rows derive client-side fromgetMediaSources()— no new endpoint:GetAllMediaSourcesForApiHandleralready filters each source'slibrariesto sync-enabled entries (ShouldIncludeLibrary=ShouldSyncItemsfor remote), so a remote source with a non-emptylibrarieslist is exactlyGetExternalCollections'sLibraries.Any(ShouldSyncItems)filter.useCollectionsScanhook: collections scans have no scan-status poll surface (the endpoint is library-keyed; Blazor observed collections locks via in-processIEntityLockerevents). So pending is optimistic + timeout-bounded — 409 "already scanning" is benign (stays disabled), 404/network surfaces the error. Follow-up #271 filed for a proper collections status surface.Scope
Pure SPA change — no backend / no OpenAPI regen. The removal PR proper (rollback tag → surgical Blazor delete + catch-all fallback → auth cleanup → verify, with the mandatory cold adversarial pass) remains a separate focused follow-up; this PR only lands the capability so
Libraries.razorcan be deleted at parity.Verification
webtypecheck ✅, lint ✅ (0 warnings), full suite 674 tests pass ✅ (8 new LibrariesScreen cases: deep-scan?deep=true, remote-only deep button, collections row derivation incl. exclusions, quick/deep collections POST, 409-benign, 404-surfaces), production build ✅.Docs
docs/blazor-route-parity.md§5 — SPA affordance marked DONE; removal PR's remaining pre-work is now just the cold adversarial pass.docs/decisions.md— derive-vs-endpoint rationale + the optimistic-timeout decision (+ #271 follow-up).Refs #91
🤖 Generated with Claude Code