diff --git a/apps/ui/src/components/views/settings-view/appearance/appearance-section.tsx b/apps/ui/src/components/views/settings-view/appearance/appearance-section.tsx
index f449140b..b96d2de1 100644
--- a/apps/ui/src/components/views/settings-view/appearance/appearance-section.tsx
+++ b/apps/ui/src/components/views/settings-view/appearance/appearance-section.tsx
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
import { Label } from '@/components/ui/label';
-import { Palette, Moon, Sun, Type } from 'lucide-react';
+import { Palette, Moon, Sun, Type, PanelLeft, Columns2 } from 'lucide-react';
import { darkThemes, lightThemes } from '@/config/theme-options';
import {
UI_SANS_FONT_OPTIONS,
@@ -11,6 +11,7 @@ import { cn } from '@/lib/utils';
import { useAppStore } from '@/store/app-store';
import { FontSelector } from '@/components/shared';
import type { Theme } from '../shared/types';
+import type { SidebarStyle } from '@automaker/types';
interface AppearanceSectionProps {
effectiveTheme: Theme;
@@ -18,7 +19,14 @@ interface AppearanceSectionProps {
}
export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceSectionProps) {
- const { fontFamilySans, fontFamilyMono, setFontSans, setFontMono } = useAppStore();
+ const {
+ fontFamilySans,
+ fontFamilyMono,
+ setFontSans,
+ setFontMono,
+ sidebarStyle,
+ setSidebarStyle,
+ } = useAppStore();
// Determine if current theme is light or dark
const isLightTheme = lightThemes.some((t) => t.value === effectiveTheme);
@@ -189,6 +197,94 @@ export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceS
+
+ {/* Sidebar Style Section */}
+
+
+
+ Choose between a modern unified sidebar or classic Discord-style layout with a separate
+ project switcher.
+
+
+
+ {/* Unified Sidebar Option */}
+
+
+ {/* Discord-style Sidebar Option */}
+
+
+
);
diff --git a/apps/ui/src/hooks/use-settings-migration.ts b/apps/ui/src/hooks/use-settings-migration.ts
index 7398aece..8f24b67c 100644
--- a/apps/ui/src/hooks/use-settings-migration.ts
+++ b/apps/ui/src/hooks/use-settings-migration.ts
@@ -698,6 +698,7 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
fontFamilySans: settings.fontFamilySans ?? null,
fontFamilyMono: settings.fontFamilyMono ?? null,
sidebarOpen: settings.sidebarOpen ?? true,
+ sidebarStyle: settings.sidebarStyle ?? 'unified',
chatHistoryOpen: settings.chatHistoryOpen ?? false,
maxConcurrency: settings.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY,
autoModeByWorktree: restoredAutoModeByWorktree,
diff --git a/apps/ui/src/hooks/use-settings-sync.ts b/apps/ui/src/hooks/use-settings-sync.ts
index 8c7d9961..15d781d9 100644
--- a/apps/ui/src/hooks/use-settings-sync.ts
+++ b/apps/ui/src/hooks/use-settings-sync.ts
@@ -53,6 +53,7 @@ const SETTINGS_FIELDS_TO_SYNC = [
'terminalFontFamily', // Maps to terminalState.fontFamily
'openTerminalMode', // Maps to terminalState.openTerminalMode
'sidebarOpen',
+ 'sidebarStyle',
'chatHistoryOpen',
'maxConcurrency',
'autoModeByWorktree', // Per-worktree auto mode settings (only maxConcurrency is persisted)
@@ -697,6 +698,7 @@ export async function refreshSettingsFromServer(): Promise {
useAppStore.setState({
theme: serverSettings.theme as unknown as ThemeMode,
sidebarOpen: serverSettings.sidebarOpen,
+ sidebarStyle: serverSettings.sidebarStyle ?? 'unified',
chatHistoryOpen: serverSettings.chatHistoryOpen,
maxConcurrency: serverSettings.maxConcurrency,
autoModeByWorktree: restoredAutoModeByWorktree,
diff --git a/apps/ui/src/routes/__root.tsx b/apps/ui/src/routes/__root.tsx
index f374b7dd..1bb006c5 100644
--- a/apps/ui/src/routes/__root.tsx
+++ b/apps/ui/src/routes/__root.tsx
@@ -4,6 +4,7 @@ import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { createLogger } from '@automaker/utils/logger';
import { Sidebar } from '@/components/layout/sidebar';
+import { ProjectSwitcher } from '@/components/layout/project-switcher';
import {
FileBrowserProvider,
useFileBrowser,
@@ -167,6 +168,7 @@ function RootLayoutContent() {
theme,
fontFamilySans,
fontFamilyMono,
+ sidebarStyle,
skipSandboxWarning,
setSkipSandboxWarning,
fetchCodexModels,
@@ -860,6 +862,8 @@ function RootLayoutContent() {
aria-hidden="true"
/>
)}
+ {/* Discord-style layout: narrow project switcher + expandable sidebar */}
+ {sidebarStyle === 'discord' && }
void;
toggleSidebar: () => void;
setSidebarOpen: (open: boolean) => void;
+ setSidebarStyle: (style: SidebarStyle) => void;
toggleMobileSidebarHidden: () => void;
setMobileSidebarHidden: (hidden: boolean) => void;
@@ -1471,6 +1474,7 @@ const initialState: AppState = {
projectHistoryIndex: -1,
currentView: 'welcome',
sidebarOpen: true,
+ sidebarStyle: 'unified', // Default to modern unified sidebar
mobileSidebarHidden: false, // Sidebar visible by default on mobile
lastSelectedSessionByProject: {},
theme: getStoredTheme() || 'dark', // Use localStorage theme as initial value, fallback to 'dark'
@@ -1929,6 +1933,7 @@ export const useAppStore = create
()((set, get) => ({
setCurrentView: (view) => set({ currentView: view }),
toggleSidebar: () => set({ sidebarOpen: !get().sidebarOpen }),
setSidebarOpen: (open) => set({ sidebarOpen: open }),
+ setSidebarStyle: (style) => set({ sidebarStyle: style }),
toggleMobileSidebarHidden: () => set({ mobileSidebarHidden: !get().mobileSidebarHidden }),
setMobileSidebarHidden: (hidden) => set({ mobileSidebarHidden: hidden }),
diff --git a/libs/types/src/index.ts b/libs/types/src/index.ts
index 87975a81..a4a7635e 100644
--- a/libs/types/src/index.ts
+++ b/libs/types/src/index.ts
@@ -145,6 +145,7 @@ export { DEFAULT_PROMPT_CUSTOMIZATION } from './prompts.js';
// Settings types and constants
export type {
ThemeMode,
+ SidebarStyle,
PlanningMode,
ThinkingLevel,
ServerLogLevel,
diff --git a/libs/types/src/settings.ts b/libs/types/src/settings.ts
index e04110c5..67b1b7b1 100644
--- a/libs/types/src/settings.ts
+++ b/libs/types/src/settings.ts
@@ -78,6 +78,14 @@ export type ServerLogLevel = 'error' | 'warn' | 'info' | 'debug';
/** ThinkingLevel - Extended thinking levels for Claude models (reasoning intensity) */
export type ThinkingLevel = 'none' | 'low' | 'medium' | 'high' | 'ultrathink';
+/**
+ * SidebarStyle - Sidebar layout style options
+ *
+ * - 'unified': Single sidebar with integrated project dropdown (default, modern)
+ * - 'discord': Two sidebars - narrow project switcher + expandable navigation sidebar (classic)
+ */
+export type SidebarStyle = 'unified' | 'discord';
+
/**
* Thinking token budget mapping based on Claude SDK documentation.
* @see https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
@@ -836,6 +844,8 @@ export interface GlobalSettings {
// UI State Preferences
/** Whether sidebar is currently open */
sidebarOpen: boolean;
+ /** Sidebar layout style ('unified' = modern single sidebar, 'discord' = classic two-sidebar layout) */
+ sidebarStyle: SidebarStyle;
/** Whether chat history panel is open */
chatHistoryOpen: boolean;
@@ -1310,6 +1320,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
skipClaudeSetup: false,
theme: 'dark',
sidebarOpen: true,
+ sidebarStyle: 'unified',
chatHistoryOpen: false,
maxConcurrency: DEFAULT_MAX_CONCURRENCY,
defaultSkipTests: true,