Skip to content

Commit ecff69a

Browse files
ambient-code[bot]Ambient Code Botclaude
authored
fix(frontend): mobile responsive layout issues (#1538)
<!-- acp:session_id=session-13bf6540-776e-4d4a-9798-d67b8132a0a8 source=#1537 last_action=2026-05-08T20:01:03Z retry_count=0 --> ## Summary - **Sidebar**: On mobile viewports (<768px), the sessions sidebar now renders as a Sheet (slide-in drawer overlay) instead of an inline panel. Tapping the hamburger menu icon opens it; navigating to a session auto-closes it. - **Explorer panel**: On mobile, the explorer panel renders as a Sheet overlay from the right instead of consuming horizontal space from the chat area. - **Content tabs**: Increased touch targets to 36px minimum height on mobile. Added hidden scrollbar for horizontal overflow. Close buttons are always visible on touch devices. - **Header**: Added responsive hamburger menu on mobile, condensed branding to "ACP", icon-only integrations button, and reduced padding. - **Chat toolbar**: Responsive popover widths (full viewport width minus margins on mobile, 500px on desktop). Added gap between toolbar sections. - **Session list items**: Increased tap targets, action menu always visible on mobile (no hover-only opacity). ## Test plan - [ ] Open the app on an iPhone or Chrome DevTools mobile emulator (375px width) - [ ] Verify sidebar opens as a full-height slide-in drawer from the left - [ ] Verify tapping a session navigates and closes the drawer - [ ] Verify the explorer panel opens as a drawer from the right on mobile - [ ] Verify chat content area fills the full viewport height (no empty black space) - [ ] Verify tabs are tappable with adequate touch targets - [ ] Verify the toolbar popovers (Agents, Commands) fit within the viewport - [ ] Verify desktop layout is unchanged (above 768px) Closes #1537 --- 🤖 [Ambient Session](https://ambient-code.apps.rosa.vteam-uat.0ksl.p3.openshiftapps.com/projects/ambient-platform-and-workflow-feedback-loop-running/sessions/session-13bf6540-776e-4d4a-9798-d67b8132a0a8) Co-authored-by: Ambient Code Bot <bot@ambient-code.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 070520c commit ecff69a

5 files changed

Lines changed: 206 additions & 102 deletions

File tree

components/frontend/src/app/projects/[name]/layout.tsx

100644100755
Lines changed: 93 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { useEffect, useMemo } from "react";
44
import { useParams, useRouter, usePathname } from "next/navigation";
5-
import { PanelLeft, Plug, LogOut } from "lucide-react";
5+
import { PanelLeft, Plug, LogOut, Menu } from "lucide-react";
66
import Link from "next/link";
77
import { useVersion } from "@/services/queries/use-version";
88
import {
@@ -12,10 +12,12 @@ import {
1212
DropdownMenuTrigger,
1313
} from "@/components/ui/dropdown-menu";
1414
import { Button } from "@/components/ui/button";
15+
import { Sheet, SheetContent, SheetTitle } from "@/components/ui/sheet";
1516
import { ThemeToggle } from "@/components/theme-toggle";
1617
import { RecentUpdatesDialog } from "@/components/recent-updates-dialog";
1718
import { UserBubble } from "@/components/user-bubble";
1819
import { cn } from "@/lib/utils";
20+
import { useIsMobile } from "@/hooks/use-mobile";
1921
import { useLocalStorage } from "@/hooks/use-local-storage";
2022
import { useResizePanel } from "@/hooks/use-resize-panel";
2123
import { SessionsSidebar } from "./sessions/[sessionName]/components/sessions-sidebar";
@@ -29,6 +31,7 @@ export default function ProjectLayout({
2931
const router = useRouter();
3032
const pathname = usePathname();
3133
const projectName = params?.name as string;
34+
const isMobile = useIsMobile();
3235

3336
// Extract session name from URL: /projects/{name}/sessions/{sessionName}
3437
const currentSessionName = useMemo(() => {
@@ -40,6 +43,10 @@ export default function ProjectLayout({
4043
"session-sidebar-visible",
4144
true
4245
);
46+
const [mobileSidebarOpen, setMobileSidebarOpen] = useLocalStorage(
47+
"session-sidebar-mobile-open",
48+
false
49+
);
4350
const sidebarResize = useResizePanel("session-sidebar-width", 280, 220, 450, "left");
4451
const { data: version } = useVersion();
4552

@@ -54,44 +61,77 @@ export default function ProjectLayout({
5461
}
5562
}, [projectName]);
5663

64+
// Close mobile sidebar on navigation
65+
useEffect(() => {
66+
setMobileSidebarOpen(false);
67+
}, [pathname, setMobileSidebarOpen]);
68+
5769
if (!projectName) return null;
5870

5971
return (
6072
<div className="absolute inset-0 overflow-hidden bg-background flex flex-col">
6173
<div className="flex-grow overflow-hidden bg-card flex">
62-
{/* Left sidebar */}
63-
<div
64-
className={cn(
65-
"h-full overflow-hidden border-r flex-shrink-0 relative",
66-
!sidebarResize.isDragging && "transition-[width] duration-200 ease-in-out",
67-
!sidebarVisible && "!w-0 border-r-0"
68-
)}
69-
style={{ width: sidebarVisible ? sidebarResize.width : 0 }}
70-
>
71-
<div className="h-full" style={{ width: sidebarResize.width }}>
72-
<SessionsSidebar
73-
projectName={projectName}
74-
currentSessionName={currentSessionName}
75-
collapsed={!sidebarVisible}
76-
onCollapse={() => setSidebarVisible(false)}
74+
{/* Mobile sidebar — Sheet overlay */}
75+
{isMobile && (
76+
<Sheet open={mobileSidebarOpen} onOpenChange={setMobileSidebarOpen}>
77+
<SheetContent side="left" className="w-[85vw] max-w-[320px] p-0" showCloseButton={false}>
78+
<SheetTitle className="sr-only">Sessions sidebar</SheetTitle>
79+
<SessionsSidebar
80+
projectName={projectName}
81+
currentSessionName={currentSessionName}
82+
collapsed={false}
83+
onCollapse={() => setMobileSidebarOpen(false)}
84+
onSessionSelect={() => setMobileSidebarOpen(false)}
85+
/>
86+
</SheetContent>
87+
</Sheet>
88+
)}
89+
90+
{/* Desktop sidebar */}
91+
{!isMobile && (
92+
<div
93+
className={cn(
94+
"h-full overflow-hidden border-r flex-shrink-0 relative",
95+
!sidebarResize.isDragging && "transition-[width] duration-200 ease-in-out",
96+
!sidebarVisible && "!w-0 border-r-0"
97+
)}
98+
style={{ width: sidebarVisible ? sidebarResize.width : 0 }}
99+
>
100+
<div className="h-full" style={{ width: sidebarResize.width }}>
101+
<SessionsSidebar
102+
projectName={projectName}
103+
currentSessionName={currentSessionName}
104+
collapsed={!sidebarVisible}
105+
onCollapse={() => setSidebarVisible(false)}
106+
/>
107+
</div>
108+
{/* Drag handle */}
109+
<div
110+
className="absolute top-0 right-0 w-1 h-full cursor-col-resize hover:bg-primary/50 transition-colors z-10"
111+
onMouseDown={sidebarResize.onMouseDown}
77112
/>
78113
</div>
79-
{/* Drag handle */}
80-
<div
81-
className="absolute top-0 right-0 w-1 h-full cursor-col-resize hover:bg-primary/50 transition-colors z-10"
82-
onMouseDown={sidebarResize.onMouseDown}
83-
/>
84-
</div>
114+
)}
85115

86116
{/* Main content */}
87117
<div className="flex-1 min-w-0 flex flex-col h-full">
88118
{/* Content header with nav items */}
89119
<div className="flex-shrink-0 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
90-
<div className="flex h-14 items-center justify-between gap-3 px-4">
91-
{/* Left: branding when sidebar is collapsed */}
92-
<div className="flex items-center gap-2">
93-
{!sidebarVisible && (
94-
<>
120+
<div className="flex h-14 items-center justify-between gap-2 px-3 md:px-4">
121+
{/* Left: sidebar toggle */}
122+
<div className="flex items-center gap-2 min-w-0">
123+
{isMobile ? (
124+
<Button
125+
variant="ghost"
126+
size="sm"
127+
onClick={() => setMobileSidebarOpen(true)}
128+
className="h-10 w-10 p-0 flex-shrink-0"
129+
title="Open sidebar"
130+
>
131+
<Menu className="h-5 w-5" />
132+
</Button>
133+
) : (
134+
!sidebarVisible && (
95135
<Button
96136
variant="ghost"
97137
size="sm"
@@ -101,31 +141,46 @@ export default function ProjectLayout({
101141
>
102142
<PanelLeft className="h-4 w-4" />
103143
</Button>
104-
<Link href="/" className="flex items-end gap-2">
105-
<span className="text-lg font-bold">Ambient Code Platform</span>
106-
{version && (
107-
<span className="text-[0.65rem] text-muted-foreground/60 pb-0.5">
108-
{version}
109-
</span>
110-
)}
111-
</Link>
112-
</>
144+
)
145+
)}
146+
{/* Branding — show when sidebar is hidden or on mobile */}
147+
{(isMobile || !sidebarVisible) && (
148+
<Link href="/" className="flex items-end gap-2 min-w-0">
149+
<span className="text-base md:text-lg font-bold truncate">
150+
{isMobile ? "ACP" : "Ambient Code Platform"}
151+
</span>
152+
{!isMobile && version && (
153+
<span className="text-[0.65rem] text-muted-foreground/60 pb-0.5">
154+
{version}
155+
</span>
156+
)}
157+
</Link>
113158
)}
114159
</div>
115160

116161
{/* Right: nav items */}
117-
<div className="flex items-center gap-3">
162+
<div className="flex items-center gap-1 md:gap-3 flex-shrink-0">
118163
<RecentUpdatesDialog />
119164
<ThemeToggle />
120165
<Button
121166
variant="ghost"
122167
size="sm"
123168
onClick={() => router.push('/integrations')}
124-
className="text-muted-foreground hover:text-foreground"
169+
className="text-muted-foreground hover:text-foreground hidden md:inline-flex"
125170
>
126171
<Plug className="w-4 h-4 mr-1" />
127172
Integrations
128173
</Button>
174+
{/* Mobile: icon-only integrations button */}
175+
<Button
176+
variant="ghost"
177+
size="sm"
178+
onClick={() => router.push('/integrations')}
179+
className="text-muted-foreground hover:text-foreground h-10 w-10 p-0 md:hidden"
180+
title="Integrations"
181+
>
182+
<Plug className="w-4 h-4" />
183+
</Button>
129184
<DropdownMenu>
130185
<DropdownMenuTrigger className="outline-none">
131186
<UserBubble />

components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/content-tabs.tsx

100644100755
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ export function ContentTabs({
3232
const isChatActive = activeTab.type === "chat";
3333

3434
return (
35-
<div className="flex items-center border-b bg-muted/30 px-3 h-10">
36-
<div className="flex items-center gap-0.5 flex-1 overflow-x-auto">
35+
<div className="flex items-center border-b bg-muted/30 px-2 md:px-3 h-11 md:h-10">
36+
<div className="flex items-center gap-0.5 flex-1 overflow-x-auto scrollbar-hide">
3737
{/* Chat tab — always present, not closable */}
3838
<button
3939
type="button"
4040
onClick={onSwitchToChat}
4141
className={cn(
42-
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-sm text-sm font-medium transition-colors whitespace-nowrap",
42+
"inline-flex items-center gap-1.5 px-3 py-2 md:py-1.5 rounded-sm text-sm font-medium transition-colors whitespace-nowrap min-h-[36px] md:min-h-0",
4343
isChatActive
4444
? "bg-background text-foreground shadow-sm"
4545
: "text-muted-foreground hover:text-foreground hover:bg-accent"
@@ -66,14 +66,14 @@ export function ContentTabs({
6666
<button
6767
type="button"
6868
onClick={() => onSwitchToFile(tab.path)}
69-
className="pl-3 py-1.5 font-medium"
69+
className="pl-3 py-2 md:py-1.5 font-medium min-h-[36px] md:min-h-0"
7070
>
7171
{tab.name}
7272
</button>
7373
<Button
7474
variant="ghost"
7575
size="icon"
76-
className="h-5 w-5 mr-1 opacity-0 group-hover:opacity-100 transition-opacity"
76+
className="h-6 w-6 md:h-5 md:w-5 mr-1 opacity-0 group-hover:opacity-100 group-active:opacity-100 transition-opacity"
7777
onClick={(e) => {
7878
e.stopPropagation();
7979
onCloseFile(tab.path);
@@ -103,15 +103,15 @@ export function ContentTabs({
103103
<button
104104
type="button"
105105
onClick={() => onSwitchToTask?.(tab.taskId)}
106-
className="pl-3 py-1.5 font-medium inline-flex items-center gap-1.5"
106+
className="pl-3 py-2 md:py-1.5 font-medium inline-flex items-center gap-1.5 min-h-[36px] md:min-h-0"
107107
>
108108
<StatusIcon status={tab.status} className="h-3.5 w-3.5 flex-shrink-0" />
109109
<span className="truncate max-w-[120px]">{tab.name}</span>
110110
</button>
111111
<Button
112112
variant="ghost"
113113
size="icon"
114-
className="h-5 w-5 mr-1 opacity-0 group-hover:opacity-100 transition-opacity"
114+
className="h-6 w-6 md:h-5 md:w-5 mr-1 opacity-0 group-hover:opacity-100 group-active:opacity-100 transition-opacity"
115115
onClick={(e) => {
116116
e.stopPropagation();
117117
onCloseTask?.(tab.taskId);

components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/sessions-sidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ export function SessionsSidebar({
445445
<Link
446446
href={sessionHref(session.metadata.name)}
447447
onClick={() => onSessionSelect?.()}
448-
className="flex items-center gap-2 flex-1 min-w-0 px-2 py-2"
448+
className="flex items-center gap-2 flex-1 min-w-0 px-2 py-2.5 md:py-2"
449449
>
450450
<AgentStatusIndicator
451451
status={agentStatus}
@@ -631,7 +631,7 @@ function SidebarSessionActions({
631631
<DropdownMenuTrigger asChild>
632632
<button
633633
type="button"
634-
className="opacity-0 group-hover:opacity-100 focus:opacity-100 focus-visible:opacity-100 flex items-center justify-center h-6 w-6 rounded-sm flex-shrink-0 mr-1 text-muted-foreground hover:text-foreground hover:bg-accent-foreground/10 transition-colors"
634+
className="md:opacity-0 md:group-hover:opacity-100 focus:opacity-100 focus-visible:opacity-100 flex items-center justify-center h-8 w-8 md:h-6 md:w-6 rounded-sm flex-shrink-0 mr-1 text-muted-foreground hover:text-foreground hover:bg-accent-foreground/10 transition-colors"
635635
>
636636
<MoreVertical className="h-3.5 w-3.5" />
637637
<span className="sr-only">Session actions</span>

0 commit comments

Comments
 (0)