diff --git a/apps/ui/src/components/ui/header-actions-panel.tsx b/apps/ui/src/components/ui/header-actions-panel.tsx
new file mode 100644
index 00000000..708652b6
--- /dev/null
+++ b/apps/ui/src/components/ui/header-actions-panel.tsx
@@ -0,0 +1,105 @@
+import { createPortal } from 'react-dom';
+import { X, Menu } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import { Button } from '@/components/ui/button';
+
+interface HeaderActionsPanelProps {
+ isOpen: boolean;
+ onClose: () => void;
+ title?: string;
+ children: React.ReactNode;
+}
+
+/**
+ * A slide-out panel for header actions on tablet and below.
+ * Shows as a right-side panel that slides in from the right edge.
+ * On desktop (lg+), this component is hidden and children should be rendered inline.
+ */
+export function HeaderActionsPanel({
+ isOpen,
+ onClose,
+ title = 'Actions',
+ children,
+}: HeaderActionsPanelProps) {
+ // Use portal to render outside parent stacking contexts (backdrop-blur creates stacking context)
+ const panelContent = (
+ <>
+ {/* Mobile backdrop overlay - only shown when isOpen is true on tablet/mobile */}
+ {isOpen && (
+
+ )}
+
+ {/* Actions panel */}
+
+ {/* Panel header with close button */}
+
+ {title}
+
+
+
+
+
+ {/* Panel content */}
+
{children}
+
+ >
+ );
+
+ // Render to document.body to escape stacking context
+ if (typeof document !== 'undefined') {
+ return createPortal(panelContent, document.body);
+ }
+
+ return panelContent;
+}
+
+interface HeaderActionsPanelTriggerProps {
+ isOpen: boolean;
+ onToggle: () => void;
+ className?: string;
+}
+
+/**
+ * Toggle button for the HeaderActionsPanel.
+ * Only visible on tablet and below (lg:hidden).
+ */
+export function HeaderActionsPanelTrigger({
+ isOpen,
+ onToggle,
+ className,
+}: HeaderActionsPanelTriggerProps) {
+ return (
+
+ {isOpen ? : }
+
+ );
+}
diff --git a/apps/ui/src/components/views/agent-view/components/agent-header.tsx b/apps/ui/src/components/views/agent-view/components/agent-header.tsx
index a6152736..a594c39e 100644
--- a/apps/ui/src/components/views/agent-view/components/agent-header.tsx
+++ b/apps/ui/src/components/views/agent-view/components/agent-header.tsx
@@ -27,18 +27,6 @@ export function AgentHeader({
return (
-
- {showSessionManager ? (
-
- ) : (
-
- )}
-
@@ -71,6 +59,19 @@ export function AgentHeader({
Clear
)}
+
+ {showSessionManager ? (
+
+ ) : (
+
+ )}
+
);
diff --git a/apps/ui/src/components/views/board-view/board-header.tsx b/apps/ui/src/components/views/board-view/board-header.tsx
index 29dce66a..b5684a08 100644
--- a/apps/ui/src/components/views/board-view/board-header.tsx
+++ b/apps/ui/src/components/views/board-view/board-header.tsx
@@ -1,11 +1,11 @@
-import { useCallback } from 'react';
+import { useCallback, useState } from 'react';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { Wand2, GitBranch, ClipboardCheck } from 'lucide-react';
import { UsagePopover } from '@/components/usage-popover';
import { useAppStore } from '@/store/app-store';
import { useSetupStore } from '@/store/setup-store';
-import { useIsMobile } from '@/hooks/use-media-query';
+import { useIsTablet } from '@/hooks/use-media-query';
import { AutoModeSettingsPopover } from './dialogs/auto-mode-settings-popover';
import { WorktreeSettingsPopover } from './dialogs/worktree-settings-popover';
import { PlanSettingsPopover } from './dialogs/plan-settings-popover';
@@ -108,7 +108,10 @@ export function BoardHeader({
// Show if Codex is authenticated (CLI or API key)
const showCodexUsage = !!codexAuthStatus?.authenticated;
- const isMobile = useIsMobile();
+ // State for mobile actions panel
+ const [showActionsPanel, setShowActionsPanel] = useState(false);
+
+ const isTablet = useIsTablet();
return (
@@ -125,11 +128,13 @@ export function BoardHeader({
{/* Usage Popover - show if either provider is authenticated, only on desktop */}
- {isMounted && !isMobile && (showClaudeUsage || showCodexUsage) &&
}
+ {isMounted && !isTablet && (showClaudeUsage || showCodexUsage) &&
}
- {/* Mobile view: show hamburger menu with all controls */}
- {isMounted && isMobile && (
+ {/* Tablet/Mobile view: show hamburger menu with all controls */}
+ {isMounted && isTablet && (
setShowActionsPanel(!showActionsPanel)}
isWorktreePanelVisible={isWorktreePanelVisible}
onWorktreePanelToggle={handleWorktreePanelToggle}
maxConcurrency={maxConcurrency}
@@ -146,7 +151,7 @@ export function BoardHeader({
{/* Desktop view: show full controls */}
{/* Worktrees Toggle - only show after mount to prevent hydration issues */}
- {isMounted && !isMobile && (
+ {isMounted && !isTablet && (
)}
- {/* Plan Button with Settings - only show on desktop, mobile has it in the menu */}
- {isMounted && !isMobile && (
+ {/* Plan Button with Settings - only show on desktop, tablet/mobile has it in the panel */}
+ {isMounted && !isTablet && (
{hasPendingPlan && (
void;
// Worktree panel visibility
isWorktreePanelVisible: boolean;
onWorktreePanelToggle: (visible: boolean) => void;
@@ -33,6 +32,8 @@ interface HeaderMobileMenuProps {
}
export function HeaderMobileMenu({
+ isOpen,
+ onToggle,
isWorktreePanelVisible,
onWorktreePanelToggle,
maxConcurrency,
@@ -46,129 +47,122 @@ export function HeaderMobileMenu({
showCodexUsage,
}: HeaderMobileMenuProps) {
return (
-
-
-
-
-
-
-
+ <>
+
+
{/* Usage Bar - show if either provider is authenticated */}
{(showClaudeUsage || showCodexUsage) && (
- <>
-
+
+
Usage
-
+
-
- >
+
)}
-
- Controls
-
-
+ {/* Controls Section */}
+
+
+ Controls
+
- {/* Auto Mode Toggle */}
-
onAutoModeToggle(!isAutoModeRunning)}
- data-testid="mobile-auto-mode-toggle-container"
- >
-
-
-
Auto Mode
+ {/* Auto Mode Toggle */}
+
onAutoModeToggle(!isAutoModeRunning)}
+ data-testid="mobile-auto-mode-toggle-container"
+ >
+
+
+ Auto Mode
+
+
+ e.stopPropagation()}
+ data-testid="mobile-auto-mode-toggle"
+ />
+ {
+ e.stopPropagation();
+ onOpenAutoModeSettings();
+ }}
+ className="p-1 rounded hover:bg-accent/50 transition-colors"
+ title="Auto Mode Settings"
+ data-testid="mobile-auto-mode-settings-button"
+ >
+
+
+
-
+
+ {/* Worktrees Toggle */}
+
onWorktreePanelToggle(!isWorktreePanelVisible)}
+ data-testid="mobile-worktrees-toggle-container"
+ >
+
+
+ Worktree Bar
+
e.stopPropagation()}
- data-testid="mobile-auto-mode-toggle"
+ data-testid="mobile-worktrees-toggle"
/>
- {
- e.stopPropagation();
- onOpenAutoModeSettings();
- }}
- className="p-1 rounded hover:bg-accent/50 transition-colors"
- title="Auto Mode Settings"
- data-testid="mobile-auto-mode-settings-button"
- >
-
-
-
-
-
- {/* Worktrees Toggle */}
-
onWorktreePanelToggle(!isWorktreePanelVisible)}
- data-testid="mobile-worktrees-toggle-container"
- >
-
-
-
Worktrees
+ {/* Concurrency Control */}
+
+
+
+ Max Agents
+
+ {runningAgentsCount}/{maxConcurrency}
+
+
+
onConcurrencyChange(value[0])}
+ min={1}
+ max={10}
+ step={1}
+ className="w-full"
+ data-testid="mobile-concurrency-slider"
+ />
-
e.stopPropagation()}
- data-testid="mobile-worktrees-toggle"
- />
+
+ {/* Plan Button */}
+ {
+ onOpenPlanDialog();
+ onToggle();
+ }}
+ data-testid="mobile-plan-button"
+ >
+
+ Plan
+
-
-
-
- {/* Concurrency Control */}
-
-
-
- Max Agents
-
- {runningAgentsCount}/{maxConcurrency}
-
-
-
onConcurrencyChange(value[0])}
- min={1}
- max={10}
- step={1}
- className="w-full"
- data-testid="mobile-concurrency-slider"
- />
-
-
-
-
- {/* Plan Button */}
-
-
- Plan
-
-
-
+
+ >
);
}
diff --git a/apps/ui/src/components/views/context-view.tsx b/apps/ui/src/components/views/context-view.tsx
index 41dc3816..024ee392 100644
--- a/apps/ui/src/components/views/context-view.tsx
+++ b/apps/ui/src/components/views/context-view.tsx
@@ -7,6 +7,10 @@ import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { HotkeyButton } from '@/components/ui/hotkey-button';
import { Card } from '@/components/ui/card';
+import {
+ HeaderActionsPanel,
+ HeaderActionsPanelTrigger,
+} from '@/components/ui/header-actions-panel';
import {
RefreshCw,
FileText,
@@ -94,6 +98,9 @@ export function ContextView() {
const [editDescriptionValue, setEditDescriptionValue] = useState('');
const [editDescriptionFileName, setEditDescriptionFileName] = useState('');
+ // Actions panel state (for tablet/mobile)
+ const [showActionsPanel, setShowActionsPanel] = useState(false);
+
// File input ref for import
const fileInputRef = useRef
(null);
@@ -691,30 +698,70 @@ export function ContextView() {
-
-
-
- Import File
-
-
setIsCreateMarkdownOpen(true)}
- hotkey={shortcuts.addContextFile}
- hotkeyActive={false}
- data-testid="create-markdown-button"
- >
-
- Create Markdown
-
+
+ {/* Desktop: show actions inline */}
+
+
+
+ Import File
+
+ setIsCreateMarkdownOpen(true)}
+ hotkey={shortcuts.addContextFile}
+ hotkeyActive={false}
+ data-testid="create-markdown-button"
+ >
+
+ Create Markdown
+
+
+ {/* Tablet/Mobile: show trigger for actions panel */}
+
setShowActionsPanel(!showActionsPanel)}
+ />
+ {/* Actions Panel (tablet/mobile) */}
+
setShowActionsPanel(false)}
+ title="Context Actions"
+ >
+ {
+ handleImportClick();
+ setShowActionsPanel(false);
+ }}
+ disabled={isUploading}
+ data-testid="import-file-button-mobile"
+ >
+
+ Import File
+
+ {
+ setIsCreateMarkdownOpen(true);
+ setShowActionsPanel(false);
+ }}
+ data-testid="create-markdown-button-mobile"
+ >
+
+ Create Markdown
+
+
+
{/* Main content area with file list and editor */}
)[iconName];
+ }
+ return Folder;
+}
+
export function DashboardView() {
const navigate = useNavigate();
const { os } = useOSDetection();
@@ -79,6 +91,7 @@ export function DashboardView() {
const [isCreating, setIsCreating] = useState(false);
const [isOpening, setIsOpening] = useState(false);
const [projectToRemove, setProjectToRemove] = useState<{ id: string; name: string } | null>(null);
+ const [searchQuery, setSearchQuery] = useState('');
// Sort projects: favorites first, then by last opened
const sortedProjects = [...projects].sort((a, b) => {
@@ -91,8 +104,15 @@ export function DashboardView() {
return dateB - dateA;
});
- const favoriteProjects = sortedProjects.filter((p) => p.isFavorite);
- const recentProjects = sortedProjects.filter((p) => !p.isFavorite);
+ // Filter projects based on search query
+ const filteredProjects = sortedProjects.filter((project) => {
+ if (!searchQuery.trim()) return true;
+ const query = searchQuery.toLowerCase();
+ return project.name.toLowerCase().includes(query) || project.path.toLowerCase().includes(query);
+ });
+
+ const favoriteProjects = filteredProjects.filter((p) => p.isFavorite);
+ const recentProjects = filteredProjects.filter((p) => !p.isFavorite);
/**
* Initialize project and navigate to board
@@ -529,14 +549,35 @@ export function DashboardView() {
-
navigate({ to: '/settings' })}
- className="titlebar-no-drag"
- >
-
-
+
+ {/* Mobile action buttons in header */}
+ {hasProjects && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Quick Setup
+
+
+
+ Interactive Mode
+
+
+
+
+ )}
@@ -646,25 +687,42 @@ export function DashboardView() {
{/* Has projects - show project list */}
{hasProjects && (
- {/* Quick actions header */}
-
-
Your Projects
-
-
-
- Open Folder
+ {/* Search and actions header */}
+
+
Your Projects
+
+ {/* Search input */}
+
+
+ setSearchQuery(e.target.value)}
+ className="pl-9 pr-8 w-full sm:w-64"
+ data-testid="project-search-input"
+ />
+ {searchQuery && (
+ setSearchQuery('')}
+ className="absolute right-2 top-1/2 -translate-y-1/2 p-0.5 rounded hover:bg-muted transition-colors"
+ title="Clear search"
+ >
+
+
+ )}
+
+ {/* Desktop only buttons */}
+
+
+ Open Folder
-
-
- New Project
- New
-
+
+
+ New Project
+
@@ -703,8 +761,24 @@ export function DashboardView() {
-
-
+
+ {project.customIconPath ? (
+
+ ) : (
+ (() => {
+ const IconComponent = getIconComponent(project.icon);
+ return (
+
+ );
+ })()
+ )}
@@ -778,8 +852,24 @@ export function DashboardView() {
-
-
+
+ {project.customIconPath ? (
+
+ ) : (
+ (() => {
+ const IconComponent = getIconComponent(project.icon);
+ return (
+
+ );
+ })()
+ )}
@@ -797,10 +887,10 @@ export function DashboardView() {
handleToggleFavorite(e, project.id)}
- className="p-1 sm:p-1.5 rounded-lg hover:bg-muted transition-colors opacity-0 group-hover:opacity-100"
+ className="p-1 sm:p-1.5 rounded-lg hover:bg-muted transition-colors"
title="Add to favorites"
>
-
+
@@ -830,6 +920,22 @@ export function DashboardView() {
)}
+
+ {/* No search results */}
+ {searchQuery && favoriteProjects.length === 0 && recentProjects.length === 0 && (
+
+
+
+
+
No projects found
+
+ No projects match "{searchQuery}"
+
+
setSearchQuery('')}>
+ Clear search
+
+
+ )}
)}
diff --git a/apps/ui/src/components/views/memory-view.tsx b/apps/ui/src/components/views/memory-view.tsx
index 886ac90f..66533413 100644
--- a/apps/ui/src/components/views/memory-view.tsx
+++ b/apps/ui/src/components/views/memory-view.tsx
@@ -4,6 +4,10 @@ import { useAppStore } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
+import {
+ HeaderActionsPanel,
+ HeaderActionsPanelTrigger,
+} from '@/components/ui/header-actions-panel';
import {
RefreshCw,
FileText,
@@ -60,6 +64,9 @@ export function MemoryView() {
const [newMemoryName, setNewMemoryName] = useState('');
const [newMemoryContent, setNewMemoryContent] = useState('');
+ // Actions panel state (for tablet/mobile)
+ const [showActionsPanel, setShowActionsPanel] = useState(false);
+
// Get memory directory path
const getMemoryPath = useCallback(() => {
if (!currentProject) return null;
@@ -310,27 +317,66 @@ export function MemoryView() {
-
-
-
- Refresh
-
-
setIsCreateMemoryOpen(true)}
- data-testid="create-memory-button"
- >
-
- Create Memory File
-
+
+ {/* Desktop: show actions inline */}
+
+
+
+ Refresh
+
+ setIsCreateMemoryOpen(true)}
+ data-testid="create-memory-button"
+ >
+
+ Create Memory File
+
+
+ {/* Tablet/Mobile: show trigger for actions panel */}
+
setShowActionsPanel(!showActionsPanel)}
+ />
+ {/* Actions Panel (tablet/mobile) */}
+
setShowActionsPanel(false)}
+ title="Memory Actions"
+ >
+ {
+ loadMemoryFiles();
+ setShowActionsPanel(false);
+ }}
+ data-testid="refresh-memory-button-mobile"
+ >
+
+ Refresh
+
+ {
+ setIsCreateMemoryOpen(true);
+ setShowActionsPanel(false);
+ }}
+ data-testid="create-memory-button-mobile"
+ >
+
+ Create Memory File
+
+
+
{/* Main content area with file list and editor */}
{/* Left Panel - File List */}
diff --git a/apps/ui/src/components/views/project-settings-view/components/project-settings-navigation.tsx b/apps/ui/src/components/views/project-settings-view/components/project-settings-navigation.tsx
index 1c06dad3..8b4addf3 100644
--- a/apps/ui/src/components/views/project-settings-view/components/project-settings-navigation.tsx
+++ b/apps/ui/src/components/views/project-settings-view/components/project-settings-navigation.tsx
@@ -31,15 +31,15 @@ export function ProjectSettingsNavigation({
{/* Navigation sidebar */}
- {/* Mobile menu button */}
-
setShowNavigation(!showNavigation)}
- className="lg:hidden h-8 w-8 p-0"
- aria-label="Toggle navigation menu"
- >
-
-
Project Settings
@@ -144,6 +134,16 @@ export function ProjectSettingsView() {
+ {/* Mobile menu button - far right */}
+ setShowNavigation(!showNavigation)}
+ className="lg:hidden h-8 w-8 p-0"
+ aria-label={showNavigation ? 'Close navigation menu' : 'Open navigation menu'}
+ >
+ {showNavigation ? : }
+
{/* Content Area with Sidebar */}
diff --git a/apps/ui/src/components/views/settings-view/components/settings-header.tsx b/apps/ui/src/components/views/settings-view/components/settings-header.tsx
index 25cf8dc5..aebe7cb1 100644
--- a/apps/ui/src/components/views/settings-view/components/settings-header.tsx
+++ b/apps/ui/src/components/views/settings-view/components/settings-header.tsx
@@ -1,4 +1,4 @@
-import { Settings, PanelLeft, PanelLeftClose, FileJson } from 'lucide-react';
+import { Cog, Menu, X, FileJson } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
@@ -11,7 +11,7 @@ interface SettingsHeaderProps {
}
export function SettingsHeader({
- title = 'Settings',
+ title = 'Global Settings',
description = 'Configure your API keys and preferences',
showNavigation,
onToggleNavigation,
@@ -28,6 +28,31 @@ export function SettingsHeader({
+
+
+
+
+
+ {title}
+
+
{description}
+
+
+
+ {/* Import/Export button */}
+ {onImportExportClick && (
+
+
+ Import / Export
+
+ )}
{/* Mobile menu toggle button - only visible on mobile */}
{onToggleNavigation && (
- {showNavigation ? (
-
- ) : (
-
- )}
+ {showNavigation ? : }
)}
-
-
-
-
-
- {title}
-
-
{description}
-
- {/* Import/Export button */}
- {onImportExportClick && (
-
-
- Import / Export
-
- )}
diff --git a/apps/ui/src/components/views/settings-view/components/settings-navigation.tsx b/apps/ui/src/components/views/settings-view/components/settings-navigation.tsx
index 84a913a0..5e8f0fa1 100644
--- a/apps/ui/src/components/views/settings-view/components/settings-navigation.tsx
+++ b/apps/ui/src/components/views/settings-view/components/settings-navigation.tsx
@@ -210,15 +210,15 @@ export function SettingsNavigation({
{/* Navigation sidebar */}
setShowRegenerateDialog(true)}
onSaveClick={saveSpec}
+ showActionsPanel={showActionsPanel}
+ onToggleActionsPanel={() => setShowActionsPanel(!showActionsPanel)}
/>
diff --git a/apps/ui/src/components/views/spec-view/components/spec-header.tsx b/apps/ui/src/components/views/spec-view/components/spec-header.tsx
index 8fd791bb..37132701 100644
--- a/apps/ui/src/components/views/spec-view/components/spec-header.tsx
+++ b/apps/ui/src/components/views/spec-view/components/spec-header.tsx
@@ -1,4 +1,8 @@
import { Button } from '@/components/ui/button';
+import {
+ HeaderActionsPanel,
+ HeaderActionsPanelTrigger,
+} from '@/components/ui/header-actions-panel';
import { Save, Sparkles, Loader2, FileText, AlertCircle } from 'lucide-react';
import { PHASE_LABELS } from '../constants';
@@ -13,6 +17,8 @@ interface SpecHeaderProps {
errorMessage: string;
onRegenerateClick: () => void;
onSaveClick: () => void;
+ showActionsPanel: boolean;
+ onToggleActionsPanel: () => void;
}
export function SpecHeader({
@@ -26,81 +32,159 @@ export function SpecHeader({
errorMessage,
onRegenerateClick,
onSaveClick,
+ showActionsPanel,
+ onToggleActionsPanel,
}: SpecHeaderProps) {
const isProcessing = isRegenerating || isCreating || isGeneratingFeatures;
const phaseLabel = PHASE_LABELS[currentPhase] || currentPhase;
return (
-
-
-
-
-
App Specification
-
{projectPath}/.automaker/app_spec.txt
+ <>
+
+
+
+
+
App Specification
+
{projectPath}/.automaker/app_spec.txt
+
+
+
+ {/* Status indicators - always visible */}
+ {isProcessing && (
+
+
+
+
+ {isGeneratingFeatures
+ ? 'Generating Features'
+ : isCreating
+ ? 'Generating Specification'
+ : 'Regenerating Specification'}
+
+ {currentPhase && (
+
+ {phaseLabel}
+
+ )}
+
+
+ )}
+ {/* Mobile processing indicator */}
+ {isProcessing && (
+
+
+ Processing...
+
+ )}
+ {errorMessage && (
+
+
+
+
+ Error
+
+
+ {errorMessage}
+
+
+
+ )}
+ {/* Mobile error indicator */}
+ {errorMessage && (
+
+ )}
+ {/* Desktop: show actions inline */}
+
+
+ {isRegenerating ? (
+
+ ) : (
+
+ )}
+ {isRegenerating ? 'Regenerating...' : 'Regenerate'}
+
+
+
+ {isSaving ? 'Saving...' : hasChanges ? 'Save Changes' : 'Saved'}
+
+
+ {/* Tablet/Mobile: show trigger for actions panel */}
+
-
+
+ {/* Actions Panel (tablet/mobile) */}
+
+ {/* Status messages in panel */}
{isProcessing && (
-
-
-
-
+
+
+
+
{isGeneratingFeatures
? 'Generating Features'
: isCreating
? 'Generating Specification'
: 'Regenerating Specification'}
- {currentPhase && (
-
- {phaseLabel}
-
- )}
+ {currentPhase && {phaseLabel} }
)}
{errorMessage && (
-
-
-
-
- Error
-
-
- {errorMessage}
-
+
+
+
+ Error
+ {errorMessage}
)}
-
-
- {isRegenerating ? (
-
- ) : (
-
- )}
- {isRegenerating ? 'Regenerating...' : 'Regenerate'}
-
-
-
- {isSaving ? 'Saving...' : hasChanges ? 'Save Changes' : 'Saved'}
-
-
-
-
+
+ {isRegenerating ? (
+
+ ) : (
+
+ )}
+ {isRegenerating ? 'Regenerating...' : 'Regenerate'}
+
+
+
+ {isSaving ? 'Saving...' : hasChanges ? 'Save Changes' : 'Saved'}
+
+
+ >
);
}
diff --git a/apps/ui/src/hooks/use-media-query.ts b/apps/ui/src/hooks/use-media-query.ts
index 6215498f..deb01a5a 100644
--- a/apps/ui/src/hooks/use-media-query.ts
+++ b/apps/ui/src/hooks/use-media-query.ts
@@ -56,3 +56,12 @@ export function useIsMobile(): boolean {
export function useIsTablet(): boolean {
return useMediaQuery('(max-width: 1024px)');
}
+
+/**
+ * Hook to detect compact layout (screen width <= 1240px)
+ * Used for collapsing top bar controls into mobile menu
+ * @returns boolean indicating if compact layout should be used
+ */
+export function useIsCompact(): boolean {
+ return useMediaQuery('(max-width: 1240px)');
+}
diff --git a/apps/ui/src/routes/__root.tsx b/apps/ui/src/routes/__root.tsx
index 9472545b..f3a662e8 100644
--- a/apps/ui/src/routes/__root.tsx
+++ b/apps/ui/src/routes/__root.tsx
@@ -28,12 +28,12 @@ import {
performSettingsMigration,
} from '@/hooks/use-settings-migration';
import { Toaster } from 'sonner';
-import { Menu } from 'lucide-react';
import { ThemeOption, themeOptions } from '@/config/theme-options';
import { SandboxRiskDialog } from '@/components/dialogs/sandbox-risk-dialog';
import { SandboxRejectionScreen } from '@/components/dialogs/sandbox-rejection-screen';
import { LoadingState } from '@/components/ui/loading-state';
import { useProjectSettingsLoader } from '@/hooks/use-project-settings-loader';
+import { useIsCompact } from '@/hooks/use-media-query';
import type { Project } from '@/lib/electron';
const logger = createLogger('RootLayout');
@@ -176,6 +176,9 @@ function RootLayoutContent() {
// Load project settings when switching projects
useProjectSettingsLoader();
+ // Check if we're in compact mode (< 1240px) to hide project switcher
+ const isCompact = useIsCompact();
+
const isSetupRoute = location.pathname === '/setup';
const isLoginRoute = location.pathname === '/login';
const isLoggedOutRoute = location.pathname === '/logged-out';
@@ -805,8 +808,9 @@ function RootLayoutContent() {
}
// Show project switcher on all app pages (not on dashboard, setup, or login)
+ // Also hide on compact screens (< 1240px) - the sidebar will show a logo instead
const showProjectSwitcher =
- !isDashboardRoute && !isSetupRoute && !isLoginRoute && !isLoggedOutRoute;
+ !isDashboardRoute && !isSetupRoute && !isLoginRoute && !isLoggedOutRoute && !isCompact;
return (
<>
@@ -820,16 +824,6 @@ function RootLayoutContent() {
)}
{showProjectSwitcher && }
- {/* Mobile menu toggle button - only shows when sidebar is closed on mobile */}
- {!sidebarOpen && (
-
-
-
- )}
; // projectPath -> sessionId
@@ -910,6 +911,8 @@ export interface AppActions {
setCurrentView: (view: ViewMode) => void;
toggleSidebar: () => void;
setSidebarOpen: (open: boolean) => void;
+ toggleMobileSidebarHidden: () => void;
+ setMobileSidebarHidden: (hidden: boolean) => void;
// Theme actions
setTheme: (theme: ThemeMode) => void;
@@ -1252,6 +1255,7 @@ const initialState: AppState = {
projectHistoryIndex: -1,
currentView: 'welcome',
sidebarOpen: true,
+ mobileSidebarHidden: false, // Sidebar visible by default on mobile
lastSelectedSessionByProject: {},
theme: getStoredTheme() || 'dark', // Use localStorage theme as initial value, fallback to 'dark'
features: [],
@@ -1681,6 +1685,8 @@ export const useAppStore = create
()((set, get) => ({
setCurrentView: (view) => set({ currentView: view }),
toggleSidebar: () => set({ sidebarOpen: !get().sidebarOpen }),
setSidebarOpen: (open) => set({ sidebarOpen: open }),
+ toggleMobileSidebarHidden: () => set({ mobileSidebarHidden: !get().mobileSidebarHidden }),
+ setMobileSidebarHidden: (hidden) => set({ mobileSidebarHidden: hidden }),
// Theme actions
setTheme: (theme) => {
diff --git a/apps/ui/src/styles/global.css b/apps/ui/src/styles/global.css
index 54a32c4a..9ffd4978 100644
--- a/apps/ui/src/styles/global.css
+++ b/apps/ui/src/styles/global.css
@@ -483,6 +483,16 @@
background: oklch(0.45 0 0);
}
+/* Hidden scrollbar - still scrollable but no visible scrollbar */
+.scrollbar-hide {
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+}
+
+.scrollbar-hide::-webkit-scrollbar {
+ display: none; /* Chrome, Safari, Opera */
+}
+
/* Glass morphism utilities */
@layer utilities {
.glass {