Two editors still need one definition of saved state
I independently designed and implemented the product, XLSX adapter, transports, editor, and recovery model as a local XLSX alpha.
If a person and an AI both read revision 12, accepting both writes without a revision boundary can lose the earlier change. A successful save followed by a lost response is different: automatically retrying a row insertion can duplicate it, and a higher server revision does not identify which request succeeded.
A workbook is more than displayed cell values. A small edit must not silently discard formulas, validation, or charts. The UI engine cannot be assumed to own the original document state.
Keep format preservation and commit authority on the server
Choose a UI engine behind a replaceable boundary
The recorded ADR compares Univer, ONLYOFFICE, and a custom engine. A custom grid would require selection, keyboard, clipboard, and formula UI work. ONLYOFFICE offers broad editing but introduces frame and deployment considerations for an embedded MCP App.
I chose Univer OSS behind SpreadsheetEngine for direct range control and embedding, while the Go adapter owns validated operations, revisions, conflicts, and persistence. This preserves a path to replace the UI engine, at the cost of maintaining compatibility mapping and verification.
Reject stale writes rather than guessing a merge
Browser and AI writes carry an actor, base revision, and operations through one commit path. Of two requests based on revision 12, the first saves revision 13 and the second receives a conflict. A whole batch is validated before candidate mutation.
Structural edits such as row insertion and merges make blind replay unsafe. This coarse boundary may reject simultaneous edits to different cells, but keeps the source state explicit. Finer automatic merging requires additional semantic rules.
Separate persistence failure from an uncertain response
A candidate workbook is written to a temporary file in the same directory and atomically replaces the original. Failure during preparation, writing, sync, close, or replacement preserves the previous authoritative state. If the workbook commits but the history manifest cannot finalize, pending history is reconciled before another write.
On an uncertain write response or event resync, the browser pauses its queue, checks the server snapshot, and presents draft and recovery state instead of replaying writes. SSE replay gaps and session mismatch also require resynchronization.
Source hashes and a cooperative writer lock detect external changes, but do not eliminate an uncooperative writer racing between the last check and replacement.
Make undo a new server revision
Undo and redo restore content through a new revision instead of moving the revision counter backward. Disk snapshots and a manifest recover bounded history after restart. Snapshot count and size limits separate unavailable history from a failed document commit.
Bound reads and defer unproven abstractions
The browser loads viewport ranges incrementally; MCP reads metadata and needed ranges. Browser and MCP credentials are separate, with a single-use fallback URL expiring after two minutes.
Format rules remain in the XLSX adapter. A shared multi-format abstraction is deferred until a second real adapter demonstrates it. Formulas are stored but not calculated by the server, and browser previews are distinguished from source cached results.
Recorded evidence for failure behavior and performance
Human and AI changes, conflicts, persistence, and history now follow one server-owned document state.
Automated safety evidence
The 2026-10-12 report records X-01 through X-06 passing, with race-enabled Go checks and separate browser recovery tests.
- Input boundaries, atomic batches, and exactly one commit from competing same-revision writes.
- Persistence rollback, pending history recovery, external replacement, and stale sessions.
- Replay gaps, slow subscribers, lost-response protection against duplicate structural edits, and session resync.
- Durable undo and redo, restart recovery, bounded history, and monotonic revisions.
Performance in its recorded environment
The 2026-10-12 release profile used a synthetic 400,000-cell workbook across four sheets on darwin/arm64, Go 1.25.0, and Excelize 2.11.0. Recorded times were 757.79 ms to open, 7.81 ms for a 10,000-cell read, 336.87 ms for one-cell commit, and 287.74 ms for a 1,000-cell commit.
The values were recorded on 2026-10-12 using the same synthetic workbook and environment. This workload serves as a release regression baseline.
Trace
- 400K cells
- Release profile workbook4 sheets · 1,000 by 100
- 287.74 ms
- Recorded 1,000-cell batch commit2026-10-12 · darwin-arm64 · Go 1.25
- X-01 to X-06
- Recorded automated safety gatesLocal atomicity, recovery, and history