nexus/.planning/milestones/v1.3-phases/26-pwa-performance/26-00-PLAN.md
Nexus Dev ffc7b130e4 chore: archive v1.3 phase directories to milestones/
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:55:48 +00:00

190 lines
9 KiB
Markdown

---
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"
---
<objective>
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.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Install dependencies, create PWA types, and rewrite service worker</name>
<files>ui/public/sw.js, ui/src/types/pwa.d.ts</files>
<read_first>
- ui/public/sw.js
- ui/src/main.tsx
- ui/public/site.webmanifest
- .planning/phases/26-pwa-performance/26-RESEARCH.md
</read_first>
<action>
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<void>` 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).
</action>
<verify>
<automated>grep -q "nexus-v1" ui/public/sw.js && grep -q "BeforeInstallPromptEvent" ui/src/types/pwa.d.ts && echo "PASS"</automated>
</verify>
<acceptance_criteria>
- `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)
</acceptance_criteria>
<done>Service worker rewritten with cache-first strategy and nexus-v1 cache name. idb and web-push installed. PWA types declared.</done>
</task>
<task type="auto">
<name>Task 2: Create Wave 0 test stubs for Phase 26 hooks and components</name>
<files>ui/src/hooks/useOfflineQueue.test.ts, ui/src/hooks/useInstallPrompt.test.ts, ui/src/hooks/usePushNotifications.test.ts, ui/src/components/PullToRefresh.test.tsx</files>
<read_first>
- ui/src/components/SwipeToArchive.test.tsx
- .planning/phases/26-pwa-performance/26-RESEARCH.md
</read_first>
<action>
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.
</action>
<verify>
<automated>pnpm --filter @paperclipai/ui test --run useOfflineQueue useInstallPrompt usePushNotifications PullToRefresh 2>&1 | grep -E "todo|Tests" | head -10</automated>
</verify>
<acceptance_criteria>
- 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)
</acceptance_criteria>
<done>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.</done>
</task>
</tasks>
<verification>
- `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
</verification>
<success_criteria>
Service worker upgraded to cache-first with nexus-v1 cache. Dependencies installed. PWA types declared. All Wave 0 test stubs created and passing.
</success_criteria>
<output>
After completion, create `.planning/phases/26-pwa-performance/26-00-SUMMARY.md`
</output>