From 4274220266e979dae8ce53c9466a677660c8e05e Mon Sep 17 00:00:00 2001 From: Quick <31828688+Quick104@users.noreply.github.com> Date: Thu, 11 Jun 2026 16:05:12 -0400 Subject: [PATCH] feat(web): show web push delivery health in notification settings Surface per-subscription delivery health on the Notifications settings page: this browser and each other device now show last-delivered / last-failed status (failures newer than the last success render in amber, mirroring webhook health), plus a device count and an explicit disabled-after-failures state. Co-Authored-By: Claude Fable 5 --- .../pages/settings/NotificationsSettings.tsx | 106 ++++++++++++++---- 1 file changed, 84 insertions(+), 22 deletions(-) diff --git a/web/src/pages/settings/NotificationsSettings.tsx b/web/src/pages/settings/NotificationsSettings.tsx index 15a6a651..33a412a6 100644 --- a/web/src/pages/settings/NotificationsSettings.tsx +++ b/web/src/pages/settings/NotificationsSettings.tsx @@ -20,6 +20,7 @@ import type { NotificationWebhook, NotificationWebhookInput, NotificationWebhookTestResult, + WebPushSubscriptionView, } from "@/api/types"; import { ConfirmDialog } from "@/components/ConfirmDialog"; import { SettingsGroup } from "@/components/settings/SettingsGroup"; @@ -140,6 +141,37 @@ function PreferencesSection() { ); } +/** + * Delivery health for one push subscription, derived the same way as webhook + * health: a failure newer than the last success means the device is failing. + */ +function webPushHealth(sub: WebPushSubscriptionView): { text: string; failing: boolean } { + if (!sub.enabled) { + return { text: "Disabled after repeated delivery failures", failing: true }; + } + const failing = + sub.last_failure_at != null && + (sub.last_success_at == null || sub.last_failure_at > sub.last_success_at); + if (failing) { + const when = formatRelativeTime(sub.last_failure_at); + return { text: when ? `Last delivery failed ${when}` : "Last delivery failed", failing: true }; + } + if (sub.last_success_at) { + const when = formatRelativeTime(sub.last_success_at); + return { text: when ? `Last delivered ${when}` : "Delivering", failing: false }; + } + return { text: "No deliveries yet", failing: false }; +} + +function webPushSubtitle(sub: WebPushSubscriptionView): { text: string; failing: boolean } { + const health = webPushHealth(sub); + const added = formatRelativeTime(sub.created_at); + return { + text: added ? `Added ${added} · ${health.text}` : health.text, + failing: health.failing, + }; +} + function WebPushSection() { const queryClient = useQueryClient(); const capability = useNotificationCapability(); @@ -155,8 +187,12 @@ function WebPushSection() { void currentWebPushSubscription().then((sub) => setThisEndpoint(sub?.endpoint ?? null)); }, []); - const subscribedHere = - thisEndpoint != null && (subscriptions ?? []).some((sub) => sub.endpoint === thisEndpoint); + const thisSub = + thisEndpoint != null + ? (subscriptions ?? []).find((sub) => sub.endpoint === thisEndpoint) + : undefined; + const subscribedHere = thisSub != null; + const thisHealth = thisSub ? webPushHealth(thisSub) : null; const enable = async () => { if (!webPushCap?.public_key) { @@ -216,8 +252,18 @@ function WebPushSection() {
This browser
-
- {subscribedHere ? "Receiving notifications" : "Not receiving notifications"} +
+ {!subscribedHere + ? "Not receiving notifications" + : thisHealth?.failing + ? thisHealth.text + : thisHealth && thisHealth.text !== "No deliveries yet" + ? `Receiving notifications · ${thisHealth.text}` + : "Receiving notifications"}
@@ -235,25 +281,41 @@ function WebPushSection() { {otherSubscriptions.length > 0 && (
-
Other devices
- {otherSubscriptions.map((sub) => ( -
-
- - {sub.device_name || "Unknown device"} +
+ Other devices ({otherSubscriptions.length}) +
+ {otherSubscriptions.map((sub) => { + const subtitle = webPushSubtitle(sub); + return ( +
+
+ +
+
{sub.device_name || "Unknown device"}
+
+ {subtitle.text} +
+
+
+
- -
- ))} + ); + })}
)}