feat(desktop): subagents, background work and PRs in the top bar
Demo: https://app.superset.sh/page/subagents-and-background-work-in-the-top-bar-soqk9f (video, plus screenshots with 20 subagents and 20 background processes). The video predates the docking changes below.
Summary
- Running subagents and background shells move out of each chat's composer drawer into the workspace activity panel in the top bar. The panel gathers them from every chat tab and agent terminal, including tabs that aren't mounted.
- Docks when it fits, popover when it doesn't. When the top-right pane has room for the centered chat column plus the card, the card is shown docked under the pane header. The chat stays centered and never moves. When there's no room, the top-bar button opens the same card as a popover at the same spot.
- Hide and show. When the card fits, the top-bar button hides and shows it, and that choice is kept across resizes. The popover's open and close never change it.
- The composer drawer keeps only queued prompts.
- One top-bar button (right pane area layout): it shows the current checkout's PR (icon, number, checks) once the host recognises one, and the diff stats otherwise. The separate PR badge in that toolbar is removed.
- Pull requests in the panel: every PR the host linked to this workspace, current checkout first. Clicking one opens the PR view in the right pane (GitHub with a modifier key). The current PR's row has a ⋯ menu with Commit, Push, Ready for review and Merge.
- Transitions are animated: dock and undock, popover, panel height, and sub-views. Reduced motion turns them off.
Why / Context
The Running section of the drawer only showed the tab in front of you, and a floating card per chat would duplicate the top-bar activity menu that already listed detached terminals. The request was one place, at the top, that is there when there's room, can be hidden, and never pushes the chat aside.
How It Works
- Data: rows come from
terminalAgents.listByWorkspace(useTerminalAgentBindings). Those bindings already carry each chat'sbackgroundTasksand each terminal agent'ssubagents, and they refresh on agent events.collectAgentWorkturns them into subagent rows and shell rows. Running subagents nested under a running subagent fold into a+Nbadge; shells never fold. - Actions:
- Open focuses the chat's pane, switching tabs if needed, then opens the subagent through
chatSubagentStore. - Stop and Move to background create a short-lived
SessionClientfor that session and close it afterwards.
- Open focuses the chat's pane, switching tabs if needed, then opens the subagent through
- Done: a subagent that leaves the live list while its chat session continues is recorded in memory (20 per workspace). Entries are filtered against live rows and the current session when shown, so a closed chat, a new session in the same terminal, or a subagent that comes back doesn't leave stale or duplicate rows.
- Layout:
- New
renderPaneAsideslot in@superset/panes. It renders over the right of the top-right pane's body, below its header, and sets--pane-aside-inseton the pane body. - Chat panes set
insetsAside: true, so they ignore the inset and stay centered. Other panes get the inset as right padding. - The aside measures the pane and docks the card when the pane is at least 816px (chat column) + 2 × 340px (card and gaps) wide. It also renders an invisible anchor at the card's spot, so the popover opens in exactly the same place.
- The panel caps at 480px, scrolls, and has sticky section headers with counts.
- New
- State:
activityPanelStorekeepsfitsand a per-workspacehiddenflag in memory. The popover's open state is local to the button. - Button: it looks active only while the popover is open, not while the card is docked.
aria-expandedandaria-controlsfollow whichever card is showing. - Motion: the docked card stays mounted while it fades and is on its own compositor layer. It is mounted only while it shows or fades out. The popover keeps its content during its exit animation.
AnimatedHeighteases the panel height with a ResizeObserver. Easing is ease-out-quint (240ms) on enter and a quick ease-in (150ms) on exit.
Pull requests
- Workspace PRs come from
pullRequests.historyByWorkspaces. The host appends a row whenever the workspace's branch upstream matches a PR on GitHub (git-watch, push/PR create, 5-min sweep); the current link comes first. - No PR on the checkout: the panel keeps the workspace header with Commit / Push / Create PR and the Changes row. With a PR: it shows the pull requests, and the current PR's row carries the ⋯ menu.
- Merge and ready-for-review live in
usePullRequestActions, shared by the panel's PR row and the classic layout's badge.
Manual QA Checklist
Done in the dev app over CDP, against a real Claude agent running subagents:
- Two subagents launched from a chat show under Subagents with their chat's name and current step. The top-bar badge counts them.
- From another chat tab, the same rows are listed (that chat's tab is unmounted).
- Clicking a row switches back to its tab and opens the subagent transcript.
- Finished subagents move to Done.
- Wide pane: the card is docked without a click, and the chat column stays centered (its center within 1px of the pane's).
- Narrow pane: the card is gone. The button opens and closes a popover at the same top and right offsets as the docked card. Outside click closes it.
- Card shown, then narrow, then popover toggled, then wide again: the card is back. Card hidden, then the same steps: the card stays hidden.
- The button looks active only while the popover is open.
- Transitions sampled per frame: opacity and height ease smoothly. Showing the docked card has no long frames. Other toggles have one 50 to 80ms frame at the start in the dev build, mostly React DevTools and dev-mode React.
- 20 subagents + 20 shells: the panel stays at 480px, scrolls, and headers stay pinned. For this one the rows were injected into the renderer's cached bindings; everything rendering them is real.
- Before/after/older-PR states of the top-bar button and panel (sample PR data injected; this dev workspace has no repo).
- Clicking a PR row opened the real PR #8975 view (summary, diff stats, checks) in the right pane. The row's ⋯ menu shows Commit, Push and Merge.
- Not tested: a production build's frame timing, split layouts with a non-chat top-right pane (terminal and browser get right padding by design), Windows and Linux.
Testing
bun testinapps/desktopforv2-workspace/$workspaceId: 1010 pass. Inpackages/panes: 141 pass. (bun 1.4.3).tsc --noEmitforapps/desktop(no errors in changed files; the fresh worktree has unrelated errors in auth packages) andpackages/panes.biome checkon changed files;bun run check:i18nwith every locale complete.- Added tests:
collectAgentWork(aggregation, nesting, shells under a subagent,endedAt: null),activityPanelStore(cap and dedupe, aside slot ownership, per-workspace hiding),topRightPaneId,chatAgentBridgetask comparison, plus the WorkDrawer tests rewritten for queue-only.
Code review
A separate agent reviewed the diff. I fixed:
- shells under a subagent being folded into its badge;
- Done rows going stale or duplicating (closed chat, new session, re-listed task);
- the aside slot being cleared by the wrong node;
- a missing pending state on Stop / Move to background;
- missing
aria-controlsand aside label; - "Done" without a translation context.
One finding I didn't act on, because it doesn't apply: clicks in the panel focusing the top-right pane. The panel's content is portaled from the tab-bar menu, so React routes its events through the tab bar, not the pane.
A second review covered the PR button and list. Fixed:
- the panel shows the checkout's PR even before the host links it, so it's never empty while the button shows a PR;
- the button's accessible name includes the PR number;
- one draft rule for the button and the rows (draft only while open; drafts keep their checks dot);
- removed the classic badge's dead open-PR path and leftover constants;
- Commit, Push, Merge and Ready for review are reachable from the current PR's row.
Design Decisions
- Host bindings instead of mounted sessions: center tabs aren't kept alive, so a renderer-side list would miss every background tab.
- The chat never moves: the card docks only when it fits beside the centered column. Otherwise it's a popover, so nothing reflows.
- Hidden is per workspace and in memory: it survives resizes and tab switches but not a restart. That avoids a new persisted renderer key.
- Top-right pane, not the focused pane: in a split it stays put when focus moves.
Known Limitations
- Docking needs a 1496px pane, so with the right pane open it usually needs a wide window.
- Non-chat panes in the top-right slot shift their right padding without animation. Animating it would refit terminals every frame.
- The old drawer's "needs approval" marker and "waiting on N subagents" text aren't carried over. Bindings don't include approval state.
- Done lives in memory and a reload clears it. Finished shells aren't kept.
- A subagent whose direct parent has finished shows as top-level.
Summary by cubic
Running subagents, background shells, and pull requests now live in a top-bar workspace activity panel, gathered from every chat tab and agent terminal rather than just the visible one. The panel docks under the top-right pane's header when it fits and keeps the chat centered; when it can't dock (narrow pane, split, no pane) the top-bar button opens it as a popover. The button also hides and shows the docked panel, a choice that persists across resizes, while the popover never changes it. The composer drawer keeps only queued prompts.
- One top-bar button replaces the separate PR badge: it shows the current checkout's PR (icon, number, checks) once the host recognizes one, and the diff stats otherwise.
- The panel lists every PR the host linked to the workspace, current checkout first; clicking opens the PR in the right pane, and each row offers Commit, Push, Ready for review, and Merge. Merge and ready-for-review live in a shared hook beside the classic layout's PR badge.
- Rows open the subagent (switching tabs), stop it, or move it to the background; finished subagents go to an in-memory Done list capped at 20 per workspace. Sticky section headers show counts, and the panel caps at 480px and scrolls.
- Panel show/hide, docking, and popover transitions are animated with reduced-motion support, and the docked panel stays on its own compositor layer.
Known limitations
- Done entries clear on reload, finished shells aren't kept, and the drawer's "needs approval" marker and "waiting on N subagents" text are dropped.
Summary by CodeRabbit
- New Features
- Pin workspace activity in a side panel to view workspace pages, pull requests and checks, running agent work, and completed subagents.
- Open activity items, stop eligible agent work, or move it to the background. Use keyboard navigation and Escape to leave active subviews.
- Access available pull-request actions, including merging and marking a pull request ready.
- Changes
- The Work Drawer now focuses on queued prompts; running-task details and controls are no longer shown there.
- Workspace content adjusts its layout when the activity panel is displayed.