diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index b1fe42d4..346901c9 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -142,12 +142,12 @@ Plans: 4. On a phone, the input bar is sticky at the bottom of the screen, touch targets are large enough to tap without errors, and the layout resizes correctly when the software keyboard appears 5. Pulling down on the conversation list on mobile triggers a refresh; push notifications arrive for agent mentions, task completions, and handoff requests where the platform supports them 6. The initial page load on broadband completes in under 2 seconds and on a 3G connection in under 5 seconds; PWA cached load completes in under 1 second -**Plans:** 2/5 plans executed +**Plans:** 3/5 plans executed Plans: - [x] 26-00-PLAN.md — Foundation: SW rewrite (cache-first), deps (idb, web-push), PWA types, Wave 0 test stubs - [x] 26-01-PLAN.md — Performance: React.lazy route splitting + Vite vendor chunk splitting -- [ ] 26-02-PLAN.md — Mobile responsive: MobileChatView, MobileNavBar, PullToRefresh, ChatPanel/ChatInput mobile wiring +- [x] 26-02-PLAN.md — Mobile responsive: MobileChatView, MobileNavBar, PullToRefresh, ChatPanel/ChatInput mobile wiring - [ ] 26-03-PLAN.md — PWA features: InstallPromptBanner, OfflineBanner, useOfflineQueue (IndexedDB message queue) - [ ] 26-04-PLAN.md — Push notifications: DB schema, server VAPID/routes, client subscription hook, permission prompt @@ -236,4 +236,4 @@ All 65 v1 requirements are mapped to exactly one phase. No orphans. | 23. Brainstormer Flow | v1.3 | 4/4 | Complete | 2026-04-01 | | 24. Search, History & Branching | v1.3 | 4/4 | Complete | 2026-04-01 | | 25. File System | v1.3 | 9/9 | Complete | 2026-04-02 | -| 26. PWA & Performance | v1.3 | 2/5 | In Progress| | +| 26. PWA & Performance | v1.3 | 3/5 | In Progress| | diff --git a/.planning/STATE.md b/.planning/STATE.md index 3cf5e5f4..3d1e3b4b 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,14 +3,14 @@ gsd_state_version: 1.0 milestone: v1.3 milestone_name: milestone status: executing -stopped_at: Completed 26-01-PLAN.md -last_updated: "2026-04-02T02:03:08.823Z" +stopped_at: Completed 26-02-PLAN.md +last_updated: "2026-04-02T02:11:26.475Z" last_activity: 2026-04-02 progress: total_phases: 6 completed_phases: 5 total_plans: 35 - completed_plans: 32 + completed_plans: 33 percent: 100 --- @@ -26,7 +26,7 @@ See: .planning/PROJECT.md (updated 2026-03-30) ## Current Position Phase: 26 (pwa-performance) — EXECUTING -Plan: 3 of 5 +Plan: 4 of 5 Status: Ready to execute Last activity: 2026-04-02 @@ -88,6 +88,7 @@ Progress: [██████████] 100% | Phase 25-file-system P07 | 10 | 2 tasks | 6 files | | Phase 26-pwa-performance P00 | 5 | 2 tasks | 9 files | | Phase 26-pwa-performance P01 | 4 | 2 tasks | 2 files | +| Phase 26-pwa-performance P02 | 20 | 2 tasks | 8 files | ## Accumulated Context @@ -167,6 +168,9 @@ Recent decisions affecting current work: - [Phase 26-pwa-performance]: [Phase 26-00]: API paths (/api/*) pass through without SW interception — network-only for all API traffic - [Phase 26-pwa-performance]: All pages use named exports so React.lazy requires .then(m => ({ default: m.X })) module re-mapping - [Phase 26-pwa-performance]: manualChunks: vendor-react, vendor-router, vendor-query, vendor-markdown (excludes @mdxeditor/editor to avoid circular deps) +- [Phase 26-pwa-performance]: useMediaQuery uses addEventListener('change') not addListener() — addListener is deprecated in modern browsers +- [Phase 26-pwa-performance]: PullToRefresh wraps ScrollArea in ChatConversationList, not the entire list component — keeps desktop layout unaffected +- [Phase 26-pwa-performance]: MobileChatView uses 100dvh not 100vh — avoids keyboard-shrink issue (RESEARCH Pitfall 3) ### Pending Todos @@ -179,6 +183,6 @@ None yet. ## Session Continuity -Last session: 2026-04-02T02:03:08.819Z -Stopped at: Completed 26-01-PLAN.md +Last session: 2026-04-02T02:11:26.472Z +Stopped at: Completed 26-02-PLAN.md Resume file: None diff --git a/.planning/phases/26-pwa-performance/26-02-SUMMARY.md b/.planning/phases/26-pwa-performance/26-02-SUMMARY.md new file mode 100644 index 00000000..3c0a1c8b --- /dev/null +++ b/.planning/phases/26-pwa-performance/26-02-SUMMARY.md @@ -0,0 +1,106 @@ +--- +phase: 26-pwa-performance +plan: "02" +subsystem: ui +tags: [mobile, pwa, responsive, touch, gestures] +dependency_graph: + requires: [26-00] + provides: [mobile-chat-layout, pull-to-refresh, responsive-breakpoints] + affects: [ChatPanel, ChatConversationList, ChatInput, MobileChatView] +tech_stack: + added: [] + patterns: + - useMediaQuery hook with SSR-safe window.matchMedia + addEventListener + - usePullToRefresh with native DOM touch events (mirrors SwipeToArchive pattern) + - Conditional render in ChatPanel — mobile/desktop branch at function top +key_files: + created: + - ui/src/hooks/useMediaQuery.ts + - ui/src/hooks/usePullToRefresh.ts + - ui/src/components/PullToRefresh.tsx + - ui/src/components/MobileChatView.tsx + modified: + - ui/src/components/ChatPanel.tsx + - ui/src/components/ChatConversationList.tsx + - ui/src/components/ChatInput.tsx + - ui/src/components/ChatConversationItem.tsx +decisions: + - "[Phase 26-02]: useMediaQuery uses addEventListener('change') not addListener() — addListener is deprecated in modern browsers" + - "[Phase 26-02]: PullToRefresh wraps ScrollArea in ChatConversationList, not the entire list component — keeps desktop layout unaffected" + - "[Phase 26-02]: MobileChatView uses useQuery for conversation title lookup (same key as ChatConversationList) — no extra API call due to React Query cache" + - "[Phase 26-02]: refetch from useChatConversations wrapped in void wrapper to satisfy PullToRefresh onRefresh type (void | Promise)" + - "[Phase 26-02]: ChatConversationItem gets min-h-[48px] touch target to satisfy UI-SPEC requirement" +metrics: + duration: "~20 min" + completed_date: "2026-04-01" + tasks_completed: 2 + files_changed: 8 +requirements_satisfied: [PWA-03, PWA-04, PWA-05] +--- + +# Phase 26 Plan 02: Mobile Responsive Chat Layout Summary + +Mobile-first chat view with full-screen layout on phones, pull-to-refresh gesture, safe-area insets, and proper 44px/48px touch targets. + +## Tasks Completed + +| Task | Name | Commit | Files | +|------|------|--------|-------| +| 1 | Create useMediaQuery hook and PullToRefresh component | 3a6adc7f | useMediaQuery.ts, usePullToRefresh.ts, PullToRefresh.tsx | +| 2 | Create MobileChatView and wire ChatPanel for responsive layout | 78b04217 | MobileChatView.tsx, ChatPanel.tsx, ChatConversationList.tsx, ChatInput.tsx, ChatConversationItem.tsx | + +## What Was Built + +**useMediaQuery** (`ui/src/hooks/useMediaQuery.ts`): SSR-safe hook that wraps `window.matchMedia` with `addEventListener("change")` for live updates. Returns boolean for any media query string. + +**usePullToRefresh** (`ui/src/hooks/usePullToRefresh.ts`): Touch gesture hook with 64px threshold. Mirrors SwipeToArchive.tsx convention (native DOM events, not React synthetic). Fires `navigator.vibrate(10)` for haptic feedback on threshold trigger. Returns `containerRef` to attach to scroll container. + +**PullToRefresh** (`ui/src/components/PullToRefresh.tsx`): Visual wrapper showing Loader2 spinner with "Pull to refresh" / "Release to refresh" text. Children are translated down by pull distance for visual feedback. Opacity scales with pull distance. + +**MobileChatView** (`ui/src/components/MobileChatView.tsx`): Full-screen mobile chat at `h-[100dvh]` (not `100vh` — avoids keyboard-shrink issue). Two views: +- No active conversation: conversation list with PullToRefresh, `pb-16` to clear global MobileBottomNav +- Active conversation: 48px header with back button + title + agent selector, message list, sticky input bar with `pb-[env(safe-area-inset-bottom)]` + +**ChatPanel updates**: Imports `useMediaQuery` and `MobileChatView`. Adds `const isDesktop = useMediaQuery("(min-width: 768px)")` at function top and returns `` when `!isDesktop`. Desktop layout unchanged. + +**ChatConversationList updates**: Wraps ScrollArea in ` { void refetch(); }} enabled={isMobile}>`. + +**ChatInput updates**: `pb-[env(safe-area-inset-bottom)]` on form container. Send button gets `min-h-[44px] min-w-[44px]`. + +**ChatConversationItem updates**: `min-h-[48px] justify-center` for touch target compliance. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Type mismatch on PullToRefresh onRefresh prop** +- **Found during:** Task 2 build +- **Issue:** `refetch` from `useInfiniteQuery` returns `Promise>`, not `Promise`. TypeScript rejected the direct assignment. +- **Fix:** Wrapped in `() => { void refetch(); }` to satisfy the `() => void | Promise` interface. +- **Files modified:** `ui/src/components/ChatConversationList.tsx` +- **Commit:** 78b04217 + +**2. [Rule 2 - Missing critical functionality] ChatConversationItem touch targets** +- **Found during:** Task 2 — review of UI-SPEC touch target rule (48px minimum) +- **Issue:** ChatConversationItem had `py-1.5` resulting in under-48px height on mobile. +- **Fix:** Added `min-h-[48px] justify-center` to item container. +- **Files modified:** `ui/src/components/ChatConversationItem.tsx` +- **Commit:** 78b04217 + +## Known Stubs + +None — all components are wired with real data (useStreamingChat, useChatMessages, useChatConversations, etc.). + +## Self-Check: PASSED + +Files verified present: +- ui/src/hooks/useMediaQuery.ts: FOUND +- ui/src/hooks/usePullToRefresh.ts: FOUND +- ui/src/components/PullToRefresh.tsx: FOUND +- ui/src/components/MobileChatView.tsx: FOUND + +Commits verified: +- 3a6adc7f: FOUND +- 78b04217: FOUND + +Build: PASSED (pnpm --filter @paperclipai/ui build succeeds)