Skip to content

feat(notebook): add useAutomergeNotebook hook via runtimed-wasm#553

Merged
rgbkrk merged 5 commits intomainfrom
540/phase-ii-set-phasers-to-wasm
Mar 6, 2026
Merged

feat(notebook): add useAutomergeNotebook hook via runtimed-wasm#553
rgbkrk merged 5 commits intomainfrom
540/phase-ii-set-phasers-to-wasm

Conversation

@rgbkrk
Copy link
Member

@rgbkrk rgbkrk commented Mar 6, 2026

Phase 2 of the local-first Automerge migration. The frontend owns a local Automerge document via the runtimed-wasm NotebookHandle. All cell mutations (add, delete, edit source) execute instantly in WASM — no RPC round-trip. React state is derived from the doc. Sync messages flow through the Tauri relay to the daemon.

What changed

  • useAutomergeNotebook — WASM-backed hook with identical interface to useNotebook. Bootstrap from get_automerge_doc_bytes, mutations via NotebookHandle, sync via automerge:from-daemon events.
  • useNotebookDispatch — Feature flag toggle. VITE_USE_AUTOMERGE=true cargo xtask dev, or localStorage.USE_AUTOMERGE_FRONTEND, or ?automerge=true.
  • materialize-cells — Extracted shared output resolution (blob manifests, stream merging) from useNotebook so both hooks use it.
  • App.tsx — Imports from useNotebookDispatch instead of useNotebook directly.

Key constraint

The JS frontend never creates Automerge objects directly. All doc mutations go through NotebookHandle methods which execute the same Rust code as the daemon — no string→Text CRDT type mismatch.

QA

VITE_USE_AUTOMERGE=true cargo xtask dev

Tested: cell CRUD, source sync, execution with outputs, ipywidgets (IntSlider with bidirectional comms), save-as with room reconnect, restart & run all (4 cells). Feature flag off: existing useNotebook works unchanged.

Known limitation

Initial load shows empty notebook briefly while WASM initializes and doc bytes load. Optimization deferred — the daemon status banner covers the gap in practice.

Depends on #547's relay infrastructure (cherry-picked to 540/relay-cherry-pick).

claude and others added 3 commits March 5, 2026 21:54
…tend automerge

Add the Tauri-side infrastructure for Phase 2 local-first migration:

- `get_automerge_doc_bytes` command: exports the local Automerge doc as
  bytes so the frontend can initialize its own replica via Automerge.load()
- `send_automerge_sync` command: receives raw sync messages from the
  frontend, applies them to the local doc, and relays to the daemon
- Raw sync relay: spawns a task that forwards incoming daemon sync
  messages to the frontend via `automerge:from-daemon` events
- `into_split_with_raw_sync`: new split variant that accepts an optional
  channel for raw sync byte forwarding
- Frontend peer sync state tracking in the background task for proper
  bidirectional sync message generation

The Tauri process keeps its Automerge replica (transitional) while also
forwarding raw sync bytes for the frontend's future local document.

Part of #540 Phase 2.

https://claude.ai/code/session_01Vkb1BVso7Bh9TxHeegQwvW
The frontend_peer_state was initialized as sync::State::new() at
run_sync_task startup. During cell population (before the frontend
called GetDocBytes), daemon sync acks triggered generate_sync_message
for the frontend, queuing stale messages in raw_sync_tx. When
GetDocBytes later reset the state via virtual sync, those stale
messages were already buffered. The frontend loaded the doc bytes,
then applied the stale sync messages with a mismatched sync state,
causing CRDT merges that produced phantom cells — cells present in
the Automerge list (cell_count=2) but with unreadable IDs
(available=1).

Fix: start frontend_peer_state as None. Only initialize it inside
GetDocBytes after the virtual sync handshake. Before that point, all
generate_sync_message guards short-circuit on the None check, so no
sync messages reach the frontend until its doc state is established.

https://claude.ai/code/session_01Vkb1BVso7Bh9TxHeegQwvW
Phase 2 of the local-first Automerge migration. The frontend owns a local
Automerge document via the runtimed-wasm NotebookHandle. All cell mutations
(add, delete, edit source) execute instantly in WASM — no RPC round-trip.
React state is derived from the doc. Sync messages flow through the Tauri
relay to the daemon.

Key changes:
- useAutomergeNotebook: WASM-backed hook with identical interface to useNotebook
- useNotebookDispatch: feature flag toggle (localStorage or ?automerge=true)
- materialize-cells: extracted shared output resolution from useNotebook
- App.tsx: imports from useNotebookDispatch instead of useNotebook directly

The JS frontend never creates Automerge objects directly — all doc mutations
go through NotebookHandle methods which execute the same Rust code as the
daemon, guaranteeing schema compatibility (no string→Text CRDT mismatch).

Feature flag OFF: existing useNotebook works unchanged.
Feature flag ON: useAutomergeNotebook handles everything locally.
@rgbkrk rgbkrk changed the base branch from 540/relay-cherry-pick to main March 6, 2026 06:34
rgbkrk added a commit that referenced this pull request Mar 6, 2026
@rgbkrk rgbkrk mentioned this pull request Mar 6, 2026
45 tasks
@rgbkrk rgbkrk enabled auto-merge (squash) March 6, 2026 06:43
@rgbkrk
Copy link
Member Author

rgbkrk commented Mar 6, 2026

It's working and it's flying.

One minor thing I Opus noticed: addCell does the WASM mutation first, then the optimistic React update. That's the right order — WASM is the source of truth. But updateCellSource does React first, then WASM. Both are fine since they're synchronous, but worth noting the inconsistency. Not blocking.

rgbkrk added 2 commits March 5, 2026 22:53
…e default

Delete useNotebook.ts (legacy RPC+optimistic hook) and
useNotebookDispatch.ts (feature flag toggle). App.tsx now imports
useAutomergeNotebook directly.
@rgbkrk rgbkrk merged commit 38f16c6 into main Mar 6, 2026
14 checks passed
@rgbkrk rgbkrk deleted the 540/phase-ii-set-phasers-to-wasm branch March 6, 2026 07:25
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.

2 participants