Skip to content

feat(frontend): introduce progressive loading for wasm - #10044

Merged
mscolnick merged 4 commits into
mainfrom
ms/feature/progressive-loading-framework-v2
Jul 6, 2026
Merged

mscolnick merged 4 commits into
mainfrom
ms/feature/progressive-loading-framework-v2

Conversation

@mscolnick

Copy link
Copy Markdown
Contributor

Unify the ~13 atoms, 5 render gates, and 6 status indicators behind a
single lifecycle phase machine, runtime adapters, and two UI primitives
( / ). Migrate PyodideLoader,
run-app, edit-app, and the Pyodide footer to the new framework.

Unify the ~13 atoms, 5 render gates, and 6 status indicators behind a
single lifecycle phase machine, runtime adapters, and two UI primitives
(<ProgressiveBoundary> / <RuntimeStatusBadge>). Migrate PyodideLoader,
run-app, edit-app, and the Pyodide footer to the new framework.
Copilot AI review requested due to automatic review settings July 1, 2026 14:24
@vercel

vercel Bot commented Jul 1, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Jul 1, 2026 3:51pm

Request Review

@mscolnick mscolnick added the enhancement New feature or request label Jul 1, 2026
@mscolnick mscolnick changed the title feat(frontend): introduce progressive loading framework feat(frontend): introduce progressive loading for wasm Jul 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Introduces a unified “progressive loading” framework in the frontend that centralizes runtime lifecycle/connection phases behind a runtime adapter abstraction and reusable UI gates, then migrates WASM (Pyodide) and app entrypoints to use it.

Changes:

  • Added lifecycle primitives: <ProgressiveBoundary> (delayed fallback gate) and <RuntimeStatusBadge> (footer status pill driven by a runtime adapter).
  • Added runtime lifecycle adapters (wasm/remote/static/islands) and a render policy (canPaint) to decide when the UI can paint meaningful content.
  • Updated Pyodide loading and run/edit apps to use the new progressive gate and consolidated WASM init state into a tagged union.

Reviewed changes

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

Show a summary per file
File Description
frontend/src/hooks/useDelayElapsed.ts New hook to implement “delay before showing fallback” behavior.
frontend/src/hooks/tests/useDelayElapsed.test.tsx Unit tests for delay-elapsed timing and cleanup behavior.
frontend/src/core/wasm/state.ts Replaces separate WASM init atoms with a tagged WasmInitState union.
frontend/src/core/wasm/PyodideLoader.tsx Switches WASM loader gating to canPaint + <ProgressiveBoundary>; toasts errors if content is already painted.
frontend/src/core/wasm/bridge.ts Updates bridge listeners to write wasmInitStateAtom (loading/ready/error) and prevent regressions from late progress messages.
frontend/src/core/wasm/tests/PyodideLoader.test.ts Removes tests for deleted shouldShowSpinner predicate.
frontend/src/core/runtime/adapter.ts Adds runtime adapter abstraction and canRunCellsAtom derived readiness for each runtime kind.
frontend/src/core/runtime/tests/adapter.test.ts Adds unit tests for adapter state mapping and canRunCellsAtom behavior.
frontend/src/core/run-app.tsx Replaces ad-hoc delayed “Connecting…” gate with <ProgressiveBoundary>.
frontend/src/core/lifecycle/render-policy.ts Adds render policy computation (computeRenderPolicy) and canPaintAtom for paintability decisions.
frontend/src/core/lifecycle/tests/render-policy.test.ts Unit tests for render policy code visibility + canPaint logic.
frontend/src/core/edit-app.tsx Uses <ProgressiveBoundary> to gate rendering until at least one cell exists.
frontend/src/components/lifecycle/RuntimeStatusBadge.tsx New footer status pill driven by runtimeAdapterAtom.
frontend/src/components/lifecycle/ProgressiveBoundary.tsx New delayed-fallback boundary primitive.
frontend/src/components/lifecycle/tests/RuntimeStatusBadge.test.tsx Unit tests for status badge render/hide behavior.
frontend/src/components/lifecycle/tests/ProgressiveBoundary.test.tsx Unit tests for boundary gating and delay behavior.
frontend/src/components/editor/chrome/wrapper/footer-items/pyodide-status.tsx Replaces bespoke Pyodide footer indicator with <RuntimeStatusBadge> wrapper.

Comment thread frontend/src/components/lifecycle/ProgressiveBoundary.tsx

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

6 issues found across 17 files

Architecture diagram
sequenceDiagram
    participant App as EditApp/RunApp
    participant PB as ProgressiveBoundary
    participant RPA as renderPolicyAtom
    participant canPaint as canPaintAtom
    participant Adpt as runtimeAdapterAtom
    participant Adapter as RuntimeAdapter
    participant AdapterState as AdapterState Atom
    participant RSB as RuntimeStatusBadge
    participant Footer as PyodideStatus Footer
    participant wasm as wasmInitStateAtom
    participant Bridge as PyodideBridge
    participant conn as connectionAtom
    participant kern as kernelStateAtom
    participant isl as islandsInitializedAtom

    Note over App,isl: UNIFIED LIFECYCLE PHASE MACHINE

    App->>PB: Render with requires={hasCellsAtom}
    Note over PB: NEW: Generic progressive boundary replacing ad-hoc gates

    alt !ready (hasCellsAtom false)
        alt delay not elapsed
            PB->>PB: return null (suppress flash)
        else delay elapsed
            PB-->>App: fallback (spinner or NotStartedConnectionAlert)
        end
    else ready (hasCellsAtom true)
        PB-->>App: children (CellsRenderer)
    end

    Note over App,Adapter: ADAPTER SELECTION (runtime-aware)

    Adpt->>Adapter: selectAdapter() based on env
    Note over Adapter: Chooses wasmAdapter / remoteAdapter / staticAdapter / islandsAdapter
    Adapter->>AdapterState: Subscribe to derived state atom

    alt WASM runtime
        AdapterState->>wasm: Read wasmInitStateAtom
        wasm-->>AdapterState: {kind: "connecting"} | {kind: "ready"} | {kind: "failed"}
        Bridge->>Bridge: On RPC message
        Bridge->>wasm: CHANGED: update wasmInitStateAtom (single atom)
        Note over Bridge: Initializing message only updates if still loading
    else Remote runtime
        AdapterState->>conn: Read connectionAtom (WebSocketState)
        conn-->>AdapterState: OPEN → ready, CONNECTING → connecting, CLOSED → failed
        AdapterState->>kern: Additional check for kernel instantiation
        kern-->>AdapterState: isInstantiated for canRunCellsAtom
    else Islands runtime
        AdapterState->>isl: Read islandsInitializedAtom
        isl-->>AdapterState: true → ready, false → connecting, string → failed
    else Static runtime
        AdapterState->>AdapterState: Always {kind: "ready"}
    end

    RSB->>Adpt: Read runtimeAdapterAtom
    RSB->>AdapterState: Read adapter.state
    alt adapter.state.kind === "ready"
        RSB->>RSB: return null (hide)
    else adapter.state.kind === "connecting"
        RSB-->>Footer: Show spinner + adapter.label + progress
    else adapter.state.kind === "failed"
        RSB-->>Footer: Show error icon + adapter.label + error message
    end

    Footer->>RSB: Delegate to RuntimeStatusBadge
    Note over Footer: Thin wrapper, no direct Jotai usage

    Note over App,canPaint: RENDER POLICY FOR WASM PROGRESSIVE LOADING

    App->>PB: Render with requires={canPaintAtom}
    PB->>RPA: Read renderPolicyAtom
    RPA->>RPA: computeRenderPolicy({mode, showAppCode, hasCells, hasCachedOutputs})
    alt mode === "edit" | "home" | "gallery"
        RPA->>RPA: showCode = true, canPaint = hasCells
    else mode === "present"
        RPA->>RPA: showCode = false, canPaint = hasCells && hasCachedOutputs
    else mode === "read"
        RPA->>RPA: showCode = readCodeVisibility (URL param, then config)
        RPA->>RPA: canPaint = hasCells && (showCode || hasCachedOutputs)
    end
    RPA-->>canPaint: canPaint boolean

    alt canPaint = true (snapshot or code visible)
        PB-->>App: Immediate paint of children
        Note over App: Pyodide downloads in background
    else canPaint = false (nothing to show yet)
        alt delay not elapsed
            PB->>PB: return null
        else delay elapsed
            PB-->>App: <WasmSpinner> with progress message from wasmInitStateAtom
        end
    end

    Note over App,Adapter: ADAPTER-AWARE CELL EXECUTION GATE

    App->>Adpt: Read canRunCellsAtom
    alt adapter.kind === "static"
        Adpt-->>App: false (never run cells)
    else adapter.kind === "remote"
        Adpt->>AdapterState: Check adapter.state.kind === "ready"
        Adpt->>kern: Check kernelStateAtom.isInstantiated
        Adpt-->>App: true only if both ready + instantiated
    else adapter.kind === "wasm" | "islands"
        Adpt->>AdapterState: Check adapter.state.kind === "ready"
        Adpt-->>App: true when ready (no separate kernel handshake)
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread frontend/src/core/lifecycle/__tests__/render-policy.test.ts
Comment thread frontend/src/core/run-app.tsx
Comment thread frontend/src/core/lifecycle/render-policy.ts
Comment thread frontend/src/components/lifecycle/ProgressiveBoundary.tsx Outdated
Comment thread frontend/src/core/lifecycle/__tests__/render-policy.test.ts
Comment thread frontend/src/core/runtime/adapter.ts Outdated
- ProgressiveBoundary: re-arm delay when the gate re-closes
- render-policy: restore idle-completion fallback (no-output notebooks
  still paint) and honor ?include-code=false
- run-app: gate the Connecting… fallback on actual connection state
- adapter: dedupe capability literals via NO_CAPABILITIES
- render-policy: use visibleForTesting; drop dead hasAnyOutputAtom
- add test coverage for the above
@mscolnick

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai please re-review

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai please re-review

@mscolnick I have started the AI code review. It will take a few minutes to complete.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Comment thread frontend/src/core/runtime/adapter.ts Outdated
Comment thread frontend/src/hooks/useDelayElapsed.ts Outdated

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 18 files

Architecture diagram
sequenceDiagram
    participant Client as Browser
    participant App as App (RunApp/EditApp)
    participant PB as ProgressiveBoundary
    participant Bdg as RuntimeStatusBadge
    participant Adp as RuntimeAdapter
    participant Pol as RenderPolicy
    participant Wasm as wasmInitStateAtom
    participant Bridge as PyodideBridge

    Note over Client,Bridge: Page Load – WASM Runtime

    App->>Adp: selectAdapter() → wasmAdapter
    App->>PB: <ProgressiveBoundary requires=canPaintAtom delay=2000>
    App->>Bdg: <RuntimeStatusBadge />

    Bdg->>Adp: read adapter.state
    Adp->>Wasm: derived from wasmInitStateAtom
    Wasm-->>Adp: {kind:"loading", message:"Initializing..."}
    Adp-->>Bdg: {kind:"connecting", progress:{label:"Initializing..."}}
    Bdg->>Client: render spinner pill "Pyodide"

    App->>Pol: compute canPaintAtom
    Pol-->>App: false (no cells yet, or headless mode)

    PB->>PB: useDelayElapsed(2000)
    PB-->>Client: null (delay not elapsed, canPaint false)

    alt Successful Initialization
        Bridge->>Wasm: set {kind:"loading", message:"Loading Pyodide…"}
        Note over Bridge,Wasm: Progress messages update label
        Bridge->>Wasm: set {kind:"ready"}
        Wasm-->>Adp: {kind:"ready"}
        Adp-->>Bdg: {kind:"ready"}
        Bdg-->>Client: return null (badge hides)

        Pol->>Pol: recompute canPaintAtom
        Note over Pol: hasCells becomes true after parse<br>or cached outputs available
        Pol-->>App: canPaint = true

        PB->>PB: ready=true → render children
        PB-->>Client: <CellsRenderer>
    else Initialization Failure
        Bridge->>Wasm: set {kind:"error", message:"ImportError: numpy"}
        Wasm-->>Adp: {kind:"failed", error:{message:"ImportError: numpy", errorKind:"init"}}
        Adp-->>Bdg: {kind:"failed", error:{message:"..."}}
        Bdg-->>Client: error icon pill

        alt canPaint is false (nothing to show)
            PB->>PB: ready=false, delay elapsed → fallback rendered
            PB-->>Client: <LargeSpinner> or fallback
            Pol->>Pol: canPaint remains false
        else canPaint is true (snapshot visible)
            PB->>PB: ready=false, but canPaint false? Actually if snapshot, canPaint true
            PB-->>Client: children rendered (snapshot)
            App->>App: toast error instead of throwing
        end
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread frontend/src/core/runtime/adapter.ts
Comment thread frontend/src/core/runtime/adapter.ts Outdated
- useDelayElapsed: use useLayoutEffect so a re-closing gate resets
  before paint, avoiding a one-frame fallback flash
- adapter: doc says module load, not mount

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Comment thread frontend/src/core/lifecycle/render-policy.ts Outdated
Comment thread frontend/src/components/lifecycle/RuntimeStatusBadge.tsx Outdated
Comment thread frontend/src/hooks/__tests__/useDelayElapsed.test.tsx
- render-policy: compute output/idle facts in one pass via a shared
  summary atom with boolean selectors
- RuntimeStatusBadge: use a focusable <button> trigger for the tooltip
- useDelayElapsed test: assert the timer is actually cleared on unmount

@peter-gy peter-gy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

really nice refactor!

@mscolnick
mscolnick merged commit 72bda05 into main Jul 6, 2026
31 checks passed
@mscolnick
mscolnick deleted the ms/feature/progressive-loading-framework-v2 branch July 6, 2026 12:43
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

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

williambdean pushed a commit to williambdean/marimo that referenced this pull request Sep 6, 2026
…0044)

Unify the ~13 atoms, 5 render gates, and 6 status indicators behind a
single lifecycle phase machine, runtime adapters, and two UI primitives
(<ProgressiveBoundary> / <RuntimeStatusBadge>). Migrate PyodideLoader,
run-app, edit-app, and the Pyodide footer to the new framework.

This branch was successfully deployed

1 active deployment
Preview — baec7337 Deployed Jul 1, 2026 by vercel[bot]
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