fix(errors,a11y): review round 1 — drop redundant onErrors, stabilize the live region

Review found my C-3.23 additions double-notified: the global error modal (api.ts req)
already surfaces 400/409/422/500 with the backend's specific message, so a caller
toast on those codes is a second, weaker surface. Only 403/404 are caller-handled and
silent — and only a YouTube WRITE action has a meaningful 403 (missing scope).

So the correct set is just the ChannelPage unsubscribe handler (a YouTube write, 403 →
"connect", matching subscribe). Reverted the redundant onErrors on block (a local DB
op, not YouTube), SettingsPanel Plex watch toggle/reimport, and the DownloadCenter job
action — the global modal already speaks for them. Removed the three now-unused keys.

Toaster: moved the polite live region onto the stable, always-mounted container (a
polite region must pre-exist to announce later insertions); error/fatal toasts keep
role="alert", which announces on insertion regardless.
This commit is contained in:
2026-07-21 23:29:51 +02:00
parent d3e466774c
commit 6ed08383e7
10 changed files with 23 additions and 20 deletions
+11 -5
View File
@@ -16,17 +16,23 @@ export default function Toaster() {
const toasts = useSyncExternalStore(subscribe, getActiveToasts, getActiveToasts);
return (
<div className="absolute bottom-4 left-4 z-overlay flex flex-col gap-2 w-80 max-w-[calc(100vw-2rem)]">
// Stable, always-mounted polite live region — a polite region must PRE-EXIST in the DOM for a
// screen reader to announce content later inserted into it (a region created together with its
// first message is unreliably announced). Non-error toasts render inside here and are announced
// politely; error/fatal toasts additionally carry role="alert", which announces assertively on
// insertion regardless.
<div
role="status"
aria-live="polite"
className="absolute bottom-4 left-4 z-overlay flex flex-col gap-2 w-80 max-w-[calc(100vw-2rem)]"
>
{toasts.map((toast) => {
const { icon: Icon, color, bar } = LEVEL_STYLE[toast.level];
const urgent = toast.level === "error" || toast.level === "fatal";
return (
<div
key={toast.id}
// Announce toasts to screen readers — the toast is the app's main feedback channel.
// Errors interrupt (assertive/alert); everything else waits its turn (polite/status).
role={urgent ? "alert" : "status"}
aria-live={urgent ? "assertive" : "polite"}
{...(urgent ? { role: "alert" } : {})}
className="toast-card glass relative overflow-hidden rounded-xl px-3 py-3 flex items-start gap-3 animate-[popIn_0.16s_ease]"
>
<Icon className={`w-5 h-5 shrink-0 mt-0.5 ${color}`} />