Repository navigation
feat(frontend): introduce progressive loading for wasm - #10044
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- 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
|
@cubic-dev-ai please re-review |
@mscolnick I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
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
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- useDelayElapsed: use useLayoutEffect so a re-closing gate resets before paint, avoiding a one-frame fallback flash - adapter: doc says module load, not mount
- 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
|
🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.14-dev10 |
…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.
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.