From 0cb87d8b662af7d838b537605f066a895f336316 Mon Sep 17 00:00:00 2001 From: David Kaya Date: Sat, 21 Mar 2026 21:34:51 +0100 Subject: [PATCH] feat: redesign sidebar with richer session cards and visual polish - Session cards now show orchestration mode icon (colored per type: indigo=single, amber=sequential, emerald=concurrent, sky=handoff, violet=group-chat) giving at-a-glance pattern visibility - Add relative timestamps (just now, 2m ago, yesterday, etc.) - Running sessions show animated pulse on left accent bar and text status label instead of static dot - Multi-agent sessions display agent count badge (Users icon + count) - Error sessions show explicit red Error label - Project headers show running session count as live badge and total count as pill; folder icon highlights on hover - Upgrade Kopaya logo from letter badge to gradient icon with Sparkles, add BETA tag - Move New Session button to prominent dashed-border CTA below header - Better empty state with composite folder+plus icon treatment and indigo Add Project button - Add sidebar-pulse CSS keyframe animation for running indicators Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/renderer/components/Sidebar.tsx | 258 ++++++++++++++++++++++------ src/renderer/styles.css | 10 ++ 2 files changed, 215 insertions(+), 53 deletions(-) diff --git a/src/renderer/components/Sidebar.tsx b/src/renderer/components/Sidebar.tsx index 40e61c5..0802efa 100644 --- a/src/renderer/components/Sidebar.tsx +++ b/src/renderer/components/Sidebar.tsx @@ -1,13 +1,21 @@ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { + ArrowLeftRight, ChevronDown, ChevronRight, FolderOpen, + GitFork, + ListOrdered, + Lock, MessageSquare, Plus, Settings, + Sparkles, + Users, + type LucideIcon, } from 'lucide-react'; +import type { OrchestrationMode, PatternDefinition } from '@shared/domain/pattern'; import type { ProjectRecord } from '@shared/domain/project'; import type { SessionRecord } from '@shared/domain/session'; import type { WorkspaceState } from '@shared/domain/workspace'; @@ -21,66 +29,191 @@ interface SidebarProps { onOpenSettings: () => void; } -function statusDot(status: SessionRecord['status']) { - if (status === 'running') return 'bg-blue-400'; - if (status === 'error') return 'bg-red-400'; - return 'bg-zinc-600'; +/* ── Mode icon + accent colour mapping ─────────────────────── */ + +const modeVisuals: Record = { + single: { icon: MessageSquare, color: 'text-indigo-400' }, + sequential: { icon: ListOrdered, color: 'text-amber-400' }, + concurrent: { icon: GitFork, color: 'text-emerald-400' }, + handoff: { icon: ArrowLeftRight, color: 'text-sky-400' }, + 'group-chat': { icon: Users, color: 'text-violet-400' }, + magentic: { icon: Lock, color: 'text-zinc-500' }, +}; + +/* ── Relative time helper ──────────────────────────────────── */ + +function relativeTime(iso: string): string { + const diff = Date.now() - new Date(iso).getTime(); + const secs = Math.floor(diff / 1000); + if (secs < 60) return 'just now'; + const mins = Math.floor(secs / 60); + if (mins < 60) return `${mins}m ago`; + const hrs = Math.floor(mins / 60); + if (hrs < 24) return `${hrs}h ago`; + const days = Math.floor(hrs / 24); + if (days === 1) return 'yesterday'; + if (days < 7) return `${days}d ago`; + return new Date(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }); } +/* ── Session item ──────────────────────────────────────────── */ + +function SessionItem({ + session, + pattern, + isActive, + onSelect, +}: { + session: SessionRecord; + pattern?: PatternDefinition; + isActive: boolean; + onSelect: () => void; +}) { + const isRunning = session.status === 'running'; + const isError = session.status === 'error'; + const mode = pattern?.mode ?? 'single'; + const visual = modeVisuals[mode]; + const ModeIcon = visual.icon; + const agentCount = pattern?.agents.length ?? 1; + + return ( + + ); +} + +/* ── Project group ─────────────────────────────────────────── */ + function ProjectGroup({ project, sessions, + patterns, selectedSessionId, onSessionSelect, }: { project: ProjectRecord; sessions: SessionRecord[]; + patterns: PatternDefinition[]; selectedSessionId?: string; onSessionSelect: (sessionId: string) => void; }) { const [expanded, setExpanded] = useState(true); + const patternMap = useMemo(() => { + const map = new Map(); + for (const p of patterns) map.set(p.id, p); + return map; + }, [patterns]); + + const runningCount = sessions.filter((s) => s.status === 'running').length; + return (
{expanded && ( -
+
{sessions.length === 0 ? ( -
No sessions yet
+
+ No sessions yet +
) : ( - sessions.map((session) => { - const isActive = selectedSessionId === session.id; - return ( - - ); - }) + sessions.map((session) => ( + onSessionSelect(session.id)} + pattern={patternMap.get(session.patternId)} + session={session} + /> + )) )}
)} @@ -88,6 +221,8 @@ function ProjectGroup({ ); } +/* ── Sidebar ───────────────────────────────────────────────── */ + export function Sidebar({ workspace, onAddProject, @@ -100,45 +235,61 @@ export function Sidebar({
{/* Header — extra top padding clears the title bar overlay zone */}
-
-
- K +
+
+ +
+
+ kopaya + + BETA +
- kopaya
-
+ {/* New session CTA */} +
+ +
+ {/* Project + Session Tree */}
{workspace.projects.length === 0 ? ( -
- +
+
+
+ +
+
+ +
+
-

No projects yet

-

- Add a project folder to start orchestrating +

No projects yet

+

+ Add a project folder to start
orchestrating AI agents

)} diff --git a/src/renderer/styles.css b/src/renderer/styles.css index 2b25573..6369813 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -103,6 +103,16 @@ textarea { max-height: 200px; } +/* Sidebar running-session pulse animation */ +@keyframes sidebar-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +.sidebar-pulse { + animation: sidebar-pulse 2s ease-in-out infinite; +} + /* Markdown prose styles for chat messages */ .markdown-content { line-height: 1.7;