Skip to content

feat(ui): subagent click opens read-only transcript (roadmap item 1) - #9

Merged
JeanBaptisteRenard merged 2 commits into
mainfrom
feat/subagent-transcript-view
May 23, 2026
Merged

JeanBaptisteRenard merged 2 commits into
mainfrom
feat/subagent-transcript-view

Conversation

@JeanBaptisteRenard

Copy link
Copy Markdown
Collaborator

Summary

  • Routing: clicking a subagent sidebar entry now opens a read-only transcript view instead of spawning claude --resume <sessionId>. Subagents are ephemeral child runs — re-resuming them makes no sense since the parent context is gone.
  • Discrimination at click time: sidebar.js branches on item.dataset.subagent && session.parentSessionId in rebindSidebarEvents. This is cleaner than discriminating at the IPC layer — the handler already has the full session object and avoids an extra round-trip. IPC stays type-agnostic.
  • Transcript rendering: showSubagentTranscript (added in this branch as part of the companion fix/post-review-hardening work) reuses the existing JSONL rendering pipeline (mergeLocalCommandEntries, renderJsonlEntry, tool result maps). A "Resume in terminal anyway" escape-hatch button is shown at the top for edge cases.
  • Viewer watch cleanup: drainViewerWatches drains live-tail watchers when navigating away, preventing indefinite fs.watchFile polling.

Files changed

File Change
public/sidebar.js Route subagent clicks to showSubagentTranscript
public/jsonl-viewer.js Add showSubagentTranscript + activeViewerWatches/drainViewerWatches
public/plans-memory-view.js Call drainViewerWatches from hideAllViewers
public/style.css .jsonl-subagent-escape-banner + .jsonl-subagent-resume-btn styles
eslint.config.js Register showSubagentTranscript + drainViewerWatches as cross-file globals
test/dom-subagent-transcript.test.js 4 new tests: routing (×2) + rendering (×2)

Test plan

  • npm test — 42 tests, 0 lint errors
  • Click a subagent entry in the sidebar → transcript view opens in the main pane (not a new terminal tab)
  • Click a top-level session → terminal opens as before (unchanged behavior)
  • "Resume in terminal anyway" button in the transcript banner triggers openSession (terminal open)
  • Error case: subagent JSONL not found → error notice shown in body

…drain viewer watches

Follow-up to the four PR reviews. Five independent improvements, none
strictly required but each shipped well-defined feedback from the agents:

- **Subagent IPCs path-traversal guard**: read-subagent-jsonl and
  start-subagent-watch took parentSessionId/agentId straight from the
  renderer and fed them to path.join. A compromised renderer (or a
  bug in cache payload) could traverse out of <projects>/<folder>.
  Add SAFE_ID_RE + a path.resolve()-based defence-in-depth check that
  the resolved jsonl path still lives under the cache row's folder.

- **Worktree name hardening**: the regex now also rejects names matching
  `.` / `..` / leading `-` (the latter to keep git from parsing the
  name as a flag in some contexts). Parent repo must be absolute and
  not flag-like.

- **Friendly git ENOENT error**: when git is absent from PATH,
  delete-worktree returned `spawn git ENOENT` to the renderer — opaque.
  Now returns "git not found on PATH — install git and retry".

- **Viewer watch leak**: stopWatch closures registered in
  activeViewerWatches; hideAllViewers / showJsonlViewer / showSubagentTranscript
  drain via drainViewerWatches. Previously every expanded Agent block
  with a live tail kept polling indefinitely after the viewer was
  dismissed (until app shutdown).

32 tests still pass, 0 lint errors.
…wning Claude

Clicking a subagent sidebar entry now routes to a read-only transcript view
(showSubagentTranscript in jsonl-viewer.js) instead of opening a new
claude --resume PTY. Subagents are ephemeral child runs — re-resuming them
makes no sense since the parent context is gone.

Routing discriminates on session.parentSessionId at the click handler level
(sidebar.js rebindSidebarEvents), not at the IPC layer. This keeps the IPC
layer ignorant of UI routing concerns and avoids an extra round-trip: the
click handler already has the full session object including parentSessionId.

showSubagentTranscript reuses the existing JSONL rendering pipeline
(mergeLocalCommandEntries, renderJsonlEntry, toolResultMap) and prepends an
escape-hatch banner with a "Resume in terminal anyway" button for edge cases.

Changes:
- public/sidebar.js: branch on item.dataset.subagent + session.parentSessionId
- public/jsonl-viewer.js: showSubagentTranscript (added by fix/post-review-hardening;
  this commit adds the routing hook and styles that complete the feature)
- public/style.css: .jsonl-subagent-escape-banner + .jsonl-subagent-resume-btn
- eslint.config.js: register showSubagentTranscript + drainViewerWatches as
  cross-file renderer globals
- test/dom-subagent-transcript.test.js: 4 new tests (routing + rendering)
@JeanBaptisteRenard
JeanBaptisteRenard merged commit 1ab01aa 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