--- phase: 26-pwa-performance plan: 00 type: execute wave: 1 depends_on: [] files_modified: - ui/public/sw.js - ui/src/types/pwa.d.ts - ui/src/hooks/useOfflineQueue.test.ts - ui/src/hooks/useInstallPrompt.test.ts - ui/src/hooks/usePushNotifications.test.ts - ui/src/components/PullToRefresh.test.tsx autonomous: true requirements: - PWA-01 - PWA-07 - PERF-05 must_haves: truths: - "Service worker uses cache-first for static assets and cache-first for navigation" - "Old paperclip-v2 cache is deleted on SW activation" - "Service worker handles push events and notificationclick events" - "PWA TypeScript types exist for BeforeInstallPromptEvent" - "Wave 0 test stubs exist for all hooks and components planned in later waves" artifacts: - path: "ui/public/sw.js" provides: "Cache-first service worker with push handler" contains: "nexus-v1" - path: "ui/src/types/pwa.d.ts" provides: "BeforeInstallPromptEvent type declaration" contains: "BeforeInstallPromptEvent" - path: "ui/src/hooks/useOfflineQueue.test.ts" provides: "Test stub for offline queue hook" contains: "it.todo" - path: "ui/src/hooks/useInstallPrompt.test.ts" provides: "Test stub for install prompt hook" contains: "it.todo" key_links: - from: "ui/public/sw.js" to: "ui/src/main.tsx" via: "SW registration on load event (already wired)" pattern: "serviceWorker\\.register" --- Foundation for Phase 26: rewrite the service worker from network-first to cache-first, install dependencies (idb, web-push), create PWA TypeScript types, and scaffold Wave 0 test stubs for all hooks/components coming in later plans. Purpose: Provides the upgraded SW that enables PERF-05 (cached load < 1s) and the test infrastructure for TDD in subsequent plans. Output: Upgraded sw.js, pwa.d.ts type declarations, 4 test stub files, idb + web-push installed. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/26-pwa-performance/26-RESEARCH.md @.planning/phases/26-pwa-performance/26-UI-SPEC.md @ui/public/sw.js @ui/src/main.tsx Task 1: Install dependencies, create PWA types, and rewrite service worker ui/public/sw.js, ui/src/types/pwa.d.ts - ui/public/sw.js - ui/src/main.tsx - ui/public/site.webmanifest - .planning/phases/26-pwa-performance/26-RESEARCH.md 1. Install dependencies: - `pnpm --filter @paperclipai/ui add idb` - `pnpm --filter @paperclipai/server add web-push` - `pnpm --filter @paperclipai/server add --save-dev @types/web-push` 2. Create `ui/src/types/pwa.d.ts` with: - `BeforeInstallPromptEvent` interface extending `Event` with `prompt(): Promise` and `userChoice: Promise<{ outcome: "accepted" | "dismissed" }>` properties - Global `WindowEventMap` augmentation adding `beforeinstallprompt: BeforeInstallPromptEvent` 3. Rewrite `ui/public/sw.js` — replace the entire file with a cache-first strategy: - Cache name: `"nexus-v1"` (replaces `"paperclip-v2"`) - `install` event: call `self.skipWaiting()`, pre-cache `["/", "/index.html"]` into `nexus-v1` - `activate` event: delete ALL caches whose name is NOT `"nexus-v1"` (this busts the old `paperclip-v2` cache), then call `self.clients.claim()` - `fetch` event handler: a. If `url.pathname.startsWith("/api")` — return immediately (network-only, no interception) b. If `request.mode === "navigate"` — cache-first: try `caches.match("/")`, fall back to `fetch(request)` c. If URL matches static extension regex `/\.(js|css|woff2?|png|svg|ico|webmanifest)$/` — cache-first: try `caches.match(request)`, on miss fetch and clone into `nexus-v1` cache, return response d. All other requests: pass through (no interception) - `push` event handler: parse `event.data.json()` for `{ title, body, icon, data }`, call `self.registration.showNotification(title, { body, icon: icon || "/android-chrome-192x192.png", badge: "/favicon-32x32.png", data })` - `notificationclick` event handler: close notification, `clients.openWindow(event.notification.data?.url || "/")` Do NOT modify `ui/src/main.tsx` — SW registration is already correct. Do NOT modify `ui/public/site.webmanifest` — manifest is already complete (PWA-02, PWA-07 already satisfied). grep -q "nexus-v1" ui/public/sw.js && grep -q "BeforeInstallPromptEvent" ui/src/types/pwa.d.ts && echo "PASS" - `grep "nexus-v1" ui/public/sw.js` returns the cache name - `grep "paperclip" ui/public/sw.js` returns nothing (old name fully removed) - `grep "cache-first" ui/public/sw.js` or the cache-first pattern (caches.match before fetch) is present for static assets - `grep "push" ui/public/sw.js` shows push event listener - `grep "notificationclick" ui/public/sw.js` shows notification click handler - `grep "BeforeInstallPromptEvent" ui/src/types/pwa.d.ts` returns the type declaration - `pnpm --filter @paperclipai/ui exec -- node -e "require('idb')"` succeeds (idb installed) - `pnpm --filter @paperclipai/server exec -- node -e "require('web-push')"` succeeds (web-push installed) Service worker rewritten with cache-first strategy and nexus-v1 cache name. idb and web-push installed. PWA types declared. Task 2: Create Wave 0 test stubs for Phase 26 hooks and components ui/src/hooks/useOfflineQueue.test.ts, ui/src/hooks/useInstallPrompt.test.ts, ui/src/hooks/usePushNotifications.test.ts, ui/src/components/PullToRefresh.test.tsx - ui/src/components/SwipeToArchive.test.tsx - .planning/phases/26-pwa-performance/26-RESEARCH.md Create 4 test stub files using `it.todo()` (not `it.skip()`) — consistent with Phase 21-25 convention. 1. `ui/src/hooks/useOfflineQueue.test.ts`: - `describe("useOfflineQueue")` with: - `it.todo("enqueues message when offline")` - `it.todo("flushes queue on online event")` - `it.todo("stops flushing on first failed POST")` - `it.todo("returns queued message count")` 2. `ui/src/hooks/useInstallPrompt.test.ts`: - `describe("useInstallPrompt")` with: - `it.todo("captures beforeinstallprompt event")` - `it.todo("returns canInstall=true when event captured and not standalone")` - `it.todo("returns canInstall=false when already installed (standalone)")` - `it.todo("calls prompt() on the deferred event when promptInstall is called")` 3. `ui/src/hooks/usePushNotifications.test.ts`: - `describe("usePushNotifications")` with: - `it.todo("subscribes when permission is granted")` - `it.todo("does not subscribe when permission is denied")` - `it.todo("sends subscription to server via POST /api/push/subscribe")` 4. `ui/src/components/PullToRefresh.test.tsx`: - Add `// @vitest-environment jsdom` pragma at top (mirrors SwipeToArchive.test.tsx pattern) - `describe("PullToRefresh")` with: - `it.todo("calls onRefresh after drag exceeds 64px threshold")` - `it.todo("does not trigger when scrollTop is not 0")` - `it.todo("resets pull distance on touch end below threshold")` All test stubs should have minimal imports — no service mocks until implementation plans wire them up. NOTE: No MobileNavBar.test.tsx is created — the existing MobileBottomNav in Layout.tsx already handles global mobile navigation. If tests are needed for MobileBottomNav, they would belong to a separate plan. pnpm --filter @paperclipai/ui test --run useOfflineQueue useInstallPrompt usePushNotifications PullToRefresh 2>&1 | grep -E "todo|Tests" | head -10 - All 4 test files exist at the specified paths - Each file uses `it.todo()` not `it.skip()` - `PullToRefresh.test.tsx` has `// @vitest-environment jsdom` at top - `pnpm --filter @paperclipai/ui test --run` passes (todos are not failures) 4 Wave 0 test stubs created covering PWA-01, PWA-03, PWA-06, PWA-08. All test files use it.todo() and pass vitest run. - `grep "nexus-v1" ui/public/sw.js` confirms cache name - `grep -c "paperclip" ui/public/sw.js` returns 0 - All 4 test stub files exist and use `it.todo()` - `pnpm --filter @paperclipai/ui test --run` passes - `idb` appears in `ui/package.json` dependencies - `web-push` appears in `server/package.json` dependencies Service worker upgraded to cache-first with nexus-v1 cache. Dependencies installed. PWA types declared. All Wave 0 test stubs created and passing. After completion, create `.planning/phases/26-pwa-performance/26-00-SUMMARY.md`