Skip to content

chore(web): mobile-responsive layout for dashboard and session views#634

Merged
ashish921998 merged 45 commits intomainfrom
feat/issue-633
Mar 26, 2026
Merged

chore(web): mobile-responsive layout for dashboard and session views#634
ashish921998 merged 45 commits intomainfrom
feat/issue-633

Conversation

@ashish921998
Copy link
Copy Markdown
Collaborator

@ashish921998 ashish921998 commented Mar 23, 2026

Summary

Adds proper mobile-responsive layouts to the dashboard and session views after the recent UI/UX redesign (#528).

  • Sidebar: Auto-hides on mobile (<768px) with hamburger toggle button and overlay drawer with backdrop
  • Dashboard hero: Stats cards and controls stack vertically on mobile
  • Kanban board: Columns stack vertically on mobile instead of horizontal scroll
  • Session cards: Flexible height with 44px minimum touch targets for all interactive elements
  • Session detail: Responsive header metadata, full-width terminal on mobile
  • Global: Added 768px and 480px breakpoints with phone-specific single-column layouts

Desktop layout remains completely unchanged.

Closes #633

Test plan

  • Open browser DevTools responsive mode
  • Verify desktop layout unchanged at 1200px+
  • At 768px: sidebar hidden, hamburger visible, hero section stacks, kanban columns stack vertically
  • At 480px: single-column stat cards, smaller title, reduced terminal padding
  • At 375px (iPhone SE): all content single-column, touch-friendly
  • Test hamburger menu: click opens drawer with backdrop, click backdrop closes
  • Verify all buttons/controls meet 44px minimum touch target
  • Test both light and dark themes at mobile widths
  • Navigate to session detail page — verify responsive header and full-width terminal

🤖 Generated with Claude Code

Add proper mobile breakpoints (768px, 480px) with structural layout changes:

- Sidebar: auto-hide on mobile with hamburger toggle and overlay drawer
- Dashboard hero: stack stats cards and controls vertically on mobile
- Kanban board: stack columns vertically instead of horizontal scroll
- Session cards: flexible height with 44px minimum touch targets
- Session detail: responsive header metadata and full-width terminal
- Global: phone-specific breakpoint (480px) for single-column layouts

Desktop layout remains completely unchanged.

Closes #633

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread packages/web/src/components/ProjectSidebar.tsx
The collapsed sidebar early-return path was missing the backdrop overlay
and close mechanism that the expanded branch had, making it impossible
to dismiss the mobile drawer by tapping outside.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread packages/web/src/app/globals.css
ashish921998 and others added 3 commits March 24, 2026 07:53
- Add web app manifest (manifest.ts) with dynamic project name, standalone display
- Add service worker (sw.js) with network-first strategy and offline fallback page
- Add PWA icons: apple-touch-icon (180x180), 192x192, 512x512 via shared renderer
- Add viewport meta with viewport-fit=cover for safe-area-inset support
- Add Apple Web App meta tags for iOS home screen installation
- Add theme-color meta for browser chrome coloring
- Extract shared icon-renderer.tsx to deduplicate icon generation logic
- Add ServiceWorkerRegistrar client component for SW registration
- Configure next.config.js with no-cache headers for service worker
- Add dvh fallbacks for 100vh usage (body, kanban-board, Terminal, DirectTerminal)
- Add safe-area-inset padding for mobile sidebar and dashboard main
- Add iOS auto-zoom prevention (16px font on inputs)
- Add -webkit-tap-highlight-color: transparent and text-size-adjust
- Add PWA standalone mode body padding for safe areas

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace unsupported eslint-env with globals comment for flat config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread packages/web/src/components/ProjectSidebar.tsx
- Increase .project-sidebar--mobile-open specificity to (0,2,0) so it
  overrides .project-sidebar.project-sidebar--collapsed transform
- Make "Hide sidebar" and "Show project sidebar" buttons also call
  onMobileClose to dismiss the mobile drawer when toggling collapse

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread packages/web/public/sw.js Outdated
ashish921998 and others added 16 commits March 24, 2026 08:12
caches.match() can return undefined under mobile storage pressure.
Fall back to a plain-text 503 response instead of passing undefined
to event.respondWith().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the lazy useState initializer that read window.matchMedia on
first render (causing SSR/client hydration mismatch) with a plain
false default; the existing useEffect already syncs the correct value
after mount. Adds a full vitest test suite covering all six specified
cases (SSR-safe default, match/no-match, number-to-query conversion,
change-event reactivity, and listener cleanup on unmount).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace vertical kanban stacking on mobile (<=767px) with a single-open
accordion ordered Respond > Merge > Review > Pending > Working.
Auto-expands the most urgent non-empty section on load; empty sections
render as header-only rows (no dashed placeholder). Desktop layout is
unchanged. Added window.matchMedia stub to the Vitest setup file so
Dashboard unit tests pass in jsdom.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix Critical 1: change onToggle to (level: AttentionLevel) => void and
  create stable handleAccordionToggle with useCallback, eliminating the
  inline arrow that defeated memo on every render
- Fix Critical 2: remove grouped from useEffect deps so user accordion
  selection is not reset on every SSE update; seed only on isMobile change
- Fix Important 3: move accordion CSS block inside @media (max-width: 767px)
  so it is scoped to mobile only, consistent with rest of file
- Fix Important 4: add Safari 16+ compatibility comment on
  grid-template-rows transition
- Fix Suggestion 5: replace ▶/▼ character swap with single ▶ rotated via
  CSS transform so the declared transition: transform actually animates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cards

On mobile (<=767px), replace the 4 stat cards with a single-row
MobileActionStrip showing non-zero urgency pills (respond/merge/review)
with colored dots. Tapping a pill calls setExpandedLevel and scrolls to
the accordion board via id="mobile-board". Hides stat cards, dashboard
subtitle, and board-section-head on mobile to target ~92px hero height.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract onPillTap as handlePillTap useCallback with prefers-reduced-motion support
- Replace role="navigation" with role="group" aria-label="Session priorities" on pill strip
- Add role="status" to all-clear fallback so screen readers announce state changes
- Add :focus-visible outline to .mobile-action-pill for keyboard accessibility
- Add scroll-margin-top: 56px to .accordion-board to prevent sticky header occlusion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On mobile (<=767px), session cards with attention level "respond" now
show an inline quick-reply panel with the agent's last message summary
(2-line clamp), Continue/Abort/Skip preset buttons, and an expandable
textarea. Enter submits; all touch targets are >=44px. Hidden on
desktop via CSS-only media query.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Quick-reply UI was invisible on mobile viewports 480–767px wide because
its styles were scoped to @media (max-width: 480px). Moved all
.quick-reply* rules into the @media (max-width: 767px) block so the
component renders correctly across the full mobile range.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…order token

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…confirmations

- Create Toast.tsx with ToastProvider + useToast hook (3s auto-dismiss, success/error/info variants, fade+slide animation)
- Create BottomSheet.tsx replacing confirm() for kill actions (swipe-to-dismiss, keyboard Escape, session context display)
- Wire ToastProvider + BottomSheet into Dashboard.tsx; replace confirm() calls; add showToast on merge/kill/restore success and error
- Add toast and bottom-sheet CSS to globals.css (no new z-index vars needed — --z-modal/--z-toast already existed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mSheet

- ToastProvider now clears its setTimeout in a useEffect cleanup to prevent
  memory leaks when the component unmounts while a toast is still pending.
- BottomSheet adds a focus trap on the sheet element so keyboard users cannot
  Tab out of the dialog; focus is also set to the first focusable element
  (Cancel button) when the sheet opens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Exposes connectionStatus ("connected" | "reconnecting" | "disconnected")
from useSessionEvents via onopen/onerror EventSource handlers, and renders
a fixed ConnectionBar at the top of the viewport that shows a pulsing amber
bar while reconnecting and a red 32px "Offline · tap to retry" bar when
disconnected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/web/src/components/Dashboard.tsx
Comment thread packages/web/src/components/PullRequestsPage.tsx Outdated
@ashish921998
Copy link
Copy Markdown
Collaborator Author

Code review

Found 1 issue:

  1. MobileBottomNav is rendered unconditionally in SessionDetail — it will appear on desktop. Both Dashboard and PullRequestsPage gate it with {isMobile ? <MobileBottomNav /> : null}, but SessionDetail renders it on every screen size.

</main>
</div>
<MobileBottomNav
ariaLabel="Session navigation"
activeTab={isOrchestrator ? "orchestrator" : undefined}
dashboardHref={dashboardHref}
prsHref={prsHref}
orchestratorHref={orchestratorHref}
/>
</div>
);

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Cache deduplication defeated by inconsistent argument passing
    • Removed redundant resolveDashboardProjectFilter calls in generateMetadata functions so both generateMetadata and page components pass the same raw searchParams.project value to getDashboardPageData, ensuring cache key consistency.
  • ✅ Fixed: Session detail page lacks mobile bottom nav padding
    • Added padding-bottom: calc(84px + env(safe-area-inset-bottom, 0px)) to .session-detail-page .mx-auto in the mobile breakpoint to match the dashboard-main padding pattern and prevent content from being hidden behind the fixed bottom nav.

Create PR

Or push these changes by commenting:

@cursor push 6e4cda3a39
Preview (6e4cda3a39)
diff --git a/packages/web/src/app/globals.css b/packages/web/src/app/globals.css
--- a/packages/web/src/app/globals.css
+++ b/packages/web/src/app/globals.css
@@ -970,7 +970,8 @@
 }
 
 @keyframes session-status-dot-pulse {
-  0%, 100% {
+  0%,
+  100% {
     transform: scale(1);
     opacity: 0.88;
   }
@@ -981,7 +982,8 @@
 }
 
 @keyframes session-status-active-breathe {
-  0%, 100% {
+  0%,
+  100% {
     box-shadow:
       inset 0 1px 0 color-mix(in srgb, white 16%, transparent),
       0 0 0 0 color-mix(in srgb, var(--color-status-working) 0%, transparent);
@@ -994,7 +996,8 @@
 }
 
 @keyframes session-status-ready-breathe {
-  0%, 100% {
+  0%,
+  100% {
     box-shadow:
       inset 0 1px 0 color-mix(in srgb, white 16%, transparent),
       0 0 0 0 color-mix(in srgb, var(--color-status-ready) 0%, transparent);
@@ -1007,7 +1010,8 @@
 }
 
 @keyframes session-status-waiting-breathe {
-  0%, 100% {
+  0%,
+  100% {
     box-shadow:
       inset 0 1px 0 color-mix(in srgb, white 16%, transparent),
       0 0 0 0 color-mix(in srgb, var(--color-status-attention) 0%, transparent);
@@ -1936,7 +1940,6 @@
 
 /* Card glow effects removed for Linear-clean look */
 
-
 @media (max-width: 960px) {
   .dashboard-hero__content {
     padding: 12px 14px;
@@ -2173,8 +2176,7 @@
     backdrop-filter: blur(14px);
     -webkit-backdrop-filter: blur(14px);
     padding: 8px max(12px, env(safe-area-inset-right, 0px))
-      calc(8px + env(safe-area-inset-bottom, 0px))
-      max(12px, env(safe-area-inset-left, 0px));
+      calc(8px + env(safe-area-inset-bottom, 0px)) max(12px, env(safe-area-inset-left, 0px));
   }
 
   .mobile-bottom-nav__item {
@@ -2407,6 +2409,7 @@
   .session-detail-page .mx-auto {
     padding-left: 12px;
     padding-right: 12px;
+    padding-bottom: calc(84px + env(safe-area-inset-bottom, 0px));
   }
 
   .session-detail-page {
@@ -2546,7 +2549,9 @@
   }
 
   /* -- iOS auto-zoom prevention on input focus -- */
-  input, textarea, select {
+  input,
+  textarea,
+  select {
     font-size: 16px;
   }
 
@@ -2876,7 +2881,10 @@
     font-family: var(--font-sans);
     font-size: 12px;
     cursor: pointer;
-    transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease;
+    transition:
+      border-color 0.12s ease,
+      color 0.12s ease,
+      background 0.12s ease;
   }
 
   .quick-reply__preset-btn:hover {
@@ -2896,7 +2904,9 @@
     font-size: 13px;
     resize: none;
     overflow: hidden;
-    transition: height 0.15s ease, border-color 0.12s ease;
+    transition:
+      height 0.15s ease,
+      border-color 0.12s ease;
     box-sizing: border-box;
     width: 100%;
   }
@@ -3301,7 +3311,9 @@
   z-index: var(--z-connection-bar, 500);
   height: 0;
   overflow: hidden;
-  transition: height 0.2s ease, opacity 0.3s ease;
+  transition:
+    height 0.2s ease,
+    opacity 0.3s ease;
   display: flex;
   align-items: center;
   justify-content: center;
@@ -3327,6 +3339,11 @@
 }
 
 @keyframes connection-pulse {
-  0%, 100% { opacity: 1; }
-  50% { opacity: 0.4; }
+  0%,
+  100% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0.4;
+  }
 }

diff --git a/packages/web/src/app/page.tsx b/packages/web/src/app/page.tsx
--- a/packages/web/src/app/page.tsx
+++ b/packages/web/src/app/page.tsx
@@ -8,8 +8,7 @@
   searchParams: Promise<{ project?: string }>;
 }): Promise<Metadata> {
   const searchParams = await props.searchParams;
-  const projectFilter = resolveDashboardProjectFilter(searchParams.project);
-  const pageData = await getDashboardPageData(projectFilter);
+  const pageData = await getDashboardPageData(searchParams.project);
   return { title: { absolute: `ao | ${pageData.projectName}` } };
 }
 

diff --git a/packages/web/src/app/prs/page.tsx b/packages/web/src/app/prs/page.tsx
--- a/packages/web/src/app/prs/page.tsx
+++ b/packages/web/src/app/prs/page.tsx
@@ -8,8 +8,7 @@
   searchParams: Promise<{ project?: string }>;
 }): Promise<Metadata> {
   const searchParams = await props.searchParams;
-  const projectFilter = resolveDashboardProjectFilter(searchParams.project);
-  const pageData = await getDashboardPageData(projectFilter);
+  const pageData = await getDashboardPageData(searchParams.project);
   return { title: { absolute: `ao | ${pageData.projectName} PRs` } };
 }

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment thread packages/web/src/app/page.tsx Outdated
Comment thread packages/web/src/components/SessionDetail.tsx
Comment thread packages/web/src/components/Dashboard.tsx
Comment thread packages/web/src/components/PRStatus.tsx
Comment thread packages/web/src/components/Dashboard.tsx
Comment thread packages/web/src/app/prs/page.tsx
Comment thread packages/web/src/app/apple-icon.tsx
Comment thread packages/web/src/components/SessionCard.tsx
Comment thread packages/web/src/components/SessionCard.tsx Outdated
Copy link
Copy Markdown
Collaborator

@harsh-batheja harsh-batheja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test comment

Copy link
Copy Markdown
Collaborator

@harsh-batheja harsh-batheja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Post-fix issues & remaining unresolved items

New issues introduced after previous review (5)

  1. handleSend throw creates inconsistent error UX on action buttons (Medium)
  2. handleMerge/handleRestore lack try/catch for network failures (Medium)
  3. projectOrchestratorId reset effect creates re-fetch burst loop (Medium)
  4. Quick-reply desktop styling uses mobile-sized 44px touch targets (Low)
  5. Desktop PRs link flashes on mobile before useMediaQuery kicks in (Low)

Still unresolved from previous review (2)

  1. Non-orchestrator session pages still poll /api/sessions every 5s (Medium)
  2. User accordion collapse overridden by SSE updates when mobileFilter === "all" (Medium)

See inline comments for details.

Comment thread packages/web/src/components/SessionCard.tsx
Comment thread packages/web/src/components/Dashboard.tsx
Comment thread packages/web/src/app/sessions/[id]/page.tsx
Comment thread packages/web/src/app/globals.css
Comment thread packages/web/src/components/Dashboard.tsx Outdated
Comment thread packages/web/src/app/sessions/[id]/page.tsx Outdated
Comment thread packages/web/src/components/Dashboard.tsx
Comment thread packages/web/src/app/sessions/[id]/page.tsx Outdated
Comment thread packages/web/src/app/globals.css
Comment thread packages/web/src/components/Dashboard.tsx Outdated
Comment thread packages/web/src/components/AttentionZone.tsx
Comment thread packages/web/src/components/Dashboard.tsx
Comment thread packages/web/src/components/Dashboard.tsx
Comment thread packages/web/src/components/Dashboard.tsx
Comment thread packages/web/src/components/SessionCard.tsx
Comment thread packages/web/src/components/SessionDetail.tsx
Comment thread packages/web/src/app/sessions/[id]/page.tsx
Comment thread packages/web/src/app/sessions/[id]/page.tsx
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread packages/web/src/app/sessions/[id]/page.tsx
Comment thread packages/web/src/components/Dashboard.tsx
@ashish921998 ashish921998 merged commit af6c4c5 into main Mar 26, 2026
9 of 10 checks passed
zendext pushed a commit to zendext/agent-orchestrator that referenced this pull request Apr 21, 2026
chore(web): mobile-responsive layout for dashboard and session views
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(web): Mobile-responsive layout for dashboard and session views

3 participants