docs(26-02): complete mobile responsive chat layout plan — MobileChatView, PullToRefresh, safe areas
This commit is contained in:
parent
6b7e54bbf9
commit
e35bba41e4
3 changed files with 119 additions and 9 deletions
|
|
@ -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
|
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
|
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
|
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:
|
Plans:
|
||||||
- [x] 26-00-PLAN.md — Foundation: SW rewrite (cache-first), deps (idb, web-push), PWA types, Wave 0 test stubs
|
- [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
|
- [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-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
|
- [ ] 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 |
|
| 23. Brainstormer Flow | v1.3 | 4/4 | Complete | 2026-04-01 |
|
||||||
| 24. Search, History & Branching | 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 |
|
| 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| |
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@ gsd_state_version: 1.0
|
||||||
milestone: v1.3
|
milestone: v1.3
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: executing
|
status: executing
|
||||||
stopped_at: Completed 26-01-PLAN.md
|
stopped_at: Completed 26-02-PLAN.md
|
||||||
last_updated: "2026-04-02T02:03:08.823Z"
|
last_updated: "2026-04-02T02:11:26.475Z"
|
||||||
last_activity: 2026-04-02
|
last_activity: 2026-04-02
|
||||||
progress:
|
progress:
|
||||||
total_phases: 6
|
total_phases: 6
|
||||||
completed_phases: 5
|
completed_phases: 5
|
||||||
total_plans: 35
|
total_plans: 35
|
||||||
completed_plans: 32
|
completed_plans: 33
|
||||||
percent: 100
|
percent: 100
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ See: .planning/PROJECT.md (updated 2026-03-30)
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 26 (pwa-performance) — EXECUTING
|
Phase: 26 (pwa-performance) — EXECUTING
|
||||||
Plan: 3 of 5
|
Plan: 4 of 5
|
||||||
Status: Ready to execute
|
Status: Ready to execute
|
||||||
Last activity: 2026-04-02
|
Last activity: 2026-04-02
|
||||||
|
|
||||||
|
|
@ -88,6 +88,7 @@ Progress: [██████████] 100%
|
||||||
| Phase 25-file-system P07 | 10 | 2 tasks | 6 files |
|
| Phase 25-file-system P07 | 10 | 2 tasks | 6 files |
|
||||||
| Phase 26-pwa-performance P00 | 5 | 2 tasks | 9 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 P01 | 4 | 2 tasks | 2 files |
|
||||||
|
| Phase 26-pwa-performance P02 | 20 | 2 tasks | 8 files |
|
||||||
|
|
||||||
## Accumulated Context
|
## 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]: [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]: 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]: 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
|
### Pending Todos
|
||||||
|
|
||||||
|
|
@ -179,6 +183,6 @@ None yet.
|
||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-02T02:03:08.819Z
|
Last session: 2026-04-02T02:11:26.472Z
|
||||||
Stopped at: Completed 26-01-PLAN.md
|
Stopped at: Completed 26-02-PLAN.md
|
||||||
Resume file: None
|
Resume file: None
|
||||||
|
|
|
||||||
106
.planning/phases/26-pwa-performance/26-02-SUMMARY.md
Normal file
106
.planning/phases/26-pwa-performance/26-02-SUMMARY.md
Normal 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)
|
||||||
Loading…
Add table
Reference in a new issue