Skip to content

fix(web): keep the right panel synced with agent edits - #210

Merged
rynfar merged 2 commits into
pylonfrom
upstream/2026-08-31-workspace-refresh
Aug 31, 2026
Merged

fix(web): keep the right panel synced with agent edits#210
rynfar merged 2 commits into
pylonfrom
upstream/2026-08-31-workspace-refresh

Conversation

@rynfar

@rynfar rynfar commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

The diff panel, file browser, and file preview loaded once and never refetched.
While an agent edited files the right panel kept showing pre-edit content, so the
user was reading stale state next to a live transcript and had to close and reopen
the panel to see the truth.

Adds useWorkspaceMutationRefresh, which derives a mutation id from the latest
terminal file-change or command-execution activity plus the latest checkpoint, and
refreshes each right-panel resource once per mutation. Completed commands count
because a shell command can change the workspace without reporting the paths it
touched. A file with unsaved local edits stays pending rather than being refreshed
out from under the user.

Adopted from upstream pingdotgg/t3code#8803 (e09b88b6a), cherry-picked clean.

Verified: vp test run --dir apps/web apps/web/src/hooks/useWorkspaceMutationRefresh.test.ts apps/web/src/components/files/projectFilesQueryState.test.tsx
(6 passed), vp run -F @t3tools/web typecheck (exit 0, no errors).

Not exercised in a browser — happy to do a real-client pass on request.

Reviewed and integrated with Claude Opus 5 in Claude Code.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com>
(cherry picked from commit e09b88b6a51bb7e4309498f01c2f36bf5ada43f4)
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L labels Aug 31, 2026
@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.6 KiB 13.6 KiB +11 B (+0.1%) 15.1 KiB
Codex Thread snapshot wire 6.9 KiB 6.9 KiB −2 B (−0.0%) 7.3 KiB
Codex Live turn WebSocket wire 6.6 KiB 6.6 KiB +13 B (+0.2%) 7.8 KiB
Codex Live turn WebSocket decoded 57.2 KiB 57.2 KiB 0 B (0.0%) 66.4 KiB
Codex Live turn messages 10 10 0 (0.0%) 21
Claude Total thread wire 13.6 KiB 13.6 KiB +14 B (+0.1%) 15.1 KiB
Claude Thread snapshot wire 6.9 KiB 6.9 KiB 0 B (0.0%) 7.3 KiB
Claude Live turn WebSocket wire 6.6 KiB 6.6 KiB +14 B (+0.2%) 7.8 KiB
Claude Live turn WebSocket decoded 58.1 KiB 58.1 KiB 0 B (0.0%) 66.4 KiB
Claude Live turn messages 10 10 0 (0.0%) 21

Baseline: 9ade80d · PR result: 77c935b · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.5 KiB
  • Claude decoded thread snapshot: 110.2 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Review follow-ups on the #8803 port.

The refresh was uncoalesced: every completed file_change or command_execution
fired a git status, a full git diff, a workspace entry listing and a file read.
A 60-tool-call turn went from one refresh to roughly 240 RPCs, several of them
git subprocesses. A burst now lands one trailing refresh.

It also refreshed on mount, cancelling and re-issuing the read the atom had
just started; opening a panel mid-turn now adopts the current mutation instead.

The file browser is no longer auto-refreshed at all: entriesQuery.refresh
produces a new entries array, which retriggers resetPaths, which rebuilds the
path store without initialExpandedPaths — the user's expanded directories
collapsed on every agent edit. The image preview's cache-buster keyed on the
global mutation id, so an open image re-downloaded and blanked on unrelated
tool calls; images now rely on normal caching. Text previews, the diff panel
and git status keep refreshing, which is where the value was.

The projectFilesQueryState effect shim now runs cleanups the way React does,
without which a timer-based effect cannot coalesce.
@rynfar

rynfar commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

Adversarial review found six issues. Four are fixed here; two are noted below for a follow-up decision.

Fixed

  1. No coalescing. Every completed file_change/command_execution fired a git status, a full git diff preview, a workspace entry listing and a file read. A 60-tool-call turn went from one refresh (previously keyed on latestTurn.turnId) to roughly 240 RPCs, several of them git subprocesses or full-tree payloads. That is a straight performance regression for the workload Pylon is built for. A burst now lands one trailing refresh.

  2. Refreshed on mount. handledTokenRef started null, so the first effect run always refreshed — a cold panel open cancelled and re-issued the read the atom had just started. The DiffPanel code this replaced seeded its ref on first observation for exactly that reason. Opening a panel mid-turn now adopts the current mutation.

  3. The file tree collapsed on every agent edit. entriesQuery.refresh() yields a new entries array, retriggering model.resetPaths(treePaths), and FileTreeController.resetPaths rebuilds the PathStore without initialExpandedPaths. Expansion reset to initialExpansion: 1, and the reveal effect is suppressed by handledRevealRef, so the open file's ancestors were not re-expanded either. The file browser is no longer auto-refreshed; its manual refresh is unchanged.

  4. Open images re-downloaded and blanked. The cache-buster keyed on the global mutation id rather than anything about the image, so any unrelated tool call re-fetched it. Removed; images use normal caching.

Text previews, the diff panel and git status still refresh — that is where the value was.

The projectFilesQueryState effect shim also now runs cleanups the way React does. Without that a timer-based effect cannot coalesce, and the old shim silently double-scheduled.

Noted, not fixed — your call

  • FilePreviewPanel.tsx:826 — the !selectedFilePending gate has no recovery path. FileSaveCoordinator.persistLatest only calls onPendingChange(false) on success, so one failed write pins the surface in pendingFileSurfaceIdsByProject for the session and that file never auto-refreshes again, even after reopening.
  • useWorkspaceMutationRefresh.ts:4 — Prime's canonicalToolItemType matches exact name tokens, so NotebookEdit falls through to dynamic_tool_call, which is not in WORKSPACE_MUTATION_ITEM_TYPES. Notebook edits stay stale until the turn checkpoint — the very case this PR exists to fix.

Surface decision: the hook lives in apps/web/src/hooks, not packages/client-runtime, so mobile keeps manual-refresh-only. Deliberate for now — the mobile right panel has a different lifecycle — but flagging it per AGENTS' "hit every surface".

Re-verified: projectFilesQueryState + useWorkspaceMutationRefresh tests pass (6), @t3tools/web typecheck clean.

@rynfar
rynfar merged commit a04f41d into pylon Aug 31, 2026
19 checks passed
@rynfar
rynfar deleted the upstream/2026-08-31-workspace-refresh branch August 31, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants