Skip to content

feat(notebook): reactive metadata via useSyncExternalStore, migrate reads+writes to WASM doc#569

Merged
rgbkrk merged 1 commit intomainfrom
540/wasm-metadata-reads
Mar 6, 2026
Merged

feat(notebook): reactive metadata via useSyncExternalStore, migrate reads+writes to WASM doc#569
rgbkrk merged 1 commit intomainfrom
540/wasm-metadata-reads

Conversation

@rgbkrk
Copy link
Member

@rgbkrk rgbkrk commented Mar 6, 2026

Migrates metadata reads and writes from Tauri IPC to the frontend's local WASM Automerge document. Uses React 18's useSyncExternalStore for reactive metadata subscriptions — hooks re-render automatically when the doc changes.

Architecture

The WASM NotebookHandle is the metadata store. Three events notify subscribers:

  • setNotebookHandle() — bootstrap / reconnect
  • receive_sync_message() — incoming daemon Automerge sync
  • setMetadataSnapshot() — local writes (add/remove deps)

React hooks subscribe via useSyncExternalStore(subscribe, getSnapshotJson).

New reactive hooks (notebook-metadata.ts)

Hook Replaces
useNotebookMetadata() invoke("get_notebook_dependencies") + invoke("get_conda_dependencies") + event listeners
useDetectRuntime() invoke("get_notebook_runtime") + event listeners
useUvDependencies() invoke("get_notebook_dependencies") + loadDependencies() callback chain
useCondaDeps() invoke("get_conda_dependencies") + loadDependencies() callback chain
useDenoFlexibleNpmImports() invoke("get_deno_flexible_npm_imports") + loadFlexibleNpmImports() callback chain

Write path

Dependency mutations (add, remove, clear, set channels/python) go through WASM:

  1. Read current metadata from WASM doc
  2. Mutate in TypeScript (with case-insensitive package name dedup)
  3. Write back via handle.set_metadata() + generate_sync_message()invoke("send_automerge_sync")
  4. notifyMetadataChanged() → subscribers re-render with new state
  5. invoke("approve_notebook_trust") for HMAC re-signing (stays in Tauri — needs filesystem access)

What was removed

  • onNotebookHandleReady callback pattern (replaced by useSyncExternalStore)
  • loadDependencies() / loadFlexibleNpmImports() imperative read functions
  • notebook:metadata_updated event listeners in dependency hooks
  • Manual re-read calls after every write operation
  • Dead Tauri commands: get_deno_permissions, set_deno_permissions

Review fixes

  • Codex: setCondaChannels/setCondaPython now create the conda section if absent (was silently failing)
  • Codex: Added language_info.name == "deno" fallback to runtime detection
  • Opus: Derived hooks wrapped in useMemo for stable object identity
  • Opus: Simplified getSnapshotJson defensive check
  • Opus: Added comment about cell mutations not notifying metadata subscribers

QA

UV dependencies

  • New notebook: add UV dep → pillbox appears immediately
  • Add second dep → both pillboxes show
  • Remove a dep → pillbox disappears
  • Clear all deps → "No inline dependencies" message returns
  • Set requires-python version

Existing notebooks

  • Open saved notebook with existing UV deps → pillboxes show on load
  • Open saved notebook with legacy metadata.uv format (pre-runt namespace) → deps show after trust approval

Conda dependencies

  • Add conda dep → pillbox appears
  • Remove conda dep → pillbox disappears
  • Set channels on notebook without existing conda section → creates section
  • Set python version on notebook without existing conda section → creates section

Runtime detection

  • File > New Notebook → shows "Python"
  • File > New Notebook As > Deno → shows "Deno"
  • Open existing Deno notebook → shows "Deno"

Deno config

  • Toggle flexible npm imports on Deno notebook → setting persists

Cross-window

  • Add dep in window A, window B updates via Automerge sync

Import from project files

  • Import from pyproject.toml → deps appear (may have brief delay for sync round-trip)
  • Import from pixi.toml → deps appear

Trust

  • Untrusted notebook shows trust banner
  • Trust & Start approves and launches kernel
  • Adding deps re-signs trust automatically

Save / Clone

  • Save notebook with deps → deps persist in .ipynb
  • Clone notebook with deps → cloned notebook has deps

@rgbkrk rgbkrk force-pushed the 540/wasm-metadata-reads branch from 728536d to 2a00193 Compare March 6, 2026 18:21
@rgbkrk rgbkrk force-pushed the 540/wasm-metadata-reads branch 5 times, most recently from 12bbbc6 to a79338a Compare March 6, 2026 19:10
@rgbkrk rgbkrk force-pushed the 540/wasm-metadata-reads branch from a79338a to 56230f7 Compare March 6, 2026 19:26
@rgbkrk rgbkrk marked this pull request as ready for review March 6, 2026 19:49
@rgbkrk rgbkrk changed the title feat(notebook): migrate metadata reads from Tauri IPC to WASM doc feat(notebook): reactive metadata via useSyncExternalStore, migrate reads+writes to WASM doc Mar 6, 2026
@rgbkrk rgbkrk force-pushed the 540/wasm-metadata-reads branch from 56230f7 to e6fa1dd Compare March 6, 2026 20:08
Phase 2 of the WASM metadata migration plan. Frontend hooks now read
metadata directly from the local Automerge document via the WASM
NotebookHandle, eliminating Tauri IPC round-trips for reads.

New shared module (notebook-metadata.ts):
- Module-level handle accessor (same pattern as daemonCommSender)
- TypeScript interfaces matching Rust NotebookMetadataSnapshot serde shape
- Typed reader functions: getUvDependencies(), getCondaDependencies(),
  getDenoFlexibleNpmImports(), getMetadataSnapshot(), detectRuntime()

Migrated reads:
- App.tsx: get_notebook_runtime → detectRuntime() from WASM
- useDependencies: get_notebook_dependencies → getUvDependencies() from WASM
- useCondaDependencies: get_conda_dependencies → getCondaDependencies() from WASM
- useDenoDependencies: get_deno_flexible_npm_imports → getDenoFlexibleNpmImports()

Still via Tauri IPC (writes, filesystem access):
- add_dependency, remove_dependency, clear_dependency_section (writes)
- set_conda_dependencies, add_conda_dependency, remove_conda_dependency (writes)
- set_deno_flexible_npm_imports (write)
- approve_notebook_trust, verify_notebook_trust (needs trust key from disk)
- check_uv_available, detect_pyproject, detect_deno_config (filesystem)

Also removes dead Tauri commands: get_deno_permissions, set_deno_permissions.
@rgbkrk rgbkrk force-pushed the 540/wasm-metadata-reads branch from e6fa1dd to e38677d Compare March 6, 2026 20:49
@rgbkrk rgbkrk enabled auto-merge (squash) March 6, 2026 21:16
@rgbkrk rgbkrk merged commit e7341a9 into main Mar 6, 2026
19 of 20 checks passed
@rgbkrk rgbkrk deleted the 540/wasm-metadata-reads branch March 6, 2026 21:19
rgbkrk added a commit that referenced this pull request Mar 6, 2026
All replaced by WASM reactive hooks in PR #569:

UV dependency commands (-190 lines):
- get_notebook_dependencies, set_notebook_dependencies
- add_dependency, remove_dependency, clear_dependency_section
- NotebookDependenciesJson struct

Conda dependency commands (-171 lines):
- get_conda_dependencies, set_conda_dependencies
- add_conda_dependency, remove_conda_dependency
- CondaDependenciesJson struct

Runtime + Deno commands (-78 lines):
- get_notebook_runtime
- get_deno_flexible_npm_imports, set_deno_flexible_npm_imports
- get_runtime_from_sync helper function

All handler registrations removed from invoke_handler.
-499 lines, zero warnings.
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