diff --git a/apps/ui/src/components/layout/sidebar.tsx b/apps/ui/src/components/layout/sidebar.tsx
index 3cafe020..39ffef97 100644
--- a/apps/ui/src/components/layout/sidebar.tsx
+++ b/apps/ui/src/components/layout/sidebar.tsx
@@ -83,159 +83,18 @@ import {
useSensors,
closestCenter,
} from '@dnd-kit/core';
-import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
-import { CSS } from '@dnd-kit/utilities';
+import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { getHttpApiClient } from '@/lib/http-api-client';
import type { StarterTemplate } from '@/lib/templates';
-interface NavSection {
- label?: string;
- items: NavItem[];
-}
-
-interface NavItem {
- id: string;
- label: string;
- icon: any;
- shortcut?: string;
-}
-
-// Sortable Project Item Component
-interface SortableProjectItemProps {
- project: Project;
- currentProjectId: string | undefined;
- isHighlighted: boolean;
- onSelect: (project: Project) => void;
-}
-
-function SortableProjectItem({
- project,
- currentProjectId,
- isHighlighted,
- onSelect,
-}: SortableProjectItemProps) {
- const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
- id: project.id,
- });
-
- const style = {
- transform: CSS.Transform.toString(transform),
- transition,
- opacity: isDragging ? 0.5 : 1,
- };
-
- return (
-
- {/* Drag Handle */}
-
-
- {/* Project content - clickable area */}
-
onSelect(project)}>
-
- {project.name}
- {currentProjectId === project.id && }
-
-
- );
-}
-
-// Theme options for project theme selector - derived from the shared config
-import { darkThemes, lightThemes } from '@/config/theme-options';
-
-const PROJECT_DARK_THEMES = darkThemes.map((opt) => ({
- value: opt.value,
- label: opt.label,
- icon: opt.Icon,
- color: opt.color,
-}));
-
-const PROJECT_LIGHT_THEMES = lightThemes.map((opt) => ({
- value: opt.value,
- label: opt.label,
- icon: opt.Icon,
- color: opt.color,
-}));
-
-// Memoized theme menu item to prevent re-renders during hover
-interface ThemeMenuItemProps {
- option: {
- value: string;
- label: string;
- icon: React.ComponentType<{ className?: string; style?: React.CSSProperties }>;
- color: string;
- };
- onPreviewEnter: (value: string) => void;
- onPreviewLeave: (e: React.PointerEvent) => void;
-}
-
-const ThemeMenuItem = memo(function ThemeMenuItem({
- option,
- onPreviewEnter,
- onPreviewLeave,
-}: ThemeMenuItemProps) {
- const Icon = option.icon;
- return (
- onPreviewEnter(option.value)}
- onPointerLeave={onPreviewLeave}
- >
-
-
- {option.label}
-
-
- );
-});
-
-// Reusable Bug Report Button Component
-const BugReportButton = ({
- sidebarExpanded,
- onClick,
-}: {
- sidebarExpanded: boolean;
- onClick: () => void;
-}) => {
- return (
-
- );
-};
+// Local imports from subfolder
+import type { NavSection, NavItem } from './sidebar/types';
+import { SortableProjectItem, ThemeMenuItem, BugReportButton } from './sidebar/components';
+import {
+ PROJECT_DARK_THEMES,
+ PROJECT_LIGHT_THEMES,
+ SIDEBAR_FEATURE_FLAGS,
+} from './sidebar/constants';
export function Sidebar() {
const navigate = useNavigate();
@@ -267,12 +126,8 @@ export function Sidebar() {
} = useAppStore();
// Environment variable flags for hiding sidebar items
- const hideTerminal = import.meta.env.VITE_HIDE_TERMINAL === 'true';
- const hideWiki = import.meta.env.VITE_HIDE_WIKI === 'true';
- const hideRunningAgents = import.meta.env.VITE_HIDE_RUNNING_AGENTS === 'true';
- const hideContext = import.meta.env.VITE_HIDE_CONTEXT === 'true';
- const hideSpecEditor = import.meta.env.VITE_HIDE_SPEC_EDITOR === 'true';
- const hideAiProfiles = import.meta.env.VITE_HIDE_AI_PROFILES === 'true';
+ const { hideTerminal, hideWiki, hideRunningAgents, hideContext, hideSpecEditor, hideAiProfiles } =
+ SIDEBAR_FEATURE_FLAGS;
// Get customizable keyboard shortcuts
const shortcuts = useKeyboardShortcutsConfig();
diff --git a/apps/ui/src/components/layout/sidebar/components/bug-report-button.tsx b/apps/ui/src/components/layout/sidebar/components/bug-report-button.tsx
new file mode 100644
index 00000000..68a413c4
--- /dev/null
+++ b/apps/ui/src/components/layout/sidebar/components/bug-report-button.tsx
@@ -0,0 +1,23 @@
+import { Bug } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import type { BugReportButtonProps } from '../types';
+
+export function BugReportButton({ sidebarExpanded, onClick }: BugReportButtonProps) {
+ return (
+
+ );
+}
diff --git a/apps/ui/src/components/layout/sidebar/components/index.ts b/apps/ui/src/components/layout/sidebar/components/index.ts
new file mode 100644
index 00000000..ecc7861e
--- /dev/null
+++ b/apps/ui/src/components/layout/sidebar/components/index.ts
@@ -0,0 +1,3 @@
+export { SortableProjectItem } from './sortable-project-item';
+export { ThemeMenuItem } from './theme-menu-item';
+export { BugReportButton } from './bug-report-button';
diff --git a/apps/ui/src/components/layout/sidebar/components/sortable-project-item.tsx b/apps/ui/src/components/layout/sidebar/components/sortable-project-item.tsx
new file mode 100644
index 00000000..9d1e567e
--- /dev/null
+++ b/apps/ui/src/components/layout/sidebar/components/sortable-project-item.tsx
@@ -0,0 +1,54 @@
+import { useSortable } from '@dnd-kit/sortable';
+import { CSS } from '@dnd-kit/utilities';
+import { Folder, Check, GripVertical } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import type { SortableProjectItemProps } from '../types';
+
+export function SortableProjectItem({
+ project,
+ currentProjectId,
+ isHighlighted,
+ onSelect,
+}: SortableProjectItemProps) {
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
+ id: project.id,
+ });
+
+ const style = {
+ transform: CSS.Transform.toString(transform),
+ transition,
+ opacity: isDragging ? 0.5 : 1,
+ };
+
+ return (
+
+ {/* Drag Handle */}
+
+
+ {/* Project content - clickable area */}
+
onSelect(project)}>
+
+ {project.name}
+ {currentProjectId === project.id && }
+
+
+ );
+}
diff --git a/apps/ui/src/components/layout/sidebar/components/theme-menu-item.tsx b/apps/ui/src/components/layout/sidebar/components/theme-menu-item.tsx
new file mode 100644
index 00000000..5d9749b2
--- /dev/null
+++ b/apps/ui/src/components/layout/sidebar/components/theme-menu-item.tsx
@@ -0,0 +1,27 @@
+import { memo } from 'react';
+import { DropdownMenuRadioItem } from '@/components/ui/dropdown-menu';
+import type { ThemeMenuItemProps } from '../types';
+
+export const ThemeMenuItem = memo(function ThemeMenuItem({
+ option,
+ onPreviewEnter,
+ onPreviewLeave,
+}: ThemeMenuItemProps) {
+ const Icon = option.icon;
+ return (
+ onPreviewEnter(option.value)}
+ onPointerLeave={onPreviewLeave}
+ >
+
+
+ {option.label}
+
+
+ );
+});
diff --git a/apps/ui/src/components/layout/sidebar/constants.ts b/apps/ui/src/components/layout/sidebar/constants.ts
new file mode 100644
index 00000000..4beca953
--- /dev/null
+++ b/apps/ui/src/components/layout/sidebar/constants.ts
@@ -0,0 +1,24 @@
+import { darkThemes, lightThemes } from '@/config/theme-options';
+
+export const PROJECT_DARK_THEMES = darkThemes.map((opt) => ({
+ value: opt.value,
+ label: opt.label,
+ icon: opt.Icon,
+ color: opt.color,
+}));
+
+export const PROJECT_LIGHT_THEMES = lightThemes.map((opt) => ({
+ value: opt.value,
+ label: opt.label,
+ icon: opt.Icon,
+ color: opt.color,
+}));
+
+export const SIDEBAR_FEATURE_FLAGS = {
+ hideTerminal: import.meta.env.VITE_HIDE_TERMINAL === 'true',
+ hideWiki: import.meta.env.VITE_HIDE_WIKI === 'true',
+ hideRunningAgents: import.meta.env.VITE_HIDE_RUNNING_AGENTS === 'true',
+ hideContext: import.meta.env.VITE_HIDE_CONTEXT === 'true',
+ hideSpecEditor: import.meta.env.VITE_HIDE_SPEC_EDITOR === 'true',
+ hideAiProfiles: import.meta.env.VITE_HIDE_AI_PROFILES === 'true',
+} as const;
diff --git a/apps/ui/src/components/layout/sidebar/types.ts b/apps/ui/src/components/layout/sidebar/types.ts
new file mode 100644
index 00000000..e76e4917
--- /dev/null
+++ b/apps/ui/src/components/layout/sidebar/types.ts
@@ -0,0 +1,36 @@
+import type { Project } from '@/lib/electron';
+
+export interface NavSection {
+ label?: string;
+ items: NavItem[];
+}
+
+export interface NavItem {
+ id: string;
+ label: string;
+ icon: React.ComponentType<{ className?: string }>;
+ shortcut?: string;
+}
+
+export interface SortableProjectItemProps {
+ project: Project;
+ currentProjectId: string | undefined;
+ isHighlighted: boolean;
+ onSelect: (project: Project) => void;
+}
+
+export interface ThemeMenuItemProps {
+ option: {
+ value: string;
+ label: string;
+ icon: React.ComponentType<{ className?: string; style?: React.CSSProperties }>;
+ color: string;
+ };
+ onPreviewEnter: (value: string) => void;
+ onPreviewLeave: (e: React.PointerEvent) => void;
+}
+
+export interface BugReportButtonProps {
+ sidebarExpanded: boolean;
+ onClick: () => void;
+}