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() {