Repository navigation
fix(islands): retain static output through initial hydration - #10298
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@cubic-dev-ai review |
@mscolnick I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
1 issue found across 10 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="frontend/src/core/islands/bootstrap.ts">
<violation number="1" location="frontend/src/core/islands/bootstrap.ts:211">
P2: When one session fails to start on a multi-island page, all islands remain on static output forever: the failed generation stays pending because it can never send `completed-run`. Clear failed generations (or track failures separately) when `startSession` is caught so successfully started islands can hydrate.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Browser as Browser
participant Bootstrap as initializeIslands()
participant Bridge as IslandsPyodideBridge
participant Store as Jotai Store
participant Worker as Pyodide Worker
participant OutputWrapper as MarimoOutputWrapper (React)
participant AnyWidget as AnyWidgetPlugin
Note over Browser,Worker: Page load: static HTML with <marimo-island> elements
Browser->>Bootstrap: Invoke initializeIslands()
Bootstrap->>Store: Set islandsPendingInitialRunsAtom = null
Bootstrap->>Bridge: Start session materialization
Bridge->>Bridge: parseMarimoIslandApps(root, {materialize: false})
Note right of Bridge: NEW: inspect only, no element replacement yet
Bridge->>Store: Set islandsPendingInitialRunsAtom = Set(sessionGenerations)
Bridge->>Bridge: parseMarimoIslandApps(root) — materialize elements
Note over Bridge,Worker: Start sessions in worker
Bridge->>Worker: startSession({code, appId, sessionGeneration})
Worker->>Worker: Attach sessionGeneration to all kernel messages
Worker-->>Bridge: Kernel messages with {message, sessionGeneration}
Bridge->>Bridge: consumeMessages() passes {message, sessionGeneration} to handler
rect rgba(0,0,0,0.1)
Note over Bridge,Store: New: completed-run handling
alt op == "completed-run"
Bridge->>Store: islandsPendingInitialRunsAtom → remove sessionGeneration
Store-->>Store: pending set shrinks
else other ops
Bridge->>Store: existing handling (ignored)
end
end
Note over OutputWrapper: React rendering of island output
OutputWrapper->>Store: Read islandsHydratedAtom (pending size === 0)
alt islandsHydratedAtom == false (still pending)
OutputWrapper->>OutputWrapper: Show static <marimo-cell-output> children
Note right of OutputWrapper: NEW: static output retained even if runtime output exists
else islandsHydratedAtom == true (all initial runs completed)
OutputWrapper->>OutputWrapper: Show runtime output from notebookAtom
end
Note over AnyWidget: AnyWidget plugin (if present)
AnyWidget->>Store: Read islandsHydratedAtom as canCreateView
alt canCreateView == false
AnyWidget->>AnyWidget: Do nothing (skip view creation)
Note right of AnyWidget: NEW: avoids “Model not found” error
else canCreateView == true
AnyWidget->>AnyWidget: Call widgetRegistry.createView(modelId)
end
Note over Bridge,Store: Session stop cleanup
Bridge->>Store: Set islandsPendingInitialRunsAtom = null
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| case "notebook-document-transaction": | ||
| return; | ||
|
|
||
| case "completed-run": |
There was a problem hiding this comment.
P2: When one session fails to start on a multi-island page, all islands remain on static output forever: the failed generation stays pending because it can never send completed-run. Clear failed generations (or track failures separately) when startSession is caught so successfully started islands can hydrate.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/core/islands/bootstrap.ts, line 211:
<comment>When one session fails to start on a multi-island page, all islands remain on static output forever: the failed generation stays pending because it can never send `completed-run`. Clear failed generations (or track failures separately) when `startSession` is caught so successfully started islands can hydrate.</comment>
<file context>
@@ -201,6 +208,17 @@ function handleMessage(
case "notebook-document-transaction":
return;
+ case "completed-run":
+ store.set(islandsPendingInitialRunsAtom, (pending) => {
+ if (!pending?.has(sessionGeneration)) {
</file context>
|
🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.15-dev65 |
…team#10298) Fixes a bug spotted bug in the wild at https://koaning.io/posts/widget-dag/ through mdx-marimo. The page initially renders exported static output while Pyodide boots. That output contains anywidget model IDs from the export. Plugin registration was trying to resolve those IDs against the live model registry, causing two `Model not found` errors after the lookup timed out. <img width="1440" height="1000" alt="before-model-registry-race" src="https://github.com/user-attachments/assets/422bf2e8-2ac9-4b1b-9a4f-ddd1361a4d50" /> The intended lifecycle we'd want is: 1. Show the exported static output immediately. 2. Start each island session and run its cells. 3. Wait for every initial session to report `completed-run`. 4. Replace the static output with live output and create model-backed views from the live session. This PR implements that lifecycle. The bridge records each pending session generation before materializing the island elements, and the worker includes that generation with every kernel message. Static output stays mounted and anywidget view creation **waits until the matching initial runs finish**. Session generations also keep late messages scoped to the run that produced them. This way, the static preview is retained with no errors, as long as startup is not completed: <img width="1440" height="1000" alt="after-static-preview-retained" src="https://github.com/user-attachments/assets/21f6925a-9696-4ba5-b4dd-62c694b0c2c4" /> And once the startup is complete, we have the error-free, interactive live outputs: <img width="1440" height="1000" alt="after-runtime-interactive" src="https://github.com/user-attachments/assets/03795cf6-e449-4957-aa89-2314210f3e3a" />
Fixes a bug spotted bug in the wild at https://koaning.io/posts/widget-dag/ through mdx-marimo.
The page initially renders exported static output while Pyodide boots. That output contains anywidget model IDs from the export. Plugin registration was trying to resolve those IDs against the live model registry, causing two
Model not founderrors after the lookup timed out.The intended lifecycle we'd want is:
completed-run.This PR implements that lifecycle. The bridge records each pending session generation before materializing the island elements, and the worker includes that generation with every kernel message. Static output stays mounted and anywidget view creation waits until the matching initial runs finish. Session generations also keep late messages scoped to the run that produced them.
This way, the static preview is retained with no errors, as long as startup is not completed:
And once the startup is complete, we have the error-free, interactive live outputs: