Skip to content

fix(tabs): notice any change to or deletion of an open SQL file, and report a failed move to Trash - #3088

Merged
datlechin merged 1 commit into
mainfrom
fix/file-tab-external-change-detection
Sep 23, 2026
Merged

datlechin merged 1 commit into
mainfrom
fix/file-tab-external-change-detection

Conversation

@datlechin

Copy link
Copy Markdown
Member

Summary

Two defects in tabs backed by an .sql file on disk, both found while investigating #2505:

  • Missed changes. An older copy restored over an open file (cp -p, Time Machine, rsync -a), a write in the same second as the load, or a file opened with File > Open outside a linked folder got no changed-on-disk notice. Cmd+S then wrote over it.
  • Trash failures. Move File to Trash on a linked SQL file ignored a failure. After a successful move, an open tab still holding the file wrote it back to its old path on Cmd+S.

This branch is stacked on #3083 (it touches FileTextLoader.load too). Review that one first; this diff is the commit on top.

Root cause

  • The check was currentMtime > loadMtime + 0.5 s, so any restore with an older or equal mtime passed. Five load paths also read the date after reading the text, so a write between the two was recorded as already seen.
  • The check ran only when a linked-folder rescan broadcast, so a file opened on its own was never checked before save.
  • trashLinkedFavorite was try? FileManager.default.trashItem(...). A tab whose file vanished read the failed stat as "not modified", and Save recreated the file.

Fix

  • FileStamp records mtime (to the nanosecond), size and inode, taken before the read in every load path (FileTextLoader, File > Open, restore, reopen).
  • SourceFileDiskChange.detect compares stamps with no tolerance and answers .modified or .missing. FileTabBaseline.diskChange(in:) is the single entry point for the save gates, batch save, save on close and the periodic check. loadMtime, externalModificationDetected and the 0.5 s slack are gone.
  • SourceFileDiskChangeMonitor checks every file tab when the window becomes key and when a linked folder changes. It reads the stamps off the main actor, so a stalled network volume cannot freeze the window, and drops a result if the tab was saved or reloaded meanwhile.
  • The banner shows Reload for a changed file and Save As for a missing one. Closing the banner hides it until the file changes again; a file that comes back and goes missing again shows it again.
  • For a missing file, Cmd+S and Save on close open Save As instead of writing to the old path, and the close finishes once Save As has written the new file.
  • A failed move to the Trash is reported ("Couldn't Move File to Trash") and logged.
  • docs/features/sql-files.mdx describes when the check runs and the missing-file banner.

Tests

  • SourceFileDiskChangeTests: stamp comparison.
  • FileTabExternalChangeTests: an older copy restored in place, a same-size rewrite with a new inode, an unchanged file, a deleted file, and the stamp travelling in the payload.
  • SourceFileDiskChangeHandlingTests:
    • deleted, modified-then-deleted, and moved-back files;
    • the window-key check;
    • dismissing, including a missing notice that returns;
    • a check outrun by a save being discarded;
    • batch save and save on close refusing an older restore;
    • saving a deleted file not recreating it;
    • Save As on close, completed and cancelled;
    • a Trash failure.
  • Existing suites that cover the changed types: FileTabBaselineTests, QueryTabManagerDeduplicationTests, TabQueryContentEqualityTests, CommandActionsBulkCloseTests, TabCloseProtectionTests, EditorTabPayloadTests, SessionStateFactoryTests, LinkedSQLFavoriteWriterTests and the FileTextLoader suites.

No UI automation. Each flow needs a file changed or deleted by another process while the app runs, and the UI-test runner is sandboxed away from the files the app has open. The coordinator tests drive the same entry points (handleWindowDidBecomeKey, saveSelectedTabWork, saveFile(of:to:), trashLinkedFavorite).

Risks

  • A file the app cannot stat for another reason (permissions, an unmounted volume) is reported as deleted or moved.
  • If a file goes missing and comes back between two checks, a notice closed before that stays closed.

Deliberately not fixed here

Every save writes UTF-8, whatever encoding the file was read in. A Latin-1 or UTF-16 file is rewritten as UTF-8, and a file whose encoding is known only from its com.apple.TextEncoding extended attribute loses it. A fix that saved in the loaded encoding was written and reviewed, and it made one case worse. Data.write(options: .atomic) drops that attribute, so a Shift-JIS, CP1251 or MacRoman file read back as ISO Latin-1 after one save (measured). It also turned a UTF-16 file identified only by its attribute into little-endian with a byte order mark. It was removed from this PR. A correct fix writes through NSString.write(to:atomically:encoding:) or sets the attribute itself, and needs a test for each encoding source.

@datlechin
datlechin changed the base branch from fix/linked-header-utf8-boundary to main September 23, 2026 15:44
@datlechin
datlechin force-pushed the fix/file-tab-external-change-detection branch from bb1c779 to 87ccc88 Compare September 23, 2026 15:48
@mintlify

mintlify Bot commented Sep 23, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
TablePro 🟢 Ready View Preview Sep 23, 2026, 3:48 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

@datlechin
datlechin merged commit f7e9a15 into main Sep 23, 2026
4 checks passed
@datlechin
datlechin deleted the fix/file-tab-external-change-detection branch September 23, 2026 19:19

This branch was successfully deployed

1 active deployment
staging - docs 87ccc88a Deployed Sep 23, 2026 by mintlify[bot]
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