Mobile Responsive App — Feature Spec¶
Date: 2026-04-10
Status: Partially implemented in Session 37 — mobile responsive fixes shipped; full spec not yet complete
Repo: curaway-health-navigator (frontend)
Audit source: Session 35 mobile responsiveness audit (25 findings)
1. Summary¶
The conversational UI (/app) is desktop-first. On mobile (375px),
the icon rail + panels + chat don't fit — the core flow is unusable.
This spec covers all 25 audit findings (6 P0, 9 P1, 10 P2) with a
mobile-first navigation pattern and systematic responsive fixes.
2. Mobile Navigation Pattern¶
Current (desktop-only)¶
┌──────┬────────────┬─────────────┐
│ Icon │ Left │ Chat + │
│ Rail │ Panel │ Input Bar │
│ 56px │ 360px │ flex-1 │
└──────┴────────────┴─────────────┘
Proposed (< md breakpoint = 768px)¶
┌─────────────────────────────┐
│ Top Bar (case + avatar) │
├─────────────────────────────┤
│ │
│ Chat + Messages │
│ │
├─────────────────────────────┤
│ Input Bar + safe-area │
├─────────────────────────────┤
│ Bottom Nav (4 icons) │
└─────────────────────────────┘
Panels open as full-screen overlays with slide-up animation
Bottom nav icons: Cases | EHR | Docs | Summary (same 4 as the icon rail, minus the "new case" button which moves to the top bar).
New case button: Moves to the top bar left side on mobile (replaces the icon rail's orange + button).
3. File-by-File Changes¶
P0 — Layout Fixes¶
3.1 ConversationApp.tsx¶
| Line | Current | Fix |
|---|---|---|
| 1678 | h-screen flex bg-chat-bg |
Add flex-col md:flex-row — column layout on mobile |
| 1680-1685 | <IconRail> always visible |
Wrap in hidden md:flex — hidden on mobile |
| 1688-1699 | <LeftPanel> inline |
On mobile: render as fixed inset-0 z-50 overlay when open |
| 1724 | <main> flex-1 |
Full width on mobile (icon rail hidden) |
| ~1777 | Chat scroll area px-6 |
px-4 md:px-6 — tighter mobile padding |
New: Add <MobileBottomNav> component after input bar, visible
only below md. Renders the 4 panel icons as a bottom tab bar.
New: Add <MobileTopBar> variant of the header that includes
the new-case button on the left (replaces icon rail's + button).
3.2 IconRail.tsx¶
| Line | Current | Fix |
|---|---|---|
| root | Always visible, 56px fixed | Add hidden md:flex to root container |
| 28-34 | w-10 h-10 buttons |
w-11 h-11 (44px WCAG touch target) |
| 41-56 | Nav icons w-10 h-10 |
w-11 h-11 |
No mobile rendering — replaced by <MobileBottomNav>.
3.3 LeftPanel.tsx¶
| Line | Current | Fix |
|---|---|---|
| 34 | w-[360px] |
w-full md:w-[360px] |
| root | Inline in flex layout | On mobile (< md): fixed inset-0 z-50 with backdrop overlay |
| - | No close gesture | Add backdrop click to close |
3.4 CasePanel.tsx (right progress panel)¶
| Line | Current | Fix |
|---|---|---|
| 82 | fixed right-0 top-0 bottom-0 w-[360px] |
w-full md:w-[360px] |
| 80 | Backdrop z-40 lg:hidden |
Change to z-40 md:hidden (show backdrop on tablet too) |
| - | No swipe gesture | P2 — defer |
3.5 FullEHRDrawer.tsx¶
| Line | Current | Fix |
|---|---|---|
| 1182 | w-full md:w-[calc(100vw-56px)] |
w-full on mobile (icon rail hidden, so full width is correct) |
| 1183 | No safe-area handling | Add pb-[env(safe-area-inset-bottom)] to content area |
| 1199-1206 | Patient name no truncate | Add truncate max-w-[60%] to name element |
| 1242 | lg:grid-cols-[1fr_380px] sidebar |
md:grid-cols-[1fr_320px] lg:grid-cols-[1fr_380px]; on mobile: stack vertically grid-cols-1 |
3.6 ProgressStrip.tsx¶
| Line | Current | Fix |
|---|---|---|
| 153 | w-[280px] expanded |
w-full md:w-[280px] — full width on mobile overlay |
| root | Fixed position right side | On mobile: render inside a bottom sheet or full-screen overlay |
3.7 EHRPanel.tsx¶
| Line | Current | Fix |
|---|---|---|
| 189-206 | Table without overflow wrapper | Wrap in <div className="overflow-x-auto -mx-3 px-3"> |
| 137-144 | SVG ring w-14 h-14 |
w-12 h-12 md:w-14 md:h-14 |
P1 — UX Improvements¶
3.8 Auth.tsx¶
| Line | Current | Fix |
|---|---|---|
| 40 | Tab switcher max-w-[420px] |
max-w-[calc(100vw-2rem)] sm:max-w-[420px] |
| 7 | Clerk card max-w-[420px] |
Same responsive max-width |
3.9 ConversationApp.tsx — Messages¶
| Line | Current | Fix |
|---|---|---|
| 125 | Agent message max-w-none |
max-w-full md:max-w-[85%] for readable line length |
| 89, 112 | text-[10px] timestamps |
text-[11px] md:text-[10px] — 11px minimum on mobile |
| 148-388 | Rich cards no max-width | Add max-w-full wrapper on each RichCard |
3.10 ConversationApp.tsx — Input Bar¶
| Current | Fix |
|---|---|
| No safe-area bottom padding | Add pb-[max(0.5rem,env(safe-area-inset-bottom))] to input container |
| No keyboard handling | Add visualViewport resize listener to scroll input into view |
3.11 ConversationApp.tsx — File Upload¶
| Current | Fix |
|---|---|
| Drag-and-drop overlay only | Add "Tap to upload" label next to paperclip icon on mobile (md:hidden) |
| Drag overlay covers full screen | Only show overlay on desktop (hidden md:flex on drag overlay) |
3.12 CaseConfirmation.tsx¶
| Line | Current | Fix |
|---|---|---|
| 55 | px-6 |
px-4 md:px-6 |
3.13 CurawayUserMenu.tsx¶
| Line | Current | Fix |
|---|---|---|
| 86-90 | Dropdown w-64 |
max-w-[calc(100vw-2rem)] w-64 |
P2 — Polish¶
3.14 Tables — overflow containers¶
All <table> elements in EHRPanel, FullEHRDrawer, SummaryPanel:
wrap in <div className="overflow-x-auto">.
3.15 RequirementsChecklist — badge wrapping¶
| Line | Current | Fix |
|---|---|---|
| 37-62 | flex items-start gap-2 |
flex flex-col sm:flex-row items-start gap-1 sm:gap-2 |
3.16 FullEHRDrawer sidebar responsive¶
| Line | Current | Fix |
|---|---|---|
| 1242 | lg:grid-cols-[1fr_380px] |
On mobile: sidebar becomes a bottom section, scrollable |
3.17 Scrollbar touch targets¶
| File | Fix |
|---|---|
| LeftPanel, FullEHRDrawer | Remove scrollbar-thin on mobile: scrollbar-thin md:scrollbar-thin or use default browser scrollbar |
3.18 Font sizes — minimum 11px on mobile¶
Scan all text-[10px] occurrences and replace with
text-[11px] md:text-[10px] for mobile readability.
3.19 Swipe gestures (future)¶
CasePanel, LeftPanel overlays — add swipe-to-close. Requires
@use-gesture/react or manual touch event handling. Defer to
follow-up PR.
3.20 Dark mode — status badges¶
Status badges (red/amber/green/blue) are hardcoded. They look fine in dark mode because they're self-contained color pairs (bg + text). No change needed.
4. New Component: MobileBottomNav¶
// src/components/MobileBottomNav.tsx (~40 LOC)
// Visible only below md breakpoint
// 4 icons: Cases, EHR, Docs, Summary
// Active state matches leftPanel state
// Tapping toggles the corresponding full-screen overlay panel
interface MobileBottomNavProps {
activePanel: PanelId | null;
hasCaseId: boolean;
onPanelSelect: (panel: PanelId) => void;
}
Rendered in ConversationApp after the input bar:
<div className="md:hidden">
<MobileBottomNav
activePanel={leftPanel}
hasCaseId={!!caseId}
onPanelSelect={openPanel}
/>
</div>
Styling: fixed bottom-0 left-0 right-0 with safe-area padding,
bg-chat-surface border-t border-chat-border, 4 equal-width
icon buttons at 44px height minimum.
5. New Component: MobileTopBar¶
Not a new component — modify the existing <header> in
ConversationApp to include the new-case button on mobile:
<header className="h-12 md:h-11 bg-chat-surface border-b ...">
{/* Mobile: new case button (replaces icon rail + button) */}
<button className="md:hidden w-8 h-8 rounded-lg bg-chat-brand ..."
onClick={handleNewCase}>
<Plus className="w-4 h-4 text-white" />
</button>
{/* Case number + procedure */}
...
<CurawayUserMenu />
</header>
6. Breakpoint Strategy¶
| Breakpoint | Layout |
|---|---|
| < 768px (mobile) | Bottom nav, full-screen overlay panels, no icon rail, px-4 padding |
| 768px–1024px (tablet) | Icon rail visible, panels as overlays with backdrop |
| > 1024px (desktop) | Icon rail + inline panels + chat (current layout) |
Using Tailwind's md: (768px) and lg: (1024px) breakpoints.
Edge Cases¶
All P0 and P1 items are layout/CSS changes with straightforward behavior. The edge cases below apply to the remaining P2/P3 items and to the mobile navigation pattern as a whole.
| Edge Case | Scenario | Handling | Severity |
|---|---|---|---|
| Bottom nav overlaps chat input on short viewports | On iPhone SE (375x667) with the virtual keyboard open, the bottom nav (fixed) + input bar (fixed) + keyboard stack can push the visible chat area to near-zero height. The patient cannot see what they're typing in context. | Use the visualViewport API (already planned in P1 item 3.10) to detect keyboard presence. When the keyboard is open, hide the bottom nav entirely (display: none) — the patient is typing, not switching panels. Restore the bottom nav when the keyboard closes. Test on iPhone SE specifically since it has the smallest viewport. |
High |
| Panel overlay blocks incoming SSE messages | Patient has the EHR panel open as a full-screen overlay on mobile. An SSE message arrives (e.g., document analysis complete). The message appends to the chat behind the overlay — the patient doesn't see it and may think the system is unresponsive. | Add a subtle notification badge (red dot) on the "Chat" area or on the bottom nav's implicit "back to chat" affordance when new messages arrive while a panel is open. Alternatively, show a toast notification ("New message from Curaway") that the patient can tap to close the panel and return to chat. | Medium |
| Swipe-to-close conflicts with horizontal scroll in tables | P2 item 3.19 proposes swipe-to-close for panel overlays. But P2 item 3.14 wraps tables in overflow-x-auto. If the patient swipes horizontally on a table, the gesture library could interpret it as a close gesture instead of a table scroll. |
When implementing swipe gestures (deferred to follow-up), set a horizontal threshold: only trigger close on swipes that start outside scrollable containers. Use event.target.closest('.overflow-x-auto') to detect if the swipe originated inside a scrollable table and suppress the close gesture. This is a P3 concern — defer until the gesture library is added. |
Low |
| Landscape orientation on phones | Patient rotates their phone to landscape. The bottom nav + input bar + chat header consume most of the 375px height (now the short axis). Very little chat content is visible. | In landscape on mobile (@media (max-height: 500px) and (orientation: landscape)), collapse the top bar to a minimal height (32px), hide the bottom nav, and show a floating "menu" button instead. This maximizes the chat viewport. Add to P2 implementation. |
Low |
7. Implementation Checklist¶
Model guidance: Opus for layout architecture decisions, Sonnet for mechanical CSS/class changes.
P0 (must-ship)¶
Opus:
- [ ] ConversationApp: flex-col md:flex-row root layout — layout architecture
- [ ] Hamburger drawer with panel nav + recent cases — new component, navigation UX
Sonnet:
- [ ] Hide IconRail on mobile (hidden md:flex)
- [ ] LeftPanel: w-full md:w-[360px] + fixed overlay on mobile
- [ ] CasePanel: w-full md:w-[360px]
- [ ] ProgressStrip: w-full md:w-[280px]
- [ ] FullEHRDrawer: safe-area padding + name truncate
- [ ] EHRPanel: overflow-x-auto on table
- [ ] Mobile top bar: hamburger + left-aligned title
P1 (should-ship) — all Sonnet¶
- [ ] Auth card:
max-w-[calc(100vw-2rem)] sm:max-w-[420px] - [ ] Agent messages:
max-w-full md:max-w-[85%] - [ ] Timestamps:
text-[11px] md:text-[10px] - [ ] Input bar: safe-area bottom padding + floating rounded card
- [ ] Rich cards:
max-w-fullwrapper - [ ] File upload:
capture="environment"on file input - [ ] CaseConfirmation:
px-4 md:px-6 - [ ] CurawayUserMenu dropdown:
max-w-[calc(100vw-2rem)] - [ ] IconRail buttons:
w-11 h-11(44px touch targets) - [ ] FullEHRDrawer header: truncate patient name
- [ ]
interactive-widget=resizes-contentviewport meta
P2 (follow-up) — all Sonnet¶
- [ ] All tables:
overflow-x-autowrapper - [ ] RequirementsChecklist: responsive badge layout
- [ ] FullEHRDrawer sidebar: stack on mobile
- [ ] Remove
scrollbar-thinon mobile - [ ] Minimum 11px font size on mobile
- [ ] Swipe-to-close gestures (future — needs gesture library)
8. Test Plan¶
Device matrix¶
- iPhone SE (375px) — smallest target
- iPhone 14 (390px) — most common
- iPhone 14 Pro Max (430px) — large phone
- iPad (768px) — tablet breakpoint edge
- iPad Pro (1024px) — lg breakpoint edge
Flows to test per device¶
- Sign in → lands in /app
- Start new case → type message → get response
- Upload document → see progress → see analysis
- Open Cases panel → switch cases
- Open EHR panel → scroll through data
- Open Docs panel → see document list
- Open Summary panel → see case summary
- Open FullEHRDrawer → navigate sections
- Switch theme (dark mode on mobile)
- Rotate device (portrait ↔ landscape)
Accessibility¶
- All touch targets ≥ 44x44px
- Text ≥ 11px on mobile
- Focus visible on all interactive elements
- Screen reader announces panel open/close
- No horizontal scroll on any view
9. Risks¶
| Risk | Mitigation |
|---|---|
| Bottom nav overlaps input bar | Safe-area padding + fixed stacking order (input z-20, nav z-30) |
| Panel overlay blocks chat | Backdrop click/tap closes panel |
| Keyboard pushes layout up | visualViewport API resize listener scrolls input into view |
| Performance on low-end Android | Bottom nav is static HTML, no animation on panel mount — only slide on open/close |
| Clerk components not responsive inside overlays | Clerk renders in a portal — unaffected by our layout changes |
10. Out of Scope¶
- Storefront pages (already responsive via StorefrontLayout)
- Native app / PWA (future)
- Offline support
- Push notifications on mobile
- Camera capture for document upload (future — use
capture="environment"on file input)