feat(desktop): open and close coding agent and subagent tabs from the side panel's +

Merged
DA
8d ago
+515-91

Summary

Dean: "I also want coding agent/subagent tabs to be closable and openable from the + icon. They don't need keyboard shortcuts."

Built on #1950 (the searchable "+" menu, merged).

Not in this PR: "New coding agent". An earlier revision added a "+" row that ran Start's press. Start hands the agent the plan as its first message, and Dean wants a new agent from the "+" to start on the developer's own first message instead. That needs service support, which doesn't exist yet. Per the agreed split, it comes in two follow-ups: a service PR that starts an agent on a developer's task, then a desktop PR stacked on it that adds the "+" row and a draft agent tab. Start and its behaviour are untouched.

  • The "+" list gains two sections, both searchable (a query also matches a section's name, so "subagents" lists them all):
    • Coding agents: each of the plan's agents in the order started. Each row shows the status dot its tab wears, its model name, and where it stands (Running / Starting shimmer, Done, Failed, Stopped). Opening the "+" re-reads the plan's agents, the way Start re-reads its models when its menu opens. Otherwise an agent whose tab isn't shown would keep its last-read status, since nothing follows it.
    • Subagents: each subagent the plan's work holds, with the bot glyph (lit while it runs), its job, and Running (shimmer) / Done / Stopped.
  • Convention for open tabs: an agent's or subagent's row whose tab is already open says Open at its end but is not dimmed. Choosing it just switches to that tab. Only single-instance fixed kinds are dimmed when open, per #1902.
  • Coding agent tabs are closable with Ɨ and Delete, like subagent tabs (#1945). Closing touches only the panel's state:
    • The agent is never stopped or deleted.
    • The "+" still lists it as it stands.
    • Choosing it there opens the tab again in its place among the agents (agents keep their start order).
    • Closing the chosen agent tab picks the open agent after it, else the one before. With no other agent open, it picks the last fixed tab, else the first subagent tab. The existing fixed and subagent rules are unchanged, apart from counting only open agents.
  • No keyboard shortcuts for any of these rows or tabs.
  • The "+" reads "Every tab is open" and is dimmed only when no row can be picked: every kind open and no agent or subagent to offer.

Persistence: what I chose

  • Subagent tabs: unchanged. They are kept nowhere and a launch opens none, as #1945 decided.
  • Agent tabs: the panel already kept all agent tabs implicitly, since every agent's tab always stood. I now keep the closed agents in the panel's localStorage preference (closedAgents, optional, so earlier saved state still reads). A tab you closed stays closed across a restart, and an agent started since opens its tab as before. If closed tabs came back on every launch, closing them would be pointless. The list holds every plan's agents, so it is capped at the latest 500 closed. This is one optional schema field plus the cap.

Evidence

  • Platform-independent checks: ./scripts/check.sh exits 0.
  • macOS Electron verification (./scripts/verify.sh): not run. CI could not verify this UI change. CI builds nothing for the Mac and this was done on a Linux VM; LUKE-159 is the manual Mac pass.

Visual evidence

Headless Chrome render of the real DesktopPlans page and side panel (stub controls, the renderer's built stylesheet). The plan has three agents: Claude running with its tab chosen, GPT 5.6 Sol done, and GPT 6 Astra stopped with its tab closed. Its work holds two subagents, one with its tab open.

The "+" list. Open tabs say Open. The closed agent doesn't, but is still listed with where it stands. The list scrolls past its bound, as the other pickers do:

DarkLight

Searched "agent", showing both sections and the Subagents rows (Running shimmers, and the running subagent's glyph is lit):

DarkLight

Physical-device evidence

  • Screenshot or screen recording: not attached (no Mac in this workspace)

Tests

Each new test fails on main (#1950) and passes here:

  • desktop-plans.test.ts, on the real page with the real side-panel hook and a coding-agents control:
    • The "+" lists Coding agents and Subagents in labelled sections. Each row has its tab's glyph and where it stands, and a running agent's state shimmers. Open tabs say Open and are not dimmed. Searching "sol" and pressing Enter chooses the open agent's tab without doubling it. Searching "subagents" (the section's name) finds the subagent, and Enter opens its tab at the strip's end, after which its row says Open.
    • An agent tab's Ɨ closes it, and the agent after it is chosen. No Stop is asked for. The "+" lists the agent still Running and not Open, and picking it reopens the tab in its place. Delete on another agent tab closes that one and leaves the chosen tab. Still no Stop.
    • Opening the "+" re-reads the plan's agents. An agent that finished with its tab closed reads Done, not Running.
  • use-side-panel.test.ts: closing agent tabs picks the open agent after, then before, then the last fixed tab, and leaves the panel open. Closing a tab that isn't chosen leaves the chosen one. Closed agents stay closed across a launch while an agent added since stands open, and choosing a closed agent's tab reopens it.
  • Updated:
    • The #1945 agent-tab test now expects an Ɨ (it asserted none).
    • #1950's "+" tests use the new placeholder.

Test count: +4.

Invariants

  • ./scripts/check.sh green; verify.sh not run (Linux VM; see above).
  • JSON Schema goldens unchanged.
  • Every behavior change has, in this PR, the test that fails without it; no change-detector test added.
  • Gateway envelope goldens unchanged.
  • No new as type assertion outside the allowlisted files.
  • No Effect.runPromise/runSync/runFork outside the runtime edges.
  • No new setTimeout/setInterval/new Promise/AbortController/fs.watch.
  • No Effect, Stream, or Layer written as a statement and dropped.
  • Test count delta listed above.
  • No hand-rolled file replaced.
  • No CLAUDE.md/AGENTS.md sentence names a changed part.
  • PRIVACY.md unchanged. (The new localStorage field holds agent ids only, in the renderer's own storage, like the chosen agent tab already kept there.)
  • No dependency changes.
  • No barrel/door change.

Notes

  • Follow-ups (agreed split): a service PR to start an agent on a developer's task instead of the plan, then the desktop "+" New coding agent row and draft tab stacked on it.

šŸ¤– Generated with Claude Code