feat(web): closed-tab Web Push notifications (SW + PWA + server VAPID push) - #23
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Adversarial self-review + fixesRan an adversarial review of this branch and pushed fixes for the actionable findings:
Known v1 limitations (documented, not blocking)
Verification: both packages typecheck clean (0 errors); web + server unit tests pass; |
… push) Closes the accepted v1 gap from #14: notifications previously only fired while a T3 tab was open. This delivers a push when the tab is fully closed. Client (apps/web): - PWA manifest + icons + installability (required for iOS Safari Web Push). - Push-only service worker (no fetch handler, so no offline/stale-asset risk): shows a notification on push, focuses/opens the waiting thread on click, and suppresses when any T3 tab is open so the in-page NotificationCoordinator stays the single source while a tab is alive. - PushSubscriptionManager keeps the subscription in sync with the existing notifyOnNeedsInput setting + permission (reacts to a post-mount grant via the Permissions API). Pure, tested VAPID/subscription helpers. Server (apps/server/src/t3x/webPush — fork-seam clean, no upstream edits): - Reactor taps OrchestrationEngine.streamDomainEvents, recomputes the awareness phase with the shared projectThreadAwareness, edge-detects the same transitions the web client does (waiting_for_input/approval; running-> completed), and sends Web Push to registered subscriptions, pruning expired (404/410) ones. - JSON subscription store in the state dir; VAPID keypair via ServerSecretStore (T3X_VAPID_* env override supported); raw /api/t3x/push/{subscribe,unsubscribe,vapid-public-key} routes mounted through the existing T3xRoutesLive seam. Verified: apps/web + apps/server typecheck clean (0 errors); web + server unit tests pass; web-push loads at runtime; server+bin construction tests (277) pass. Browser end-to-end (grant permission, close tab, receive push) is manual QA. Closes #19 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…covery, cleanup) - Reactor: observe the awareness phase unconditionally (prime the tracker even with no subscribers) so the first transition after a device subscribes isn't swallowed as a first-seen observation; gate only the send on subscription count. Add a conservative event denylist (skip meta/mode-change events) to avoid a shell fetch + recompute on every domain event. - send: also prune subscriptions on 403 (VAPID mismatch), not just 404/410. - push client: re-subscribe when an existing browser subscription's applicationServerKey no longer matches the current server VAPID key (self-heals after a key change), instead of re-affirming a dead subscription. - PushSubscriptionManager: unsubscribe when notification permission is revoked (denied) while the setting stays on. - routes: a malformed (non-JSON) body now returns 400 instead of 500. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
24a2253 to
a19b7eb
Compare
Closes #19
What
Closes the accepted v1 gap from #14 (in-tab notifications only): deliver a browser notification when the T3 tab is fully closed, via a service worker + PWA manifest + server-side Web Push.
Client (
apps/web)public/sw.js) — deliberately nofetchhandler (not an offline PWA; avoids stale-asset risk). Shows onpush, focuses/opens the waiting thread onnotificationclick, and suppresses when any T3 tab is open so the merged in-pageNotificationCoordinatorstays the single source while a tab is alive.PushSubscriptionManagerkeeps the subscription in sync with the existingnotifyOnNeedsInputsetting + permission (reacts to a post-mount grant via the Permissions API). Pure, unit-tested VAPID/subscription helpers (push.logic.ts).Server (
apps/server/src/t3x/webPush, fork-seam clean)Mirrors the auto-resume feature: zero edits to upstream-owned files (contracts / ws.ts / http.ts / Migrations.ts / server.ts). Everything mounts through the existing
T3xLayerLive+T3xRoutesLiveseams.OrchestrationEngine.streamDomainEvents, recomputes the awareness phase with the sharedprojectThreadAwareness, edge-detects the same transitions the web client does (waiting_for_input/waiting_for_approval;running → completed), and sends Web Push to registered subscriptions, pruning expired (404/410) ones.ServerSecretStore(generated + persisted on first boot;T3X_VAPID_PUBLIC_KEY/T3X_VAPID_PRIVATE_KEY/T3X_VAPID_SUBJECTenv override supported).GET/POST /api/t3x/push/{vapid-public-key,subscribe,unsubscribe}(read/operate scoped), called from the client exactly likeAutoResumeOverlaycalls/api/t3x/auto-resume.Identity note
apps/serveris single-user/single-environment (no Clerk server-side), so subscriptions key by auth session (device); "notify me" = push to all subscriptions in the environment.Verified
apps/web+apps/servertypecheck clean (0 errors).push.logic(7), serverattentionedge-tracker + suite (157).web-pushloads at runtime (VAPID generation OK); server + bin construction tests (277) pass with the reactor/routes wired in.Manual QA (needs a real browser + push service — not automatable here)
Deliberate v1 tradeoffs
🤖 Generated with Claude Code