Skip to content

fix(workspace): scope Switch Agents and Close Old Agents by project tab - #909

Merged
Juliusolsson05 merged 4 commits into
mainfrom
fix/project-scope-by-tab
Sep 12, 2026
Merged

Juliusolsson05 merged 4 commits into
mainfrom
fix/project-scope-by-tab

Conversation

@Juliusolsson05

Copy link
Copy Markdown
Owner

Problem

The Selected projects scope in Switch Agents (bulk provider switch) and Close Old Agents grouped agents by working directory, keyed by the full path and labelled by the folder name. An agent in .worktrees/grok-package-wiring therefore showed up as a project called "grok-package-wiring", and a project tab with agents in several worktrees was shredded into one entry per directory. There was no way to say "every agent in this project" in one click, which is the whole point of the bulk switch when a provider's window is exhausted.

This was never a product decision. Both modals were written in June, before Grid Dispatch rows had project bindings, when the tab title was the only project label and was not unique.

Implemented behavior

  • Both modals list projects the way the Dispatch index does: one entry per project tab, labelled with the tab letter and title (A · agent-code, E · agent-code), in tab order.
  • Each entry shows the distinct working directories it contains as its secondary line, so worktrees stay visible as directories inside the project rather than as projects.
  • Membership is by tab: selecting a project selects its worktree agents too. Close Old Agents keeps its matching/total count per tab.
  • The agent rows' secondary line uses the same letter vocabulary (E · agent-code · /path) instead of tab 5.
  • The project filter matches the label, the title and the directory names, so typing a worktree name still finds the project that holds it.

Design decisions

  • One shared derivation (features/workspace/lib/projectScope.ts) owns grouping, labelling, filtering and membership for both modals, replacing two copy-pasted ProjectRow implementations. Labels come from the same tabIndexLabel the Dispatch row filter uses, so no third vocabulary is introduced.
  • Ordering follows tab order rather than the previous count-first sort, because the list is meant to read like the Dispatch index.

Linked issues

Fixes #908

Tests and verification

  • projectScope.test.ts (new): two agents in one tab across different worktrees produce one entry with both directories; same-titled tabs get distinct letters and keep tab order regardless of counts; matching counts only the eligible rows; the filter finds a project by its worktree name; membership filters by tab.
  • Existing CloseOldAgentsModal.rows, BulkProviderSwitchModal.policy and closedModalDerivations renderer suites pass unchanged.
  • tsc -b clean on both projects under Node 24; npm run test:contract satisfied.

Known limitations

  • Not exercised in the running app; verified from source and tests.

🤖 Generated with Claude Code

https://claude.ai/code/session_016vtjk6o4nukQp7Vfbt5TDc

Juliusolsson05 and others added 4 commits September 11, 2026 19:48
…e Old Agents

Refs #908

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vtjk6o4nukQp7Vfbt5TDc
Both scope pickers grouped agents by working directory, a fallback from
before Grid Dispatch gave every tab a stable letter plus title. That split
each worktree into a pseudo-project, so "everyone in this project" was not
selectable exactly when a bulk switch is needed. One shared derivation now
lists tabs in Dispatch order with the Dispatch label, keeps worktrees visible
as directories inside the project, and owns the membership rule for both
modals.

Refs #908

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vtjk6o4nukQp7Vfbt5TDc
Rename the bulk toggle's stale parameter, count only tabs that still exist
in "N selected", drop the redundant title clause from the project filter,
import the basename helper from its owner, make the ordering test decide by
population rather than a tie, and drive the selected-scope path through the
real modal so the checkbox-to-preview wiring that carried #908 has a
component-level regression test.

Refs #908

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vtjk6o4nukQp7Vfbt5TDc
@Juliusolsson05

Copy link
Copy Markdown
Owner Author

Review pass (one orchestrated Claude Code reviewer, read-only, from the fix worktree): APPROVE WITH COMMENTS, no blocking findings. It verified selection consistency in both modals, the per-kill revalidation in Close Old Agents, the Dispatch label vocabulary, the sort-change consumers, and ran tsc -b, the unit and renderer suites and the test contract.

Its six non-blocking comments are all taken in the follow-up commit: stale cwd parameter name in the bulk toggle; N selected now counts only tabs that still exist; redundant title clause dropped from the filter; cwdBasename imported from its owner instead of through the provider registry; the ordering test now has a later tab with the most agents so it cannot pass on a tie; and a new renderer test (BulkProviderSwitchModal.scope.renderer.test.tsx) drives "Selected projects" through the real modal: two entries named A · agent-code and B · startup, ticking the tab brings its detached worktree agent into the preview.

@Juliusolsson05
Juliusolsson05 merged commit 1a82ca6 into main Sep 12, 2026
3 of 4 checks passed
@Juliusolsson05
Juliusolsson05 deleted the fix/project-scope-by-tab branch September 12, 2026 03:13
Juliusolsson05 added a commit that referenced this pull request Sep 12, 2026
…ose-safety

Brings the agent-close safety work (#153, #886) onto current main.

The only textual conflict was the CloseOldAgentsModal import block: main
(#908/#909) added the tab-keyed project scope helpers and tabIndexLabel,
while this branch added sessionDisplayTitle for per-agent row titles. Both
are kept. The per-kill onlyIf revalidation reuses filterMatchingRows, which
main now routes through rowsInSelectedProjects, so the bulk-close grant is
re-checked against the tab-id project scope main introduced rather than the
old cwd scope.

pane.ts, undoClose.ts and hook/index.ts merged cleanly: main's changes there
(MCP overrides, TLDR identity, provider runtime for orchestration children,
extracted transcript hydration) do not overlap the close paths.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S4AoJJUBex14FAEZZfHPsV
Juliusolsson05 added a commit that referenced this pull request Sep 12, 2026
Main's #908/#909 re-keyed the Close Old Agents project picker from working
directory to project tab and labels each row like the Dispatch index
(`A · Project`). This branch's #886 regression test still located the
checkbox by the retired cwd label (`project /project`), so it failed after
the merge even though the behavior it guards is unchanged: nothing closes
until a project is ticked, ticking it admits exactly that project's agents,
and an invalid threshold closes nothing.

The matcher now derives the label from projectScopeLabel, the picker's own
source of truth, so the test follows the vocabulary rather than a string
snapshot of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S4AoJJUBex14FAEZZfHPsV
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(workspace): Switch Agents and Close Old Agents list projects by working directory, so worktree agents become their own pseudo-projects

1 participant