From 2d95db70f2fe777c422a9fe237258079079be344 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 13:49:49 +0000 Subject: [PATCH] refactor(notifications): keep the push sender contract next to the fan-out The payload, target and outcome types are shared by the Expo and web push senders, so they now live with PushSender in push-notifications.ts instead of in the web push adapter. The settings E2E also stubs the Notification permission (headless Chromium reports it as denied even when granted), and the quiet-hours inputs are wide enough for 12-hour times. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u --- apps/web/e2e/notification-settings.spec.ts | 20 +++++++++++++++++-- .../components/NotificationSettingsPanel.tsx | 4 ++-- packages/adapters/src/expo-push.test.ts | 2 +- packages/adapters/src/expo-push.ts | 2 +- .../adapters/src/notify-user.postgres.test.ts | 7 +++++-- .../adapters/src/push-notifications.test.ts | 8 ++++++-- packages/adapters/src/push-notifications.ts | 15 +++++++++++++- packages/adapters/src/web-push.test.ts | 3 ++- packages/adapters/src/web-push.ts | 16 +-------------- 9 files changed, 50 insertions(+), 27 deletions(-) diff --git a/apps/web/e2e/notification-settings.spec.ts b/apps/web/e2e/notification-settings.spec.ts index 0716977..4d7893f 100644 --- a/apps/web/e2e/notification-settings.spec.ts +++ b/apps/web/e2e/notification-settings.spec.ts @@ -8,9 +8,26 @@ import { captureScreenshot, completeOnboarding, openUserSettings, rpc, signup } const ENDPOINT = "https://push.example.test/e2e-device"; -/** A fake service worker registration whose push subscription lives in sessionStorage. */ +/** + * A fake Notification permission and service worker registration, kept in sessionStorage so they + * survive a reload. Headless Chromium reports notifications as denied even when granted. + */ async function stubPushApi(page: Page, endpoint: string) { await page.addInitScript((pushEndpoint) => { + const permissionKey = "e2e:notification-permission"; + Object.defineProperty(window, "Notification", { + configurable: true, + writable: true, + value: class { + static get permission() { + return sessionStorage.getItem(permissionKey) ?? "default"; + } + static async requestPermission() { + sessionStorage.setItem(permissionKey, "granted"); + return "granted"; + } + }, + }); const storageKey = "e2e:push-subscription"; const current = () => { const stored = sessionStorage.getItem(storageKey); @@ -59,7 +76,6 @@ async function stubPushApi(page: Page, endpoint: string) { test("notification settings: push on this device, quiet hours and nudges", async ({ page, }, testInfo) => { - await page.context().grantPermissions(["notifications"]); await stubPushApi(page, ENDPOINT); await signup(page, `notifications-${Date.now()}@rakazo.test`, "password12", "Push Tester"); await completeOnboarding(page); diff --git a/apps/web/src/components/NotificationSettingsPanel.tsx b/apps/web/src/components/NotificationSettingsPanel.tsx index 48df0f9..de71979 100644 --- a/apps/web/src/components/NotificationSettingsPanel.tsx +++ b/apps/web/src/components/NotificationSettingsPanel.tsx @@ -167,7 +167,7 @@ export function NotificationSettingsPanel() { aria-label={t`Quiet hours start`} value={quietDraft.start} onChange={(event) => changeQuietTime("start", event.target.value)} - className="w-32" + className="w-36" />