fix(spa): gate mutation controls + add-all on stale result sets during refetch (fixes #221)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m11s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 13s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m56s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped

Search and Media browse keep the previous successful result set rendered
during a refetch (query on Search; kind/query/page on Media browse) with no
gating, so per-card Add-to, Select/select-mode, the selection action bar, Add
all, and Save-as-smart-collection stayed live over stale, about-to-be-replaced
items. Worst path: SearchScreen.addAll only checked activeRef, so a late
GET /api/search/all-items could open a bulk-add dialog scoped to the previous
query's entire result set.

Key the success state to the request params that produced it and derive a
`refreshing` flag; while refreshing, keep cards visible but disable every
mutation surface, show a "Refreshing…" cue, and dim the grid. Card navigation
stays live. Bind addAll's completion to its query via lastQueryRef so a stale
all-items result is discarded. Same pattern applied to both screens.

Docs: spa-conventions §3a (refreshing/gating pattern) + §8 (temporal-semantics
review checklist); blazor-route-parity search/media-browse verdicts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 23:27:20 +02:00
co-authored by Claude Fable 5
parent 8b77d5e739
commit 81ddd4e5ca
7 changed files with 280 additions and 17 deletions
+8
View File
@@ -2509,6 +2509,14 @@ body {
gap: var(--space-6, 12px);
}
/* Dim the stale result set while a refetch is in flight (search/media browse — issue #221). The
per-card mutation menu is withheld in the same state; navigation stays live so no pointer-events
change here. */
.ctv-media-grid-dim {
opacity: 0.5;
transition: opacity var(--dur-fast, 120ms) var(--ease-standard, ease);
}
.ctv-media-card {
position: relative;
border-radius: var(--radius-sm);