feat(ui): subagent click opens read-only transcript (roadmap item 1) - #9
Merged
Merged
Conversation
…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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
claude --resume <sessionId>. Subagents are ephemeral child runs — re-resuming them makes no sense since the parent context is gone.sidebar.jsbranches onitem.dataset.subagent && session.parentSessionIdinrebindSidebarEvents. 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.showSubagentTranscript(added in this branch as part of the companionfix/post-review-hardeningwork) 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.drainViewerWatchesdrains live-tail watchers when navigating away, preventing indefinitefs.watchFilepolling.Files changed
public/sidebar.jsshowSubagentTranscriptpublic/jsonl-viewer.jsshowSubagentTranscript+activeViewerWatches/drainViewerWatchespublic/plans-memory-view.jsdrainViewerWatchesfromhideAllViewerspublic/style.css.jsonl-subagent-escape-banner+.jsonl-subagent-resume-btnstyleseslint.config.jsshowSubagentTranscript+drainViewerWatchesas cross-file globalstest/dom-subagent-transcript.test.jsTest plan
npm test— 42 tests, 0 lint errorsopenSession(terminal open)