Skip to content

feat(runtimed-wasm): WASM bindings for notebook doc operations from automerge 0.7#552

Merged
rgbkrk merged 6 commits intomainfrom
540/runtimed-wasm
Mar 6, 2026
Merged

feat(runtimed-wasm): WASM bindings for notebook doc operations from automerge 0.7#552
rgbkrk merged 6 commits intomainfrom
540/runtimed-wasm

Conversation

@rgbkrk
Copy link
Member

@rgbkrk rgbkrk commented Mar 6, 2026

After taking a whole run around with trying to get our frontend speaking automerge directly, I found out (yet again) that Automerge.change auto converts string fields to Object(Text) CRDTs even if they're intended to be Str. There's some frustration in knowing that we have to special case certain kinds of fields. This is a typing problem underneath but also has meaning for the CRDT.

This PR creates a WASM binding of the NotebookHandle from the same automerge = "0.7" crate as the daemon, solving the "phantom cell bug" that was appearing during work on #540.

Root cause

When JS Automerge creates cells via object literal inside Automerge.change(), all string fields become Object(Text) CRDTs. But the Rust NotebookDoc::add_cell() creates id, cell_type, execution_count as scalar Str via doc.put(). The Rust read_str() helper sees Object(Text) where it expects ScalarValue::Str and returns None — the cell is in the doc, sync worked, but get_cells() can't read it.

This isn't a version mismatch or wire format issue. It's a fundamental JS Automerge API behavior: plain strings in object literals become collaborative Text CRDTs. The @automerge/automerge JS package (both v2.2.x and v3.2.x) produces the same result.

Solution

crates/runtimed-wasm — a thin wasm-bindgen wrapper around the same NotebookDoc operations the daemon uses. All cell operations go through doc.put() (scalar Str) and doc.put_object() (Text for source only), matching the daemon's schema byte-for-byte. The frontend calls NotebookHandle methods instead of Automerge.* functions.

What's included

  • NotebookHandle WASM class: load, save, add_cell, delete_cell, update_source, append_source, get_cells, get_cells_json, get_metadata, set_metadata, generate_sync_message, receive_sync_message
  • wasm-pack build output at apps/notebook/src/wasm/runtimed-wasm/ (345KB gzip)
  • 18 Rust unit tests
  • 15 Deno smoke tests: cell CRUD, save/load round-trip, sync between two handles, concurrent cell merges, character-level Text CRDT merge
  • Cross-implementation tests via Python Session (daemon integration)

What's NOT included

This PR is pure additive — no changes to existing code. Wiring NotebookHandle into useAutomergeNotebook is the next step (separate PR on the Phase 2 branch).

Pragmatic vs end state

This WASM is the pragmatic unblock for Phase 2, not the permanent architecture. Once the frontend has a working Automerge doc (even via our WASM) and the Tauri relay is simplified, switching back to @automerge/automerge JS with ImmutableString for non-text fields is a well-scoped change. The WASM gets us to the paved path; the ecosystem (@automerge/codemirror, presence, cursors) is the destination.

Part of #540.

rgbkrk added 6 commits March 5, 2026 20:04
…om automerge 0.7

Spike C: compile NotebookHandle WASM from the same automerge crate as the
daemon, eliminating the JS/Rust version mismatch that causes phantom cells.

- NotebookHandle wraps NotebookDoc with wasm-bindgen exports
- Cell CRUD: add_cell, delete_cell, update_source, append_source
- Sync: generate_sync_message, receive_sync_message (same wire format as daemon)
- Metadata: get/set
- wasm-pack builds to apps/notebook/src/wasm/runtimed-wasm/
- 18 Rust tests pass, 345KB gzipped WASM
…ession

Tests WASM byte compatibility with Rust automerge through the daemon.
Validates save/load round-trips, sync between handles, and full
daemon integration (create cell → execute → output via Python Session).
@rgbkrk rgbkrk mentioned this pull request Mar 6, 2026
45 tasks
@rgbkrk rgbkrk merged commit 8428f47 into main Mar 6, 2026
10 checks passed
@rgbkrk rgbkrk deleted the 540/runtimed-wasm branch March 6, 2026 05:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant