Skip to content

fix(fileSync): refresh open editors in real time and stop spurious save conflicts#259

Open
iabdousd wants to merge 1 commit into
mainfrom
leaking-hydration-706
Open

fix(fileSync): refresh open editors in real time and stop spurious save conflicts#259
iabdousd wants to merge 1 commit into
mainfrom
leaking-hydration-706

Conversation

@iabdousd

@iabdousd iabdousd commented May 28, 2026

Copy link
Copy Markdown
Member

Summary

  • Open editors auto-refresh on every external file change (Claude Code edits, formatters, other tools) by subscribing to the watcher's file-tree-changed event instead of waiting for visibilitychange. Clean tabs reload silently; dirty tabs surface a persistent "Conflicting changes" toast that mirrors the save-time modal.
  • checkBeforeSave no longer fires the conflict dialog when the user has no local edits - it silently syncs the editor to disk and skips the no-op write. Fixes the bug where pressing cmd+S on a file you didn't touch claims you have unsaved edits about to be overwritten.
  • Deleted files flip the tab to a red ⊘ glyph + strikethrough; cmd+S routes to a new Recreate confirmation modal. Atomic-write tmp→rename sequences are debounced 250ms so they don't briefly flash the deleted UI.

Behaviour matrix

Tab state Disk state Behaviour
clean unchanged nothing
clean diverged silent reload, no UI
dirty diverged "Conflicting changes" toast (Cancel / Take disk / Keep mine)
any deleted tab marker → ⊘ + strikethrough; cmd+S opens Recreate modal
dirty both edited "Conflicting changes" modal at save time

Key changes

  • src/store/fileSync.ts: new file-tree-changed listener, optional filterTaskId / filterRelativePath on the sweep, deleted-files signal with debounced confirmation, requestRecreate / resolveRecreate flow, new checkBeforeSave short-circuit
  • src/store/ui.ts + src/components/ToastContainer.tsx: Toast gets a description field rendered as a muted second line
  • src/components/FileConflictDialog.tsx: new "Conflicting changes" copy and Cancel / Take disk / Keep mine actions
  • src/components/RecreateFileDialog.tsx (new): mirrors FileConflictDialog at 24rem, mounted in both App.tsx and TaskWindowShell.tsx
  • src/components/TaskPanel.tsx: deleted-state tab marker (red ⊘ + red-tinted strikethrough); close button stays visible in deleted state
  • src/components/CodeEditor.tsx: re-verifies disk on cache-hit tab open; save flow routes deleted-state tabs to Recreate modal
  • src-tauri/src/ipc.rs: read_worktree_file emits NotFound: <path> for missing files so the frontend can distinguish deletion from other read failures

Deferred

Best-effort rename-follow (option D5a from the UX discussion) split out to #260: needs in-place migration of editorView open tabs, MRU stack, mainView, files content cache, and CodeEditor's editorStateCache / scrollPositionCache keys. Phase E deleted-state UX is the fallback in the meantime.

Test plan

  • pnpm check clean
  • pnpm test - 629/629 pass (17 new in fileSync.test.ts, including the atomic-write debounce case)
  • cargo check + cargo test - 753 tests pass
  • cargo clippy --no-deps - no warnings
  • Manually: open a file in the main window, edit it externally → editor refreshes without a focus change
  • Manually: edit a file, switch away, then edit externally → cmd+S shows the new "Conflicting changes" modal with Take disk / Keep mine
  • Manually: delete a file externally → tab gets red ⊘ + strikethrough; cmd+S opens the Recreate modal
  • Manually: repeat the above in a detached task window to confirm parity

…ve conflicts

Open editors now react to disk changes immediately instead of waiting for
window focus. fileSync subscribes to the watcher's file-tree-changed event
and sweeps that task's open tabs: clean tabs silently reload, dirty tabs
get a persistent "Conflicting changes" toast that mirrors the save-time
modal (Cancel / Take disk / Keep mine).

Other related fixes in the same area:

- checkBeforeSave short-circuits when the editor has no local edits, so
  pressing cmd+S on an externally-modified file no longer opens a conflict
  dialog that claims you have unsaved edits to lose
- Cached tabs re-verify against disk on reopen, so the editor can't serve
  stale content for files edited externally while the tab was closed
- File deletion: read_worktree_file emits a NotFound: prefix for missing
  files; fileSync flips the tab to a deleted state (red ghost glyph +
  strikethrough). cmd+S on a deleted-state tab opens a Recreate confirm
  modal instead of writing blindly. Deletion declaration is debounced 250ms
  so atomic-write tmp->rename sequences don't briefly flash the deleted UI
- FileConflictDialog and the dirty-tab toast share the same vocabulary
  (Conflicting changes / Cancel / Take disk / Keep mine). Toast description
  is rendered as a second muted line via a new Toast.description field
- RecreateFileDialog mounts in both App.tsx and TaskWindowShell.tsx so
  detached task windows see the same Recreate flow

Best-effort rename detection (follow an open tab when the file moves) is
deferred to a follow-up issue.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant