fix(api): a write that hit a connection failure is still reported, and every failure is reported once

Deep-review round 4, the "one report per failure" contract round 3 introduced.

- `req` marked gateway (502/503/504) and network failures "already reported" because they raise
  the connection notice. But that notice is about connectivity and removes itself on the very next
  answer — which every caller's own resync refetch fetches at once — so a delete that hit a
  keepalive reset flashed the notice and was then reported nowhere, the list still there. Only the
  durable reports (the error dialog, the 401 sign-out) count now; the caller says the action failed.
- A connection notice the user closed swallowed every later one for the rest of the visit; it is
  raised again.
- `req` and `uploadReq` share one failure policy (`reportFailure`). The upload copy had drifted: a
  502 opened the blocking "Server error (502)" dialog and cleared a notice that was still true.
- A taken Plex collection name is a structured 409, localized, and the create/rename calls are
  quiet: it was an English dialog plus a Hungarian toast. `notifyCollectionEditError` is gone.
- The mutation onErrors that toasted unconditionally after a non-quiet request (invite approve /
  deny / add, demo whitelist, audit clear, scheduler, share, full history, edit, config tests, tag
  attach) go through `notifyActionError`; the playlist delete's hand-rolled copy of it too (it takes
  a level now). The user role / suspend / delete actions had no onError at all: a user deleted in
  another tab failed silently and stayed listed — now said, and the list refetched.
This commit is contained in:
2026-09-11 23:27:48 +02:00
parent 6a92585098
commit 6d49da17fe
22 changed files with 328 additions and 137 deletions
+13 -8
View File
@@ -505,15 +505,19 @@ export const plexApi = {
return req(`/api/plex/collections/${encodeURIComponent(rk)}${qs ? `?${qs}` : ""}`);
},
// --- Collection editing (admin only; writes back to Plex) ---
// `quiet`: the editor reports a failure itself, as a toast beside the name being typed (a taken
// name comes back structured and localized — see localizeDetail). Not quiet, it was reported twice:
// the global error dialog in English AND the editor's toast.
plexCreateCollection: (
library: string,
title: string,
item_rating_key: string,
): Promise<PlexCollection> =>
req(`/api/plex/collections`, {
method: "POST",
body: JSON.stringify({ library, title, item_rating_key }),
}),
req(
`/api/plex/collections`,
{ method: "POST", body: JSON.stringify({ library, title, item_rating_key }) },
{ quiet: true },
),
plexCollectionAddItem: (rk: string, itemRk: string): Promise<PlexCollection> =>
req(`/api/plex/collections/${encodeURIComponent(rk)}/items/${encodeURIComponent(itemRk)}`, {
method: "POST",
@@ -523,10 +527,11 @@ export const plexApi = {
method: "DELETE",
}),
plexRenameCollection: (rk: string, title: string): Promise<PlexCollection> =>
req(`/api/plex/collections/${encodeURIComponent(rk)}`, {
method: "PATCH",
body: JSON.stringify({ title }),
}),
req(
`/api/plex/collections/${encodeURIComponent(rk)}`,
{ method: "PATCH", body: JSON.stringify({ title }) },
{ quiet: true }, // as plexCreateCollection
),
plexDeleteCollection: (rk: string): Promise<{ deleted: string }> =>
req(`/api/plex/collections/${encodeURIComponent(rk)}`, { method: "DELETE" }),
plexSetCollectionEditable: (rk: string, editable: boolean): Promise<PlexCollection> =>