Repository navigation
Tab header activity counter misses dispatch agents — only walks grid tree (another grid-vs-dispatch divergence) #104
Description
What happened
The active/total counter rendered in each tab's header in `TabBar` (`src/renderer/src/workspace/tile-tree/TabBar.tsx:58-66`) computes its numbers from `collectSessionIds(tab.root)` — i.e. it walks only the visible tile-tree. Detached Dispatch agents owned by the tab are not counted. If a tab has 2 grid panes and 8 dispatched agents active, the counter shows "2 alive / 2 total" instead of the actual 10. The visual cue for "what's happening in this tab" silently lies for every user who works in Dispatch Mode.
This is another instance of the same recurring grid-vs-dispatch divergence captured in #94 — each surface independently decides how to walk "sessions in this tab," and the ones that forget about `detachedDispatchSessionIdsForTab` get it wrong.
Repro
- Open a tab.
- Spawn 2 grid panes (normal split).
- Enter Dispatch Mode and start 5 detached agents in this tab.
- Look at the tab's header counter — shows 2 / 2. Should show 7 / 7 (or whatever subset is currently `sessionStatus === 'running'`).
Where
- `src/renderer/src/workspace/tile-tree/TabBar.tsx:58-66` — the offending derivation:
```ts
const sessionIds = collectSessionIds(tab.root)
const total = sessionIds.length
const alive = sessionIds.filter(id => runtimes[id]?.sessionStatus === 'running').length
``` - `src/renderer/src/workspace/dispatch/dispatchSelectors.ts` — exposes `detachedDispatchSessionIdsForTab(state, tab.id)` which is the missing piece.
Output
N/A — UI misreports state, no error.
State
Reproducible any time a tab has detached/dispatch agents. Worse the more Dispatch Mode is used.
Prior PRs that tried to fix symptoms in this exact bug class
Same root pattern surfaced repeatedly across the dispatch-vs-grid split:
- feat(dispatch): detached sessions — stop dispatch from poisoning grid layout #37 — `feat(dispatch): detached sessions — stop dispatch from poisoning grid layout` (the original split that introduced the asymmetry)
- fix(dispatch): two #37 follow-ups — undo loses detached agents; rehydrate stale focus #39 — `fix(dispatch): two feat(dispatch): detached sessions — stop dispatch from poisoning grid layout #37 follow-ups — undo loses detached agents; rehydrate stale focus`
- fix(dispatch): close selected dispatch agent #44 — `fix(dispatch): close selected dispatch agent`
- fix(dispatch): close selected dispatch row #45 — `fix(dispatch): close selected dispatch row`
- fix(dispatch): row-derive selection across close, nav, attach, and jump #46 — `fix(dispatch): row-derive selection across close, nav, attach, and jump`
- feat(dispatch): attach all detached agents for tab #58 — `feat(dispatch): attach all detached agents for tab`
- fix(dispatch): make reader and spotlight target visible row #59 — `fix(dispatch): make reader and spotlight target visible row`
- feat(dispatch): attach-all in grid mode + project terminal setting #69 — `feat(dispatch): attach-all in grid mode + project terminal setting`
- Include detached Dispatch agents in active-tab transcripts template #83 — `Include detached Dispatch agents in active-tab transcripts template` (most recent, today)
Nine PRs (this would be the tenth) all chasing the same shape: a surface that asks "which sessions belong to this tab?" and forgets to ask the detached side. Patching one surface at a time hasn't stopped the next surface from re-tripping.
Sketch
Two paths:
-
Spot fix (cheap, doesn't address the class): swap `collectSessionIds(tab.root)` for the union with `detachedDispatchSessionIdsForTab(state, tab.id)`. Direct mirror of the PR Include detached Dispatch agents in active-tab transcripts template #83 pattern. Probably ~5 lines.
-
Right fix (addresses the class): consume the canonical resolver proposed in Commands target grid-only sessions — dispatch agents recurringly fall through every command path #94 (`resolveTabSessions(workspace, tabId): SessionId[]`) once it exists. The TabBar becomes one of the migration call sites for that audit.
Recommend (1) as an immediate patch since the bug is user-visible and the resolver from #94 doesn't exist yet — then (2) folds this site into the resolver migration when #94 lands. Mark this issue as resolved-by-#94 after the spot fix ships, so the consolidation work picks it up cleanly.
Related: #94 (the audit + canonical-resolver work).
Activity