feat(desktop): canvas version history, restore, and conflict-checked save - #6775
Closed
wpfleger96 wants to merge 3 commits into
Closed
feat(desktop): canvas version history, restore, and conflict-checked save#6775wpfleger96 wants to merge 3 commits into
wpfleger96 wants to merge 3 commits into
Conversation
Member
Author
|
the crates-side PR is up: #6780 (relay |
…save Expose the canvas head event id through the TS API and send it as the optimistic-concurrency `expected-revision` on every save. When a concurrent edit moved the head, the relay reject surfaces as a distinct "canvas changed — reload" state instead of a generic error. Add a get_canvas_history command over the retained kind:40100 stream and a history panel with author, timestamp, a line diff against the current content, and a Restore action. Restore publishes a new head carrying the selected revision's content under the same conflict guard — it never mutates or deletes history. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
force-pushed
the
hayt/canvas-history-desktop
branch
from
August 25, 2026 16:15
4441474 to
7b3aa19
Compare
Gate canvas existence on the persisted revision id, not content truthiness: an empty-string canvas is a valid kind:40100 revision (and restore can republish one), so keying existence, the Create/Edit label, and the History section on content hid retained history for an existing empty canvas. Bound get_canvas_history's page size to the relay read maximum. A request above 1,000 is silently clamped by the relay, which made `events.len() == page_size` false and nulled the cursor even when older revisions remained, stranding them behind an unreachable page. Reject outside 1..=1000. Reset the shared restore mutation on revision selection change so a failed restore's error can no longer render under a different row. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…esktop * origin/main: Add database pressure observability (#6700) revert fixed mention highlight (#6716) highlight search terms in results and messages (#6702) fix(desktop): make lightbox zoom controls interactive (#6710) Support community deletion in versioned media buckets (#6738) Fix TipTap editor mount race (#6779) feat(buzz-agent): gate LLM tool calls on session/request_permission (#5712) Add staging dev relay image workflow (#6709) Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
Member
Author
|
Consolidated into #6780, which now covers the full canvas version-history feature (relay/DB/SDK/CLI + Desktop history UI). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds channel canvas version history, restore, and conflict-checked saves to the desktop app. Every canvas write is a regular signed kind:40100 event the relay retains, so the full revision stream is already queryable — this exposes it and guards writes with optimistic concurrency.
What changed
getCanvasnow surfaces the loaded revision aseventIdonCanvasResponse(the Rust command already returned it; the TS layer dropped it).set_canvas/setCanvastake an optionalexpectedRevision, and the editor sends the head id it snapshotted at edit-start (not the live head — a background refetch can move it mid-edit). When the head moved, the relay rejects and the editor renders a distinct "canvas changed — reload" message instead of a raw error.canvasConflict.tsrecognizes all three frozen reject strings as reload-required conflicts:conflict: canvas changed since it was loaded,conflict: canvas revision does not exist, andconflict: canvas write does not supersede the current head.expected-revision: "none"rather than omitting the tag, so a concurrent first creation is rejected as a conflict instead of silently overwritten.set_canvassignscreated_at = max(now, asserted_head.created_at + 1)so an accepted tagged write always sorts strictly ahead of the head it asserts (created_at DESC, id ASC), keeping the accepted write the visible head even under same-second or behind-clock writers.get_canvas_historyTauri command pages the kind:40100 stream newest-first via a composite(until, before_id)cursor that mirrors the relay read order, so ties on a second never skip or repeat a revision. The canvas view gains a History toggle listing each revision's author, timestamp, a line diff against the current content, a "Load older" control, and — for editors — a Restore action.expected-revisionguard, so a concurrent edit surfaces the same conflict state as a normal save.Canvas API and types moved into dedicated
tauriCanvas.ts/canvasTypes.tsmodules, and the canvas query/mutation hooks intocanvasHooks.ts(all re-exported from the existing barrels) to keep the cappedtauri.ts,types.ts, andhooks.tsunder the desktop file-size ratchet.expectedRevisionis optional end-to-end, so every flow except the conflict path works against currentmain; the relay-side precondition,nonesentinel, head-advancement guarantee, and idempotent-replay exception ship in the crates PR, #6780. PR2 merges after PR1.Related: #6780 (relay + SDK + CLI)
Originating discussion: buzz://message?channel=326d56bc-c96c-4af0-86a1-5e804cd1b467&id=d63c63f432a49166b25a27e48bec69b9767b6c0f148c383baa12da574b383415