diff --git a/docs/qualification/recovery-feedback/1440-destinations-dark.png b/docs/qualification/recovery-feedback/1440-destinations-dark.png
new file mode 100644
index 000000000..0501a06ee
Binary files /dev/null and b/docs/qualification/recovery-feedback/1440-destinations-dark.png differ
diff --git a/docs/qualification/recovery-feedback/1440-destinations-light.png b/docs/qualification/recovery-feedback/1440-destinations-light.png
new file mode 100644
index 000000000..0bcaf2e48
Binary files /dev/null and b/docs/qualification/recovery-feedback/1440-destinations-light.png differ
diff --git a/docs/qualification/recovery-feedback/1440-overview-dark.png b/docs/qualification/recovery-feedback/1440-overview-dark.png
new file mode 100644
index 000000000..2d12ba7da
Binary files /dev/null and b/docs/qualification/recovery-feedback/1440-overview-dark.png differ
diff --git a/docs/qualification/recovery-feedback/1440-overview-light.png b/docs/qualification/recovery-feedback/1440-overview-light.png
new file mode 100644
index 000000000..63a7d7aba
Binary files /dev/null and b/docs/qualification/recovery-feedback/1440-overview-light.png differ
diff --git a/docs/qualification/recovery-feedback/390-destinations-dark.png b/docs/qualification/recovery-feedback/390-destinations-dark.png
new file mode 100644
index 000000000..14e7b6331
Binary files /dev/null and b/docs/qualification/recovery-feedback/390-destinations-dark.png differ
diff --git a/docs/qualification/recovery-feedback/390-destinations-light.png b/docs/qualification/recovery-feedback/390-destinations-light.png
new file mode 100644
index 000000000..41af585e6
Binary files /dev/null and b/docs/qualification/recovery-feedback/390-destinations-light.png differ
diff --git a/docs/qualification/recovery-feedback/390-overview-dark.png b/docs/qualification/recovery-feedback/390-overview-dark.png
new file mode 100644
index 000000000..81846381e
Binary files /dev/null and b/docs/qualification/recovery-feedback/390-overview-dark.png differ
diff --git a/docs/qualification/recovery-feedback/390-overview-light.png b/docs/qualification/recovery-feedback/390-overview-light.png
new file mode 100644
index 000000000..ad7068fc3
Binary files /dev/null and b/docs/qualification/recovery-feedback/390-overview-light.png differ
diff --git a/docs/qualification/recovery-feedback/900-destinations-dark.png b/docs/qualification/recovery-feedback/900-destinations-dark.png
new file mode 100644
index 000000000..4fa63ee56
Binary files /dev/null and b/docs/qualification/recovery-feedback/900-destinations-dark.png differ
diff --git a/docs/qualification/recovery-feedback/900-destinations-light.png b/docs/qualification/recovery-feedback/900-destinations-light.png
new file mode 100644
index 000000000..241c2699b
Binary files /dev/null and b/docs/qualification/recovery-feedback/900-destinations-light.png differ
diff --git a/docs/qualification/recovery-feedback/900-overview-dark.png b/docs/qualification/recovery-feedback/900-overview-dark.png
new file mode 100644
index 000000000..6db3ed7cd
Binary files /dev/null and b/docs/qualification/recovery-feedback/900-overview-dark.png differ
diff --git a/docs/qualification/recovery-feedback/900-overview-light.png b/docs/qualification/recovery-feedback/900-overview-light.png
new file mode 100644
index 000000000..64bf06dfd
Binary files /dev/null and b/docs/qualification/recovery-feedback/900-overview-light.png differ
diff --git a/docs/qualification/recovery-feedback/README.md b/docs/qualification/recovery-feedback/README.md
new file mode 100644
index 000000000..bc6d9490b
--- /dev/null
+++ b/docs/qualification/recovery-feedback/README.md
@@ -0,0 +1,66 @@
+# Recovery feedback qualification — 7 September 2026
+
+Run from the repository root:
+
+```sh
+pulse-heavy-run -- node scripts/check-recovery-feedback.mjs
+```
+
+The final run passed 12 cases: real OverviewTab and DestinationsTab, shared
+Card/Button/ToastContainer, 1440/900/390px, light and dark. Source digests and
+the result are adjacent; the exact changed-source receipt is
+frontend-modern/browser-verification.json. Screenshots show retained feedback
+after the final toast expires. Representative narrow, intermediate and desktop
+screenshots were inspected for readable feedback and reachable controls.
+
+The harness exercises keyboard Retry/Dismiss and native confirmation,
+cancellation without mutation, superseding actions, ten-second error-toast
+expiry (accelerated browser clock plus exit-animation timer), successful
+mutation followed by failed health read, subsequent health refresh, and
+keyboard clear with retained focus. On Destinations a healthy refresh removes
+the health warning without removing action-failure information. Unit tests
+add healthy reconciliation independently of the available Overview controls,
+callback throw/rejection, and older completion versus clear/newer ownership.
+
+54 focused tests passed across useNotificationDeliveryHealth,
+AlertDeliveryHealthCard, useAlertDestinationsTabState,
+OverviewTab.deliveryactions and OverviewTab.emptystate. TypeScript and
+changed-file ESLint passed. No full suite, build, installed notification
+delivery, recipient receipt, app-shell navigation, or screen-reader testing
+is claimed.
+
+## Failed harness attempts, retained rather than counted as passes
+
+1. Initial run timed out waiting for Retry. The API route stub matched
+ /src/api modules as well as application API requests, leaving a blank page.
+2. Diagnostic rerun confirmed an empty body and no pageerror event; failed.png
+ retains that image. The in-flight script had loaded before the correction.
+3. With the route constrained to the fixture origin's /api/ prefix, Overview
+ rendered and passed, but the destination Refresh selector matched both
+ health and activity controls.
+4. A remaining healthy-reconciliation Refresh selector had the same ambiguity.
+ Both selectors now identify the health card, not the first matching button.
+5. The full destination render exposed a missing required email 'to' array in
+ the fixture (undefined.join). The fixture now supplies complete email and
+ Apprise settings and a correctly shaped empty delivery log.
+
+The final rerun passed all cases with zero page errors. No runtime source was
+changed to suppress these harness failures.
+
+## Evidence and boundaries
+
+The current demand-ledger named bet, “Recovery feedback without a reading
+deadline” (pulse-pro FEATURE_REQUESTS.md, introduced by 9c25eaf1), authorises
+this main-only scope. Fresh W3C guidance read on 7 September distinguishes
+temporary information with an untimed equivalent from information lost at
+expiry:
+https://www.w3.org/WAI/WCAG22/Understanding/timing-adjustable.html
+Its status technique supports an existing status container and explicit
+atomic announcement semantics:
+https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA22
+
+This is not a WCAG verdict or a fix for the reporter's HTTP503. Feedback is
+view-local: clear, a newer confirmed action, leaving the view or reload can
+remove it. Queue acceptance is not delivery receipt. The optional activity
+callback is tested separately; the current destination log loader already
+handles its own read failures.
diff --git a/docs/qualification/recovery-feedback/browser-result.json b/docs/qualification/recovery-feedback/browser-result.json
new file mode 100644
index 000000000..303655a2a
--- /dev/null
+++ b/docs/qualification/recovery-feedback/browser-result.json
@@ -0,0 +1,10 @@
+{
+ "result": "passed",
+ "cases": 12,
+ "viewports": [
+ 1440,
+ 900,
+ 390
+ ],
+ "scope": "Real OverviewTab and DestinationsTab, shared toast and feedback in Chromium; scripted API only, not installed delivery or recipient receipt."
+}
diff --git a/docs/qualification/recovery-feedback/failed.png b/docs/qualification/recovery-feedback/failed.png
new file mode 100644
index 000000000..a33cd16fd
Binary files /dev/null and b/docs/qualification/recovery-feedback/failed.png differ
diff --git a/docs/qualification/recovery-feedback/source-sha256.json b/docs/qualification/recovery-feedback/source-sha256.json
new file mode 100644
index 000000000..6bf27433b
--- /dev/null
+++ b/docs/qualification/recovery-feedback/source-sha256.json
@@ -0,0 +1,8 @@
+{
+ "frontend-modern/src/features/alerts/AlertQueueActionFeedback.tsx": "4fd4e2757b11bc4adb52bad293eb5a7a8d55301e12e5f4c3816fdbe0682bc578",
+ "frontend-modern/src/features/alerts/OverviewTab.tsx": "6700ce02ad19b4133860a7b680ed145e21ce27fa7fd4053937ce9cd3fc0c02e1",
+ "frontend-modern/src/features/alerts/tabs/DestinationsTab.tsx": "968d6d3f82d59235a13bfcd89b8405558d0286c89a22efc12bbc7e2ff5ad1600",
+ "frontend-modern/src/features/alerts/useAlertDestinationsTabState.ts": "25d47c0ce64f433ce0fd8ca851434d3c09441970d1e929de8c7787f209ab1ddb",
+ "frontend-modern/src/features/alerts/useNotificationDeliveryHealth.ts": "863e1b167bc27df5d46976b704a883ba6df9066bb6e42133935dbaa2c02f0b03",
+ "scripts/check-recovery-feedback.mjs": "1cf11b716b9a65c710f16e4765645208663cfe98dd26b87bd953e5309a3b6c4b"
+}
diff --git a/docs/release-control/v6/internal/subsystems/alerts.md b/docs/release-control/v6/internal/subsystems/alerts.md
index 297254d6b..27fd5c156 100644
--- a/docs/release-control/v6/internal/subsystems/alerts.md
+++ b/docs/release-control/v6/internal/subsystems/alerts.md
@@ -15,6 +15,34 @@
## Purpose
+### Retained-queue recovery feedback has no reading deadline
+
+Retry and Dismiss failures have a view-local untimed equivalent beside the
+delivery-health card on Overview and Notifications. A health refresh, including
+a verified healthy result that removes the warning, does not erase a rejected
+action's message. Cancellation preserves it. Deliberate clear or the next
+confirmed action supersedes it; reloading or leaving the view resets it.
+The existing error toast remains transient.
+
+An accepted queue mutation still emits success if the health read fails.
+That read owns the existing unavailable warning. A synchronous throw or rejected
+promise from the optional activity-refresh callback must not claim the mutation
+failed: retained feedback explicitly distinguishes accepted action from failed
+activity refresh. Older action/callback completions cannot restore feedback
+after a clear or overwrite a newer action's message. Neither acceptance nor a
+healthy queue proves intended-recipient receipt; no retry policy changes.
+
+Proof: useNotificationDeliveryHealth.test.tsx pins expiry-independent state,
+health reconciliation, cancellation, callback failure and ownership;
+AlertDeliveryHealthCard.test.tsx pins mounted live-region composition and clear
+focus. scripts/check-recovery-feedback.mjs exercises the real Overview and
+destination views with scripted API responses, real toast expiry, keyboard
+actions/clear, unavailable-to-healthy reconciliation and three viewport widths.
+This is the main-only named bet “Recovery feedback without a reading deadline”
+from the current demand ledger, not a backend failure diagnosis or backport.
+
+
+
Overview delivery diagnoses use latest-started refresh ownership. Older bulk
responses cannot overwrite newer card notification status, and an empty active
alert set invalidates outstanding reads. Disposal also prevents updates. Failed
diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
index 0e745279f..9b9875cc7 100644
--- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md
+++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
@@ -20,6 +20,24 @@
## Purpose
+### Recovery feedback composes shared controls without a timer
+
+The alerts-owned AlertQueueActionFeedback composes Card and Button rather than
+altering global toast lifetimes. Its polite atomic status region is mounted
+before failure text arrives; the warning card uses semantic foreground and wraps
+text and the explicit “Clear recovery message” control at narrow widths.
+The enclosing labelled region remains mounted and receives focus before clear
+removes the button, avoiding focus loss to the document body. Updating feedback
+does not steal focus. This is view-local feedback, not durable delivery history.
+
+The live region stays independent of delivery-health conditional rendering:
+a later healthy observation can remove the health warning without removing
+failed-action information. AlertDeliveryHealthCard.test.tsx verifies status
+and focus composition. scripts/check-recovery-feedback.mjs verifies both real
+feature views, keyboard activation/clear and text/control containment at 1440,
+900 and 390px, with scripted responses and genuine toast expiry. It does not
+establish screen-reader announcement quality or installed notification delivery.
+
The shared action evidence disclosure preserves the named observer independently
of the executor. Its observation timestamp uses the neutral label `Observed`,
followed by the separate Pulse receipt time. Independent Proxmox API evidence
diff --git a/frontend-modern/src/components/Toast/__tests__/Toast.test.tsx b/frontend-modern/src/components/Toast/__tests__/Toast.test.tsx
index 047da5a10..8187601f2 100644
--- a/frontend-modern/src/components/Toast/__tests__/Toast.test.tsx
+++ b/frontend-modern/src/components/Toast/__tests__/Toast.test.tsx
@@ -11,6 +11,32 @@ describe('Toast', () => {
vi.useRealTimers();
});
+ it.each([
+ ['error', 'alert', 'assertive'],
+ ['warning', 'alert', 'assertive'],
+ ['success', 'status', 'polite'],
+ ['info', 'status', 'polite'],
+ ] as const)('preserves %s announcement semantics without moving focus', (type, role, live) => {
+ render(() => (
+ <>
+
+
+ >
+ ));
+ const action = screen.getByRole('button', { name: 'Recovery action' });
+ action.focus();
+
+ window.showToast(type, 'Recovery result', 'Action context');
+
+ const announcement = screen.getByRole(role);
+ expect(announcement).toHaveAttribute('aria-live', live);
+ expect(announcement).toHaveAttribute('aria-atomic', 'true');
+ expect(announcement).toHaveTextContent('Recovery result');
+ expect(announcement).toHaveTextContent('Action context');
+ expect(action).toHaveFocus();
+ expect(screen.queryByRole(role === 'alert' ? 'status' : 'alert')).not.toBeInTheDocument();
+ });
+
it('keeps all toasts created within a batch', () => {
render(() => );
diff --git a/frontend-modern/src/features/alerts/AlertDeliveryHealthCard.test.tsx b/frontend-modern/src/features/alerts/AlertDeliveryHealthCard.test.tsx
index 099a00675..3c2fdd021 100644
--- a/frontend-modern/src/features/alerts/AlertDeliveryHealthCard.test.tsx
+++ b/frontend-modern/src/features/alerts/AlertDeliveryHealthCard.test.tsx
@@ -5,6 +5,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
import type { NotificationQueueHealth } from '@/api/notifications';
+import { AlertQueueActionFeedback } from './AlertQueueActionFeedback';
import { AlertDeliveryHealthCard } from './AlertDeliveryHealthCard';
const degradedHealth: NotificationQueueHealth = {
@@ -215,3 +216,19 @@ describe('AlertDeliveryHealthCard', () => {
);
});
});
+
+describe('recovery feedback beside delivery health', () => {
+ afterEach(() => cleanup());
+ it('keeps a mounted live region and returns focus to it when deliberately cleared', () => {
+ const [message, setMessage] = createSignal(null);
+ render(() => setMessage(null)} />);
+ expect(screen.getByRole('status')).toBeEmptyDOMElement();
+ setMessage('Unable to retry retained notification deliveries.');
+ expect(screen.getByRole('status')).toHaveTextContent('Unable to retry');
+ const clear = screen.getByRole('button', { name: 'Clear recovery message' });
+ clear.focus();
+ fireEvent.click(clear);
+ expect(screen.getByRole('status')).toBeEmptyDOMElement();
+ expect(screen.getByRole('region', { name: 'Notification recovery feedback' })).toHaveFocus();
+ });
+});
diff --git a/frontend-modern/src/features/alerts/AlertQueueActionFeedback.tsx b/frontend-modern/src/features/alerts/AlertQueueActionFeedback.tsx
new file mode 100644
index 000000000..c356f4081
--- /dev/null
+++ b/frontend-modern/src/features/alerts/AlertQueueActionFeedback.tsx
@@ -0,0 +1,34 @@
+import { Show } from 'solid-js';
+import { Card } from '@/components/shared/Card';
+import { Button } from '@/components/shared/Button';
+
+// Keep the live region mounted before its content changes, and retain a focus
+// destination when the user clears the message. This is view-local, not history.
+export function AlertQueueActionFeedback(props: { message: string | null; onClear: () => void }) {
+ let region!: HTMLDivElement;
+ return (
+
+
+
+
+
+
+ {props.message}
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend-modern/src/features/alerts/OverviewTab.tsx b/frontend-modern/src/features/alerts/OverviewTab.tsx
index de8319f39..64daf8a1b 100644
--- a/frontend-modern/src/features/alerts/OverviewTab.tsx
+++ b/frontend-modern/src/features/alerts/OverviewTab.tsx
@@ -1,3 +1,4 @@
+import { AlertQueueActionFeedback } from './AlertQueueActionFeedback';
import { createSignal, onCleanup, createEffect, onMount, Show } from 'solid-js';
import { useLocation } from '@solidjs/router';
@@ -78,6 +79,10 @@ export function OverviewTab(props: {
return (