Skip to content

(sidebar): cap subagent lists and build the remainder on demand - #154

Merged
devsuitup merged 1 commit into
mainfrom
feat/subagent-list-cap
Aug 23, 2026
Merged

(sidebar): cap subagent lists and build the remainder on demand#154
devsuitup merged 1 commit into
mainfrom
feat/subagent-list-cap

Conversation

@devsuitup

Copy link
Copy Markdown
Owner

Both subagent lists built every row on every render, then hid most of what they had just built: appendSubagentChildren filled the children container whatever the caret state and set display:none on it, and the project-level orphan bucket appended every orphan behind a CSS collapse. buildSubagentItem produces 7 elements per row, so a project with 1300 children and 1300 orphans built 18 200 elements per render — 99% of them never looked at. Neither the + N older pattern nor the collapsed-group CSS helps here: they save screen space, not construction.

Each list now renders the union of

  • every subagent for which isSubagentActive() holds, and
  • the 10 most recent by modified

and emits a + N more toggle for the rest. The remainder is not built until that toggle is clicked.

The union is deliberate rather than a truncated sort: a long-running subagent whose transcript stopped growing must keep its running dot, and a recency-only cut would silently extinguish it — the exact class of bug this sidebar has been chasing all week.

Clicking the toggle builds the remainder, removes the toggle, and records the list in the expandedSubagentRest localStorage set (p:<parentSessionId>, o:<projectPath>). The next render reads that set before splitting, so what the user expanded survives morphdom — same mechanism as expandedSubagents and orphanExpanded:<projectPath>, GC included. The toggle's payload lives in a module-level map keyed by its id rather than in its closure, because morphdom keeps the old element whenever ids match and a build-time closure would pin the first render's data forever. An active search bypasses the cap, for the same reason the orphan bucket already auto-expands during a search.

Measured on the jsdom harness, 1300 children + 1300 orphans:

before after
elements in the sidebar 18 275 217
.sidebar-subagent rows built 2 600 20
render time 13 177 ms 699 ms

No information is lost silently: the caret still announces the total ("54 subagents") and the toggle says "+ 44 more".

Tests

test/dom-subagent-list-cap.test.js, 11 cases. The load-bearing ones assert that a capped-out row has no element in the document at all, not merely a hidden one. Six mutations were applied and reverted; each failed with its own message:

no cap on children            → exactly SUBAGENT_PREVIEW_COUNT children rendered — 15 !== 10
union reduced to recency      → a running subagent must be rendered however old its transcript is
build-then-hide instead       → 7 failures, incl. every child is rendered after the click — 20 !== 15
no persistence on click       → a re-render must not collapse a remainder the user expanded
no search bypass              → every matched orphan must be rendered during a search
no cap on orphans             → exactly SUBAGENT_PREVIEW_COUNT orphans rendered — 15 !== 10

698 tests, 691 pass, 0 fail, 7 pre-existing skips. ESLint: 0 errors, 265 warnings, unchanged.

Deliberately out of scope

  • No "show less". Once expanded, the key stays in expandedSubagentRest; only the GC removes entries whose session is gone. Symmetry with the orphan bucket's collapse would be a second click, easy but not asked for.
  • A collapsed caret still builds its 10 rows. Another ×10 is available by building nothing until the caret opens, but that moves the laziness into the caret handler and breaks an existing test about the container surviving a re-render.

Rationale in .ai/contexts/subagent-observability.md ("Capped subagent lists").

Both subagent lists built every row on every render and then hid most of
them: appendSubagentChildren filled the children container whatever the
caret state, and the project-level orphan bucket appended all its orphans
behind a CSS collapse. At 1300 children plus 1300 orphans that is 18 200
DOM elements per render, 99% of them never looked at.

Each list now renders the union of the active subagents and the 10 most
recent, and emits a "+ N more" toggle for the rest. The remainder is not
built until that toggle is clicked; the click records the list in the
expandedSubagentRest localStorage set so the expansion survives morphdom.
The union is deliberate: a long-running subagent whose transcript stopped
growing must keep its running dot, which a recency-only cut would put out.
An active search bypasses the cap so no hit hides behind a click.

Same fixture after the change: 217 elements, 20 subagent rows.
@devsuitup
devsuitup merged commit 8e47c2d into main Aug 23, 2026
7 checks passed
@devsuitup
devsuitup deleted the feat/subagent-list-cap branch August 23, 2026 23:00
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.

1 participant