Skip to content

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-full wrapper
  • [ ] 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-content viewport meta

P2 (follow-up) — all Sonnet

  • [ ] All tables: overflow-x-auto wrapper
  • [ ] RequirementsChecklist: responsive badge layout
  • [ ] FullEHRDrawer sidebar: stack on mobile
  • [ ] Remove scrollbar-thin on 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

  1. Sign in → lands in /app
  2. Start new case → type message → get response
  3. Upload document → see progress → see analysis
  4. Open Cases panel → switch cases
  5. Open EHR panel → scroll through data
  6. Open Docs panel → see document list
  7. Open Summary panel → see case summary
  8. Open FullEHRDrawer → navigate sections
  9. Switch theme (dark mode on mobile)
  10. 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)