| author | |
| committer | |
| log | c2f8cb7ca1576afd70294b98dfe1de9be17aacae |
| tree | 123db90d8b341c6193b38ca055763a732e897f09 |
| parent | 89ee692bf0436505d008c1d70215e70836eba4e2 |
| signature | Signed by PGP key B5690EEEBB952194 |
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) { |
| 5913 | 5913 | rightPanelAvailable={activeProject !== null} |
| 5914 | 5914 | rightPanelOpen={rightPanelOpen} |
| 5915 | 5915 | 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 | } | |
| 5916 | 5921 | onToggleTerminal={toggleTerminalVisibility} |
| 5917 | 5922 | onToggleRightPanel={toggleRightPanel} |
| 5918 | 5923 | /> |
| ... | ... | @@ -6419,6 +6424,7 @@ function ChatViewContent(props: ChatViewProps) { |
| 6419 | 6424 | browserAvailable={isPreviewSupportedInRuntime()} |
| 6420 | 6425 | diffAvailable={isServerThread && isGitRepo} |
| 6421 | 6426 | filesAvailable={activeProject !== null} |
| 6427 | liveAgentCount={agentPanelModel.liveCount} | |
| 6422 | 6428 | > |
| 6423 | 6429 | {rightPanelContent} |
| 6424 | 6430 | </RightPanelTabs> |
| ... | ... | @@ -6447,6 +6453,7 @@ function ChatViewContent(props: ChatViewProps) { |
| 6447 | 6453 | browserAvailable={isPreviewSupportedInRuntime()} |
| 6448 | 6454 | diffAvailable={isServerThread && isGitRepo} |
| 6449 | 6455 | filesAvailable={activeProject !== null} |
| 6456 | liveAgentCount={agentPanelModel.liveCount} | |
| 6450 | 6457 | > |
| 6451 | 6458 | {rightPanelContent} |
| 6452 | 6459 | </RightPanelTabs> |
apps/web/src/components/RightPanelTabs.tsx+20-1| ... | ... | @@ -48,6 +48,8 @@ interface RightPanelTabsProps { |
| 48 | 48 | browserAvailable: boolean; |
| 49 | 49 | diffAvailable: boolean; |
| 50 | 50 | filesAvailable: boolean; |
| 51 | /** Running + waiting subagents; badges the Agents card in the empty state. */ | |
| 52 | liveAgentCount: number; | |
| 51 | 53 | children: ReactNode; |
| 52 | 54 | } |
| 53 | 55 | |
| ... | ... | @@ -96,6 +98,7 @@ function RightPanelEmptyState(props: { |
| 96 | 98 | browserAvailable: boolean; |
| 97 | 99 | diffAvailable: boolean; |
| 98 | 100 | filesAvailable: boolean; |
| 101 | liveAgentCount: number; | |
| 99 | 102 | }) { |
| 100 | 103 | const actions = [ |
| 101 | 104 | { |
| ... | ... | @@ -105,6 +108,7 @@ function RightPanelEmptyState(props: { |
| 105 | 108 | available: props.browserAvailable, |
| 106 | 109 | disabledReason: SURFACE_DISABLED_REASONS.browser, |
| 107 | 110 | onClick: props.onAddBrowser, |
| 111 | badgeCount: 0, | |
| 108 | 112 | }, |
| 109 | 113 | { |
| 110 | 114 | label: "Terminal", |
| ... | ... | @@ -113,6 +117,7 @@ function RightPanelEmptyState(props: { |
| 113 | 117 | available: true, |
| 114 | 118 | disabledReason: null, |
| 115 | 119 | onClick: props.onAddTerminal, |
| 120 | badgeCount: 0, | |
| 116 | 121 | }, |
| 117 | 122 | { |
| 118 | 123 | label: "Files", |
| ... | ... | @@ -121,6 +126,7 @@ function RightPanelEmptyState(props: { |
| 121 | 126 | available: props.filesAvailable, |
| 122 | 127 | disabledReason: SURFACE_DISABLED_REASONS.files, |
| 123 | 128 | onClick: props.onAddFiles, |
| 129 | badgeCount: 0, | |
| 124 | 130 | }, |
| 125 | 131 | { |
| 126 | 132 | label: "Diff", |
| ... | ... | @@ -129,6 +135,7 @@ function RightPanelEmptyState(props: { |
| 129 | 135 | available: props.diffAvailable, |
| 130 | 136 | disabledReason: SURFACE_DISABLED_REASONS.diff, |
| 131 | 137 | onClick: props.onAddDiff, |
| 138 | badgeCount: 0, | |
| 132 | 139 | }, |
| 133 | 140 | { |
| 134 | 141 | label: "Agents", |
| ... | ... | @@ -137,6 +144,7 @@ function RightPanelEmptyState(props: { |
| 137 | 144 | available: true, |
| 138 | 145 | disabledReason: null, |
| 139 | 146 | onClick: props.onAddAgents, |
| 147 | badgeCount: props.liveAgentCount, | |
| 140 | 148 | }, |
| 141 | 149 | ] as const; |
| 142 | 150 | |
| ... | ... | @@ -154,7 +162,17 @@ function RightPanelEmptyState(props: { |
| 154 | 162 | const Icon = action.icon; |
| 155 | 163 | const content = ( |
| 156 | 164 | <> |
| 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> | |
| 158 | 176 | <span className="text-sm font-medium">{action.label}</span> |
| 159 | 177 | <span className="mt-1 text-xs leading-relaxed text-muted-foreground"> |
| 160 | 178 | {action.description} |
| ... | ... | @@ -498,6 +516,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { |
| 498 | 516 | browserAvailable={props.browserAvailable} |
| 499 | 517 | diffAvailable={props.diffAvailable} |
| 500 | 518 | filesAvailable={props.filesAvailable} |
| 519 | liveAgentCount={props.liveAgentCount} | |
| 501 | 520 | /> |
| 502 | 521 | ) : ( |
| 503 | 522 | props.children |
apps/web/src/components/chat/PanelLayoutControls.tsx+21-2| ... | ... | @@ -11,6 +11,8 @@ interface PanelLayoutControlsProps { |
| 11 | 11 | rightPanelAvailable: boolean; |
| 12 | 12 | rightPanelOpen: boolean; |
| 13 | 13 | rightPanelShortcutLabel: string | null; |
| 14 | /** Running + waiting subagents in this thread; badges the right panel toggle. */ | |
| 15 | liveAgentCount: number; | |
| 14 | 16 | onToggleTerminal: () => void; |
| 15 | 17 | onToggleRightPanel: () => void; |
| 16 | 18 | } |
| ... | ... | @@ -22,6 +24,7 @@ export const PanelLayoutControls = memo(function PanelLayoutControls({ |
| 22 | 24 | rightPanelAvailable, |
| 23 | 25 | rightPanelOpen, |
| 24 | 26 | rightPanelShortcutLabel, |
| 27 | liveAgentCount, | |
| 25 | 28 | onToggleTerminal, |
| 26 | 29 | onToggleRightPanel, |
| 27 | 30 | }: PanelLayoutControlsProps) { |
| ... | ... | @@ -59,18 +62,34 @@ export const PanelLayoutControls = memo(function PanelLayoutControls({ |
| 59 | 62 | className="shrink-0 [-webkit-app-region:no-drag]" |
| 60 | 63 | pressed={rightPanelOpen} |
| 61 | 64 | 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 | } | |
| 63 | 70 | variant="ghost" |
| 64 | 71 | size="sm" |
| 65 | 72 | disabled={!rightPanelAvailable} |
| 66 | 73 | > |
| 67 | 74 | <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} | |
| 68 | 83 | </Toggle> |
| 69 | 84 | } |
| 70 | 85 | /> |
| 71 | 86 | <TooltipPopup side="bottom"> |
| 72 | 87 | {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 | }` | |
| 74 | 93 | : "Right panel is unavailable"} |
| 75 | 94 | </TooltipPopup> |
| 76 | 95 | </Tooltip> |