feat(push): harden web push delivery reliability (t095)#9
Merged
Conversation
- E1: revocation-proof SW push handler — always show notification - E0: endpoint-reconciled per-device identity — survives storage wipe - E1b: once-per-foreground re-validation gate — iOS PWA recovery - E2: push send options (urgency:high, TTL:1800) for timely delivery - E4: regression guard on Slack health-alert muteKey stamping - Layer 1: pure tests for E1/E0/E1b/E2/E4 components - Layer 2: e2e reconcile keystone test validates endpoint-deduping - All gates green: tests + typecheck + build + web server boot
Dokploy Preview Deployment
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Task: docs/tasks/done/095-harden-web-push-delivery-reliability.md
Makes the web-build Web Push subscription stay alive, recoverable, and timely on the daily-driver iPad PWA. Bundles one reliability story — "a push subscription that survives the things iOS does to it".
What changed
public/sw.js'spushhandler now always callsshowNotification(a generic "New message" fallback on!e.data, a parse failure, or any processing error). On iOS apushthat renders nothing is auserVisibleOnlyviolation and WebKit revokes the subscription with no documented grace count. Content shaping is the pure, testedbuildNotificationContentinsrc/lib/push-notification.ts, mirrored into the static SW (thesw-cache-name.tspattern).deviceIdis now server-authoritative, reconciled by push subscription endpoint (core/push-subscriptions.js#reconcileDeviceId).POST /api/notifications/subscribereturns the reconciled{ deviceId }; the renderer adopts it as the single source for device-keyed ui-state. A storage wipe + re-subscribe on the same endpoint recovers the priordeviceId(and thus prior mutes/master) — the push endpoint is the only identity that outlives an iOS script-storage eviction.visibilitychange(the only recovery hook iOS leaves, sincepushsubscriptionchangeis dead on iOS PWAs), gated once-per-foreground by the purecreatePushRevalidateGate(src/lib/push-revalidate.ts).webpush.sendNotificationnow sends{ urgency: "high", TTL: 1800 }from the purepushSendOptions(), so a triage ping isn't battery-deferred and an undeliverable one doesn't linger ~4 weeks and resurrect stale.muteKeystamping.Tests
buildNotificationContent,reconcileDeviceId, the foreground-revalidate gate,pushSendOptions, themuteKeyguard.test/e2e/server.e2e.test.tsproves the E0 reconcile end-to-end through the fake-CDP +server.mjsharness — same endpoint → samedeviceId+ no duplicate sub record; new endpoint → distinct id; client cached id ignored.pnpm test(909),pnpm test:e2e(43),pnpm typecheck,pnpm build,node --check web/server.mjs,pnpm webboot.Not in this PR (non-blocking)
Device-only iOS confirmations (no-revocation on malformed push, storage-wipe recovery, foreground re-validate firing) — logged in the task for the next real-device session. The AFK gates prove the logic + contracts; the iOS behavioral guarantees can't be asserted headlessly.
https://claude.ai/code/session_01NnSM463eFUgkLNtYBQwe6u