You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Make the Code tab (right-panel diff/browse) stay current as the agent edits files. This combines two workstreams that only pay off together:
Server side — the outstanding scope of Phase 5 of Code tab: diff review & file browser #514 ("Live file watching"): add streaming endpoints that emit snapshot-then-deltas on fs.watch events, scoped to the terminal's repo root with a debounce.
Client side — item 1 of Code-tab subsystem: three structural fixes from Hickey+Lowy review #615 (CodeTab transport insulation): route CodeTab's RPC call sites through the existing stream namespace in packages/client/src/rpc/rpc.ts instead of client.* directly, so ClientRetryPlugin handles reconnects and the snapshot-then-deltas contract is consumed via createSubscription (the pattern already used by preferences, activity, session, metadata).
Shipping them separately: item 1 of #615 alone is a pure alias with zero user value (the endpoints stay pull-shaped); Phase 5 of #514 alone leaves the client on client.* and would have to be retrofitted. Shipping together: one PR, one structural migration.
Note (post-#708, the @pierre/diffs + @pierre/trees migration). That PR removed fs.listDir and the oldContent/newContent fields of GitDiffOutput (zero callers after Pierre took over). Scope below has been updated accordingly: fs.onDirChange is dropped in favor of fs.onListAllChange, and git.onDiffChange's payload is slimmer.
Scope
Server
Add streaming variants in packages/server/src/router.ts + packages/common/src/contract.ts:
git.onStatusChange(input) — yields current GitStatusOutput on subscribe, then re-emits on fs.watch events that affect the tracked/untracked set.
git.onDiffChange(input) — yields current GitDiffOutput for a (repoPath, filePath, mode, oldPath?) tuple, then re-emits on writes to the file. Payload is now slimmer (just oldFileName / newFileName / hunks) since oldContent / newContent were dropped in feat(code-tab): swap file tree + diff view for @pierre/trees and @pierre/diffs #708.
fs.onListAllChange(input) — yields current FsListAllOutput (flat path list) on subscribe, then re-emits when the working-tree path set changes (file added / removed / renamed, respecting .gitignore). Replaces what was originally scoped as fs.onDirChange — Pierre's @pierre/trees consumes a flat path list, not per-directory entries.
fs.onFileChange(input) — yields current FsReadFileOutput for a file, then re-emits on writes.
Implementation:
One shared fs.watch per repo root, refcounted by active subscriber count (integration-perf rule: no per-subscription watchers). Scope to the repo root; filter events by the subscriber's path-of-interest.
Debounce window ~150ms trailing-edge (matches the DEBOUNCE_MS in watchGitHead and the TRANSCRIPT_DEBOUNCE_MS pattern in agent session watchers).
Each handler yields snapshot first, then deltas — per rules/streaming.md invariant 2. Re-subscribe on reconnect (handled automatically by ClientRetryPlugin) restores full snapshot with no delta replay.
.git/ excluded from the watch (constant noise from git's own plumbing).
Two spots to decide:
Delta vs full replacement shape.git.onStatusChange could yield full GitStatusOutput each time (implicit replacement, simplest, matches onMetadataChange pattern) or {kind:"snapshot", ...} | {kind:"delta", ...}. Default to implicit replacement for all four endpoints — deltas only pay off if a single response's bytes dominate, which they don't for status/list-all; they might for onFileChange if large files are in play. Start implicit; add discriminated-union later if profiling shows it matters. (See client-side note below for fs.onListAllChange — Pierre's incremental mutation API is the natural delta consumer there.)
Pull endpoints stay or go. Keep git.status/git.diff and fs.listAll/fs.readFile as one-shot RPCs alongside the streaming variants — the streaming ones are additive. After the CodeTab migration, audit and delete pull endpoints with no remaining consumers (likely all four; worktree* ops stay one-shot regardless).
Client
packages/client/src/rpc/rpc.ts — add the four streaming entries to the stream namespace (with STREAM_RETRY context so reconnects work).
packages/client/src/right-panel/CodeTab.tsx and BrowseFileView.tsx — replace each createResource with createSubscription(() => stream.xxx(input), { onError }). Today there are three subscriptions in CodeTab.tsx (status, allPaths, diff) and one in BrowseFileView.tsx (readFile). Remove the ↻ refresh button (handleRefresh + refetchStatus / refetchDiff / refetchAll) — live watching makes manual refresh redundant. Wire onError to toast.error per rules/code-police-rules.md:subscription-must-surface-errors.
Pierre incremental-mutation optimization (second pass). Pierre's FileTree exposes tree.add(path), remove(path), move(from, to), batch(ops). For fs.onListAllChange specifically, the first pass keeps things simple: full-replacement snapshots dispatched into tree.resetPaths(paths) (matches the current pattern). The second pass, only if the all-paths reset proves expensive on big repos, switches that one endpoint to discriminated-union deltas ({kind:"snapshot"} | {kind:"add", path} | {kind:"remove", path} | {kind:"move", from, to}) and dispatches into Pierre's per-path methods. Don't speculate the optimization in v1.
E2e
Update packages/tests/features/code-tab.feature to assert that a write from outside the right panel is reflected in the diff list / file tree within a bounded wait, without clicking refresh.
First milestone: ship the server streaming endpoints + client stream.* wrapper + CodeTab migration in one PR. E2e in the same PR.
Follow-ups: kill the pull endpoints if no consumer remains; switch fs.onListAllChange to discriminated-union deltas + Pierre per-path mutations if profiling warrants.
Memory-leak concerns in @parcel/watcher or chokidar — stick with Node's built-in fs.watch first; escalate only if reliability proves inadequate on Linux under load (the risk Phase 5 of Code tab: diff review & file browser #514 explicitly called out).
Make the Code tab (right-panel diff/browse) stay current as the agent edits files. This combines two workstreams that only pay off together:
fs.watchevents, scoped to the terminal's repo root with a debounce.streamnamespace inpackages/client/src/rpc/rpc.tsinstead ofclient.*directly, soClientRetryPluginhandles reconnects and the snapshot-then-deltas contract is consumed viacreateSubscription(the pattern already used by preferences, activity, session, metadata).Shipping them separately: item 1 of #615 alone is a pure alias with zero user value (the endpoints stay pull-shaped); Phase 5 of #514 alone leaves the client on
client.*and would have to be retrofitted. Shipping together: one PR, one structural migration.Scope
Server
Add streaming variants in
packages/server/src/router.ts+packages/common/src/contract.ts:git.onStatusChange(input)— yields currentGitStatusOutputon subscribe, then re-emits on fs.watch events that affect the tracked/untracked set.git.onDiffChange(input)— yields currentGitDiffOutputfor a (repoPath, filePath, mode, oldPath?) tuple, then re-emits on writes to the file. Payload is now slimmer (justoldFileName/newFileName/hunks) sinceoldContent/newContentwere dropped in feat(code-tab): swap file tree + diff view for @pierre/trees and @pierre/diffs #708.fs.onListAllChange(input)— yields currentFsListAllOutput(flat path list) on subscribe, then re-emits when the working-tree path set changes (file added / removed / renamed, respecting.gitignore). Replaces what was originally scoped asfs.onDirChange— Pierre's@pierre/treesconsumes a flat path list, not per-directory entries.fs.onFileChange(input)— yields currentFsReadFileOutputfor a file, then re-emits on writes.Implementation:
fs.watchper repo root, refcounted by active subscriber count (integration-perf rule: no per-subscription watchers). Scope to the repo root; filter events by the subscriber's path-of-interest.DEBOUNCE_MSinwatchGitHeadand theTRANSCRIPT_DEBOUNCE_MSpattern in agent session watchers).rules/streaming.mdinvariant 2. Re-subscribe on reconnect (handled automatically byClientRetryPlugin) restores full snapshot with no delta replay..git/excluded from the watch (constant noise from git's own plumbing).Two spots to decide:
git.onStatusChangecould yield fullGitStatusOutputeach time (implicit replacement, simplest, matchesonMetadataChangepattern) or{kind:"snapshot", ...} | {kind:"delta", ...}. Default to implicit replacement for all four endpoints — deltas only pay off if a single response's bytes dominate, which they don't for status/list-all; they might foronFileChangeif large files are in play. Start implicit; add discriminated-union later if profiling shows it matters. (See client-side note below forfs.onListAllChange— Pierre's incremental mutation API is the natural delta consumer there.)git.status/git.diffandfs.listAll/fs.readFileas one-shot RPCs alongside the streaming variants — the streaming ones are additive. After the CodeTab migration, audit and delete pull endpoints with no remaining consumers (likely all four;worktree*ops stay one-shot regardless).Client
packages/client/src/rpc/rpc.ts— add the four streaming entries to thestreamnamespace (withSTREAM_RETRYcontext so reconnects work).packages/client/src/right-panel/CodeTab.tsxandBrowseFileView.tsx— replace eachcreateResourcewithcreateSubscription(() => stream.xxx(input), { onError }). Today there are three subscriptions inCodeTab.tsx(status,allPaths,diff) and one inBrowseFileView.tsx(readFile). Remove the↻refresh button (handleRefresh+refetchStatus/refetchDiff/refetchAll) — live watching makes manual refresh redundant. WireonErrortotoast.errorperrules/code-police-rules.md:subscription-must-surface-errors.Reactive derivation becomes automatic: select-a-file →
selectedPath()changes → subscription input changes → SolidJS re-subscribes → new snapshot arrives. No manual refetch plumbing.Pierre incremental-mutation optimization (second pass). Pierre's
FileTreeexposestree.add(path),remove(path),move(from, to),batch(ops). Forfs.onListAllChangespecifically, the first pass keeps things simple: full-replacement snapshots dispatched intotree.resetPaths(paths)(matches the current pattern). The second pass, only if the all-paths reset proves expensive on big repos, switches that one endpoint to discriminated-union deltas ({kind:"snapshot"} | {kind:"add", path} | {kind:"remove", path} | {kind:"move", from, to}) and dispatches into Pierre's per-path methods. Don't speculate the optimization in v1.E2e
Update
packages/tests/features/code-tab.featureto assert that a write from outside the right panel is reflected in the diff list / file tree within a bounded wait, without clicking refresh.Sequencing
stream.*wrapper + CodeTab migration in one PR. E2e in the same PR.fs.onListAllChangeto discriminated-union deltas + Pierre per-path mutations if profiling warrants.Out of scope
@parcel/watcherorchokidar— stick with Node's built-infs.watchfirst; escalate only if reliability proves inadequate on Linux under load (the risk Phase 5 of Code tab: diff review & file browser #514 explicitly called out).Related
@pierre/diffs+@pierre/treesmigration; reshaped the surface this issue plugs into (drove theonDirChange→onListAllChangeswap and the slimmergit.onDiffChangepayload).rules/streaming.md— snapshot-then-deltas +ClientRetryPlugincontext threading invariants.rules/lowy-volatilities.md— the "server-pushed state delivery" axis this migration settles for CodeTab.rules/code-police-rules.md—subscription-must-surface-errorsandintegration-perf-hygiene(shared watcher, debounce) both apply.