docs(26-02): complete mobile responsive chat layout plan — MobileChatView, PullToRefresh, safe areas

This commit is contained in:
Nexus Dev 2026-04-02 02:11:39 +00:00
parent 6b7e54bbf9
commit e35bba41e4
3 changed files with 119 additions and 9 deletions

View file

@ -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| |

View file

@ -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

View file

@ -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<void>)"
- "[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 `<MobileChatView />` when `!isDesktop`. Desktop layout unchanged.
**ChatConversationList updates**: Wraps ScrollArea in `<PullToRefresh onRefresh={() => { 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<QueryObserverResult<...>>`, not `Promise<void>`. TypeScript rejected the direct assignment.
- **Fix:** Wrapped in `() => { void refetch(); }` to satisfy the `() => void | Promise<void>` 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)