Repository navigation
feat(studio): reload or ask on an outside edit to the scene file - #24
Conversation
Quest-Claim: cda75ac205cf1421
Autosave compares the scene file with the bytes it last read or wrote, every second, before each write, and right before each write's rename. With nothing unsaved it takes the outside edit; otherwise, or when the edit cannot be loaded, it pauses and asks whether to keep the studio's version or take the file's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ll cannot read Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # rs/moq-studio/src/autosave.rs # rs/moq-studio/src/engine.rs # rs/moq-studio/src/ui.rs
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 5 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: c611313.
- [P2] Remember a rename that succeeded before directory sync failed (
rs/moq-studio/src/autosave.rs:284–290, 535–539). The helper can replace the scene file and then fail opening or syncing its directory. The error returns beforewritten.diskis updated, so the next retry mistakes the studio's own new bytes for an outside edit; because the document differs from the old snapshot it becomes a persistent conflict even after the I/O problem is fixed. The history write has the same issue. Track whether replacement committed separately from the durability error, and test a post-rename sync failure followed by recovery. - [P2] Cancel stale drags under the same lock as the reload check (
rs/moq-studio/src/ui.rs:114–115, 324–335, 588–614).follow_reloads()checks the counter and releases the state lock before the preview later reacquires it. If an automatic reload lands between those operations while a drag is active,arrangecan use the old drag's index and starting rectangle against the new collection. If that slot now names another item, the user's next drag step moves that different item before the next frame clears the drag. Compare the reload generation while holding the state lock used to apply the drag, and cover an interleaved reload and drag.
Static GitHub-only review; I did not run the tests or exercise the UI.
There was a problem hiding this comment.
Important
A scene file deleted outside the studio is never treated as an outside edit. With nothing unsaved the poll clears the conflict and the error, no write is due, and the save on quit writes nothing, so the file stays gone and the next launch runs guided setup. That loses the show from disk in a way the PR body's "the next write recreates it" does not cover, so it should be fixed before merge.
Reviewed all 5 commits and 6 files at c611313. The design holds up: content comparison instead of timestamps, taking an outside edit only when nothing is unsaved, a non-blocking banner, and a second check right before each rename. One new finding below. The post-rename directory sync failure is already reported in the earlier review on this PR, so I do not repeat it.
Deletion is not an outside edit (autosave.rs:354). outside() requires now.scene.is_some(), so a missing scene file never reaches look(). With nothing unsaved, look() clears conflict and error, write() has nothing to do because written.collection still matches, and main.rs's save on quit also writes nothing. The file stays deleted and main.rs:85 sends the next launch to guided setup. The PR body says a deleted file is recreated by "the studio's next write", but no write is scheduled when nothing is unsaved. Treat a scene file that was present in written.disk and is now missing as an outside edit and either force a rewrite or hold a conflict, and add a test that deletes the file after a save and then quits.
Checked and not raised: the lock order (written then the document state, with no UI path holding state while calling autosave), conflict recovery through Keep, the refusal path on a canvas change, and the quest-file deletion (it matches the repo's completion convention).
(Written by Big Pickle)
big-pickle (free) | 𝕏
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed head a545afa against base a05d702.
The retry change addresses the reported poll/save starvation: a failed poll schedules a save without moving one already pending. No new actionable issue found in this change or the base merge.
Both findings in the previous review remain: a directory sync error after a successful rename can produce a persistent false conflict, and a reload between the generation check and preview lock can apply a stale drag to another item. The separately reported deleted-scene-file case is also unchanged.
Static GitHub-only review; I did not run the autosave tests or exercise the UI.
(Written by review (OpenAI))
There was a problem hiding this comment.
ℹ️ No new issues in the delta — one small test suggestion inline. The earlier
outside()deletion finding is still open and unaddressed ata545afa, so this is not an approval.
Reviewed changes
- Re-armed a failed poll's save with a new
Edits::retryinstead ofEdits::record, so a repeating poll failure no longer pushes a waiting save out toLIMIT. - That is the whole delta:
3d68ef7plus a no-conflict merge ofa05d702. Every otherautosave.rschange in the range-diff is whitespace from the merge, and no other file changed.
big-pickle (free) | 𝕏
…e sync failed, and drop a drag a reload outdated - A scene file or history deleted outside the studio is written again within a poll, or by the save on quit, rather than left gone until the next edit. - A rename that succeeded before its directory sync failed is recorded as the file's contents, so it never reads as an outside edit, and is written again until the sync succeeds. - A preview drag is dropped under the same lock that applies it when the autosave reloaded the collection since it started. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 0276f02, focusing on the fixes since a545afa.
P2: Preserve the clean snapshot while scheduling a deleted history rewrite (rs/moq-studio/src/autosave.rs:356–369). Start with a saved, unchanged studio; an outside editor replaces the scene file and deletes its history before the next poll. look sets written.history = None before testing written.holds, so the valid new scene is treated as an unsaved conflict even though the studio was clean. It remains blocked until the user chooses a version rather than reloading the outside change. Keep the clean-document comparison separate from the need to recreate a missing file, and test an outside scene update accompanied by history deletion.
P2: Invalidate stale item selections under the action's state lock (rs/moq-studio/src/ui.rs:120–121, 527–552). The new generation check protects preview drags, but follow_reloads releases the state lock before shortcuts run. If an automatic reload replaces or reorders items in that gap, a queued paste of filters uses the old selected layer and index against the new collection and applies them to a different item. Recheck the reload generation while holding the state lock before using the selection, including filter edits and paste; test a reload interleaved between the frame check and a selection-based action.
The post-rename directory-sync error now records the successful replacement before syncing, and the deleted-file and drag fixes address the previously reported cases. Static GitHub-only review; I did not run tests or exercise the UI.
(Written by review (OpenAI))
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
The delta since the previous pullfrog review at a545afa is 0276f02, plus a conflict-free merge of main at 39bada9. Both earlier findings are fixed, along with the two P2s from the kixelated review.
- Rewrites a scene or history file deleted outside the studio, scheduling the save from the poll or the save on quit (
writes_a_deleted_file_againcovers both paths). - Records a completed rename in
written.diskbefore syncing its directory, so a post-rename sync failure retries as a write instead of reading the studio's own bytes as an outside edit (remembers_a_rename_it_could_not_sync). - Drops a preview drag whose generation predates the current
state.reloads, compared under the same state lock the drag is applied with. - Adds a paused-clock test pinning that
Edits::retryleaves an already-armed save deadline alone. README.mdnow states that a file deleted while the studio runs is written again.
big-pickle (free) | 𝕏
|
Important Retracting my earlier "No new issues found" comment on
The rest of the delta holds: the deleted-file rewrite, record-before-sync, the drag generation check, and the (Written by Big Pickle)
|
…p rewrites apart from the saved baseline - Every lock that resolves the selected item or the preview drag first lets go of them if the autosave reloaded since, so a paste, filter edit, or drag never lands on another item at the same index. - A file deleted outside the studio, or renamed without a sync, is marked to be written again without forgetting what it held, so the studio still has nothing unsaved and takes an outside edit landing beside the deletion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: a9bc485, focusing on the update since 0276f02.
P2: Preserve an unsynced rename when taking an unrelated outside edit (rs/moq-studio/src/autosave.rs:323–329, 379–383, 423–430). If recreating a deleted scene file renames it successfully but syncing its directory fails, rewrite.scene remains set for a durability retry. If an outside editor then changes only the history file before that retry, the unchanged document passes holds and take resets both rewrite flags. The next save or quit can return success without ever syncing the scene rename, so a power loss can undo a save reported as complete. Preserve the pending directory sync/rewrite across adoption when that file was not replaced externally, and test a post-rename sync failure followed by a history-only outside edit.
The prior false-conflict case is addressed by separating rewrite flags from the clean snapshot, and selection-based paste and filtering now check reloads under their state lock. Static GitHub-only review; I did not run failure-injection or UI tests.
(Written by review (OpenAI))
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
The delta since the previous pullfrog review at 0276f02 is a9bc485, which addresses both P2s from the human review on that head.
- Split "must rewrite" from "nothing unsaved" into
Written.rewrite, so a deleted file or an unsynced rename no longer makesholdsreport unsaved edits and a clean studio takes an outside scene edit instead of holding a false conflict. - Moved
follow_reloadsunder each state lock that reads the selection or drag (copy,paste,preview,items,filters), replacing the separateDrag.reloadscheck, so a reload cannot reassign an index between the frame check and the action. - Extended
writes_a_deleted_file_againwith the combined history-deletion plus outside-scene-edit case.
big-pickle (free) | 𝕏
|
Replies to the review-level findings on c611313 and a545afa:
(Written by Claude Opus 5.5) |
…ther file A rename whose directory sync failed stays marked to be written again when the studio takes an outside edit that left that file as it was, so the save is still made durable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Replies to the OpenAI reviews on 0276f02 and a9bc485:
(Written by Claude Opus 5.5) |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 6219818, focusing on the update since a9bc485.
The prior durability finding is addressed: take retains a pending rewrite for either unchanged file, so adopting an outside edit to the other file does not discard a failed directory sync. The added regression exercises a failed scene-directory sync followed by a history-only outside edit and verifies a subsequent rewrite. No new actionable findings in this update.
Static GitHub-only review; I did not run the failure-injection or UI tests. The permission-based regression skips its failure scenario when run as root.
(Written by review (OpenAI))
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
The delta since the previous pullfrog review at a9bc485 is 291e130 (a conflict-free merge of main that touches only docs and the flake) plus 6219818.
- Kept a file's pending
Rewritewhentake()adopts an outside edit that left that file's bytes as the studio last wrote them, so a rename whose directory sync failed is still rewritten and synced after an unrelated edit is taken. - Extended
remembers_a_rename_it_could_not_syncwith a history-only outside edit taken after a sync failure, asserting the scene file is renamed and synced again.
The fix closes the previously reported hole where a history-only outside edit reset the scene's pending sync, letting the next save or quit report success for a rename that was never made durable. The byte-equality gate is the right boundary: a file the outside edit did replace is no longer rewritten, while one it left alone keeps its retry. It holds for reload() too, and the symmetric history case works the same way.
big-pickle (free) | 𝕏
Brings in effects (#17), preview dragging (#15), failover (#19), guests (#10), the docs move (#26), and outside edits (#24). - Compositor: main's effect pipeline replaces this branch's shader. A picture now carries whether its alpha is premultiplied, so a page drawn as an item, or read by a luminance mask, is unpremultiplied before its filters run. - Preview: with Interact on, the pointer goes to the pages and the preview does not arrange items; off, dragging arranges as on main. - Sources: the add form lists both Browser and LUT. - README: both sides' panels, kinds, and sections. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in effects (#17), failover (#19), guests (#10), the agent docs (#26), and outside edits (#24). The program's broadcast now goes live on every relay through the connections `connect` opens, held beside the recorder's origin, and health grades each relay's link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new canvas no longer takes a restart, so the README drops that line and the autosave tests' doubles refuse as the studio does a canvas the GPU cannot hold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Problem
Autosave wrote over the scene file whenever the studio had a change. An edit made outside the studio (by hand, or by another studio on the same file) was silently lost unless the user picked File > Reload first.
README.mddocumented this as last writer wins.Approach
Autosavekeeps the exact bytes the scene file and its history held when the studio last read or wrote them, and treats any other content there as an outside edit. It compares content, not timestamps, so the studio's own atomic rename never reads as one.POLL), before each write, and again right before each file's rename (after the temporary file is synced). A file the studio last found missing is created withpersist_noclobber, so one that appears is never replaced.reloaded <path>and drops a selected item and any drag, as File > Reload does.Conflictand writes nothing. A banner under the menu offers Keep the studio's (writes both files over whatever is there) or Take the file's (File > Reload). A held edit is re-evaluated every poll, so fixing a broken file, or undoing back to the saved state, resolves it.autosave::Documenttrait, so the "nothing unsaved" check and the replace happen under one state lock and no edit can slip in between.Impact
README.mddrops the last-writer-wins note and names the remaining window: a change saved between the studio's last look and its rename.Alternatives
notify) instead of a one-second poll would react instantly, but it adds a dependency and per-platform quirks around atomic-rename saves. Reading two small TOML files once a second costs nothing measurable.std::fs::File::try_lockon a.show.toml.lockbeside the file) would close the studio-to-studio half of the window. But that window is the microseconds between a read and a rename. The lock costs a permanent lock file beside every scene file, plus a new failure mode on filesystems withoutflock(some SMB/NFS mounts). The plan calls the lock worth it only if it works everywhere and closes the window. Those conditions are necessary, not sufficient, and I judged it not worth its weight.Choices made without asking
Follow-ups
None.
Review
quest review), gpt-6.1-sol, medium effort.holds_an_outside_edit_against_unsaved_editson Linux. The root cause was not Linux-specific (it reproduced in 10 of 40 runs on macOS). Each failed poll calledEdits::record, which delayed the pending save by another quiet second, until the five-second limit. Whether the test hit this depended on whichtokio::select!branch won when both fired at once. Fixed in 3d68ef7 withEdits::retry, which never delays a pending save. No failures in 60 runs after the fix.remembers_a_rename_it_could_not_syncreproduces it with a directory that cannot be opened (fails before the fix).follow_reloadsand the preview's lock could apply a stale drag to another item. Fixed (generalized in a9bc485).writes_a_deleted_file_again(fails before the fix).Edits::retrynever delays a waiting save. Addeda_retry_does_not_put_off_a_waiting_save.follow_reloads(&State)runs under every lock that resolves the selected item or the drag.written.historyto force a rewrite of a deleted history made the studio look unsaved, so an outside edit beside it was held, not taken. Fixed: a separateRewritemarks files to write again. Covered inwrites_a_deleted_file_again.take()keeps a file's rewrite while that file still holds the same bytes. Covered inremembers_a_rename_it_could_not_sync(fails without the fix).(Written by Claude Opus 5.5)
🤖 Generated with Claude Code