--- phase: 26-pwa-performance plan: 03 subsystem: ui tags: [pwa, offline, indexeddb, idb, install-prompt, service-worker, react-hooks] requires: - phase: 26-00 provides: idb dependency installed, pwa.d.ts types, BeforeInstallPromptEvent interface - phase: 26-02 provides: ChatPanel with MobileChatView mobile/desktop split, useMediaQuery hook provides: - useOnlineStatus hook — reactive navigator.onLine boolean - useInstallPrompt hook — captures beforeinstallprompt, iOS detection, promptInstall() - useOfflineQueue hook — IndexedDB message queue with auto-flush on online event - InstallPromptBanner component — PWA install CTA with iOS fallback and 7-day dismiss cooldown - OfflineBanner component — amber offline status with queued message count - ChatPanel offline integration — enqueues messages when offline via useOfflineQueue affects: [26-04, ChatPanel, MobileChatView, pwa-push-notifications] tech-stack: added: ["idb@^8.0.3"] patterns: - "useOfflineQueue uses openDB with autoIncrement store for message replay" - "OfflineBanner uses dark: prefix for light/dark amber theme switching" - "InstallPromptBanner checks localStorage timestamp for 7-day cooldown" - "handleSend in ChatPanel guards on isOnline before proceeding to network calls" key-files: created: - ui/src/hooks/useOnlineStatus.ts - ui/src/hooks/useInstallPrompt.ts - ui/src/hooks/useOfflineQueue.ts - ui/src/components/InstallPromptBanner.tsx - ui/src/components/OfflineBanner.tsx - ui/src/types/pwa.d.ts modified: - ui/src/components/ChatPanel.tsx - ui/package.json - pnpm-lock.yaml key-decisions: - "idb + pwa.d.ts added inline in this plan — parallel 26-00 worktree commits were not on gsd/phase-26-pwa-performance branch" - "OfflineBanner uses dark: Tailwind prefix for amber light/dark theming — consistent with design system approach" - "ChatPanel offline guard only enqueues when activeConversationId exists — new conversation creation requires network" - "useOfflineQueue flush stops on first failure — prevents partial replay; retries on next online event" patterns-established: - "Offline-first guard at handleSend entry: check isOnline before any network operation" - "IndexedDB queue pattern: openDB with autoIncrement, getAllKeys/get/delete for sequential flush" requirements-completed: [PWA-01, PWA-02, PWA-08] duration: 5min completed: 2026-04-02 --- # Phase 26 Plan 03: PWA Install Prompt, Offline Banner, and Message Queue Summary **PWA install prompt with iOS fallback, amber offline status banner, and IndexedDB message queue that auto-flushes on reconnection** ## Performance - **Duration:** ~5 min - **Started:** 2026-04-02T02:15:40Z - **Completed:** 2026-04-02T02:20:35Z - **Tasks:** 2 - **Files modified:** 8 ## Accomplishments - Created three production hooks: `useOnlineStatus` (reactive online state), `useInstallPrompt` (captures beforeinstallprompt + iOS detection), `useOfflineQueue` (IndexedDB-backed queue with auto-flush) - Built `InstallPromptBanner` component with 7-day localStorage dismiss cooldown, iOS Share menu instruction text, and "Add to Home Screen" CTA - Built `OfflineBanner` component with amber theming (dark/light), queued message count display, and 3-second auto-dismiss on reconnection - Wired `ChatPanel` to enqueue messages to IndexedDB when offline and display a queued toast notification ## Task Commits 1. **Task 1: useOnlineStatus, useInstallPrompt, useOfflineQueue hooks** - `2b172bda` (feat) 2. **Task 2: InstallPromptBanner, OfflineBanner, ChatPanel wiring** - `427e6c80` (feat) ## Files Created/Modified - `ui/src/hooks/useOnlineStatus.ts` — Reactive boolean from navigator.onLine with online/offline event listeners - `ui/src/hooks/useInstallPrompt.ts` — Captures beforeinstallprompt, iOS detection via userAgent, promptInstall() callback - `ui/src/hooks/useOfflineQueue.ts` — openDB nexus-offline/message_queue store, enqueue/flush with stop-on-failure strategy - `ui/src/components/InstallPromptBanner.tsx` — Fixed-position banner with 7-day localStorage cooldown and iOS text variant - `ui/src/components/OfflineBanner.tsx` — Fixed top-0 amber banner showing queue count, 3s auto-dismiss on reconnect - `ui/src/components/ChatPanel.tsx` — Added isOnline guard in handleSend, enqueue call, OfflineBanner and InstallPromptBanner renders - `ui/src/types/pwa.d.ts` — BeforeInstallPromptEvent interface with global WindowEventMap extension - `ui/package.json` — Added idb@^8.0.3 dependency ## Decisions Made - `idb` and `pwa.d.ts` were added here as a deviation — the parallel 26-00 worktree commits were not merged into `gsd/phase-26-pwa-performance` branch - Offline guard in `handleSend` only enqueues when `activeConversationId` exists — creating a new conversation requires network, so we skip enqueue for that path - `useOfflineQueue.flush()` breaks on first failure — stops partial replay and retries on next `online` event ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 3 - Blocking] Added idb dependency and pwa.d.ts that were missing from branch** - **Found during:** Task 1 (creating hooks) - **Issue:** The `gsd/phase-26-pwa-performance` branch was missing the `idb` package and `ui/src/types/pwa.d.ts` that were created in a parallel 26-00 worktree but never merged to the main phase branch - **Fix:** Added `idb@^8.0.3` to `ui/package.json`, ran `pnpm install`, created `ui/src/types/pwa.d.ts` with BeforeInstallPromptEvent interface - **Files modified:** ui/package.json, pnpm-lock.yaml, ui/src/types/pwa.d.ts - **Verification:** `import { openDB } from "idb"` compiles without errors - **Committed in:** `2b172bda` (Task 1 commit) --- **Total deviations:** 1 auto-fixed (1 blocking) **Impact on plan:** Required for task completion. No scope creep. ## Issues Encountered - Pre-existing TypeScript errors in `packages/shared` (`zod` module not found) and `packages/adapters` prevent `pnpm --filter @paperclipai/ui build` from passing at the workspace level, but `tsc -b` within the `ui` directory itself produces zero errors. These are pre-existing issues unrelated to this plan. ## User Setup Required None - no external service configuration required. ## Next Phase Readiness - PWA install prompt, offline banner, and message queue all functional - `useOfflineQueue` relies on `chatApi.postMessage` for replay — the flush will restore messages when reconnected - Plan 26-04 (push notifications) can build on the same `useOnlineStatus` pattern ## Self-Check: PASSED - FOUND: ui/src/hooks/useOnlineStatus.ts - FOUND: ui/src/hooks/useInstallPrompt.ts - FOUND: ui/src/hooks/useOfflineQueue.ts - FOUND: ui/src/components/InstallPromptBanner.tsx - FOUND: ui/src/components/OfflineBanner.tsx - FOUND: ui/src/types/pwa.d.ts - FOUND commit: 2b172bda (Task 1) - FOUND commit: 427e6c80 (Task 2) --- *Phase: 26-pwa-performance* *Completed: 2026-04-02*