Skip to content

feat(wasm): render notebook snapshot while Pyodide initializes#9502

Merged
mscolnick merged 2 commits into
mainfrom
ms/feature/wasm-snapshot
May 11, 2026
Merged

feat(wasm): render notebook snapshot while Pyodide initializes#9502
mscolnick merged 2 commits into
mainfrom
ms/feature/wasm-snapshot

Conversation

@mscolnick
Copy link
Copy Markdown
Contributor

Previously the WASM loader blocked the entire app behind a spinner until
Pyodide finished downloading. With the new snapshot embedded by
marimo export html-wasm --execute, render cells (code + cached outputs)
on first paint and let Pyodide download in the background.

  • Gate spinner on whether cells exist, not whether Pyodide is pending
  • Toast Pyodide init failures instead of throwing when a snapshot is shown
  • Add a footer "Pyodide" indicator that mirrors the "Kernel" one

Previously the WASM loader blocked the entire app behind a spinner until
Pyodide finished downloading. With the new snapshot embedded by
`marimo export html-wasm --execute`, render cells (code + cached outputs)
on first paint and let Pyodide download in the background.

- Gate spinner on whether cells exist, not whether Pyodide is pending
- Toast Pyodide init failures instead of throwing when a snapshot is shown
- Add a footer "Pyodide" indicator that mirrors the "Kernel" one
Copilot AI review requested due to automatic review settings May 11, 2026 18:43
@vercel
Copy link
Copy Markdown

vercel Bot commented May 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment May 11, 2026 6:53pm

Request Review

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 7 files

Architecture diagram
sequenceDiagram
    participant UI as React UI
    participant Footer as Footer Component
    participant PyStatus as PyodideStatus (NEW)
    participant PyApp as RunApp (CHANGED)
    participant PyLoader as PyodideLoader (CHANGED)
    participant Cells as Cells Store
    participant Bridge as PyodideBridge
    participant Pyodide as Pyodide Runtime

    Note over UI,Pyodide: Start-up Sequence (snapshot path)

    UI->>PyApp: Render notebook
    
    alt Snapshot with cells present
        PyApp->>Cells: hasCellsAtom
        Cells-->>PyApp: true
        Note over PyApp: Skip spinner gate — show cells immediately
        PyApp->>UI: Paint cells (code + cached outputs)
    else No cells yet
        PyApp->>Cells: hasCellsAtom
        Cells-->>PyApp: false
        PyApp->>UI: Show spinner (original behavior)
    end

    Note over UI,Pyodide: Background Pyodide Initialization

    UI->>PyLoader: Mount (parallel with cell render)
    PyLoader->>Cells: hasCellsAtom
    PyLoader->>Cells: hasAnyOutputAtom
    PyLoader-->>PyLoader: shouldShowSpinner(hasCells, hasOutput, mode, codeHidden)

    alt shouldShowSpinner = true (nothing visible)
        PyLoader->>UI: Show WasmSpinner (full-screen)
    else hasCells and (code visible or has output)
        PyLoader->>UI: Render children (snapshot stays visible)
    end

    PyLoader->>Bridge: Wait for initialized.promise
    Bridge->>Pyodide: Initialize (download + boot)

    alt Pyodide ready
        Bridge->>Bridge: set wasmInitStatusAtom = "ready"
        Bridge-->>PyLoader: Promise resolves
        PyLoader->>PyLoader: No longer pending
    else Pyodide fails
        Bridge->>Bridge: set wasmInitStatusAtom = "error"
        Bridge-->>PyLoader: Promise rejects

        alt Snapshot is showing (nothingToShow = false)
            PyLoader->>UI: toast("Failed to start notebook runtime")
            Note over PyLoader: Error is surfaced as toast, snapshot remains visible
        else No snapshot (nothingToShow = true)
            PyLoader->>UI: throw error (caught by error boundary)
        end
    end

    Note over UI,Pyodide: Footer Indicator (NEW)

    Footer->>PyStatus: Render component
    PyStatus->>PyStatus: Check isWasm() and wasmInitStatusAtom

    alt Status = "ready" or not WASM
        PyStatus->>PyStatus: Return null (hidden)
    else Status = "loading"
        PyStatus->>UI: Show spinner + "Pyodide" label
    else Status = "error"
        PyStatus->>UI: Show error icon + "Pyodide" label
        PyStatus->>PyStatus: Tooltip: "Pyodide failed to initialize"
    end

    Note over Footer: Mirrors Kernel status indicator behavior
Loading

@mscolnick mscolnick added the enhancement New feature or request label May 11, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Enables WASM (Pyodide) notebooks exported with an embedded snapshot to render immediately (cells + cached outputs) while Pyodide continues initializing in the background, instead of blocking the entire UI behind a loader.

Changes:

  • Update the WASM loader gating logic to show a spinner only when there’s nothing meaningful to render yet (no hydrated cells, or headless run mode with no outputs).
  • Surface Pyodide init failures via a toast (when snapshot content is already visible) and add a footer “Pyodide” status indicator.
  • Skip the “Connecting…” gate in run mode when cells already exist (e.g., from an embedded snapshot).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
frontend/src/core/wasm/state.ts Adds a wasmInitStatusAtom to track Pyodide init state.
frontend/src/core/wasm/PyodideLoader.tsx Stops blocking render on Pyodide init; introduces shouldShowSpinner predicate and error-toasting behavior.
frontend/src/core/wasm/bridge.ts Updates WASM bridge to set init status to ready/error based on worker messages.
frontend/src/core/wasm/tests/PyodideLoader.test.ts Adds unit tests for the spinner gating predicate.
frontend/src/core/run-app.tsx Avoids showing the connecting spinner when cells already exist.
frontend/src/components/editor/chrome/wrapper/footer.tsx Adds the Pyodide status indicator to the footer.
frontend/src/components/editor/chrome/wrapper/footer-items/pyodide-status.tsx Implements the footer Pyodide init indicator and tooltip.

Comment on lines 128 to 140
this.rpc.addMessageListener("initializedError", ({ error }) => {
// If already resolved, show a toast
if (this.initialized.status === "resolved") {
Logger.error(error);
toast({
title: "Error initializing",
description: error,
variant: "danger",
});
}
store.set(wasmInitStatusAtom, "error");
this.initialized.reject(new Error(error));
});
If `initializedError` fires after Pyodide has already resolved, return
early instead of corrupting the Deferred status and flipping the footer
"Pyodide" indicator to error. The worker is healthy; the toast already
surfaces the runtime error.
Copy link
Copy Markdown
Collaborator

@dmadisetti dmadisetti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 Nice

@mscolnick mscolnick merged commit af0556c into main May 11, 2026
32 checks passed
@mscolnick mscolnick deleted the ms/feature/wasm-snapshot branch May 11, 2026 20:14
@github-actions
Copy link
Copy Markdown

🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.6-dev32

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants