` — renders `ChatMessageList` (reuse existing)
d. Input bar: `
` — renders `ChatInput` (reuse existing)
- Height calculation: use `h-[100dvh]` on the outer container (NOT `100vh` — per RESEARCH Pitfall 3)
- Two views within MobileChatView:
a. When `activeConversationId` is null: show conversation list (full screen) wrapped in `PullToRefresh`. Add bottom padding `pb-16` to account for the existing MobileBottomNav at the bottom.
b. When `activeConversationId` is set: show header + message list + input (full screen, MobileBottomNav is hidden by Layout when chat is active via `mobileNavVisible` logic)
2. Update `ui/src/components/ChatPanel.tsx`:
- Import `useMediaQuery` from `../hooks/useMediaQuery`
- Import `MobileChatView` from `./MobileChatView`
- At the top of ChatPanel function body, add: `const isDesktop = useMediaQuery("(min-width: 768px)");`
- Conditional render: if `!isDesktop`, render `
` passing all necessary props/context. If `isDesktop`, render existing desktop panel layout unchanged.
- The existing `"hidden md:flex"` class on the desktop container already hides it on mobile, but the explicit conditional ensures MobileChatView renders on mobile.
- Do NOT render any MobileNavBar — the global MobileBottomNav in Layout.tsx handles navigation.
3. Update `ui/src/components/ChatConversationList.tsx`:
- Import `PullToRefresh` from `./PullToRefresh`
- Import `useMediaQuery` from `../hooks/useMediaQuery`
- Wrap the ScrollArea content in `
` where `refetch` is from `useChatConversations` and `isMobile = !useMediaQuery("(min-width: 768px)")`
- Each conversation list item already has adequate height but verify `min-h-[48px]` — add it if missing per UI-SPEC touch target rule
4. Update `ui/src/components/ChatInput.tsx`:
- Add `pb-[env(safe-area-inset-bottom)]` class to the outermost input container (only on mobile — use a conditional class or always apply since it's a no-op on desktop)
- Ensure the Send button has `min-h-[44px] min-w-[44px]` for touch target compliance
grep -q "MobileChatView" ui/src/components/ChatPanel.tsx && grep -q "100dvh" ui/src/components/MobileChatView.tsx && grep -q "PullToRefresh" ui/src/components/ChatConversationList.tsx && grep -q "safe-area-inset-bottom" ui/src/components/ChatInput.tsx && echo "PASS"
- `MobileChatView.tsx` exists with `100dvh` height, back button with `aria-label="Back to conversations"`, sticky input bar with `safe-area-inset-bottom`
- `MobileChatView.tsx` does NOT render any MobileNavBar — relies on Layout's MobileBottomNav
- `MobileChatView.tsx` conversation list view has `pb-16` to account for MobileBottomNav
- `ChatPanel.tsx` imports and conditionally renders `MobileChatView` for mobile
- `ChatConversationList.tsx` wraps content in `PullToRefresh` for mobile
- `ChatInput.tsx` has `safe-area-inset-bottom` padding
- Send button has minimum 44px touch target
- `pnpm --filter @paperclipai/ui build` succeeds
MobileChatView renders full-screen chat on mobile, leveraging existing MobileBottomNav from Layout for navigation. ChatPanel conditionally renders mobile vs desktop. ChatConversationList has pull-to-refresh. ChatInput has safe area padding and proper touch targets.
- `pnpm --filter @paperclipai/ui build` succeeds
- MobileChatView uses `100dvh` not `100vh`
- No MobileNavBar component created — existing MobileBottomNav in Layout handles global mobile nav
- PullToRefresh triggers after 64px threshold
- ChatPanel conditionally renders MobileChatView on mobile
- Safe area insets applied on input bar
Mobile responsive layout complete. Phone users see full-screen chat with pull-to-refresh and properly sized touch targets. Global MobileBottomNav (already in Layout) provides navigation across all pages. Desktop layout unchanged.