Skip to content

perf/fix: PR#48 observability followup — readdir cache, bootstrap spawns, morphdom id, localStorage GC - #12

Merged
JeanBaptisteRenard merged 3 commits into
mainfrom
perf/pr48-observability-followup
May 23, 2026
Merged

JeanBaptisteRenard merged 3 commits into
mainfrom
perf/pr48-observability-followup

Conversation

@JeanBaptisteRenard

Copy link
Copy Markdown
Collaborator

Summary

Non-blocking improvements from the PR#48 (subagent observability) review.
All 40 tests pass, 0 lint errors.


Fix 1 — session-transitions.js: readdir cache hot path

detectSubagentTransitions() previously called fs.readdirSync() + N × fs.statSync() on every flush. With 1372 subagents this blocked the main thread ~70 ms per flush.

The function now statSyncs the subagents dir first. If the dir mtime hasn't changed since the last scan, no new files can have appeared → skip readdirSync entirely AND skip statSync for unknown-file entries. Known-active entries still get statSync so the stability timer keeps advancing.

Session-local cache: _prevDirMtime, _subFileList stored on the session object (per-session, per-folder). Edge case handled: when the previous scan returned 0 files, we always rescan regardless of mtime (protects against fast writes within the same filesystem-clock tick).


Fix 2 — session-transitions.js: synthetic spawn for live bootstrap files

Cold-start agents with mtime within BOOTSTRAP_LIVE_MS (60 s) are looksAlive. Previously they were recorded silently, meaning the renderer's liveSubagents / activeSubagents Maps never saw a spawned event for them, so any subsequent subagent-completed was a no-op.

Now we emit subagent-spawned with _bootstrap: true so the renderer can set up tracking state. The continue after the bootstrap block ensures mutual exclusivity with the post-bootstrap path.


Fix 3 — public/sidebar.js: orphan group stable id

Assign id = 'orphan-' + fId to the orphan-subagents container element. Without it, morphdom couldn't key the element between renders and rebuilt it from scratch every flush, causing minor flicker on projects with many orphan subagents.


Fix 4 — public/sidebar.js: localStorage expandedSubagents GC

Added _gcExpandedSubagentsOnce() called lazily on the first getExpandedSubagents() invocation. It intersects the stored set with the current sessionMap keys and saves the pruned result. Guarded by a module-level boolean so it runs at most once per page load.


Tests

  • 40/40 pass (up from 38, added 2 new tests covering the cache and GC)
  • 0 lint errors

🤖 Generated with Claude Code

Fix 1 - readdir cache hot path:
  detectSubagentTransitions() now statSync's the subagentsDir first.
  If the dir mtime hasn't changed since the previous call, no new files
  can have appeared, so we skip readdirSync entirely and skip statSync
  for any unknown-file entries. Known-active entries still get statSync
  so the stability timer keeps advancing.
  Session-local cache: _prevDirMtime, _subFileList on session object.
  Edge case: when the previous scan returned 0 files, we always rescan
  regardless of mtime (fast-write within same clock tick).

Fix 2 - synthetic spawn for live bootstrap files:
  Cold-start agents with mtime within BOOTSTRAP_LIVE_MS (60 s) are
  'looksAlive'. Previously they were recorded silently, meaning the
  renderer's liveSubagents / activeSubagents Maps never had a spawned
  event for them, so any subsequent subagent-completed was a no-op.
  Now we emit subagent-spawned with _bootstrap:true so the renderer
  can set up its tracking state. The _bootstrap flag lets it dedupe
  if it already has independent state.
  The bootstrap branch continues to use 'continue', keeping it strictly
  mutually exclusive with the post-bootstrap spawn path.
Assign id='orphan-' + fId to the orphan-subagents container element.
Without a stable id, morphdom couldn't match the element between renders
and rebuilt it from scratch on every flush, causing minor flicker on
projects with many orphan subagents.

The id follows the same pattern as other keyed elements in buildSessionsList
(sessions-<fId>, older-<fId>, older-list-<fId>).
Add _gcExpandedSubagentsOnce() called lazily on first getExpandedSubagents()
invocation. It intersects the stored set with the current sessionMap keys and
saves the pruned result, preventing unbounded growth of the key across
long-lived Switchboard instances with many agent sessions.

The GC is guarded by a module-level boolean so it runs at most once per page
load regardless of how many times the sidebar re-renders.
@JeanBaptisteRenard
JeanBaptisteRenard merged commit de51d02 into main May 23, 2026
7 checks passed
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