Versioned document session (save queue, drafts, CAS, explicit conflict)
Generated from
pattern-library/registry.json— do not edit by hand; change the registry and runbun run patterns:build.
Category: Durable Execution · Tier: deep · Maturity: proven (verified 2026-10-10 @ 91f61a76-dirty) · Risk: low — the failure mode is a silent overwrite, which the version check turns into a visible conflict
A browser-edited document stays server-authoritative: debounced serialised saves carrying their base version, compare-and-swap rejection as an explicit conflict state, per-writer local drafts no other tab can erase, and reloads that fetch before they replace.
When to use: Use for documents a user edits over time while the server, another tab or an AI write can change them — sheets, notes, settings pages with autosave.
Requires: postgres
Avoid when
- The input is append-only (a message, a form submission) — one request with an idempotency key suffices
- Two people must type into the same paragraph at once — that is the CRDT case
Alternatives
- A CRDT (Yjs, Automerge) or sync engine — prefer when concurrent rich-text co-editing is the product, and per-document authorisation can be scoped
- Pessimistic save (button, no autosave) — prefer when edits are rare and a lost tab costs nothing
Docs
- docs/blueprint/integrity/execution.md#versioned-document-session — The state machine, and why not a CRDT (GitHub · GitLab)
Code
src/lib/state/document-autosave.ts— Framework-free save queue owned by the document, generic over its content (GitHub · GitLab)src/lib/state/document-drafts.ts— One localStorage slot per writer; siblings offered, never applied (GitHub · GitLab)src/lib/components/desk/panels/spreadsheet/spreadsheet-session.svelte.ts— PUT with expectedVersion (GitHub · GitLab)src/routes/api/desk/files/[id]/+server.ts— 409 version_conflict on a stale base (GitHub · GitLab)
Tests
src/lib/state/document-autosave.test.ts(GitHub · GitLab)src/routes/api/desk/files/[id]/server.test.ts(GitHub · GitLab)
Invariants
- Every save carries its base version, and the server rejects a stale one.
- Unsaved work lives in exactly one place per writer and survives until a save of it succeeds or the user discards it.
- A conflict is a state the user resolves — never last-write-wins.
- The session lives as long as the document, not a component; closing a panel flushes, never drops.
Emulation notes
- Keep the queue framework-free and test it with fake timers; the UI only subscribes, so a remounting component cannot cancel a save.
- Name draft slots by user, document and writer: one shared slot per document lets tab B silently erase tab A's only copy.
- Make reload fetch-then-replace: a reload that clears first turns an offline moment into data loss.
Machine-readable record: versioned-document-session in pattern-library/registry.json.