* Apply teammates' changes item by item instead of reloading the page Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hold duplicates and full page swaps, look up rotation targets by id, keep held items in place Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep each element's sortIndex so a deletion re-sends no other element Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Send a moved element's new stacking, keep held undrawable lineup bases Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Stack by hydrated order on ties; held items take the server's place Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Ask what to do with unsaved work on a page a teammate deleted Live sync cannot send work to a page the server no longer has, so the unsaved mark never cleared and the page swap waited forever: the canvas stayed on the deleted page with the sync button spinning, and switching pages dropped the work without a word. When the page on screen is gone and holds work the server never got, the session now stops and asks: restore the page (re-created with everything on screen, then synced from it) or discard the changes and move to a page that exists. With nothing unsent, the stale unsaved mark is cleared and the canvas moves on as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Restore only what can be restored whole, and wait out work in flight Review round 1: - Rejected work discarded before a restore completes the queue's adoption, so its replacement ops are not skipped. - The restore waits for its page add's ack behind other batches, and takes the page as its base only when it came back empty: a page a teammate restored first is never read as deletions here. - A page whose images this device cannot upload again is not restored. - Discard waits for ops already sent for the page, then withdraws what they left; while any remain, the choice stays open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tell, don't restore: the server cannot take a deleted page back whole Review round 2 found restoring the page in place still loses or corrupts work: the purge of its rows runs after the page is gone, so restored ids can be refused while the page reads as saved; lineup screenshots were not re-uploaded; page metadata queued before the deletion was dropped. Each is a guess the client cannot verify, and AGENTS.md says to fail loudly rather than save something wrong. The dialog now tells the user their latest changes could not be saved, and the canvas moves on once they have read it. Keeping the work needs the server's help. Switching pages checks the page being left first, so the notice shows even when a switch comes before anything else asked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Check a deleted page's work wherever the canvas leaves it Review round 3: - Leaving a page (a switch, or the replace a teammate's change waits on) checks for lost work itself, busy editor or not, and whatever the save flags say; only the automatic notice waits for a gesture to end. - The user's own delete of the page, still on its way, is not a teammate's deletion and says nothing. - A new queue operation, discardDeletedPage, drops every record of the page with its successor, so a paused edit with a successor no longer keeps the notice open for good. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Recheck a switch after its flush; discard a deleted page's work whole Review round 4: - A switch checks the page it leaves again after flushing it, so a deletion that lands meanwhile still tells the user; the prepared page transition is unwound. - discardDeletedPage also drops work whose first save could not be verified, and recomputes the queue's error from what remains, so a dropped op's error no longer holds the canvas on the deleted page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Leave a deleted page by loading the next one, not by waiting for it Review round 5: OK used to hand the swap to the reapply that waits for all pending cloud work, so another page's paused edit left the deleted page on screen, editable, with its protection gone. OK now loads the server's page itself, as a page switch would; work queued for other pages is not on this canvas and keeps waiting. The notice stays until the new page is on screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep an unsent map change and read again when leaving a deleted page Review round 6: - Loading the page that replaces the deleted one keeps the map and theme on screen while a change to them is still on its way, as using the cloud version after a conflict already did (shared helper now). - OK reads the server again first, so a read that failed earlier does not fail every retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Resume a page's live read when a refresh reads it Review round 7: when selecting a page failed (say the page a teammate deleted moved the editor to one that would not load), no live read was left running, and a later refresh fetched the page without starting one: teammates' edits stopped arriving. A refresh that reads the selected page now starts its live read if none is running for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Address CodeRabbit review - A page's live read claims its turn before cancelling the old one, so two refreshes at once start one watcher, not two. - An image still uploading keeps the unsaved mark: it is not stale. - The sync button shows the deleted-page notice if it mounts while one is already waiting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep unchanged items as they are on a merge; rebuild only new drawing paths Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Judge unchanged against what the canvas drew, not the last ack Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Held items conflict against what the user saw, not an unseen ack Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Count acks of ops this session sent as seen, even after more typing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Back out the unchanged-item reuse; keep its regression tests Reusing unchanged items on a merge needed live sync to track what the canvas drew separately from what the server acked, and each attempt at that left another way to rewind a revision. Server-wins for every item the user isn't holding is the reviewed behaviour; its path rebuild cost matches the full reload it replaced. The three new tests still hold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Let a deleted page go of a send the app closed in the middle of A page op saved as in flight before the app closed is only waiting to be replayed after a restart; nothing is sending it. discardDeletedPage kept every in-flight record, so offline, OK could never clear the deleted page's work and the user stayed on a page that no longer exists. It now keeps a record only while this process is sending its strategy, the same rule the queue view uses. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Never blame a teammate for the user's own page delete Once the user's delete of the page on screen was accepted, it left the queue, so an earlier edit on the page still paused or refused made the page read as deleted by a teammate. The notice then asked them to throw away work to leave a page they deleted, and blocked the switch the delete planned. The session now remembers pages whose delete from this device the server accepted; the paused or refused edit stays in the sync status. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Drop uploads only a deleted page's discarded work referenced Leaving a deleted page drops its queued changes but left their image uploads, and the bytes they hold, retrying later. An image placed after the delete never got a change the server could take, so its staged upload waited for a reference forever. Leaving now marks the strategy's uploads for a check before their next attempt: one goes, with its bytes, only if no queued change and no page on the server references its image. Marks made offline wait for the connection. A check that cannot prove the image unreferenced lets the upload go on. Session tests now use a recording media queue: the real one had no storage there, and nothing in them exercised it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Notice the user's own delete even before the queue listener runs The save state listens to the queue before the session does, so when the ack takes the delete out of the queue, the save state's change can run the deleted-page check before the session's queue listener records the page as deleted here. With the live read ahead of the ack and an earlier edit paused, the teammate notice still showed. The check now records the accepted deletes in the queue it reads. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Check restored uploads for a reference before their first attempt The mark a discarded page's work leaves on its uploads lives in memory, so an app closed before the connection came back lost it and the upload went on. Every upload restored at launch with a durable reference now starts marked; staged ones already had their own check. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Keep an upload's reference check when removing it fails The check before an upload's attempt ran outside the upload's error handling, and dropped its mark first. When removing an unreferenced job failed, the error escaped the upload worker, stopping it, and the next attempt uploaded the discarded image. The mark now stays until the check settles, a failed removal waits for the usual retry, and the job never uploads in between. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Settle upload reference checks before uploading, not inside it A job marked for a reference check was checked when the worker picked it. A removal that kept failing then left it first in line on every attempt, holding up the uploads behind it, and a staged job took the removal on a path that could throw out of retryNow. A marked job now never uploads: each retry first settles the checks it can (online, no upload running), a failed removal leaves the job marked and waiting, and every other job goes on. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Keep a staged upload's check when the server can't answer; one check at a time A reference check that could not tell (a failed server read) dropped the job's mark. A staged job has no other way out, so it then waited forever. The check now tells deleted, still referenced, and unknown apart: unknown keeps a staged job's mark and lets a job that can upload go on, as before. Two retries could each run the check on the same job; one that could not tell let it start uploading while the other deleted it. Checks now run one pass at a time, and every retry waits for the running pass before starting an upload. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Say what the one-pass reference check actually guarantees Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
13 files changedLine totals unavailable