fix(api): a gone row and a refused playlist action are said in the reader's language, and a failure is reported once

Deep-review round 3 of bug hunt 2026-09.

- get_or_404 / owned_or_404 answer a structured {code: "not_found", reason: <what>}
  (errors.notFound.* in HU+EN, generic fallback). The structured-404 fix of round 2 had gone into the
  ad-hoc `_own_playlist` (CLAUDE.md's "Not" pattern) while the shared helpers stayed string-only, so
  views, tags, pins, Plex playlists, invites and users still 404'd in English. `_own_playlist` now
  goes through owned_or_404; admin's hand-rolled user lookups use get_or_404.
- A move into a list that is gone names the TARGET (reason "move_target") — "this playlist doesn't
  exist any more" was read as the list on screen, which still exists.
- Every refusal in the playlists router is a {code: "playlist", reason} (playlists.errors.* HU+EN)
  instead of a bare English string shown verbatim in the Hungarian UI.
- `req` / `uploadReq` mark the errors they already put on screen (the error dialog, the connection
  notice, the 401 sign-out), and notifyActionError skips those. The watched toggles, the saved views
  and a playlist delete toasted a fault the dialog was already showing; Playlists' two hand-written
  status filters now go through the same check.
This commit is contained in:
2026-09-11 18:05:19 +02:00
parent 33fe895539
commit 1cfe6a7f59
22 changed files with 358 additions and 94 deletions
+7 -1
View File
@@ -1,4 +1,6 @@
import { HttpError } from "./api";
// From the core, not the `./api` barrel: these two are pure (no request), and a test that mocks the
// barrel wholesale to stub its requests must not take the error path's own plumbing with it.
import { alreadyReported, HttpError } from "./api/core";
import { notify } from "./notifications";
/**
@@ -9,6 +11,10 @@ import { notify } from "./notifications";
* for its non-403 case.
*/
export function notifyActionError(err: unknown, fallbackMessage: string): void {
// Already on screen (`req` opened the error dialog, or raised the connection notice): a toast on
// top reported one fault twice. Playlists filtered this by status by hand; the watched toggles,
// the saved views and a playlist delete did not.
if (alreadyReported(err)) return;
const detail = err instanceof HttpError ? err.detail : null;
notify({ level: "error", message: detail || fallbackMessage });
}