fix(desktop): make external file changes and save conflicts explicit
What & why
Editing an open .env while a script replaces the file leaves two different versions. The v2 editor preserved the unsaved buffer but gave no visible warning, and saving showed a generic “Do you want to save?” dialog whose Save action force-overwrote the newer disk contents. In a CDP reproduction, this removed the script-generated token.
Original report: Slack thread in #ext-ardent.
- Show a visible Save button and an external-change banner with Compare.
- Replace the generic conflict alert with a shared, localized side-by-side disk/local diff. Offer Keep editing, Reload from disk, and explicitly destructive Overwrite file. Focus Keep editing by default; prevent reload when the disk version cannot be read.
- Preserve edits across overlapping reads, obsolete read errors, and save/delete/rename races. Refresh open documents on watcher overflow.
- Resolve canceled close requests and activate the conflicted file when a pane or background tab cannot close safely. Only the active tab/pane presents the review dialog.
- Reuse the review dialog in the legacy editor/diff view; translate new copy into every shipping locale.
Local reference implementations: VS Code's textFileEditorModel.ts guards dirty buffers and version changes during reads; textFileSaveErrorHandler.ts uses explicit Compare/Overwrite conflict actions. Wave's preview-model.tsx exposes a visible Save action for edited buffers. Manual save remains intentional; this PR does not add autosave or silently merge simultaneous edits.
How I tested it
Real desktop CDP before/after: worktree low-foxtrot, renderer http://localhost:6825, CDP 9482, verified local-dev session, isolated fixture repo with a gitignored .env containing synthetic values. Opened the file through the Files tree and used real mouse/key input; external writes and atomic replacements used the filesystem. No injected editor/document state.
| Journey | Before | After |
|---|---|---|
Clean .env, script atomically replaces it | Refreshes correctly | Refreshes correctly, including two consecutive replacements |
Dirty .env, script adds a token | Buffer stays stale with no external-change warning | Buffer preserved; visible warning and Compare |
Cmd+S with newer disk contents | Generic Save/Don't Save/Cancel; Save removes the generated token | Disk/local diff shows the token and email edit; Keep editing focused; review performs no write |
| Dismiss / compare / reload | — | Escape preserves both versions; Compare does not write; Reload loads the token-bearing file and clears warning |
| Save normally | Keyboard or close prompt | Visible Save immediately writes the edited email and retained token |
| Delete/recreate while dirty | — | Buffer preserved; reload disabled for unreadable disk; recreation restores external-change warning |
| Close/reopen; conflict while closing | — | Cancel works; Save/reopen retains contents; conflicts keep the file open for review |
| Close a background dirty tab and Save All | — | Activates the conflicted file and displays exactly one review dialog |
| Explicit overwrite | — | Replaces disk contents only through the clearly labeled overwrite action |
Captured before/after screenshots and a detailed verification record locally in /tmp/low-foxtrot-evidence/; archive: /tmp/low-foxtrot-file-sync-evidence.zip. Screenshots are not attached to GitHub. Console error capture was empty during the initial after-flow and save/delete/close checks.
The original reporter's app build and make script were unavailable; the CDP reproduction uses the stated edit → external replacement → save workflow. Reload-ordering and overflow races are unit-tested, not claimed as CDP reproductions. A symlink resolving outside the workspace was rejected by the existing filesystem boundary; that policy is unchanged.
- 14 focused tests pass (file-document store and snapshot adapter). Six initial reload/overflow regression cases failed before the first fix.
bun run lintpasses.bun run check:i18npasses, including strict compilation of every shipping locale.- Repository
bun run typecheckpassed all 42 tasks; desktopbunx tsc --noEmitpassed again after the final focus/close-guard changes. - Full
bun run testreported 34 failures across five desktop Git/worktree/shell test files. Each file passes in isolation, totaling 65 passing tests. No full-suite pass is claimed.
Checklist
- PR title follows conventional commits (
type(scope): subject) -
bun run lintandbun run typecheckpass (CI fails on lint warnings too) - New user-facing strings translated and
bun run check:i18npasses - Original report linked; real CDP before/after captured
- "Allow edits from maintainers" is checked on fork PRs (same-repository branch)
Summary by CodeRabbit
-
New Features
- Added a visual file-conflict dialog showing local edits alongside the disk version.
- Added options to keep editing, reload from disk, or overwrite the disk file.
- Added a banner for externally changed files with a Compare action.
- Added a Save button to text-file panes with progress feedback.
-
Bug Fixes
- Prevented stale file reads from overwriting newer edits.
- Improved save and close flows with clearer focus and dismissal behavior.
-
Localization
- Added translations for file-conflict messages across supported languages.