From 17cf96bfeb973bf553b9136a512bbb70934ff775 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:13:03 +1000 Subject: [PATCH 1/5] fix(web): show which right-panel surface the agent badge belongs to The live-agent count on the right-panel toggle said something was happening but, with another surface already open, nothing inside the panel identified what. The count now also appears on the Agents tab and on the Agents item in the add-surface menu, matching the existing empty-state card and toggle suppression rules. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- apps/web/src/components/RightPanelTabs.tsx | 72 +++++++++++++++++++--- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/apps/web/src/components/RightPanelTabs.tsx b/apps/web/src/components/RightPanelTabs.tsx index 001342e48262..26f5f991d21a 100644 --- a/apps/web/src/components/RightPanelTabs.tsx +++ b/apps/web/src/components/RightPanelTabs.tsx @@ -132,7 +132,11 @@ interface RightPanelTabsProps { agentsAvailable: boolean; deviceAvailable: boolean; pullRequestStatusSeeds?: Readonly>; - /** Running + waiting subagents; badges the Agents card in the empty state. */ + /** + * Running + waiting subagents; badges the Agents tab, the Agents item in the + * add-surface menu, and the Agents card in the empty state so the count on + * the panel toggle is traceable to the surface it belongs to. + */ liveAgentCount: number; children: ReactNode; } @@ -277,6 +281,24 @@ export function surfaceShortcutTargetsTypingContext( ); } +function agentsWorkingLabel(count: number): string { + return `${count} ${count === 1 ? "agent" : "agents"} working`; +} + +function AgentCountPill({ count, className }: { count: number; className?: string }) { + return ( + + {count} + + ); +} + function DisabledReasonTooltip(props: { reason: string; trigger: ReactElement }) { return ( @@ -291,6 +313,7 @@ function SurfaceMenuItem(props: { disabledReason?: string; shortcut: string; onClick: () => void; + "aria-label"?: string | undefined; children: ReactNode; }) { const item = ( @@ -299,6 +322,7 @@ function SurfaceMenuItem(props: { onClick={props.onClick} disabled={!props.available} aria-keyshortcuts={props.shortcut} + aria-label={props["aria-label"]} > {props.children} {props.shortcut} @@ -486,12 +510,7 @@ function RightPanelEmptyState(props: { {action.badgeCount > 0 ? ( - - {action.badgeCount} - + ) : null} ); @@ -533,6 +552,11 @@ function RightPanelEmptyState(props: { } /> @@ -1335,10 +1385,18 @@ export function RightPanelTabs(props: RightPanelTabsProps) { available={action.available} disabledReason={action.disabledReason} shortcut={action.shortcut} + aria-label={ + action.badgeCount > 0 + ? `${action.label}, ${agentsWorkingLabel(action.badgeCount)}` + : undefined + } onClick={action.onClick} > {action.label} + {action.badgeCount > 0 ? ( + + ) : null} ); })} From d67039902e0541f6d2b40475db2cda3e20bb5aeb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 20 Sep 2026 13:35:11 +1000 Subject: [PATCH 2/5] fix(web): reuse shared badge for agent counts --- apps/web/src/components/RightPanelTabs.tsx | 12 ++++-------- apps/web/src/components/ui/badge.tsx | 2 ++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/web/src/components/RightPanelTabs.tsx b/apps/web/src/components/RightPanelTabs.tsx index 26f5f991d21a..ff73671421bf 100644 --- a/apps/web/src/components/RightPanelTabs.tsx +++ b/apps/web/src/components/RightPanelTabs.tsx @@ -45,6 +45,8 @@ import type { RightPanelSurface } from "~/rightPanelStore"; import { cn } from "~/lib/utils"; import { readLocalApi } from "~/localApi"; import { Button } from "~/components/ui/button"; +// Reuses the local shadcn Badge: https://ui.shadcn.com/docs/components/base/badge +import { Badge } from "~/components/ui/badge"; import { AndroidIcon, AppleIcon } from "~/components/Icons"; import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip"; import { Kbd } from "~/components/ui/kbd"; @@ -287,15 +289,9 @@ function agentsWorkingLabel(count: number): string { function AgentCountPill({ count, className }: { count: number; className?: string }) { return ( - + {count} - + ); } diff --git a/apps/web/src/components/ui/badge.tsx b/apps/web/src/components/ui/badge.tsx index db688707fb91..d60b5ae5e2bc 100644 --- a/apps/web/src/components/ui/badge.tsx +++ b/apps/web/src/components/ui/badge.tsx @@ -15,6 +15,7 @@ const badgeVariants = cva( }, variants: { size: { + count: "h-3.5 min-w-3.5 rounded-full border-0 px-1 text-[9px] font-semibold tabular-nums", control: "h-7 min-w-7 rounded-[var(--control-radius)] px-[calc(--spacing(2)-1px)] text-sm sm:h-6 sm:min-w-6 sm:text-xs", default: @@ -29,6 +30,7 @@ const badgeVariants = cva( destructive: "bg-destructive text-white [button&,a&]:hover:bg-destructive/90", error: "bg-destructive/8 text-destructive-foreground dark:bg-destructive/16", info: "bg-info/8 text-info-foreground dark:bg-info/16", + infoSolid: "bg-info text-white", outline: "border-input bg-background text-foreground dark:bg-input/32 [button&,a&]:hover:bg-accent/50 dark:[button&,a&]:hover:bg-input/48", secondary: "bg-secondary text-secondary-foreground [button&,a&]:hover:bg-secondary/90", From 6528df372f5ad1503f5a625ac6feb67309758776 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 23:07:34 +1000 Subject: [PATCH 3/5] fix(web): announce the working agent count on the disabled Agents action The disabled empty-state Agents row rendered AgentCountPill but the pill is aria-hidden, so assistive technology heard only "Agents" with no indication that agents were running. Add the same accessible label the enabled row already uses. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../src/components/RightPanelTabs.test.tsx | 53 +++++++++++++++++++ apps/web/src/components/RightPanelTabs.tsx | 5 ++ 2 files changed, 58 insertions(+) diff --git a/apps/web/src/components/RightPanelTabs.test.tsx b/apps/web/src/components/RightPanelTabs.test.tsx index dfaaf171c429..a2e73dd97b4b 100644 --- a/apps/web/src/components/RightPanelTabs.test.tsx +++ b/apps/web/src/components/RightPanelTabs.test.tsx @@ -139,6 +139,59 @@ function renderTabs( ); } +function renderEmptyState(liveAgentCount: number) { + return renderToStaticMarkup( + undefined} + onCloseSurface={() => undefined} + onCloseOtherSurfaces={() => undefined} + onCloseSurfacesToRight={() => undefined} + onCloseAllSurfaces={() => undefined} + onCopyFilePath={() => undefined} + onAddBrowser={() => undefined} + onAddBrowserInProfile={() => undefined} + onAddTerminal={() => undefined} + onAddPullRequest={() => undefined} + onAddPullRequests={() => undefined} + onAddDiff={() => undefined} + onAddFiles={() => undefined} + onAddAgents={() => undefined} + onAddDevice={() => undefined} + liveAgentCount={liveAgentCount} + browserAvailable + terminalAvailable={false} + diffAvailable={false} + filesAvailable={false} + pullRequestAvailable={false} + pullRequestsAvailable={false} + agentsAvailable={false} + deviceAvailable={false} + > +
content
+
, + ); +} + +describe("RightPanelTabs empty-state agent badge", () => { + it("exposes the working agent count on the disabled Agents action", () => { + const html = renderEmptyState(2); + expect(html).toContain('aria-label="Agents, 2 agents working"'); + }); + + it("leaves the label unset when no agents are working", () => { + const html = renderEmptyState(0); + expect(html).not.toContain("agents working"); + }); +}); + describe("RightPanelTabs preview favicon", () => { it("prefers a live capture and never asks Google about a private hostname", () => { const captured = renderTabs(favicon("data:image/png;base64,AAAA", "http://24x.xf.local/")); diff --git a/apps/web/src/components/RightPanelTabs.tsx b/apps/web/src/components/RightPanelTabs.tsx index ff73671421bf..84c746e90fa6 100644 --- a/apps/web/src/components/RightPanelTabs.tsx +++ b/apps/web/src/components/RightPanelTabs.tsx @@ -609,6 +609,11 @@ function RightPanelEmptyState(props: {
0 + ? `${action.label}, ${agentsWorkingLabel(action.badgeCount)}` + : undefined + } className="flex h-8 w-full cursor-default items-center gap-2.5 rounded-(--control-radius) px-2.5 text-left text-sm opacity-50" > {actionIcon(action, "size-4")} From 3045184cb10af5a764022631f19db010f9ced94b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:24:08 +1000 Subject: [PATCH 4/5] test(web): stop asserting aria-label via static-markup render The disabled-Agents aria-label test rendered the whole RightPanelTabs component to static markup and grepped the HTML string for an aria-label attribute, which AGENTS.md rules out ("do not render components to static markup to assert props or attributes"). The label itself is built by the existing pure agentsWorkingLabel helper, so export it and test it directly instead of adding a second render harness. Keeps the accessibility fix from 6528df372f, drops the static-markup assertions and the now-unused renderEmptyState helper. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../src/components/RightPanelTabs.test.tsx | 62 +++---------------- apps/web/src/components/RightPanelTabs.tsx | 2 +- 2 files changed, 10 insertions(+), 54 deletions(-) diff --git a/apps/web/src/components/RightPanelTabs.test.tsx b/apps/web/src/components/RightPanelTabs.test.tsx index a2e73dd97b4b..d88a6daba261 100644 --- a/apps/web/src/components/RightPanelTabs.test.tsx +++ b/apps/web/src/components/RightPanelTabs.test.tsx @@ -5,6 +5,7 @@ import { describe, expect, it } from "vite-plus/test"; import { RightPanelTabs, + agentsWorkingLabel, resolvePullRequestTabLink, shouldOpenDefaultBrowserProfileFromMenuClick, surfaceShortcutActionForKey, @@ -20,6 +21,14 @@ describe("browser profile submenu", () => { }); }); +describe("agentsWorkingLabel", () => { + it("pluralizes the count of agents currently working", () => { + expect(agentsWorkingLabel(1)).toBe("1 agent working"); + expect(agentsWorkingLabel(2)).toBe("2 agents working"); + expect(agentsWorkingLabel(0)).toBe("0 agents working"); + }); +}); + function shortcutEvent( key: string, overrides: Partial[1]> = {}, @@ -139,59 +148,6 @@ function renderTabs( ); } -function renderEmptyState(liveAgentCount: number) { - return renderToStaticMarkup( - undefined} - onCloseSurface={() => undefined} - onCloseOtherSurfaces={() => undefined} - onCloseSurfacesToRight={() => undefined} - onCloseAllSurfaces={() => undefined} - onCopyFilePath={() => undefined} - onAddBrowser={() => undefined} - onAddBrowserInProfile={() => undefined} - onAddTerminal={() => undefined} - onAddPullRequest={() => undefined} - onAddPullRequests={() => undefined} - onAddDiff={() => undefined} - onAddFiles={() => undefined} - onAddAgents={() => undefined} - onAddDevice={() => undefined} - liveAgentCount={liveAgentCount} - browserAvailable - terminalAvailable={false} - diffAvailable={false} - filesAvailable={false} - pullRequestAvailable={false} - pullRequestsAvailable={false} - agentsAvailable={false} - deviceAvailable={false} - > -
content
-
, - ); -} - -describe("RightPanelTabs empty-state agent badge", () => { - it("exposes the working agent count on the disabled Agents action", () => { - const html = renderEmptyState(2); - expect(html).toContain('aria-label="Agents, 2 agents working"'); - }); - - it("leaves the label unset when no agents are working", () => { - const html = renderEmptyState(0); - expect(html).not.toContain("agents working"); - }); -}); - describe("RightPanelTabs preview favicon", () => { it("prefers a live capture and never asks Google about a private hostname", () => { const captured = renderTabs(favicon("data:image/png;base64,AAAA", "http://24x.xf.local/")); diff --git a/apps/web/src/components/RightPanelTabs.tsx b/apps/web/src/components/RightPanelTabs.tsx index 84c746e90fa6..78e3091f8cd1 100644 --- a/apps/web/src/components/RightPanelTabs.tsx +++ b/apps/web/src/components/RightPanelTabs.tsx @@ -283,7 +283,7 @@ export function surfaceShortcutTargetsTypingContext( ); } -function agentsWorkingLabel(count: number): string { +export function agentsWorkingLabel(count: number): string { return `${count} ${count === 1 ? "agent" : "agents"} working`; } From e7c25ebc543c411faa78bf54eb2c65ce58eb6a13 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 27 Sep 2026 09:26:15 +1000 Subject: [PATCH 5/5] fix(web): make agent counts readable and accessible --- apps/web/src/components/RightPanelTabs.tsx | 1 + apps/web/src/components/ui/badge.tsx | 2 +- apps/web/src/index.css | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/RightPanelTabs.tsx b/apps/web/src/components/RightPanelTabs.tsx index 78e3091f8cd1..cb38f5044b43 100644 --- a/apps/web/src/components/RightPanelTabs.tsx +++ b/apps/web/src/components/RightPanelTabs.tsx @@ -607,6 +607,7 @@ function RightPanelEmptyState(props: { reason={action.disabledReason} trigger={