From 9138aa8819e2e1d7f9181aee91f5d2cb1b018491 Mon Sep 17 00:00:00 2001 From: David Kaya Date: Sat, 21 Mar 2026 18:34:05 +0100 Subject: [PATCH] feat: add right-side activity panel for agent status - Create ActivityPanel component showing per-agent status with activity dots, agent names, and contextual status labels - Add detailPanel slot to AppShell layout (right aside, w-64) - Move agent activity display from inline ChatPane card to the dedicated side panel - Remove activity prop and inline panel from ChatPane - Panel appears when a session is selected, hidden on WelcomePane Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/renderer/App.tsx | 11 +++- src/renderer/components/ActivityPanel.tsx | 75 +++++++++++++++++++++++ src/renderer/components/AppShell.tsx | 8 ++- src/renderer/components/ChatPane.tsx | 47 +------------- 4 files changed, 94 insertions(+), 47 deletions(-) create mode 100644 src/renderer/components/ActivityPanel.tsx diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 1823fef..ba48f69 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from 'react'; import { AppShell } from '@renderer/components/AppShell'; +import { ActivityPanel } from '@renderer/components/ActivityPanel'; import { ChatPane } from '@renderer/components/ChatPane'; import { NewSessionModal } from '@renderer/components/NewSessionModal'; import { SettingsPanel } from '@renderer/components/SettingsPanel'; @@ -117,6 +118,7 @@ export default function App() { // Determine main content let content: React.ReactNode; + let detailPanel: React.ReactNode | undefined; if (error) { content = (
@@ -129,13 +131,19 @@ export default function App() { } else if (selectedSession && patternForSession && projectForSession) { content = ( api.sendSessionMessage({ sessionId: selectedSession.id, content: c })} pattern={patternForSession} project={projectForSession} session={selectedSession} /> ); + detailPanel = ( + + ); } else { content = ( buildAgentActivityRows(activity, pattern.agents), + [activity, pattern.agents], + ); + + const isBusy = session.status === 'running'; + + return ( +
+ {/* Header — top padding clears the title bar overlay zone */} +
+
+ + + Activity + + {isBusy && } +
+
+ + {/* Agent list */} +
+
+ {activityRows.map((row) => ( +
+ +
+
+ {row.agentName} +
+
+ {formatAgentActivityLabel(row.activity)} +
+
+
+ ))} +
+ + {activityRows.length === 0 && ( +

+ No agents configured +

+ )} +
+
+ ); +} diff --git a/src/renderer/components/AppShell.tsx b/src/renderer/components/AppShell.tsx index 9ea2eaa..39bc568 100644 --- a/src/renderer/components/AppShell.tsx +++ b/src/renderer/components/AppShell.tsx @@ -3,10 +3,11 @@ import type { ReactNode } from 'react'; interface AppShellProps { sidebar: ReactNode; content: ReactNode; + detailPanel?: ReactNode; overlay?: ReactNode; } -export function AppShell({ sidebar, content, overlay }: AppShellProps) { +export function AppShell({ sidebar, content, detailPanel, overlay }: AppShellProps) { return (
{/* Full-width drag region matching the title bar overlay height */} @@ -16,6 +17,11 @@ export function AppShell({ sidebar, content, overlay }: AppShellProps) { {sidebar}
{content}
+ {detailPanel && ( + + )} {overlay}
); diff --git a/src/renderer/components/ChatPane.tsx b/src/renderer/components/ChatPane.tsx index 5221a96..4fe4bb7 100644 --- a/src/renderer/components/ChatPane.tsx +++ b/src/renderer/components/ChatPane.tsx @@ -1,13 +1,6 @@ -import { type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react'; +import { type KeyboardEvent, useEffect, useRef, useState } from 'react'; import { AlertCircle, ArrowUp, Bot, Loader2, User } from 'lucide-react'; -import { - buildAgentActivityRows, - formatAgentActivityLabel, - isAgentActivityActive, - isAgentActivityCompleted, - type SessionActivityState, -} from '@renderer/lib/sessionActivity'; import type { PatternDefinition } from '@shared/domain/pattern'; import type { ProjectRecord } from '@shared/domain/project'; import type { SessionRecord } from '@shared/domain/session'; @@ -23,25 +16,18 @@ function ThinkingDots() { } interface ChatPaneProps { - activity?: SessionActivityState; project: ProjectRecord; pattern: PatternDefinition; session: SessionRecord; onSend: (content: string) => Promise; } -export function ChatPane({ activity, project, pattern, session, onSend }: ChatPaneProps) { +export function ChatPane({ project, pattern, session, onSend }: ChatPaneProps) { const [input, setInput] = useState(''); const transcriptRef = useRef(null); const textareaRef = useRef(null); const isBusy = session.status === 'running'; - const activityRows = useMemo( - () => buildAgentActivityRows(activity, pattern.agents), - [activity, pattern.agents], - ); - const hasObservedActivity = activityRows.some((row) => !!row.activity); - const showActivityPanel = (isBusy || hasObservedActivity) && activityRows.length > 0; useEffect(() => { transcriptRef.current?.scrollTo({ @@ -139,35 +125,6 @@ export function ChatPane({ activity, project, pattern, session, onSend }: ChatPa ); })}
- - {showActivityPanel && ( -
-
- Agent activity -
-
- {activityRows.map((row) => ( -
- -
-
{row.agentName}
-
- {formatAgentActivityLabel(row.activity)} -
-
-
- ))} -
-
- )} )}