From 136d2c3c4361c8b4a3acb6e242f60decc766c1db Mon Sep 17 00:00:00 2001 From: ParkerES Date: Fri, 20 Feb 2026 12:08:15 -0500 Subject: [PATCH 01/11] refactor(layout): remove CommandBar and utility buttons from TopBar TopBar now only renders project tabs and "Add project" button. ScreenshotButton, HealthIndicator, HubStatus, and CommandBar removed (utilities move to TitleBar; AssistantWidget replaces CommandBar). Co-Authored-By: Claude Opus 4.6 --- ai-docs/user-interface-flow.md | 5 ++--- src/renderer/app/layouts/TopBar.tsx | 22 ++++------------------ 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/ai-docs/user-interface-flow.md b/ai-docs/user-interface-flow.md index 4cf23e5..84e954c 100644 --- a/ai-docs/user-interface-flow.md +++ b/ai-docs/user-interface-flow.md @@ -244,7 +244,7 @@ After auth + onboarding, the user sees the main app shell: ``` ┌─────────────────────────────────────────────────────┐ -│ TopBar: [Project Tabs] [+] ─ [📷] [Health] [Hub] [⌘K] │ +│ TopBar: [Project Tabs] [+] │ ├──────────┬──────────────────────────────────────────┤ │ Sidebar │ Main Content Area () │ │ │ │ @@ -279,8 +279,7 @@ After auth + onboarding, the user sees the main app shell: |-----------|------|---------| | `RootLayout` | `src/renderer/app/layouts/RootLayout.tsx` | Shell: sidebar + topbar + outlet + notifications | | `Sidebar` | `src/renderer/app/layouts/Sidebar.tsx` | Nav items (top-level + project-scoped), collapsible | -| `TopBar` | `src/renderer/app/layouts/TopBar.tsx` | Project tabs + add button + ScreenshotButton + Health + Hub status + command bar | -| `CommandBar` | `src/renderer/app/layouts/CommandBar.tsx` | Global assistant input (Cmd+K) | +| `TopBar` | `src/renderer/app/layouts/TopBar.tsx` | Project tabs + add button (utility buttons moved to TitleBar; CommandBar replaced by AssistantWidget) | | `ProjectTabBar` | `src/renderer/app/layouts/ProjectTabBar.tsx` | Horizontal tab bar for switching between open projects | | `UserMenu` | `src/renderer/app/layouts/UserMenu.tsx` | Avatar + logout dropdown in sidebar footer (above HubConnectionIndicator) | | `AssistantWidget` | `src/renderer/features/assistant/components/AssistantWidget.tsx` | Floating chat widget (Ctrl+J toggle), renders WidgetFab + WidgetPanel | diff --git a/src/renderer/app/layouts/TopBar.tsx b/src/renderer/app/layouts/TopBar.tsx index d16d891..2fd50a7 100644 --- a/src/renderer/app/layouts/TopBar.tsx +++ b/src/renderer/app/layouts/TopBar.tsx @@ -1,8 +1,9 @@ /** - * TopBar — Unified top bar with project tabs and command bar + * TopBar — Project tabs bar * - * Replaces ProjectTabBar. Left side has project tabs, right side has - * the global command bar for assistant input. + * Displays open project tabs and an "Add project" button. + * Utility buttons (screenshot, health, hub status) live in TitleBar. + * The AssistantWidget (chat popup) replaces the former CommandBar. */ import { useNavigate } from '@tanstack/react-router'; @@ -10,15 +11,10 @@ import { FolderOpen, Plus, X } from 'lucide-react'; import { ROUTES, PROJECT_VIEWS, projectViewPath } from '@shared/constants'; -import { HubStatus } from '@renderer/shared/components/HubStatus'; import { cn } from '@renderer/shared/lib/utils'; import { useLayoutStore } from '@renderer/shared/stores'; -import { HealthIndicator } from '@features/health'; import { useProjects } from '@features/projects'; -import { ScreenshotButton } from '@features/screen'; - -import { CommandBar } from './CommandBar'; export function TopBar() { // 1. Hooks @@ -91,16 +87,6 @@ export function TopBar() { - - {/* Right: Screenshot + Health + Hub status + Command bar */} -
- - - -
- -
-
); } From 884865760d7a506c0dfccd8392512a6ab826669b Mon Sep 17 00:00:00 2001 From: ParkerES Date: Fri, 20 Feb 2026 12:08:38 -0500 Subject: [PATCH 02/11] refactor(css): remove 6 hardcoded color theme blocks from globals.css Remove all 12 [data-theme='...'] CSS blocks (6 themes x light/dark): dusk, lime, ocean, retro, neo, forest. Keep only :root (light defaults) and .dark (dark defaults) as the base fallback. Custom themes will override these at runtime via style.setProperty(). File reduced from 1422 to 899 lines (-523 lines). Co-Authored-By: Claude Opus 4.6 --- src/renderer/styles/globals.css | 522 -------------------------------- 1 file changed, 522 deletions(-) diff --git a/src/renderer/styles/globals.css b/src/renderer/styles/globals.css index e97f4c5..8b8e8af 100644 --- a/src/renderer/styles/globals.css +++ b/src/renderer/styles/globals.css @@ -301,528 +301,6 @@ --shadow-focus: 0 0 0 2px rgba(214, 216, 118, 0.2); } -/* ============================================ - DUSK THEME (Light) - Warm, muted palette inspired by Fey/Oscura - ============================================ */ -[data-theme='dusk'] { - --background: #f5f5f0; - --foreground: #131419; - --card: #ffffff; - --card-foreground: #131419; - --primary: #b8b978; - --primary-foreground: #131419; - --secondary: #eaeae5; - --secondary-foreground: #131419; - --muted: #f0f0eb; - --muted-foreground: #5c6974; - --accent: #f0f0e0; - --accent-foreground: #b8b978; - --destructive: #d84f68; - --destructive-foreground: #ffffff; - --border: #e0e0db; - --input: #e0e0db; - --ring: #b8b978; - --sidebar: #ffffff; - --sidebar-foreground: #131419; - --popover: #ffffff; - --popover-foreground: #131419; - --success: #4ebe96; - --success-foreground: #ffffff; - --success-light: #e0f5ed; - --warning: #d2d714; - --warning-foreground: #131419; - --warning-light: #f5f5d0; - --info: #479ffa; - --info-foreground: #ffffff; - --info-light: #e8f4ff; - --error: #d84f68; - --error-light: #fce8ec; - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.05); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04); - --shadow-focus: 0 0 0 3px rgba(184, 185, 120, 0.2); -} - -/* ============================================ - DUSK THEME (Dark) - ============================================ */ -[data-theme='dusk'].dark { - --background: #131419; - --foreground: #e6e6e6; - --card: #1a1b21; - --card-foreground: #e6e6e6; - --primary: #e6e7a3; - --primary-foreground: #131419; - --secondary: #222329; - --secondary-foreground: #e6e6e6; - --muted: #16171d; - --muted-foreground: #868f97; - --accent: #2a2b1f; - --accent-foreground: #e6e7a3; - --destructive: #d84f68; - --destructive-foreground: #131419; - --border: #282828; - --input: #282828; - --ring: #e6e7a3; - --sidebar: #16171d; - --sidebar-foreground: #e6e6e6; - --popover: #222329; - --popover-foreground: #e6e6e6; - --success: #4ebe96; - --success-foreground: #131419; - --success-light: #1a2e28; - --warning: #d2d714; - --warning-foreground: #131419; - --warning-light: #2a2b1a; - --info: #479ffa; - --info-foreground: #131419; - --info-light: #1a2433; - --error: #d84f68; - --error-light: #2e1a1f; - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.6); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.7); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.8); - --shadow-focus: 0 0 0 2px rgba(230, 231, 163, 0.25); -} - -/* ============================================ - LIME THEME (Light) - Fresh, energetic lime/chartreuse - ============================================ */ -[data-theme='lime'] { - --background: #e8f5a3; - --foreground: #1a1a2e; - --card: #ffffff; - --card-foreground: #1a1a2e; - --primary: #7c3aed; - --primary-foreground: #ffffff; - --secondary: #f5f9e8; - --secondary-foreground: #1a1a2e; - --muted: #f8fafc; - --muted-foreground: #64748b; - --accent: #ede9fe; - --accent-foreground: #7c3aed; - --destructive: #dc2626; - --destructive-foreground: #ffffff; - --border: #e2e8f0; - --input: #e2e8f0; - --ring: #7c3aed; - --sidebar: #ffffff; - --sidebar-foreground: #1a1a2e; - --popover: #ffffff; - --popover-foreground: #1a1a2e; - --success: #059669; - --success-foreground: #ffffff; - --success-light: #d1fae5; - --warning: #d97706; - --warning-foreground: #ffffff; - --warning-light: #fef3c7; - --info: #2563eb; - --info-foreground: #ffffff; - --info-light: #dbeafe; - --error: #dc2626; - --error-light: #fee2e2; - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.05); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04); - --shadow-focus: 0 0 0 3px rgba(124, 58, 237, 0.2); -} - -/* ============================================ - LIME THEME (Dark) - ============================================ */ -[data-theme='lime'].dark { - --background: #0f0f1a; - --foreground: #f8fafc; - --card: #1e1e2e; - --card-foreground: #f8fafc; - --primary: #8b5cf6; - --primary-foreground: #0f0f1a; - --secondary: #1a1a2e; - --secondary-foreground: #f8fafc; - --muted: #13131f; - --muted-foreground: #a1a1b5; - --accent: #2e2350; - --accent-foreground: #8b5cf6; - --destructive: #f87171; - --destructive-foreground: #0f0f1a; - --border: #2e2e40; - --input: #2e2e40; - --ring: #8b5cf6; - --sidebar: #13131f; - --sidebar-foreground: #f8fafc; - --popover: #262638; - --popover-foreground: #f8fafc; - --success: #34d399; - --success-foreground: #0f0f1a; - --success-light: #134e4a; - --warning: #fbbf24; - --warning-foreground: #0f0f1a; - --warning-light: #451a03; - --info: #60a5fa; - --info-foreground: #0f0f1a; - --info-light: #1e3a8a; - --error: #f87171; - --error-light: #450a0a; - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.6); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.7); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.8); - --shadow-focus: 0 0 0 3px rgba(139, 92, 246, 0.3); -} - -/* ============================================ - OCEAN THEME (Light) - Calm, professional blue tones - ============================================ */ -[data-theme='ocean'] { - --background: #e0f2fe; - --foreground: #0c4a6e; - --card: #ffffff; - --card-foreground: #0c4a6e; - --primary: #0284c7; - --primary-foreground: #ffffff; - --secondary: #f0f9ff; - --secondary-foreground: #0c4a6e; - --muted: #f8fafc; - --muted-foreground: #64748b; - --accent: #e0f2fe; - --accent-foreground: #0284c7; - --destructive: #dc2626; - --destructive-foreground: #ffffff; - --border: #bae6fd; - --input: #bae6fd; - --ring: #0284c7; - --sidebar: #ffffff; - --sidebar-foreground: #0c4a6e; - --popover: #ffffff; - --popover-foreground: #0c4a6e; - --success: #059669; - --success-foreground: #ffffff; - --success-light: #d1fae5; - --warning: #d97706; - --warning-foreground: #ffffff; - --warning-light: #fef3c7; - --info: #2563eb; - --info-foreground: #ffffff; - --info-light: #dbeafe; - --error: #dc2626; - --error-light: #fee2e2; - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.05); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04); - --shadow-focus: 0 0 0 3px rgba(2, 132, 199, 0.2); -} - -/* ============================================ - OCEAN THEME (Dark) - ============================================ */ -[data-theme='ocean'].dark { - --background: #082f49; - --foreground: #f0f9ff; - --card: #164e63; - --card-foreground: #f0f9ff; - --primary: #38bdf8; - --primary-foreground: #082f49; - --secondary: #0c4a6e; - --secondary-foreground: #f0f9ff; - --muted: #0a3d5c; - --muted-foreground: #7dd3fc; - --accent: #0c4a6e; - --accent-foreground: #38bdf8; - --destructive: #f87171; - --destructive-foreground: #082f49; - --border: #0e7490; - --input: #0e7490; - --ring: #38bdf8; - --sidebar: #0a3d5c; - --sidebar-foreground: #f0f9ff; - --popover: #1e6b8a; - --popover-foreground: #f0f9ff; - --success: #34d399; - --success-foreground: #082f49; - --success-light: #134e4a; - --warning: #fbbf24; - --warning-foreground: #082f49; - --warning-light: #451a03; - --info: #60a5fa; - --info-foreground: #082f49; - --info-light: #1e3a8a; - --error: #f87171; - --error-light: #450a0a; - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.6); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.7); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.8); - --shadow-focus: 0 0 0 3px rgba(56, 189, 248, 0.3); -} - -/* ============================================ - RETRO THEME (Light) - Warm, nostalgic amber/orange vibes - ============================================ */ -[data-theme='retro'] { - --background: #fef3c7; - --foreground: #78350f; - --card: #ffffff; - --card-foreground: #78350f; - --primary: #d97706; - --primary-foreground: #ffffff; - --secondary: #fffbeb; - --secondary-foreground: #78350f; - --muted: #fefce8; - --muted-foreground: #92400e; - --accent: #fef3c7; - --accent-foreground: #d97706; - --destructive: #b91c1c; - --destructive-foreground: #ffffff; - --border: #fde68a; - --input: #fde68a; - --ring: #d97706; - --sidebar: #ffffff; - --sidebar-foreground: #78350f; - --popover: #ffffff; - --popover-foreground: #78350f; - --success: #15803d; - --success-foreground: #ffffff; - --success-light: #dcfce7; - --warning: #ca8a04; - --warning-foreground: #78350f; - --warning-light: #fef9c3; - --info: #1d4ed8; - --info-foreground: #ffffff; - --info-light: #dbeafe; - --error: #b91c1c; - --error-light: #fee2e2; - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.05); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04); - --shadow-focus: 0 0 0 3px rgba(217, 119, 6, 0.2); -} - -/* ============================================ - RETRO THEME (Dark) - ============================================ */ -[data-theme='retro'].dark { - --background: #1c1917; - --foreground: #fefce8; - --card: #44403c; - --card-foreground: #fefce8; - --primary: #fbbf24; - --primary-foreground: #1c1917; - --secondary: #292524; - --secondary-foreground: #fefce8; - --muted: #1c1917; - --muted-foreground: #fde68a; - --accent: #451a03; - --accent-foreground: #fbbf24; - --destructive: #f87171; - --destructive-foreground: #1c1917; - --border: #78716c; - --input: #78716c; - --ring: #fbbf24; - --sidebar: #1c1917; - --sidebar-foreground: #fefce8; - --popover: #57534e; - --popover-foreground: #fefce8; - --success: #4ade80; - --success-foreground: #1c1917; - --success-light: #14532d; - --warning: #facc15; - --warning-foreground: #1c1917; - --warning-light: #422006; - --info: #60a5fa; - --info-foreground: #1c1917; - --info-light: #1e3a8a; - --error: #f87171; - --error-light: #450a0a; - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.6); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.7); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.8); - --shadow-focus: 0 0 0 3px rgba(251, 191, 36, 0.3); -} - -/* ============================================ - NEO THEME (Light) - Modern, cyberpunk-inspired pink/purple - ============================================ */ -[data-theme='neo'] { - --background: #fdf4ff; - --foreground: #581c87; - --card: #ffffff; - --card-foreground: #581c87; - --primary: #d946ef; - --primary-foreground: #ffffff; - --secondary: #faf5ff; - --secondary-foreground: #581c87; - --muted: #f5f3ff; - --muted-foreground: #7c3aed; - --accent: #fae8ff; - --accent-foreground: #d946ef; - --destructive: #e11d48; - --destructive-foreground: #ffffff; - --border: #f0abfc; - --input: #f0abfc; - --ring: #d946ef; - --sidebar: #ffffff; - --sidebar-foreground: #581c87; - --popover: #ffffff; - --popover-foreground: #581c87; - --success: #06b6d4; - --success-foreground: #ffffff; - --success-light: #cffafe; - --warning: #f59e0b; - --warning-foreground: #581c87; - --warning-light: #fef3c7; - --info: #8b5cf6; - --info-foreground: #ffffff; - --info-light: #ede9fe; - --error: #e11d48; - --error-light: #ffe4e6; - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.05); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04); - --shadow-focus: 0 0 0 3px rgba(217, 70, 239, 0.2); -} - -/* ============================================ - NEO THEME (Dark) - ============================================ */ -[data-theme='neo'].dark { - --background: #0f0720; - --foreground: #faf5ff; - --card: #2d1b4e; - --card-foreground: #faf5ff; - --primary: #f0abfc; - --primary-foreground: #0f0720; - --secondary: #1a0a30; - --secondary-foreground: #faf5ff; - --muted: #150825; - --muted-foreground: #e879f9; - --accent: #581c87; - --accent-foreground: #f0abfc; - --destructive: #fb7185; - --destructive-foreground: #0f0720; - --border: #581c87; - --input: #581c87; - --ring: #f0abfc; - --sidebar: #150825; - --sidebar-foreground: #faf5ff; - --popover: #3d2563; - --popover-foreground: #faf5ff; - --success: #22d3ee; - --success-foreground: #0f0720; - --success-light: #164e63; - --warning: #fbbf24; - --warning-foreground: #0f0720; - --warning-light: #451a03; - --info: #a78bfa; - --info-foreground: #0f0720; - --info-light: #4c1d95; - --error: #fb7185; - --error-light: #4c0519; - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.4), 0 0 20px rgba(217, 70, 239, 0.1); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 0 30px rgba(217, 70, 239, 0.1); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.6), 0 0 40px rgba(217, 70, 239, 0.15); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.6), 0 0 50px rgba(217, 70, 239, 0.2); - --shadow-focus: 0 0 0 3px rgba(240, 171, 252, 0.4); -} - -/* ============================================ - FOREST THEME (Light) - Natural, earthy green tones - ============================================ */ -[data-theme='forest'] { - --background: #dcfce7; - --foreground: #14532d; - --card: #ffffff; - --card-foreground: #14532d; - --primary: #16a34a; - --primary-foreground: #ffffff; - --secondary: #f0fdf4; - --secondary-foreground: #14532d; - --muted: #ecfdf5; - --muted-foreground: #166534; - --accent: #dcfce7; - --accent-foreground: #16a34a; - --destructive: #dc2626; - --destructive-foreground: #ffffff; - --border: #86efac; - --input: #86efac; - --ring: #16a34a; - --sidebar: #ffffff; - --sidebar-foreground: #14532d; - --popover: #ffffff; - --popover-foreground: #14532d; - --success: #059669; - --success-foreground: #ffffff; - --success-light: #d1fae5; - --warning: #ca8a04; - --warning-foreground: #14532d; - --warning-light: #fef9c3; - --info: #0284c7; - --info-foreground: #ffffff; - --info-light: #e0f2fe; - --error: #dc2626; - --error-light: #fee2e2; - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.05); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04); - --shadow-focus: 0 0 0 3px rgba(22, 163, 74, 0.2); -} - -/* ============================================ - FOREST THEME (Dark) - ============================================ */ -[data-theme='forest'].dark { - --background: #052e16; - --foreground: #f0fdf4; - --card: #166534; - --card-foreground: #f0fdf4; - --primary: #4ade80; - --primary-foreground: #052e16; - --secondary: #14532d; - --secondary-foreground: #f0fdf4; - --muted: #0a3d1f; - --muted-foreground: #86efac; - --accent: #14532d; - --accent-foreground: #4ade80; - --destructive: #f87171; - --destructive-foreground: #052e16; - --border: #166534; - --input: #166534; - --ring: #4ade80; - --sidebar: #0a3d1f; - --sidebar-foreground: #f0fdf4; - --popover: #15803d; - --popover-foreground: #f0fdf4; - --success: #34d399; - --success-foreground: #052e16; - --success-light: #064e3b; - --warning: #fbbf24; - --warning-foreground: #052e16; - --warning-light: #451a03; - --info: #38bdf8; - --info-foreground: #052e16; - --info-light: #0c4a6e; - --error: #f87171; - --error-light: #450a0a; - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.6); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.7); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.8); - --shadow-focus: 0 0 0 3px rgba(74, 222, 128, 0.3); -} - /* ============================================ BASE STYLES ============================================ */ From ad0bf585c7dfb339f398aa4a7d8796e4b7ba2a0c Mon Sep 17 00:00:00 2001 From: ParkerES Date: Fri, 20 Feb 2026 12:09:59 -0500 Subject: [PATCH 03/11] feat(types): replace hardcoded theme constants with custom theme types and token defaults - Add ThemeTokens interface (33 CSS variable token fields) and new CustomTheme type with light/dark palettes + timestamps - Add ThemeTokensSchema and CustomThemeSchema Zod validation - Add customThemes field to AppSettingsSchema - Add THEME_TOKEN_KEYS, DEFAULT_LIGHT_TOKENS, DEFAULT_DARK_TOKENS constants extracted from globals.css :root and .dark blocks - Add ROUTES.THEMES ('/settings/themes') route constant - Reduce COLOR_THEMES/COLOR_THEME_LABELS to backward-compat stubs (to be removed by Task #4 when ColorThemeSection.tsx is updated) Co-Authored-By: Claude Opus 4.6 --- src/shared/constants/index.ts | 8 +- src/shared/constants/routes.ts | 1 + src/shared/constants/themes.ts | 126 ++++++++++++++++++++++++++--- src/shared/ipc/settings/schemas.ts | 48 +++++++++++ src/shared/types/settings.ts | 43 +++++++++- 5 files changed, 214 insertions(+), 12 deletions(-) diff --git a/src/shared/constants/index.ts b/src/shared/constants/index.ts index 9dbdf51..05aec75 100644 --- a/src/shared/constants/index.ts +++ b/src/shared/constants/index.ts @@ -10,5 +10,11 @@ export { export { ROUTES, PROJECT_VIEWS, ROUTE_PATTERNS, projectViewPath } from './routes'; export { CLAUDE_MODELS, MODEL_SHORT_LABELS } from './models'; -export { COLOR_THEMES, COLOR_THEME_LABELS } from './themes'; +export { + THEME_TOKEN_KEYS, + DEFAULT_LIGHT_TOKENS, + DEFAULT_DARK_TOKENS, + COLOR_THEMES, + COLOR_THEME_LABELS, +} from './themes'; export type { ColorTheme } from './themes'; diff --git a/src/shared/constants/routes.ts b/src/shared/constants/routes.ts index 250c7e9..2d47d3f 100644 --- a/src/shared/constants/routes.ts +++ b/src/shared/constants/routes.ts @@ -24,6 +24,7 @@ export const ROUTES = { PRODUCTIVITY: '/productivity', PROJECTS: '/projects', SETTINGS: '/settings', + THEMES: '/settings/themes', } as const; /** Project sub-view path segments (appended to /projects/$projectId/) */ diff --git a/src/shared/constants/themes.ts b/src/shared/constants/themes.ts index 0cca94b..434f42d 100644 --- a/src/shared/constants/themes.ts +++ b/src/shared/constants/themes.ts @@ -1,15 +1,121 @@ -/** Available color themes — maps to [data-theme] CSS selectors */ -export const COLOR_THEMES = ['default', 'dusk', 'lime', 'ocean', 'retro', 'neo', 'forest'] as const; +import type { ThemeTokens } from '../types/settings'; + +/** All CSS variable token keys that make up a color theme (33 total) */ +export const THEME_TOKEN_KEYS = [ + 'background', + 'foreground', + 'card', + 'card-foreground', + 'primary', + 'primary-foreground', + 'secondary', + 'secondary-foreground', + 'muted', + 'muted-foreground', + 'accent', + 'accent-foreground', + 'destructive', + 'destructive-foreground', + 'border', + 'input', + 'ring', + 'sidebar', + 'sidebar-foreground', + 'popover', + 'popover-foreground', + 'success', + 'success-foreground', + 'warning', + 'warning-foreground', + 'info', + 'info-foreground', + 'error', + 'error-light', + 'success-light', + 'warning-light', + 'info-light', + 'shadow-focus', +] as const; + +/** Default light theme token values — extracted from :root in globals.css */ +export const DEFAULT_LIGHT_TOKENS: ThemeTokens = { + background: '#f2f2ed', + foreground: '#0b0b0f', + card: '#ffffff', + 'card-foreground': '#0b0b0f', + primary: '#a5a66a', + 'primary-foreground': '#0b0b0f', + secondary: '#e8e8e3', + 'secondary-foreground': '#0b0b0f', + muted: '#edede8', + 'muted-foreground': '#5c6974', + accent: '#efefe0', + 'accent-foreground': '#a5a66a', + destructive: '#d84f68', + 'destructive-foreground': '#ffffff', + border: '#deded9', + input: '#deded9', + ring: '#a5a66a', + sidebar: '#ffffff', + 'sidebar-foreground': '#0b0b0f', + popover: '#ffffff', + 'popover-foreground': '#0b0b0f', + success: '#4ebe96', + 'success-foreground': '#ffffff', + 'success-light': '#e0f5ed', + warning: '#d2d714', + 'warning-foreground': '#0b0b0f', + 'warning-light': '#f5f5d0', + info: '#479ffa', + 'info-foreground': '#ffffff', + 'info-light': '#e8f4ff', + error: '#d84f68', + 'error-light': '#fce8ec', + 'shadow-focus': '0 0 0 3px rgba(165, 166, 106, 0.2)', +}; + +/** Default dark theme token values — extracted from .dark in globals.css */ +export const DEFAULT_DARK_TOKENS: ThemeTokens = { + background: '#0b0b0f', + foreground: '#e6e6e6', + card: '#121216', + 'card-foreground': '#e6e6e6', + primary: '#d6d876', + 'primary-foreground': '#0b0b0f', + secondary: '#1a1a1f', + 'secondary-foreground': '#e6e6e6', + muted: '#1a1a1f', + 'muted-foreground': '#868f97', + accent: '#2a2a1f', + 'accent-foreground': '#d6d876', + destructive: '#ff5c5c', + 'destructive-foreground': '#0b0b0f', + border: '#232323', + input: '#232323', + ring: '#d6d876', + sidebar: '#0e0e12', + 'sidebar-foreground': '#e6e6e6', + popover: '#1a1a1f', + 'popover-foreground': '#e6e6e6', + success: '#4ebe96', + 'success-foreground': '#0b0b0f', + 'success-light': '#1a2924', + warning: '#d2d714', + 'warning-foreground': '#0b0b0f', + 'warning-light': '#262618', + info: '#479ffa', + 'info-foreground': '#0b0b0f', + 'info-light': '#1a2230', + error: '#ff5c5c', + 'error-light': '#2a1a1a', + 'shadow-focus': '0 0 0 2px rgba(214, 216, 118, 0.2)', +}; + +// TODO: Remove after Task #4 updates ColorThemeSection.tsx — kept for backward compat +export const COLOR_THEMES = ['default'] as const; export type ColorTheme = (typeof COLOR_THEMES)[number]; -/** Human-readable labels for color themes */ export const COLOR_THEME_LABELS: Record = { - default: 'Oscura', - dusk: 'Dusk', - lime: 'Lime', - ocean: 'Ocean', - retro: 'Retro', - neo: 'Neo', - forest: 'Forest', + default: 'Default', } as const; diff --git a/src/shared/ipc/settings/schemas.ts b/src/shared/ipc/settings/schemas.ts index 0cd8aef..804cf09 100644 --- a/src/shared/ipc/settings/schemas.ts +++ b/src/shared/ipc/settings/schemas.ts @@ -9,11 +9,59 @@ import { z } from 'zod'; import { DataRetentionSettingsSchema } from '../data-management/schemas'; +// ── Theme Schemas ─────────────────────────────────────────────── + +export const ThemeTokensSchema = z.object({ + background: z.string(), + foreground: z.string(), + card: z.string(), + 'card-foreground': z.string(), + primary: z.string(), + 'primary-foreground': z.string(), + secondary: z.string(), + 'secondary-foreground': z.string(), + muted: z.string(), + 'muted-foreground': z.string(), + accent: z.string(), + 'accent-foreground': z.string(), + destructive: z.string(), + 'destructive-foreground': z.string(), + border: z.string(), + input: z.string(), + ring: z.string(), + sidebar: z.string(), + 'sidebar-foreground': z.string(), + popover: z.string(), + 'popover-foreground': z.string(), + success: z.string(), + 'success-foreground': z.string(), + warning: z.string(), + 'warning-foreground': z.string(), + info: z.string(), + 'info-foreground': z.string(), + error: z.string(), + 'error-light': z.string(), + 'success-light': z.string(), + 'warning-light': z.string(), + 'info-light': z.string(), + 'shadow-focus': z.string(), +}); + +export const CustomThemeSchema = z.object({ + id: z.string(), + name: z.string(), + light: ThemeTokensSchema, + dark: ThemeTokensSchema, + createdAt: z.string(), + updatedAt: z.string(), +}); + // ── App Settings Schemas ──────────────────────────────────────── export const AppSettingsSchema = z.object({ theme: z.enum(['light', 'dark', 'system']), colorTheme: z.string(), + customThemes: z.array(CustomThemeSchema).optional(), language: z.string(), uiScale: z.number(), onboardingCompleted: z.boolean(), diff --git a/src/shared/types/settings.ts b/src/shared/types/settings.ts index 4d02b55..891b7db 100644 --- a/src/shared/types/settings.ts +++ b/src/shared/types/settings.ts @@ -35,10 +35,51 @@ export interface AppSettings { dataRetention?: DataRetentionSettings; } +/** All CSS variable tokens that make up a color theme */ +export interface ThemeTokens { + background: string; + foreground: string; + card: string; + 'card-foreground': string; + primary: string; + 'primary-foreground': string; + secondary: string; + 'secondary-foreground': string; + muted: string; + 'muted-foreground': string; + accent: string; + 'accent-foreground': string; + destructive: string; + 'destructive-foreground': string; + border: string; + input: string; + ring: string; + sidebar: string; + 'sidebar-foreground': string; + popover: string; + 'popover-foreground': string; + success: string; + 'success-foreground': string; + warning: string; + 'warning-foreground': string; + info: string; + 'info-foreground': string; + error: string; + 'error-light': string; + 'success-light': string; + 'warning-light': string; + 'info-light': string; + 'shadow-focus': string; +} + +/** A saved custom theme with light and dark palettes */ export interface CustomTheme { id: string; name: string; - css: string; + light: ThemeTokens; + dark: ThemeTokens; + createdAt: string; + updatedAt: string; } export interface Profile { From ab218e3f6e088d16ac8ba239eff64c5253cb43e6 Mon Sep 17 00:00:00 2001 From: ParkerES Date: Fri, 20 Feb 2026 12:10:21 -0500 Subject: [PATCH 04/11] feat(projects): enhance dashboard with search, metrics, and rich project cards - Add search bar that filters projects by name or path (live, case-insensitive) - Add metrics row showing real data: total projects, active tasks, active agents - Replace raw HTML buttons with Button from @ui design system - Replace Loader2 with Spinner from @ui - Wrap project rows in Card components with Badge for repo structure - Add per-project task counts derived from useAllTasks() - Add search no-results empty state - All metrics use real data from useProjects() and useAllTasks() hooks Co-Authored-By: Claude Opus 4.6 --- .../projects/components/ProjectListPage.tsx | 332 ++++++++++++------ 1 file changed, 221 insertions(+), 111 deletions(-) diff --git a/src/renderer/features/projects/components/ProjectListPage.tsx b/src/renderer/features/projects/components/ProjectListPage.tsx index ae3ffce..e121864 100644 --- a/src/renderer/features/projects/components/ProjectListPage.tsx +++ b/src/renderer/features/projects/components/ProjectListPage.tsx @@ -1,28 +1,32 @@ /** - * ProjectListPage — Shows all projects with add/remove controls + * ProjectListPage — Projects dashboard with search, metrics, and rich project cards */ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { useNavigate } from '@tanstack/react-router'; -import { FolderOpen, Layers, Loader2, Pencil, Sparkles, Trash2, Wand2 } from 'lucide-react'; +import { FolderOpen, Layers, Pencil, Search, Sparkles, Trash2, Wand2 } from 'lucide-react'; import { PROJECT_VIEWS, projectViewPath } from '@shared/constants'; import type { Project, RepoType } from '@shared/types'; -import { cn, formatRelativeTime } from '@renderer/shared/lib/utils'; +import { formatRelativeTime } from '@renderer/shared/lib/utils'; import { useLayoutStore, useToastStore } from '@renderer/shared/stores'; +import { Badge, Button, Card, CardContent, Input, Separator, Spinner } from '@ui'; + +import { useAllTasks } from '@features/tasks'; + import { useProjects, useRemoveProject, useSubProjects } from '../api/useProjects'; import { CreateProjectWizard } from './CreateProjectWizard'; import { ProjectEditDialog } from './ProjectEditDialog'; import { ProjectInitWizard } from './ProjectInitWizard'; -function repoStructureBadgeClass(structure: RepoType): string { - if (structure === 'monorepo') return 'bg-info/10 text-info'; - if (structure === 'multi-repo') return 'bg-warning/10 text-warning'; - return 'bg-muted text-muted-foreground'; +function repoStructureBadgeVariant(structure: RepoType): 'default' | 'secondary' | 'outline' { + if (structure === 'monorepo') return 'default'; + if (structure === 'multi-repo') return 'secondary'; + return 'outline'; } function repoStructureLabel(structure: RepoType): string { @@ -31,92 +35,147 @@ function repoStructureLabel(structure: RepoType): string { return 'single'; } -interface ProjectRowProps { +interface ProjectCardProps { project: Project; + taskCount: number; onEdit: (e: React.MouseEvent | React.KeyboardEvent, project: Project) => void; onOpen: (projectId: string) => void; onRemove: (e: React.MouseEvent | React.KeyboardEvent, projectId: string) => void; } -function ProjectRow({ project, onEdit, onOpen, onRemove }: ProjectRowProps) { +function ProjectCard({ project, taskCount, onEdit, onOpen, onRemove }: ProjectCardProps) { const { data: subProjects } = useSubProjects(project.id); const subCount = subProjects?.length ?? 0; return ( - +
+ {taskCount > 0 ? ( + + {String(taskCount)} task{taskCount === 1 ? '' : 's'} + + ) : null} + + {formatRelativeTime(project.updatedAt)} + + { + e.stopPropagation(); + onEdit(e, project); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.stopPropagation(); + onEdit(e, project); + } + }} + > + + + { + e.stopPropagation(); + onRemove(e, project.id); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.stopPropagation(); + onRemove(e, project.id); + } + }} + > + + +
+ + ); } export function ProjectListPage() { const navigate = useNavigate(); const { data: projects, isLoading } = useProjects(); + const { data: allTasks } = useAllTasks(); const removeProject = useRemoveProject(); const { addProjectTab } = useLayoutStore(); const { addToast } = useToastStore(); const [editingProject, setEditingProject] = useState(null); const [wizardOpen, setWizardOpen] = useState(false); const [createWizardOpen, setCreateWizardOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + + const filteredProjects = useMemo(() => { + if (!projects) return []; + const query = searchQuery.toLowerCase().trim(); + if (query.length === 0) return projects; + return projects.filter( + (p) => p.name.toLowerCase().includes(query) || p.path.toLowerCase().includes(query), + ); + }, [projects, searchQuery]); + + const metrics = useMemo(() => { + const tasks = allTasks ?? []; + const totalProjects = projects?.length ?? 0; + const activeTasks = tasks.filter((t) => ['in_progress', 'running'].includes(t.status)).length; + const activeAgents = tasks.filter( + (t) => + t.status === 'running' && + Boolean((t.metadata as Record | undefined)?.agentName), + ).length; + return { totalProjects, activeTasks, activeAgents }; + }, [allTasks, projects]); + + const taskCountByProject = useMemo(() => { + const tasks = allTasks ?? []; + const counts = new Map(); + for (const task of tasks) { + const pid = task.projectId; + if (pid) { + counts.set(pid, (counts.get(pid) ?? 0) + 1); + } + } + return counts; + }, [allTasks]); function handleOpenProject(projectId: string) { addProjectTab(projectId); @@ -150,61 +209,115 @@ export function ProjectListPage() { if (isLoading) { return (
- + +
+ ); + } + + const hasProjects = (projects?.length ?? 0) > 0; + const hasFilteredResults = filteredProjects.length > 0; + + function renderProjectList() { + if (!hasProjects) { + return ( +
+ +

No projects yet

+

Add a project folder to get started

+
+ ); + } + + if (!hasFilteredResults) { + return ( +
+ +

No matching projects

+

Try a different search term

+
+ ); + } + + return ( +
+ {filteredProjects.map((project) => ( + + ))}
); } return ( -
-
+
+ {/* Header */} +

Projects

- - + +
- {projects && projects.length > 0 ? ( -
- {projects.map((project) => ( - - ))} + {/* Search */} + {hasProjects ? ( +
+ + setSearchQuery(e.target.value)} + />
- ) : ( -
- -

No projects yet

-

Add a project folder to get started

-
- )} + ) : null} + {/* Metrics */} +
+ + +

+ Total Projects +

+

{String(metrics.totalProjects)}

+
+
+ + +

+ Active Tasks +

+

{String(metrics.activeTasks)}

+
+
+ + +

+ Active Agents +

+

{String(metrics.activeAgents)}

+
+
+
+ + + + {/* Project list */} + {renderProjectList()} + + {/* Wizards and dialogs */} {wizardOpen ? ( setWizardOpen(false)} @@ -218,10 +331,7 @@ export function ProjectListPage() { onProjectCreated={handleProjectCreated} /> - setEditingProject(null)} - /> + setEditingProject(null)} />
); } From 6721dd5dc5ba8c5d2754eb405b020a8419703a23 Mon Sep 17 00:00:00 2001 From: ParkerES Date: Fri, 20 Feb 2026 12:10:26 -0500 Subject: [PATCH 05/11] feat(layout): add screenshot, health, hub status buttons to TitleBar Add utility button group between drag area and window controls in the TitleBar. Includes TitleBarScreenshot (camera icon, captures primary screen to clipboard with 1.5s checkmark feedback), HealthIndicator, and HubStatus, separated from window controls by a vertical Separator. Co-Authored-By: Claude Opus 4.6 --- ai-docs/ARCHITECTURE.md | 2 +- ai-docs/FEATURES-INDEX.md | 3 +- src/renderer/app/layouts/TitleBar.tsx | 20 ++++- .../app/layouts/TitleBarScreenshot.tsx | 78 +++++++++++++++++++ 4 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 src/renderer/app/layouts/TitleBarScreenshot.tsx diff --git a/ai-docs/ARCHITECTURE.md b/ai-docs/ARCHITECTURE.md index 90af1a7..403d174 100644 --- a/ai-docs/ARCHITECTURE.md +++ b/ai-docs/ARCHITECTURE.md @@ -704,7 +704,7 @@ The task dashboard uses AG-Grid Community v35.1.0 for the main data grid: | Component | Location | Purpose | |-----------|----------|---------| | `RootLayout` | `src/renderer/app/layouts/RootLayout.tsx` | Root shell: renders TitleBar at top, then `react-resizable-panels` (Group/Panel/Separator) for resizable sidebar + content layout. Sidebar panel is collapsible (syncs with layout store). Layout persists to localStorage via `useDefaultLayout`. | -| `TitleBar` | `src/renderer/app/layouts/TitleBar.tsx` | Custom frameless window title bar (32px). Drag region for window movement + minimize/maximize/close controls via `window.*` IPC channels. | +| `TitleBar` | `src/renderer/app/layouts/TitleBar.tsx` | Custom frameless window title bar (32px). Drag region for window movement, utility buttons (screenshot, health, hub status) separated by vertical divider from minimize/maximize/close window controls via `window.*` IPC channels. | | `Sidebar` | `src/renderer/app/layouts/Sidebar.tsx` | Navigation sidebar (fills its parent panel container) | | `TopBar` | `src/renderer/app/layouts/TopBar.tsx` | Top bar with CommandBar trigger | | `CommandBar` | `src/renderer/app/layouts/CommandBar.tsx` | Global command palette (Cmd+K) | diff --git a/ai-docs/FEATURES-INDEX.md b/ai-docs/FEATURES-INDEX.md index 8a10a7c..422a231 100644 --- a/ai-docs/FEATURES-INDEX.md +++ b/ai-docs/FEATURES-INDEX.md @@ -389,7 +389,8 @@ Location: `src/renderer/app/layouts/` | Layout | Purpose | |--------|---------| | `RootLayout.tsx` | Root shell: renders TitleBar at top, then `react-resizable-panels` (Group/Panel/Separator) for sidebar + content layout with localStorage persistence. Sidebar panel is collapsible and syncs with layout store. | -| `TitleBar.tsx` | Custom frameless window title bar (32px). Drag region for window movement + minimize/maximize/close controls. Uses `window.*` IPC channels. | +| `TitleBar.tsx` | Custom frameless window title bar (32px). Drag region for window movement, utility buttons (screenshot, health indicator, hub status) separated by vertical divider from minimize/maximize/close window controls. Uses `window.*` IPC channels. | +| `TitleBarScreenshot.tsx` | Camera icon button that captures the primary screen via `screen.listSources` + `screen.capture` IPC channels and copies PNG to clipboard. Shows checkmark feedback for 1.5s on success. | | `Sidebar.tsx` | Navigation sidebar (fills its parent panel, collapse state driven by layout store) | | `TopBar.tsx` | Top bar with assistant command input | | `CommandBar.tsx` | Global command palette (Cmd+K) | diff --git a/src/renderer/app/layouts/TitleBar.tsx b/src/renderer/app/layouts/TitleBar.tsx index 9482afd..04678c3 100644 --- a/src/renderer/app/layouts/TitleBar.tsx +++ b/src/renderer/app/layouts/TitleBar.tsx @@ -2,16 +2,22 @@ * TitleBar — Custom frameless window title bar * * Replaces the native OS title bar. Provides drag region for window - * movement and minimize/maximize/close buttons for window controls. + * movement, utility buttons (screenshot, health, hub status), and + * minimize/maximize/close buttons for window controls. */ import { useCallback, useEffect, useState } from 'react'; import { Minus, Square, X } from 'lucide-react'; +import { HubStatus } from '@renderer/shared/components/HubStatus'; import { ipc } from '@renderer/shared/lib/ipc'; -import { Button } from '@ui'; +import { Button, Separator } from '@ui'; + +import { HealthIndicator } from '@features/health'; + +import { TitleBarScreenshot } from './TitleBarScreenshot'; export function TitleBar() { const [isMaximized, setIsMaximized] = useState(false); @@ -67,6 +73,16 @@ export function TitleBar() { {/* Spacer — fills remaining drag area */}
+ {/* Utility buttons — screenshot, health, hub status */} +
+ + + +
+ + {/* Divider between utility buttons and window controls */} + + {/* Window controls — right side */}
+ ); +} From c5ac91c5bcc85917e045de6eb54e45a2cac59095 Mon Sep 17 00:00:00 2001 From: ParkerES Date: Fri, 20 Feb 2026 12:10:35 -0500 Subject: [PATCH 06/11] feat(layout): restructure Sidebar into Personal/Development collapsible sections Replace flat nav list with two collapsible accordion sections using Collapsible/CollapsibleTrigger/CollapsibleContent from @ui. Personal section contains Dashboard through Comms. Development section contains project-scoped items (Tasks through Insights). Add "Add Project" button between sections navigating to /projects. Collapsed sidebar mode preserves icon-only rendering without section headers. Co-Authored-By: Claude Opus 4.6 --- src/renderer/app/layouts/Sidebar.tsx | 170 ++++++++++++++++++--------- 1 file changed, 112 insertions(+), 58 deletions(-) diff --git a/src/renderer/app/layouts/Sidebar.tsx b/src/renderer/app/layouts/Sidebar.tsx index 5170cd8..9c4bce3 100644 --- a/src/renderer/app/layouts/Sidebar.tsx +++ b/src/renderer/app/layouts/Sidebar.tsx @@ -1,7 +1,7 @@ /** * Sidebar -- Navigation sidebar * - * Shows nav items for the active project's views. + * Shows nav items grouped into collapsible "Personal" and "Development" sections. * Collapses to icon-only mode. */ @@ -12,6 +12,7 @@ import { Bot, Briefcase, CalendarDays, + ChevronDown, Dumbbell, Globe, GitBranch, @@ -23,6 +24,7 @@ import { Newspaper, PanelLeft, PanelLeftClose, + Plus, ScrollText, Settings, StickyNote, @@ -36,6 +38,8 @@ import { HubConnectionIndicator } from '@renderer/shared/components/HubConnectio import { cn } from '@renderer/shared/lib/utils'; import { useLayoutStore } from '@renderer/shared/stores'; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@ui'; + import { UserMenu } from './UserMenu'; interface NavItem { @@ -51,8 +55,8 @@ const INACTIVE_STYLE = 'text-muted-foreground'; const ACTIVE_STYLE = 'bg-accent text-foreground font-medium'; const COLLAPSED_STYLE = 'justify-center px-0'; -/** Top-level nav items (not project-scoped) */ -const topLevelItems: NavItem[] = [ +/** Personal nav items (not project-scoped) */ +const personalItems: NavItem[] = [ { label: 'Dashboard', icon: Home, path: ROUTES.DASHBOARD }, { label: 'Briefing', icon: Newspaper, path: ROUTES.BRIEFING }, { label: 'My Work', icon: Briefcase, path: ROUTES.MY_WORK }, @@ -64,8 +68,8 @@ const topLevelItems: NavItem[] = [ { label: 'Comms', icon: Globe, path: ROUTES.COMMUNICATIONS }, ]; -/** Project-scoped nav items */ -const projectItems: NavItem[] = [ +/** Development nav items (project-scoped) */ +const developmentItems: NavItem[] = [ { label: 'Tasks', icon: ListTodo, path: PROJECT_VIEWS.TASKS }, { label: 'Terminals', icon: Terminal, path: PROJECT_VIEWS.TERMINALS }, { label: 'Agents', icon: Bot, path: PROJECT_VIEWS.AGENTS }, @@ -96,10 +100,70 @@ export function Sidebar() { void navigate({ to: projectViewPath(activeProjectId, path) }); } + function renderPersonalItem(item: NavItem) { + const isActive = + currentPath === item.path || currentPath.startsWith(`${item.path}/`); + return ( + + ); + } + + function renderDevelopmentItem(item: NavItem) { + const isActive = + activeProjectId !== null && currentPath.endsWith(`/${item.path}`); + return ( + + ); + } + + function renderAddProjectButton() { + return ( + + ); + } + return ( -