authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-08-08 19:20:06-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-08 16:20:06-07:00
logc2f8cb7ca1576afd70294b98dfe1de9be17aacae
tree123db90d8b341c6193b38ca055763a732e897f09
parent89ee692bf0436505d008c1d70215e70836eba4e2
signature Signed by PGP key B5690EEEBB952194

feat(web): show how many subagents are running at a glance (#5745)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

3 files changed, 48 insertions(+), 3 deletions(-)

apps/web/src/components/ChatView.tsx+7
......@@ -5913,6 +5913,11 @@ function ChatViewContent(props: ChatViewProps) {
59135913 rightPanelAvailable={activeProject !== null}
59145914 rightPanelOpen={rightPanelOpen}
59155915 rightPanelShortcutLabel={shortcutLabelForCommand(keybindings, "rightPanel.toggle")}
5916 // Suppressed while the Agents surface is visible: the roster itself is
5917 // on screen, so the toggle badge would be pointing at nothing.
5918 liveAgentCount={
5919 rightPanelOpen && activeRightPanelSurface?.kind === "agents" ? 0 : agentPanelModel.liveCount
5920 }
59165921 onToggleTerminal={toggleTerminalVisibility}
59175922 onToggleRightPanel={toggleRightPanel}
59185923 />
......@@ -6419,6 +6424,7 @@ function ChatViewContent(props: ChatViewProps) {
64196424 browserAvailable={isPreviewSupportedInRuntime()}
64206425 diffAvailable={isServerThread && isGitRepo}
64216426 filesAvailable={activeProject !== null}
6427 liveAgentCount={agentPanelModel.liveCount}
64226428 >
64236429 {rightPanelContent}
64246430 </RightPanelTabs>
......@@ -6447,6 +6453,7 @@ function ChatViewContent(props: ChatViewProps) {
64476453 browserAvailable={isPreviewSupportedInRuntime()}
64486454 diffAvailable={isServerThread && isGitRepo}
64496455 filesAvailable={activeProject !== null}
6456 liveAgentCount={agentPanelModel.liveCount}
64506457 >
64516458 {rightPanelContent}
64526459 </RightPanelTabs>
apps/web/src/components/RightPanelTabs.tsx+20-1
......@@ -48,6 +48,8 @@ interface RightPanelTabsProps {
4848 browserAvailable: boolean;
4949 diffAvailable: boolean;
5050 filesAvailable: boolean;
51 /** Running + waiting subagents; badges the Agents card in the empty state. */
52 liveAgentCount: number;
5153 children: ReactNode;
5254}
5355
......@@ -96,6 +98,7 @@ function RightPanelEmptyState(props: {
9698 browserAvailable: boolean;
9799 diffAvailable: boolean;
98100 filesAvailable: boolean;
101 liveAgentCount: number;
99102}) {
100103 const actions = [
101104 {
......@@ -105,6 +108,7 @@ function RightPanelEmptyState(props: {
105108 available: props.browserAvailable,
106109 disabledReason: SURFACE_DISABLED_REASONS.browser,
107110 onClick: props.onAddBrowser,
111 badgeCount: 0,
108112 },
109113 {
110114 label: "Terminal",
......@@ -113,6 +117,7 @@ function RightPanelEmptyState(props: {
113117 available: true,
114118 disabledReason: null,
115119 onClick: props.onAddTerminal,
120 badgeCount: 0,
116121 },
117122 {
118123 label: "Files",
......@@ -121,6 +126,7 @@ function RightPanelEmptyState(props: {
121126 available: props.filesAvailable,
122127 disabledReason: SURFACE_DISABLED_REASONS.files,
123128 onClick: props.onAddFiles,
129 badgeCount: 0,
124130 },
125131 {
126132 label: "Diff",
......@@ -129,6 +135,7 @@ function RightPanelEmptyState(props: {
129135 available: props.diffAvailable,
130136 disabledReason: SURFACE_DISABLED_REASONS.diff,
131137 onClick: props.onAddDiff,
138 badgeCount: 0,
132139 },
133140 {
134141 label: "Agents",
......@@ -137,6 +144,7 @@ function RightPanelEmptyState(props: {
137144 available: true,
138145 disabledReason: null,
139146 onClick: props.onAddAgents,
147 badgeCount: props.liveAgentCount,
140148 },
141149 ] as const;
142150
......@@ -154,7 +162,17 @@ function RightPanelEmptyState(props: {
154162 const Icon = action.icon;
155163 const content = (
156164 <>
157 <Icon className="mb-3 size-5" />
165 <span className="relative mb-3 inline-flex">
166 <Icon className="size-5" />
167 {action.badgeCount > 0 ? (
168 <span
169 aria-hidden
170 className="absolute -top-1.5 -right-2 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-info px-1 text-[9px] font-semibold tabular-nums text-white"
171 >
172 {action.badgeCount}
173 </span>
174 ) : null}
175 </span>
158176 <span className="text-sm font-medium">{action.label}</span>
159177 <span className="mt-1 text-xs leading-relaxed text-muted-foreground">
160178 {action.description}
......@@ -498,6 +516,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) {
498516 browserAvailable={props.browserAvailable}
499517 diffAvailable={props.diffAvailable}
500518 filesAvailable={props.filesAvailable}
519 liveAgentCount={props.liveAgentCount}
501520 />
502521 ) : (
503522 props.children
apps/web/src/components/chat/PanelLayoutControls.tsx+21-2
......@@ -11,6 +11,8 @@ interface PanelLayoutControlsProps {
1111 rightPanelAvailable: boolean;
1212 rightPanelOpen: boolean;
1313 rightPanelShortcutLabel: string | null;
14 /** Running + waiting subagents in this thread; badges the right panel toggle. */
15 liveAgentCount: number;
1416 onToggleTerminal: () => void;
1517 onToggleRightPanel: () => void;
1618}
......@@ -22,6 +24,7 @@ export const PanelLayoutControls = memo(function PanelLayoutControls({
2224 rightPanelAvailable,
2325 rightPanelOpen,
2426 rightPanelShortcutLabel,
27 liveAgentCount,
2528 onToggleTerminal,
2629 onToggleRightPanel,
2730}: PanelLayoutControlsProps) {
......@@ -59,18 +62,34 @@ export const PanelLayoutControls = memo(function PanelLayoutControls({
5962 className="shrink-0 [-webkit-app-region:no-drag]"
6063 pressed={rightPanelOpen}
6164 onPressedChange={onToggleRightPanel}
62 aria-label="Toggle right panel"
65 aria-label={
66 liveAgentCount > 0
67 ? `Toggle right panel, ${liveAgentCount} ${liveAgentCount === 1 ? "agent" : "agents"} working`
68 : "Toggle right panel"
69 }
6370 variant="ghost"
6471 size="sm"
6572 disabled={!rightPanelAvailable}
6673 >
6774 <PanelRightIcon className="size-3.5" />
75 {liveAgentCount > 0 ? (
76 <span
77 aria-hidden
78 className="absolute -top-1 -right-1 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-info px-1 text-[9px] font-semibold tabular-nums text-white"
79 >
80 {liveAgentCount}
81 </span>
82 ) : null}
6883 </Toggle>
6984 }
7085 />
7186 <TooltipPopup side="bottom">
7287 {rightPanelAvailable
73 ? `Toggle right panel${rightPanelShortcutLabel ? ` (${rightPanelShortcutLabel})` : ""}`
88 ? `Toggle right panel${rightPanelShortcutLabel ? ` (${rightPanelShortcutLabel})` : ""}${
89 liveAgentCount > 0
90 ? ` · ${liveAgentCount} ${liveAgentCount === 1 ? "agent" : "agents"} working`
91 : ""
92 }`
7493 : "Right panel is unavailable"}
7594 </TooltipPopup>
7695 </Tooltip>