Repository navigation
UI Consistency: 3 issues found
Three findings in the new file-save rollback path, all posted as inline comments.
apps/web/src/components/files/fileSaveCoordinator.ts— every non-Successresult now triggersonRollback, including interrupted commands (Cause.hasInterruptsOnly). An interrupted write never reached the server, so the editor surface is reverted to the last confirmed contents and a save error is shown, silently discarding the user's text. The rest ofapps/webfilters interrupted results withisAtomCommandInterruptedbefore any user-facing failure handling.apps/web/src/components/files/FilePreviewPanel.tsx— the rollback error toast drops the write failure cause and passes only the relative path asdescription, inconsistent with the app-wide error-toast shape (title+squashAtomCommandFailure(result)); thetype: "error""Copy error" button would copy just the path.apps/web/src/components/files/FilePreviewPanel.tsx— the rollback usessetProjectFileQueryData, which leaves an unconfirmed optimistic overlay in place indefinitely, pinning the panel to that snapshot and hiding laterreadFilerefreshes (agent/external edits) until the next successful save.
Details
Note
Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.
Methodology:
- Read the changed files at head (
fileSaveCoordinator.ts,FilePreviewPanel.tsx,fileSaveCoordinator.test.ts) and traced the rollback data flow throughprojectFilesQueryState.ts(setProjectFileQueryData,confirmProjectFileQueryData,clearProjectFileQueryData,useProjectFileQuery) andfileContentRevision.ts(projectFileEditorCacheKey). - Confirmed
FileSaveCoordinatorhas exactly one production consumer (useFileSaveCoordinator, used byEditableFileSurfaceandRenderedMarkdownSurface), so the new requiredinitialContents/onRollbackoptions break no other call site; the surface mounts only oncefile.dataexists and is keyed byrelativePath:resolvedTheme, soinitialContentsis the loaded file and the memo's intentional omission ofcontentsfrom its dependency list is correct and documented. - Compared the new error toast against the repository's error-toast conventions (
toastManager/stackedThreadToastusage inServerUpdateAction.tsx,ChatView.tsx,Sidebar.tsx,FilePreviewPanel.tsx:859-867) and against the app-wideisAtomCommandInterruptedhandling pattern (~50 call sites across web and mobile).
No findings on shared UI primitives, Tailwind/CSS ownership, theme variants, scroll/virtualized masks, or environment routing: the diff adds no markup or styling, and the rollback/toast callbacks both use the explicitly threaded environmentId prop rather than an active-environment fallback. The added coordinator tests appropriately cover the new state transition (rollback on latest-write failure, no rollback when a superseded write fails).
🔍 Agent activity — 15 tool calls
💭 I want to look at the actual code to understand this change, particularly focusing on the rollback logic in the relevant files.
1. browse_code — apps/web/src/components/files/fileSaveCoordinator.ts
2. browse_code — apps/web/src/components/files/FilePreviewPanel.tsx
💭 This is a UI consistency check on a file save coordinator with rollback and toast logic. I'm considering whether the rollback correctly refreshes the editor's displayed contents, since setting query data updates React state but the imperative editor might not re-read it, which touches on visual/layout preservation concerns. I want to verify the toastManager import and its API—whether the "error" toast type is valid and whether there's a shared toast pattern to follow. I also need to check that environment routing is handled correctly with explicit environmentId, and look at the imports for FilePreviewPanel and toastManager to confirm everything lines up.
3. browse_code — apps/web/src/components/files/FilePreviewPanel.tsx
4. browse_code — In apps/web/src, what is the toastManager API (file path, add() options: type/title/description fields, allowed type values)? Show the implementation and 2-3 example call sites for error toasts.
💭 Toast usage looks consistent with codebase conventions. The real question is whether there's a genuine regression: rollback updates React query state, but the Editor is imperative and persists its own state, so I need to check whether it actually re-renders when the
contentsprop changes after a rollback. I'm also wondering ifuseFileSaveCoordinator's memoized deps could capture a stale or emptyinitialContentsif the file loads asynchronously, which would cause a failed first write to roll back the editor to empty content — real data loss risk. I should check whether EditableFileSurface only mounts once contents are loaded, and look more closely at where FilePreviewPanel renders it and howonPendingChange(false)now always fires on failure, unlike before when pending stayed true.
5. git_tools — EditableFileSurface|RenderedMarkdownSurface|contents=
💭 I'm confirming that initial contents load correctly since the file data is keyed by path and theme, and that the optimistic overlay updates on every keystroke without affecting the memoized deps. Now I'm checking whether the rollback actually propagates to the imperative editor instance, which is initialized separately from the contents prop. If the editor is only initialized once and controlled imperatively, updating the query cache on rollback wouldn't visibly revert the editor's displayed text, leaving a divergence between the UI and the underlying data. I'm not sure this counts as a violation under the specific scoped rules, but it's worth checking how EditableFileSurface actually wires contents into the File/editor component before deciding.
6. browse_code — apps/web/src/components/files/FilePreviewPanel.tsx
💭 I'm noting that File keys off contents and a cache key built from environment, path, and file contents, so I should check fileContentRevision.ts to see if externally rolling back contents actually re-renders the editor. I also want to clarify the difference between setProjectFileQueryData and confirmProjectFileQueryData in projectFilesQueryState.
7. browse_code — apps/web/src/components/files/fileContentRevision.ts
8. browse_code — apps/web/src/components/files/projectFilesQueryState.ts
💭 I'm tracing whether the rollback actually resets the contenteditable DOM—setting the optimistic atom changes the contents prop and cacheKey, forcing the File to re-render with disk contents, but I need to check whether the editor's in-memory persisted state would override that reset. Nothing here touches Tailwind, CSS, or primitives, so the UI-consistency rules don't apply—the main scope-relevant detail is toast behavior and that the rollback correctly targets the explicit environmentId. I'm also noting a possible coherence issue: with
onPendingChange(false)now firing unconditionally instead of only on success, and rollback restoring content, that seems consistent—but I suspect the coordinator's internallatestContents/latestRevisionstate isn't reset alongside the rollback, which could leave stale state behind on dispose. That's exactly the bug: sincelatestRevision > 0still triggerspersistLatest()on unmount, the coordinator would silently re-save the failed, already-rolled-back-in-the-UI content to disk after the user has moved on—resurrecting an edit the user believes was discarded. I'm trying to figure out whether the rollback itself triggers the editor's onChange handler with the reverted content, which would updatelatestContentsto match disk and make this harmless, or whether it's treated as an external prop update that bypasses onChange entirely, leaving the stale failed content in place. That said, I don't think this state-divergence issue clearly falls under the UI consistency check's actual rule categories—shared controls, CSS/Tailwind ownership, themes, scroll behavior, visual regressions, environment routing, or change discipline. Nothing else in the diff seems to trigger those rules either, so I'm leaning toward there being no qualifying finding here, meaning the response should indicate a clean pass. That's minor enough not to count as a rule violation, so I'll conclude with no findings and mark this clean. Let me double check other call sites of FileSaveCoordinator, since initialContents/onRollback are now required props, to see who else needs updating.
9. git_tools — FileSaveCoordinator
💭 That's odd — the grep result for that line doesn't match what I saw when viewing the file directly, which suggests grep might have pulled from a different revision than the one under review, maybe the merge base. I should double check which revision is actually being used. Also, PreviewView.tsx at line 165 instantiates FileSaveCoordinator, but that file isn't part of the diff, so if the constructor signature changed, this call site would break with missing required fields unless it's untouched for another reason. Let me look at PreviewView.tsx around lines 130-220 at head to verify.
10. browse_code — apps/web/src/components/preview/PreviewView.tsx
💭 The line numbers from the grep don't line up with this file's current head, so it likely ran against a different revision. I should re-run the grep against the explicit commit b0045bb to check for FileSaveCoordinator in PreviewView.tsx.
11. git_tools — FileSaveCoordinator
💭 I notice the PR head doesn't show PreviewView.tsx using FileSaveCoordinator, but the earlier grep may have hit main HEAD instead, which could have a new call site that's moved ahead of the PR. That raises the concern that merging could break typecheck if main already updated PreviewView without the initialContents/onRollback params — though I need to check against the actual merge base commit to be sure.
12. git_tools — new FileSaveCoordinator
💭 It's odd that the path-filtered grep returned nothing when the earlier unfiltered search found matches at those lines. The path I specified should match the file, so maybe the branch reference is off—I'll retry the grep without the path filter on the default branch to double-check.
13. git_tools — fileSaveCoordinator
💭 That earlier PreviewView reference must have been a stale artifact from an inconsistent tool result, so I can disregard it—there's really just the one consumer. Now I'm checking whether the rollback logic, which sets an optimistic overlay via setProjectFileQueryData, raises any UI consistency concerns worth flagging. Since that overlay never gets cleared or confirmed, it permanently masks the real query data, meaning external file changes (like agent writes) would never surface in the preview after a rollback. The fix should be to use clearProjectFileQueryData instead, which drops the overlay and lets the actual server/disk contents show through. That said, I'm weighing whether this counts as a concrete violation worth flagging given the review scope leans toward UI-consistency issues rather than state-ownership bugs — this feels like a real but somewhat out-of-category concern, so I'm hesitant about how hard to press on it. Cursor and scroll position resetting seems acceptable. Weighing the evidence, I'm leaning toward no findings since the specific rule families aren't violated — the diff only touches three files, environment routing is handled properly with explicit environmentId props, and focused tests were added for the behavior change. I'm double-checking whether the error toast should surface the actual failure message, comparing against patterns elsewhere in the codebase for file write failures. I notice the rollback discards the persisted error and only shows the path, so users can't tell why it failed (permission denied vs disk full), and the "Copy error" button just copies the path — this seems more like a UX quality nit than a concrete violation of the rules against failing for aesthetic preferences. I'm also tracing where interrupted command detection is used in the file preview panel to see if there's a related gap.
14. git_tools — isAtomCommandInterrupted
💭 I'm noticing the codebase consistently avoids treating interrupted commands as user-facing failures and always includes the real error message in toasts—but this new rollback path breaks both conventions. If a write gets interrupted, like from a superseded or cancelled atom command, it would trigger onRollback and silently wipe out the user's typed edits while showing a generic "Could not save file" message, which is a real regression since interruptions happen often during normal use. Also, the toast description just shows the file path instead of the underlying error, which breaks the app-wide pattern of pairing a title with the actual error message, and the error-type toast would misleadingly let users "copy" just the path as if it were the error. I think both issues fall within scope as UI/behavioral consistency problems, so I'll flag them as findings on the changed lines in FilePreviewPanel.tsx. There's also a secondary concern: since the optimistic overlay pins the file to the rolled-back snapshot with an undefined confirmedAgainst, later successful writes from elsewhere (like an agent) may not surface until the next save — a real state-ownership issue, though correctness of clearing it depends on whether query data matches the last confirmed version. I'll mention this as a softer suggestion rather than a hard finding to avoid noise, and focus my main report on the two strongest issues: the destructive interrupted-write rollback and the toast's missing error message. For the rollback fix, the actual detection should happen in fileSaveCoordinator.ts, since that's where success/failure is judged for the atom command result — it should distinguish "interrupted" outcomes from real failures and skip rollback/toast for those, likely reusing an existing interruption-check helper rather than treating any non-success as a hard failure. At line 62, interrupted results get treated as write failures, causing rollback to discard edits — I should suggest checking isAtomCommandInterrupted and skipping rollback instead, matching the pattern used elsewhere. Since fixing this properly needs a new import (converting the type-only import to include a value), I'll provide a unified diff for the logic change and note the import addition separately rather than bundling it into a suggestion block. Next I'm checking the FilePreviewPanel's onRollback toast at line 433 — it only shows "Could not save file" with the path, omitting the actual error reason, unlike other error toasts in the app. Since the coordinator doesn't currently pass the error through to onRollback, fixing this would require a cross-file signature change, so I'll flag it as a medium-priority consistency issue rather than provide a diff. I'm also verifying the sticky optimistic overlay problem: after a rollback, the overlay keeps confirmedAgainst as undefined and never clears it, and since resolveProjectFileQueryData/useProjectFileQuery always prefer overlay data, the panel's displayed file contents stay pinned to stale data. But there's an edge case: if a failure occurs while confirm's refresh hasn't finished, the overlay might still be needed since the coordinator serializes and holds it until refresh completes, so clearing early could briefly expose stale content. That makes me less certain my "fix" is definitively correct—I should frame it as a risk to flag rather than a firm claim, and ask for confirmation on whether to clear or reconfirm the overlay instead of assuming. I'm now pinpointing the exact lines to comment on: the onRollback handler, the setProjectFileQueryData call, and the toast description in FilePreviewPanel, all within the added diff lines around 428-435, plus a related concern in fileSaveCoordinator.ts near line 62. Given the interrupted-rollback issue touches on potential data loss, I think failing the check is warranted, and I'm drafting the inline comments now. I'm also flagging that any non-Success result currently triggers onRollback, including interrupted commands—but an interrupted write never reached the server, so rolling back silently discards what the user typed. Since interrupted results are excluded elsewhere before failure handling via isAtomCommandInterrupted, I'm considering how to fix this without breaking the saving state flag, since an early return would skip resetting it. I'm setting a local interrupted flag and only skipping the onRollback call while still clearing the pending/saving state, keeping the rest of the flow intact. I also want to flag two more issues in FilePreviewPanel.tsx: the toast around lines 430-434 doesn't surface the actual failure reason, and around line 429 the overlay is never cleared or confirmed. I'll post all of this as a summary review comment.
15. modify_pr — UI consistency review found three issues in the new file-save rollback path. The main one is that an interrupted write is now treated as a failed write, which makes the editor discard the user's text and surface a save error — every other command result handler in apps/web filters interrupted results out first.
Agent Credits: 248 credits