Commit 2693111
authored
feat(runtimed): add RuntimeStateDoc — per-notebook daemon-authoritative state via Automerge sync (#977)
* feat(runtimed): add RuntimeStateDoc — per-notebook daemon-authoritative state via Automerge sync
Phase 1 of the runtime state documents architecture. Replaces broadcast-
carried state with a read-only Automerge document that the daemon pushes
and clients subscribe to via the existing notebook sync connection.
New frame type 0x05 (RuntimeStateSync) carries Automerge sync messages
for the RuntimeStateDoc alongside the existing notebook doc sync. The
daemon writes kernel status, queue state, and env sync drift. Clients
receive updates via normal Automerge sync — no broadcasts to drop, no
stale state for late joiners.
Daemon side:
- RuntimeStateDoc added to NotebookRoom, synced per-peer in run_sync_loop_v2
- Dual-writes at all KernelStatus, QueueChanged, and EnvSyncState sites
- Change stripping enforces read-only (client mutations silently dropped)
- RoomKernel and IOPub task write status/queue transitions
WASM side:
- Second AutoCommit in NotebookHandle for the state doc
- receive_frame routes 0x05 frames, returns RuntimeStateSyncApplied events
- generate_runtime_state_sync_reply for the Automerge handshake
Frontend:
- runtime-state.ts reactive store with useRuntimeState() hook
- Frame pipeline handles RuntimeStateSyncApplied, sends sync replies
- useSyncExternalStore for zero-overhead React subscriptions
* docs: update protocol and plan docs for RuntimeStateDoc (Phase 1 complete)
* fix(runtimed): wire state_doc writes for kernel_died and clear_queue
The sync functions can't await tokio::RwLock, but the async command
processor in notebook_sync_server.rs can. Write error status + cleared
queue to RuntimeStateDoc in the QueueCommand::KernelDied and
QueueCommand::CellError handlers, right alongside the existing
presence updates.
* feat(frontend): migrate useDaemonKernel to read state from RuntimeStateDoc
Replace broadcast-driven useState for kernel status, queue state,
kernel info, and env sync with derived state from useRuntimeState().
- kernelStatus: derived from RuntimeStateDoc with busy throttle preserved
- queueState: derived from RuntimeStateDoc queue fields
- kernelInfo: derived from RuntimeStateDoc kernel.language + env_source
- envSyncState: derived from RuntimeStateDoc env fields
Broadcast handler slimmed to event-only cases: execution_started,
output, display_update, execution_done, outputs_cleared, comm,
comm_sync, env_progress. State broadcasts (kernel_status, queue_changed,
kernel_error, env_sync_state) kept as no-op cases to avoid log spam.
Removed fetchKernelInfo polling — RuntimeStateDoc sync provides initial
state immediately. Disconnect handler calls resetRuntimeState().
Launch/shutdown no longer manually set status — daemon writes arrive
via sync.
* fix: address Copilot review feedback
- Split RuntimeStateDoc constructors: new() for daemon (stable actor +
scaffold), new_empty() for clients (random actor, no scaffold). Fixes
DuplicateSeqNumber during Automerge sync.
- Remove unused last_state dedup cache field.
- Reuse existing list ObjIds in set_queue/set_env_sync instead of
creating new Automerge objects on every write.
- Conditional state_changed_tx notifications — only send when setters
actually mutated the doc (18 sites across both files).
- Fix lock-then-send pattern in initial RuntimeStateDoc sync to avoid
holding write lock across async I/O.
- Fix sync reply doc comment (immediate, not debounced).
* fix: allow RuntimeStateSync as outgoing frame type + restore runAllCells return type
- send_frame_bytes in Tauri app was rejecting 0x05 frames — the WASM
client needs to send sync replies so the Automerge handshake converges.
- runAllCells accidentally changed to void return — callers in App.tsx
check response.result.
* feat(ui): show queue indicator in execution counter
Differentiate executing vs queued cells in the gutter. Previously both
were shown as executing (pulsing stop icon). Now:
- Executing: [■] with red pulse (unchanged)
- Queued: [⏳] with subtle pulse — proves RuntimeStateDoc queue sync
Split executingCellIds/queuedCellIds in App.tsx. Thread isQueued prop
through NotebookView → CodeCell → CompactExecutionButton.
* feat(ui): replace hourglass emoji with CSS dot + slow breathe animation
6px rounded dot with a 3s ease-in-out opacity cycle (35%→70%).
Reads as ambient 'standby LED' — alive but patient. Font-independent,
doesn't compete with the red pulse on the executing cell.
A column of queued cells looks like a calm queue, not a Christmas tree.
* fix(ui): don't re-execute when clicking a queued cell's gutter button
* fix(ui): show pointer cursor on execution button when clickable
* feat(python): add RuntimeStateDoc support to Python bindings
- SharedDocState now holds a RuntimeStateDoc + sync state
- sync_task.rs applies RuntimeStateSync frames and sends replies
(same encode-inside-lock, send-outside pattern as AutomergeSync)
- DocHandle.get_runtime_state() reads from local replica (no round-trip)
- PyRuntimeState/PyKernelState/PyEnvState exposed to Python
- Session.get_runtime_state() available from Python
- collect_outputs polls RuntimeStateDoc queue instead of waiting solely
on ExecutionDone broadcasts (falls back to broadcast for KernelError)
* fix: address Codex review — bootstrap race, collect_outputs race, teardown reset
P1: do_initial_sync now buffers RuntimeStateSync frames during the
handshake and replays them into SharedDocState. Fixes Python/full-peer
clients starting with an empty RuntimeStateDoc on quiet notebooks.
P1: collect_outputs requires the cell to be SEEN in the queue before
treating its absence as completion. Prevents returning empty outputs
when the RuntimeStateDoc replica hasn't converged yet.
P2: resetRuntimeState() called on both daemon:ready re-bootstrap and
effect teardown, preventing stale state from a previous notebook.1 parent 877b5af commit 2693111
File tree
33 files changed
+2088
-465
lines changed- apps/notebook/src
- components
- hooks
- lib
- wasm/runtimed-wasm
- contributing
- crates
- notebook-doc/src
- notebook-protocol/src
- notebook-sync/src
- notebook/src
- runtimed-py/src
- runtimed-wasm/src
- runtimed
- src
- tests
- src/components/cell
33 files changed
+2088
-465
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
303 | 303 | | |
304 | 304 | | |
305 | 305 | | |
306 | | - | |
| 306 | + | |
| 307 | + | |
307 | 308 | | |
308 | | - | |
309 | | - | |
310 | | - | |
| 309 | + | |
311 | 310 | | |
| 311 | + | |
312 | 312 | | |
313 | 313 | | |
314 | 314 | | |
| |||
1193 | 1193 | | |
1194 | 1194 | | |
1195 | 1195 | | |
| 1196 | + | |
1196 | 1197 | | |
1197 | 1198 | | |
1198 | 1199 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
| 96 | + | |
96 | 97 | | |
97 | 98 | | |
98 | 99 | | |
| |||
131 | 132 | | |
132 | 133 | | |
133 | 134 | | |
| 135 | + | |
134 | 136 | | |
135 | 137 | | |
136 | 138 | | |
| |||
338 | 340 | | |
339 | 341 | | |
340 | 342 | | |
| 343 | + | |
341 | 344 | | |
342 | 345 | | |
343 | 346 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
| 48 | + | |
48 | 49 | | |
49 | 50 | | |
50 | 51 | | |
| |||
318 | 319 | | |
319 | 320 | | |
320 | 321 | | |
| 322 | + | |
321 | 323 | | |
322 | 324 | | |
323 | 325 | | |
| |||
487 | 489 | | |
488 | 490 | | |
489 | 491 | | |
| 492 | + | |
490 | 493 | | |
491 | 494 | | |
492 | 495 | | |
| |||
554 | 557 | | |
555 | 558 | | |
556 | 559 | | |
| 560 | + | |
557 | 561 | | |
558 | 562 | | |
559 | 563 | | |
| |||
652 | 656 | | |
653 | 657 | | |
654 | 658 | | |
| 659 | + | |
655 | 660 | | |
656 | 661 | | |
657 | 662 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| 28 | + | |
28 | 29 | | |
29 | 30 | | |
30 | 31 | | |
| |||
176 | 177 | | |
177 | 178 | | |
178 | 179 | | |
| 180 | + | |
179 | 181 | | |
180 | 182 | | |
181 | 183 | | |
| |||
268 | 270 | | |
269 | 271 | | |
270 | 272 | | |
| 273 | + | |
271 | 274 | | |
272 | 275 | | |
273 | 276 | | |
| |||
0 commit comments