@marimo-team/islands 0.23.6-dev31 → 0.23.6-dev32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{code-visibility-tF2qI5eL.js → code-visibility-CyBvO931.js} +1 -1
- package/dist/main.js +2 -2
- package/dist/{reveal-component-Bdl6U1k_.js → reveal-component-_FaJp0v-.js} +1 -1
- package/package.json +1 -1
- package/src/components/editor/chrome/wrapper/footer-items/pyodide-status.tsx +47 -0
- package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
- package/src/core/run-app.tsx +9 -2
- package/src/core/wasm/PyodideLoader.tsx +54 -16
- package/src/core/wasm/__tests__/PyodideLoader.test.ts +72 -0
- package/src/core/wasm/bridge.ts +6 -2
- package/src/core/wasm/state.ts +3 -0
|
@@ -25507,7 +25507,7 @@ ${_}`,
|
|
|
25507
25507
|
return Logger.warn("Failed to get version from mount config"), null;
|
|
25508
25508
|
}
|
|
25509
25509
|
}
|
|
25510
|
-
marimoVersionAtom = atom(getVersionFromMountConfig() || "0.23.6-
|
|
25510
|
+
marimoVersionAtom = atom(getVersionFromMountConfig() || "0.23.6-dev32");
|
|
25511
25511
|
showCodeInRunModeAtom = atom(true);
|
|
25512
25512
|
atom(null);
|
|
25513
25513
|
var import_compiler_runtime = require_compiler_runtime();
|
package/dist/main.js
CHANGED
|
@@ -26,7 +26,7 @@ import { $ as useCellActions, At as DeferredRequestRegistry, B as safeExtractSet
|
|
|
26
26
|
import { __tla as __tla_1 } from "./chunk-5FQGJX7Z-CO1e63h_.js";
|
|
27
27
|
import { o as useSize, s as Root$3, u as createLucideIcon } from "./dist-ESg7xyoD.js";
|
|
28
28
|
import { A as SquareFunction, C as DEFAULT_COLOR_SCHEME, D as SCALE_TYPE_DESCRIPTIONS, E as EMPTY_VALUE$1, O as TIME_UNIT_DESCRIPTIONS, S as DEFAULT_AGGREGATION, T as DEFAULT_TIME_UNIT, _ as AGGREGATION_TYPE_DESCRIPTIONS, a as AGGREGATION_FNS$1, b as COLOR_SCHEMES, c as COLOR_BY_FIELDS, d as NONE_VALUE, f as SELECTABLE_DATA_TYPES, g as TIME_UNITS, h as STRING_AGGREGATION_FNS, i as convertDataTypeToSelectable, j as ChartColumn, k as escapeFieldName, l as COMBINED_TIME_UNITS, m as SORT_TYPES, n as createSpecWithoutData, o as BIN_AGGREGATION, p as SINGLE_TIME_UNITS, r as isFieldSet, s as CHART_TYPES, t as augmentSpecWithData, u as ChartType, v as AGGREGATION_TYPE_ICON, w as DEFAULT_MAX_BINS_FACET, x as COUNT_FIELD, y as CHART_TYPE_ICON } from "./spec-DSIuqd3f.js";
|
|
29
|
-
import { $ as getPageIndexForRow, A as contextAwarePanelType, At as EyeOff, B as TableHead, Bt as ArrowDownWideNarrow, C as useInternalStateWithSync, Ct as RenderTextWithLinks, D as PANEL_TYPES, Dt as TextWrap, E as ContextAwarePanelItem, Et as EmotionCacheProvider, F as Fill, Ft as ChevronsUpDown, G as inferFieldTypes, H as TableRow, I as Provider$1, It as ChevronsRight, J as ColumnChartSpecModel, K as renderCellValue, L as Table, Lt as ChevronsLeft, M as SlotNames, Mt as Ellipsis, N as slotsController, Nt as Download, O as contextAwarePanelOpen, Ot as GripHorizontal, P as Toggle, Pt as Code, Q as filtersToFilterGroup, R as TableBody, Rt as ChevronsDownUp, S as prettifyRowCount, St as useOverflowDetection, T as ComboboxItem, Tt as HtmlOutput, U as NAMELESS_COLUMN_PREFIX, V as TableHeader, W as generateColumns, X as useIntersectionObserver, Y as DelayMount, Z as usePrevious$1, _ as downloadBlob, _t as TabsTrigger, at as toFieldTypes, b as Filenames, bt as ChartLoadingState, c as Slide, ct as CommandEmpty, d as JsonOutput, dt as CommandList, et as loadTableAndRawData, f as OutputArea, ft as CommandSeparator, g as ADD_PRINTING_CLASS, gt as TabsList, h as InstallPackageButton, ht as TabsContent, it as TOO_MANY_ROWS, j as isCellAwareAtom, k as contextAwarePanelOwner, kt as Funnel, l as RadioGroup, lt as CommandInput, m as DataTable, mt as Tabs, n as marimoVersionAtom, nt as INDEX_COLUMN_NAME, o as SLIDE_TYPE_OPTIONS_BY_VALUE, ot as getMimeValues, p as OutputRenderer, pt as Maps, q as ColumnChartContext, r as showCodeInRunModeAtom, rt as SELECT_COLUMN_ID, st as Command, t as useNotebookCodeAvailable, tt as loadTableData, u as RadioGroupItem, ut as CommandItem, v as downloadByURL, vt as ChartErrorState, w as Combobox, wt as Kbd, x as prettifyRowColumnCount, xt as LazyVegaEmbed, y as downloadHTMLAsImage, yt as ChartInfoState, z as TableCell, zt as ChevronLeft, __tla as __tla_2 } from "./code-visibility-
|
|
29
|
+
import { $ as getPageIndexForRow, A as contextAwarePanelType, At as EyeOff, B as TableHead, Bt as ArrowDownWideNarrow, C as useInternalStateWithSync, Ct as RenderTextWithLinks, D as PANEL_TYPES, Dt as TextWrap, E as ContextAwarePanelItem, Et as EmotionCacheProvider, F as Fill, Ft as ChevronsUpDown, G as inferFieldTypes, H as TableRow, I as Provider$1, It as ChevronsRight, J as ColumnChartSpecModel, K as renderCellValue, L as Table, Lt as ChevronsLeft, M as SlotNames, Mt as Ellipsis, N as slotsController, Nt as Download, O as contextAwarePanelOpen, Ot as GripHorizontal, P as Toggle, Pt as Code, Q as filtersToFilterGroup, R as TableBody, Rt as ChevronsDownUp, S as prettifyRowCount, St as useOverflowDetection, T as ComboboxItem, Tt as HtmlOutput, U as NAMELESS_COLUMN_PREFIX, V as TableHeader, W as generateColumns, X as useIntersectionObserver, Y as DelayMount, Z as usePrevious$1, _ as downloadBlob, _t as TabsTrigger, at as toFieldTypes, b as Filenames, bt as ChartLoadingState, c as Slide, ct as CommandEmpty, d as JsonOutput, dt as CommandList, et as loadTableAndRawData, f as OutputArea, ft as CommandSeparator, g as ADD_PRINTING_CLASS, gt as TabsList, h as InstallPackageButton, ht as TabsContent, it as TOO_MANY_ROWS, j as isCellAwareAtom, k as contextAwarePanelOwner, kt as Funnel, l as RadioGroup, lt as CommandInput, m as DataTable, mt as Tabs, n as marimoVersionAtom, nt as INDEX_COLUMN_NAME, o as SLIDE_TYPE_OPTIONS_BY_VALUE, ot as getMimeValues, p as OutputRenderer, pt as Maps, q as ColumnChartContext, r as showCodeInRunModeAtom, rt as SELECT_COLUMN_ID, st as Command, t as useNotebookCodeAvailable, tt as loadTableData, u as RadioGroupItem, ut as CommandItem, v as downloadByURL, vt as ChartErrorState, w as Combobox, wt as Kbd, x as prettifyRowColumnCount, xt as LazyVegaEmbed, y as downloadHTMLAsImage, yt as ChartInfoState, z as TableCell, zt as ChevronLeft, __tla as __tla_2 } from "./code-visibility-CyBvO931.js";
|
|
30
30
|
import { c as Calendar, i as createReducerAndAtoms, n as useOnUnmount, o as ToggleLeft, t as useOnMount } from "./useLifecycle-CjMjllqy.js";
|
|
31
31
|
import { n as $fb18d541ea1ad717$export$ad991b66133851cf, r as $5a387cc49350e6db$export$722debc0e56fea39, t as $896ba0a80a8f4d36$export$85fd5fdf27bacc79 } from "./useDateFormatter-B3mCQMP3.js";
|
|
32
32
|
import { t as Check } from "./check-CFM2mVDr.js";
|
|
@@ -44649,7 +44649,7 @@ ${c}
|
|
|
44649
44649
|
if (l && l !== "slide") return l;
|
|
44650
44650
|
if (c == null ? void 0 : c.has(e)) return "skip";
|
|
44651
44651
|
}
|
|
44652
|
-
var LazySlidesComponent = import_react.lazy(() => import("./reveal-component-
|
|
44652
|
+
var LazySlidesComponent = import_react.lazy(() => import("./reveal-component-_FaJp0v-.js"));
|
|
44653
44653
|
const SlidesLayoutPlugin = {
|
|
44654
44654
|
type: "slides",
|
|
44655
44655
|
name: "Slides",
|
|
@@ -8,7 +8,7 @@ import { t as require_react } from "./react-DA-nE2FX.js";
|
|
|
8
8
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
9
9
|
import "./html-to-image-Gawx981h.js";
|
|
10
10
|
import "./chunk-5FQGJX7Z-CO1e63h_.js";
|
|
11
|
-
import { At as EyeOff, Pt as Code, a as DEFAULT_SLIDE_TYPE, c as Slide, i as DEFAULT_DECK_TRANSITION, jt as Expand, s as SlideSidebar, t as useNotebookCodeAvailable } from "./code-visibility-
|
|
11
|
+
import { At as EyeOff, Pt as Code, a as DEFAULT_SLIDE_TYPE, c as Slide, i as DEFAULT_DECK_TRANSITION, jt as Expand, s as SlideSidebar, t as useNotebookCodeAvailable } from "./code-visibility-CyBvO931.js";
|
|
12
12
|
import "./input-BAOe64zx.js";
|
|
13
13
|
import "./toDate-CGmcCn8J.js";
|
|
14
14
|
import "./react-dom-BWRJ_g_k.js";
|
package/package.json
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useAtomValue } from "jotai";
|
|
4
|
+
import { AlertCircleIcon } from "lucide-react";
|
|
5
|
+
import type React from "react";
|
|
6
|
+
import { Spinner } from "@/components/icons/spinner";
|
|
7
|
+
import { Tooltip } from "@/components/ui/tooltip";
|
|
8
|
+
import { wasmInitializationAtom, wasmInitStatusAtom } from "@/core/wasm/state";
|
|
9
|
+
import { isWasm } from "@/core/wasm/utils";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Footer indicator that surfaces Pyodide initialization progress. Mirrors
|
|
13
|
+
* the "Kernel" indicator but tracks the WASM runtime instead of the server
|
|
14
|
+
* connection. Hides itself once Pyodide is ready.
|
|
15
|
+
*/
|
|
16
|
+
export const PyodideStatus: React.FC = () => {
|
|
17
|
+
const status = useAtomValue(wasmInitStatusAtom);
|
|
18
|
+
const message = useAtomValue(wasmInitializationAtom);
|
|
19
|
+
|
|
20
|
+
if (!isWasm() || status === "ready") {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const icon =
|
|
25
|
+
status === "error" ? (
|
|
26
|
+
<AlertCircleIcon className="w-4 h-4 text-destructive" />
|
|
27
|
+
) : (
|
|
28
|
+
<Spinner size="small" />
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const tooltip = status === "error" ? "Pyodide failed to initialize" : message;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Tooltip
|
|
35
|
+
content={<div className="text-sm whitespace-pre-line">{tooltip}</div>}
|
|
36
|
+
data-testid="footer-pyodide-status"
|
|
37
|
+
>
|
|
38
|
+
<div
|
|
39
|
+
className="p-1 hover:bg-accent rounded flex items-center gap-1.5 text-xs text-muted-foreground"
|
|
40
|
+
data-testid="pyodide-status"
|
|
41
|
+
>
|
|
42
|
+
{icon}
|
|
43
|
+
<span>Pyodide</span>
|
|
44
|
+
</div>
|
|
45
|
+
</Tooltip>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "./footer-items/backend-status";
|
|
19
19
|
import { CopilotStatusIcon } from "./footer-items/copilot-status";
|
|
20
20
|
import { MachineStats } from "./footer-items/machine-stats";
|
|
21
|
+
import { PyodideStatus } from "./footer-items/pyodide-status";
|
|
21
22
|
import { RTCStatus } from "./footer-items/rtc-status";
|
|
22
23
|
import { RuntimeSettings } from "./footer-items/runtime-settings";
|
|
23
24
|
import { useSetDependencyPanelTab } from "./useDependencyPanelTab";
|
|
@@ -85,6 +86,7 @@ export const Footer: React.FC = () => {
|
|
|
85
86
|
|
|
86
87
|
<div className="mx-auto" />
|
|
87
88
|
|
|
89
|
+
<PyodideStatus />
|
|
88
90
|
<ConnectingKernelIndicatorItem />
|
|
89
91
|
|
|
90
92
|
<ShowInKioskMode>
|
package/src/core/run-app.tsx
CHANGED
|
@@ -10,7 +10,11 @@ import { buttonVariants } from "@/components/ui/button";
|
|
|
10
10
|
import { DelayMount } from "@/components/utils/delay-mount";
|
|
11
11
|
import { cn } from "@/utils/cn";
|
|
12
12
|
import { CellsRenderer } from "../components/editor/renderers/cells-renderer";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
hasCellsAtom,
|
|
15
|
+
notebookIsRunningAtom,
|
|
16
|
+
useCellActions,
|
|
17
|
+
} from "./cells/cells";
|
|
14
18
|
import type { AppConfig } from "./config/config-schema";
|
|
15
19
|
import { RuntimeState } from "./kernel/RuntimeState";
|
|
16
20
|
import { getSessionId } from "./kernel/session";
|
|
@@ -42,10 +46,13 @@ export const RunApp: React.FC<AppProps> = ({ appConfig }) => {
|
|
|
42
46
|
|
|
43
47
|
const isRunning = useAtomValue(notebookIsRunningAtom);
|
|
44
48
|
const isConnecting = isAppConnecting(connection.state);
|
|
49
|
+
// Skip the "Connecting..." gate when we already have cells to show — from
|
|
50
|
+
// an embedded snapshot or a prior connection.
|
|
51
|
+
const hasExistingCells = useAtomValue(hasCellsAtom);
|
|
45
52
|
|
|
46
53
|
const renderCells = () => {
|
|
47
54
|
// If we are connecting for more than 2 seconds, show a spinner
|
|
48
|
-
if (isConnecting) {
|
|
55
|
+
if (isConnecting && !hasExistingCells) {
|
|
49
56
|
return (
|
|
50
57
|
<DelayMount milliseconds={2000} fallback={null}>
|
|
51
58
|
<Spinner className="mx-auto" />
|
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
import { useAtomValue } from "jotai";
|
|
4
4
|
import type React from "react";
|
|
5
5
|
import type { PropsWithChildren } from "react";
|
|
6
|
+
import { useEffect, useRef } from "react";
|
|
6
7
|
import { LargeSpinner } from "@/components/icons/large-spinner";
|
|
8
|
+
import { toast } from "@/components/ui/use-toast";
|
|
9
|
+
import { hasCellsAtom } from "@/core/cells/cells";
|
|
7
10
|
import { showCodeInRunModeAtom } from "@/core/meta/state";
|
|
8
11
|
import { store } from "@/core/state/jotai";
|
|
9
12
|
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
13
|
+
import { prettyError } from "@/utils/errors";
|
|
14
|
+
import { Logger } from "@/utils/Logger";
|
|
10
15
|
import { hasQueryParam } from "@/utils/urls";
|
|
11
16
|
import { KnownQueryParams } from "../constants";
|
|
12
|
-
import { getInitialAppMode } from "../mode";
|
|
17
|
+
import { type AppMode, getInitialAppMode } from "../mode";
|
|
13
18
|
import { PyodideBridge } from "./bridge";
|
|
14
19
|
import { hasAnyOutputAtom, wasmInitializationAtom } from "./state";
|
|
15
20
|
import { isWasm } from "./utils";
|
|
@@ -26,30 +31,44 @@ export const PyodideLoader: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
26
31
|
};
|
|
27
32
|
|
|
28
33
|
const PyodideLoaderInner: React.FC<PropsWithChildren> = ({ children }) => {
|
|
29
|
-
//
|
|
30
|
-
|
|
34
|
+
// Don't block render on Pyodide: a hydrated snapshot can paint immediately
|
|
35
|
+
// while Pyodide downloads in the background.
|
|
36
|
+
const { error } = useAsyncData(async () => {
|
|
31
37
|
await PyodideBridge.INSTANCE.initialized.promise;
|
|
32
38
|
return true;
|
|
33
39
|
}, []);
|
|
34
40
|
|
|
41
|
+
const hasCells = useAtomValue(hasCellsAtom);
|
|
35
42
|
const hasOutput = useAtomValue(hasAnyOutputAtom);
|
|
43
|
+
const nothingToShow = shouldShowSpinner({
|
|
44
|
+
hasCells,
|
|
45
|
+
hasOutput,
|
|
46
|
+
mode: getInitialAppMode(),
|
|
47
|
+
codeHidden: isCodeHidden(),
|
|
48
|
+
});
|
|
36
49
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
const didToastErrorRef = useRef(false);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
// With snapshot content on-screen, toast instead of throwing so the
|
|
53
|
+
// snapshot stays readable. The ref ensures we only toast once even if
|
|
54
|
+
// nothingToShow toggles later.
|
|
55
|
+
if (error && !nothingToShow && !didToastErrorRef.current) {
|
|
56
|
+
didToastErrorRef.current = true;
|
|
57
|
+
Logger.error("Pyodide failed to initialize", error);
|
|
58
|
+
toast({
|
|
59
|
+
title: "Failed to start the notebook runtime",
|
|
60
|
+
description: prettyError(error),
|
|
61
|
+
variant: "danger",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}, [error, nothingToShow]);
|
|
40
65
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// - we are not showing the code
|
|
44
|
-
// - and there is no output
|
|
45
|
-
// then show the spinner
|
|
46
|
-
if (!hasOutput && getInitialAppMode() === "read" && isCodeHidden()) {
|
|
47
|
-
return <WasmSpinner />;
|
|
66
|
+
if (error && nothingToShow) {
|
|
67
|
+
throw error;
|
|
48
68
|
}
|
|
49
69
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
throw error;
|
|
70
|
+
if (nothingToShow) {
|
|
71
|
+
return <WasmSpinner />;
|
|
53
72
|
}
|
|
54
73
|
|
|
55
74
|
return children;
|
|
@@ -65,6 +84,25 @@ function isCodeHidden() {
|
|
|
65
84
|
);
|
|
66
85
|
}
|
|
67
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Pure predicate: should the WASM loader render a spinner instead of its
|
|
89
|
+
* children? We block render only when nothing user-visible would appear:
|
|
90
|
+
* - no cells have been hydrated (Pyodide hasn't parsed the notebook), or
|
|
91
|
+
* - we are in headless run mode (code hidden) with no outputs to display.
|
|
92
|
+
*/
|
|
93
|
+
export function shouldShowSpinner(input: {
|
|
94
|
+
hasCells: boolean;
|
|
95
|
+
hasOutput: boolean;
|
|
96
|
+
mode: AppMode;
|
|
97
|
+
codeHidden: boolean;
|
|
98
|
+
}): boolean {
|
|
99
|
+
const { hasCells, hasOutput, mode, codeHidden } = input;
|
|
100
|
+
if (!hasCells) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return !hasOutput && mode === "read" && codeHidden;
|
|
104
|
+
}
|
|
105
|
+
|
|
68
106
|
export const WasmSpinner: React.FC<PropsWithChildren> = ({ children }) => {
|
|
69
107
|
const wasmInitialization = useAtomValue(wasmInitializationAtom);
|
|
70
108
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { shouldShowSpinner } from "../PyodideLoader";
|
|
5
|
+
|
|
6
|
+
describe("shouldShowSpinner", () => {
|
|
7
|
+
it("shows the spinner when there are no cells yet (Pyodide hasn't parsed)", () => {
|
|
8
|
+
expect(
|
|
9
|
+
shouldShowSpinner({
|
|
10
|
+
hasCells: false,
|
|
11
|
+
hasOutput: false,
|
|
12
|
+
mode: "read",
|
|
13
|
+
codeHidden: false,
|
|
14
|
+
}),
|
|
15
|
+
).toBe(true);
|
|
16
|
+
|
|
17
|
+
expect(
|
|
18
|
+
shouldShowSpinner({
|
|
19
|
+
hasCells: false,
|
|
20
|
+
hasOutput: true,
|
|
21
|
+
mode: "edit",
|
|
22
|
+
codeHidden: false,
|
|
23
|
+
}),
|
|
24
|
+
).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("renders children once cells exist with code visible", () => {
|
|
28
|
+
// run mode, code visible, no outputs yet — the user can read the code
|
|
29
|
+
expect(
|
|
30
|
+
shouldShowSpinner({
|
|
31
|
+
hasCells: true,
|
|
32
|
+
hasOutput: false,
|
|
33
|
+
mode: "read",
|
|
34
|
+
codeHidden: false,
|
|
35
|
+
}),
|
|
36
|
+
).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("renders children once cells exist with cached outputs (snapshot case)", () => {
|
|
40
|
+
expect(
|
|
41
|
+
shouldShowSpinner({
|
|
42
|
+
hasCells: true,
|
|
43
|
+
hasOutput: true,
|
|
44
|
+
mode: "read",
|
|
45
|
+
codeHidden: true,
|
|
46
|
+
}),
|
|
47
|
+
).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("keeps the spinner up in headless run mode with no outputs", () => {
|
|
51
|
+
// read mode + code hidden + no outputs = nothing visible to render
|
|
52
|
+
expect(
|
|
53
|
+
shouldShowSpinner({
|
|
54
|
+
hasCells: true,
|
|
55
|
+
hasOutput: false,
|
|
56
|
+
mode: "read",
|
|
57
|
+
codeHidden: true,
|
|
58
|
+
}),
|
|
59
|
+
).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("never blocks edit mode once cells exist", () => {
|
|
63
|
+
expect(
|
|
64
|
+
shouldShowSpinner({
|
|
65
|
+
hasCells: true,
|
|
66
|
+
hasOutput: false,
|
|
67
|
+
mode: "edit",
|
|
68
|
+
codeHidden: true,
|
|
69
|
+
}),
|
|
70
|
+
).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
});
|
package/src/core/wasm/bridge.ts
CHANGED
|
@@ -37,7 +37,7 @@ import type { IConnectionTransport } from "../websocket/transports/transport";
|
|
|
37
37
|
import { PyodideRouter } from "./router";
|
|
38
38
|
import { getWorkerRPC } from "./rpc";
|
|
39
39
|
import { createShareableLink } from "./share";
|
|
40
|
-
import { wasmInitializationAtom } from "./state";
|
|
40
|
+
import { wasmInitializationAtom, wasmInitStatusAtom } from "./state";
|
|
41
41
|
import { fallbackFileStore, notebookFileStore } from "./store";
|
|
42
42
|
import { isWasm } from "./utils";
|
|
43
43
|
import type { SaveWorkerSchema } from "./worker/save-worker";
|
|
@@ -119,13 +119,15 @@ export class PyodideBridge implements RunRequests, EditRequests {
|
|
|
119
119
|
// By initializing after, we get hits on cached network requests
|
|
120
120
|
this.saveRpc = this.getSaveWorker();
|
|
121
121
|
this.setInterruptBuffer();
|
|
122
|
+
store.set(wasmInitStatusAtom, "ready");
|
|
122
123
|
this.initialized.resolve();
|
|
123
124
|
});
|
|
124
125
|
this.rpc.addMessageListener("initializingMessage", ({ message }) => {
|
|
125
126
|
store.set(wasmInitializationAtom, message);
|
|
126
127
|
});
|
|
127
128
|
this.rpc.addMessageListener("initializedError", ({ error }) => {
|
|
128
|
-
// If already
|
|
129
|
+
// If already initialized, surface as a toast and leave the deferred /
|
|
130
|
+
// init status alone — the worker is healthy, this is a runtime error.
|
|
129
131
|
if (this.initialized.status === "resolved") {
|
|
130
132
|
Logger.error(error);
|
|
131
133
|
toast({
|
|
@@ -133,7 +135,9 @@ export class PyodideBridge implements RunRequests, EditRequests {
|
|
|
133
135
|
description: error,
|
|
134
136
|
variant: "danger",
|
|
135
137
|
});
|
|
138
|
+
return;
|
|
136
139
|
}
|
|
140
|
+
store.set(wasmInitStatusAtom, "error");
|
|
137
141
|
this.initialized.reject(new Error(error));
|
|
138
142
|
});
|
|
139
143
|
this.rpc.addMessageListener("kernelMessage", ({ message }) => {
|
package/src/core/wasm/state.ts
CHANGED
|
@@ -5,6 +5,9 @@ import { isOutputEmpty } from "../cells/outputs";
|
|
|
5
5
|
|
|
6
6
|
export const wasmInitializationAtom = atom<string>("Initializing...");
|
|
7
7
|
|
|
8
|
+
export type WasmInitStatus = "loading" | "ready" | "error";
|
|
9
|
+
export const wasmInitStatusAtom = atom<WasmInitStatus>("loading");
|
|
10
|
+
|
|
8
11
|
export const hasAnyOutputAtom = atom<boolean>((get) => {
|
|
9
12
|
const notebook = get(notebookAtom);
|
|
10
13
|
const runtimeStates = Object.values(notebook.cellRuntime);
|