Skip to content

Reload the open document when its .comp package changes on disk - #116

Merged
robbietilton merged 1 commit into
robbietilton:mainfrom
Josusanz:feature/reload-on-disk-change
Sep 25, 2026
Merged

robbietilton merged 1 commit into
robbietilton:mainfrom
Josusanz:feature/reload-on-disk-change

Conversation

@Josusanz

Copy link
Copy Markdown
Contributor

Closes #103. Follows the requirements you listed there, point by point.

Event-driven, no polling, reload off the main thread. ProjectWatcher listens to the kernel's file system events (DispatchSource.makeFileSystemObjectSource) for the package folder, its manifest.json and its images folder. I went with vnode events instead of NSFilePresenter after checking how writers behave: a presenter is only told about writes that go through NSFileCoordinator, and agents, scripts, git checkout and a fair share of sync clients write plainly, so a presenter never fires for exactly the cases the issue is about. Events are coalesced (300 ms) and the reload goes through ProjectStore.shared.load, the same actor path as an open, so nothing blocks the main thread on big projects.

Only reload on a real change. ProjectDigest hashes the manifest and every asset's bytes. A package that was only touched (metadata rewritten by a sync client, identical bytes saved again) has the same digest and is ignored; there is no reload and no flicker. The digest is remembered after every open, save and reload.

Half-written packages are ignored. If the load fails, the open document is left alone with no message; the next change on disk is checked afresh. A package caught mid-write also fails the digest match against nothing, so it waits too.

Never react to our own saves. The save path sets a flag for its duration and then records the digest of what it wrote, so the watch sees our own package as already known.

Unsaved work is never replaced without asking. A document with unsaved changes gets a sheet, Revert / Keep Mine. Keep Mine remembers the version on disk so it is not asked again until the package changes once more. A hidden tab with unsaved changes is asked when it comes to the front, not while it is out of sight. Edits in progress (brush, transform, sheets) defer the check with a backing-off retry rather than pulling the document out from under them.

No change to the .comp format. The reload installs the snapshot while keeping the viewport, collapsed folders and selection where the layers still exist; undo history starts over, as after an open (it is session-only, per docs/project-format.md).

Tests: ExternalChangeTests covers the digest (touch vs. content), the in-place reload keeping viewport and selection, an external save and an in-place manifest rewrite both reloading, our own save and metadata touches not reloading, a half-written manifest being ignored until it loads, and unsaved work staying put. The full CompositorTests target passes here (Xcode 26.6, macOS 26.5); TiledLayerTests/paintingAtTheLayersEdgeDoesNotChangeIt failed once under the full parallel run and passed in isolation, unrelated to this change.

I have not been able to test against Dropbox from here; happy to adjust anything that shows up in your run.

🤖 Generated with Claude Code

When something else writes an open project's .comp package, the document
follows it: same tab, same viewport, same selection where the layers still
exist. Only a real change counts, unsaved work is never replaced without
asking, and a package caught half written is left alone.

- ProjectWatcher listens to the kernel's file system events for the package
  folder, its manifest and its images folder. No polling, and it sees writes
  from any process, coordinated or not, which NSFilePresenter would miss.
  Events are coalesced, and every event re-arms the watch by path because an
  atomic save swaps the package folder.
- ProjectDigest hashes the manifest and every asset, so a package that was
  only touched (sync clients rewriting metadata, identical bytes saved again)
  is not a change.
- ProjectController checks a change off the main thread, loads through the
  same ProjectStore path as an open, ignores its own saves and packages that
  fail to load, waits for an edit in progress to finish, and asks with a
  Revert / Keep Mine sheet when the document has unsaved changes; a hidden
  tab with unsaved changes is asked when it comes to the front.
- EditorSession.reloadProject installs the snapshot while keeping the
  viewport, collapsed folders and selection. Undo history starts over, as
  after an open.

No change to the .comp format. Closes robbietilton#103.
@robbietilton
robbietilton merged commit 25ff6c3 into robbietilton:main Sep 25, 2026
1 check passed
@robbietilton

Copy link
Copy Markdown
Owner

Merged, thanks — this is great, and it follows #103 exactly. One change on main: the fingerprint now reads the manifest plus each image's name and size instead of hashing every image's bytes. Every save and open takes a fresh digest, and on a large project reading all the PNGs held each save noticeably. Anything editing a project rewrites the manifest, and a PNG with changed pixels all but always changes size, so it still catches real edits. All your ExternalChangeTests pass with it.

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.

Reload the open document when its .comp package changes on disk

2 participants