refactor(tui): consolidate agents overlay — share duration/root helpers via lib

Pull duplicated rules into ui-tui/src/lib/subagentTree so the live overlay,
disk snapshot label, and diff pane all speak one dialect:

- export fmtDuration(seconds) — was a private helper in subagentTree;
  agentsOverlay's local secLabel/fmtDur/fmtElapsedLabel now wrap the same
  core (with UI-only empty-string policy).
- export topLevelSubagents(items) — matches buildSubagentTree's orphan
  semantics (no parent OR parent not in snapshot). Replaces three hand-
  rolled copies across createGatewayEventHandler (disk label), agentsOverlay
  DiffPane, and prior inline filters.

Also collapse agentsOverlay boilerplate:
- replace IIFE title + inner `delta` helper with straight expressions;
- introduce module-level diffMetricLine for replay-diff rows;
- tighten OverlayScrollbar (single thumbColor expression, vBar/thumbBody).

Adds unit coverage for the new exports (fmtDuration + topLevelSubagents).
No behaviour change; 221 tests pass.
This commit is contained in:
Brooklyn Nicholson 2026-04-22 12:10:21 -05:00
parent 9e1f606f7f
commit 5b0741e986
4 changed files with 121 additions and 153 deletions

View file

@ -2,6 +2,7 @@ import { STREAM_BATCH_MS } from '../config/timing.js'
import { buildSetupRequiredSections, SETUP_REQUIRED_TITLE } from '../content/setup.js'
import type { CommandsCatalogResponse, DelegationStatusResponse, GatewayEvent, GatewaySkin } from '../gatewayTypes.js'
import { rpcErrorMessage } from '../lib/rpc.js'
import { topLevelSubagents } from '../lib/subagentTree.js'
import { formatToolCall, stripAnsi } from '../lib/text.js'
import { fromSkin } from '../theme.js'
import type { Msg, SubagentProgress } from '../types.js'
@ -66,20 +67,12 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev:
return min === 0 ? s.startedAt : Math.min(min, s.startedAt)
}, 0)
// Match buildSubagentTree semantics: an agent is top-level if it has
// no parent OR its parent isn't in the snapshot (orphan). Otherwise
// the disk label would fall back to `${N} subagents` for any turn
// whose roots got pruned mid-flight.
const ids = new Set(subagents.map(s => s.id))
const top = subagents.filter(s => !s.parentId || !ids.has(s.parentId)).slice(0, 2)
const top = topLevelSubagents(subagents)
.map(s => s.goal)
.filter(Boolean)
.slice(0, 2)
const label = top.length
? top
.map(s => s.goal)
.filter(Boolean)
.slice(0, 2)
.join(' · ')
: `${subagents.length} subagents`
const label = top.length ? top.join(' · ') : `${subagents.length} subagents`
await rpc('spawn_tree.save', {
finished_at: Date.now() / 1000,