@marimo-team/islands 0.19.2-dev0 → 0.19.3-dev10
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/main.js +4 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/editor/KernelStartupErrorModal.tsx +101 -0
- package/src/components/editor/notebook-cell.tsx +13 -13
- package/src/core/MarimoApp.tsx +2 -0
- package/src/core/cells/cells.ts +1 -4
- package/src/core/errors/state.ts +7 -1
- package/src/core/islands/main.ts +2 -0
- package/src/core/websocket/types.ts +1 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +18 -1
- package/src/css/app/Cell.css +6 -0
package/package.json
CHANGED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useAtom } from "jotai";
|
|
4
|
+
import { CopyIcon, HomeIcon, XCircleIcon } from "lucide-react";
|
|
5
|
+
import { kernelStartupErrorAtom } from "@/core/errors/state";
|
|
6
|
+
import {
|
|
7
|
+
AlertDialog,
|
|
8
|
+
AlertDialogAction,
|
|
9
|
+
AlertDialogContent,
|
|
10
|
+
AlertDialogDescription,
|
|
11
|
+
AlertDialogFooter,
|
|
12
|
+
AlertDialogHeader,
|
|
13
|
+
AlertDialogTitle,
|
|
14
|
+
} from "../ui/alert-dialog";
|
|
15
|
+
import { Button } from "../ui/button";
|
|
16
|
+
import { toast } from "../ui/use-toast";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Modal that displays kernel startup errors.
|
|
20
|
+
* Shows when the kernel fails to start in sandbox mode,
|
|
21
|
+
* displaying the stderr output so users can diagnose the issue.
|
|
22
|
+
*/
|
|
23
|
+
export const KernelStartupErrorModal: React.FC = () => {
|
|
24
|
+
const [error, setError] = useAtom(kernelStartupErrorAtom);
|
|
25
|
+
|
|
26
|
+
if (error === null) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const handleCopy = async () => {
|
|
31
|
+
try {
|
|
32
|
+
await navigator.clipboard.writeText(error);
|
|
33
|
+
toast({
|
|
34
|
+
title: "Copied to clipboard",
|
|
35
|
+
description: "Error details have been copied to your clipboard.",
|
|
36
|
+
});
|
|
37
|
+
} catch {
|
|
38
|
+
toast({
|
|
39
|
+
title: "Failed to copy",
|
|
40
|
+
description: "Could not copy to clipboard.",
|
|
41
|
+
variant: "danger",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleClose = () => {
|
|
47
|
+
setError(null);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleReturnHome = () => {
|
|
51
|
+
const withoutSearch = document.baseURI.split("?")[0];
|
|
52
|
+
window.open(withoutSearch, "_self");
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<AlertDialog open={true} onOpenChange={(open) => !open && handleClose()}>
|
|
57
|
+
<AlertDialogContent className="max-w-2xl">
|
|
58
|
+
<AlertDialogHeader>
|
|
59
|
+
<AlertDialogTitle className="flex items-center gap-2 text-destructive">
|
|
60
|
+
<XCircleIcon className="h-5 w-5" />
|
|
61
|
+
Kernel Startup Failed
|
|
62
|
+
</AlertDialogTitle>
|
|
63
|
+
<AlertDialogDescription>
|
|
64
|
+
The kernel failed to start. This usually happens when the package
|
|
65
|
+
manager can't install your notebook's dependencies.
|
|
66
|
+
</AlertDialogDescription>
|
|
67
|
+
</AlertDialogHeader>
|
|
68
|
+
<div className="my-4">
|
|
69
|
+
<div className="flex items-center justify-between mb-2">
|
|
70
|
+
<span className="text-sm font-medium text-muted-foreground">
|
|
71
|
+
Error Details
|
|
72
|
+
</span>
|
|
73
|
+
<Button
|
|
74
|
+
variant="outline"
|
|
75
|
+
size="xs"
|
|
76
|
+
onClick={handleCopy}
|
|
77
|
+
className="flex items-center gap-1"
|
|
78
|
+
>
|
|
79
|
+
<CopyIcon className="h-3 w-3" />
|
|
80
|
+
Copy
|
|
81
|
+
</Button>
|
|
82
|
+
</div>
|
|
83
|
+
<pre className="bg-muted p-4 rounded-md text-sm font-mono overflow-auto max-h-80 whitespace-pre-wrap break-words">
|
|
84
|
+
{error}
|
|
85
|
+
</pre>
|
|
86
|
+
</div>
|
|
87
|
+
<AlertDialogFooter>
|
|
88
|
+
<Button
|
|
89
|
+
variant="outline"
|
|
90
|
+
onClick={handleReturnHome}
|
|
91
|
+
className="flex items-center gap-2"
|
|
92
|
+
>
|
|
93
|
+
<HomeIcon className="h-4 w-4" />
|
|
94
|
+
Return to Home
|
|
95
|
+
</Button>
|
|
96
|
+
<AlertDialogAction onClick={handleClose}>Dismiss</AlertDialogAction>
|
|
97
|
+
</AlertDialogFooter>
|
|
98
|
+
</AlertDialogContent>
|
|
99
|
+
</AlertDialog>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
@@ -585,7 +585,7 @@ const EditableCellComponent = ({
|
|
|
585
585
|
className={cn(
|
|
586
586
|
className,
|
|
587
587
|
navigationProps.className,
|
|
588
|
-
"focus:ring-1 focus:ring-(--slate-
|
|
588
|
+
"focus:ring-1 focus:ring-(--slate-8) focus:ring-offset-2",
|
|
589
589
|
)}
|
|
590
590
|
ref={cellContainerRef}
|
|
591
591
|
{...cellDomProps(cellId, cellData.name)}
|
|
@@ -1032,16 +1032,12 @@ const SetupCellComponent = ({
|
|
|
1032
1032
|
const hasConsoleOutput = cellRuntime.consoleOutputs.length > 0;
|
|
1033
1033
|
const isErrorOutput = isErrorMime(cellRuntime.output?.mimetype);
|
|
1034
1034
|
|
|
1035
|
-
const className = clsx(
|
|
1036
|
-
|
|
1037
|
-
"
|
|
1038
|
-
"
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
"has-error": cellRuntime.errored,
|
|
1042
|
-
stopped: cellRuntime.stopped,
|
|
1043
|
-
},
|
|
1044
|
-
);
|
|
1035
|
+
const className = clsx("marimo-cell", "hover-actions-parent z-10", {
|
|
1036
|
+
interactive: true,
|
|
1037
|
+
"needs-run": needsRun,
|
|
1038
|
+
"has-error": cellRuntime.errored,
|
|
1039
|
+
stopped: cellRuntime.stopped,
|
|
1040
|
+
});
|
|
1045
1041
|
|
|
1046
1042
|
const handleRefactorWithAI: OnRefactorWithAI = useEvent(
|
|
1047
1043
|
(opts: { prompt: string; triggerImmediately: boolean }) => {
|
|
@@ -1076,7 +1072,7 @@ const SetupCellComponent = ({
|
|
|
1076
1072
|
{...mergeProps(navigationProps, {
|
|
1077
1073
|
className: cn(
|
|
1078
1074
|
className,
|
|
1079
|
-
"focus:ring-1 focus:ring-(--
|
|
1075
|
+
"focus:ring-1 focus:ring-(--slate-8) focus:ring-offset-2",
|
|
1080
1076
|
),
|
|
1081
1077
|
onBlur: closeCompletionHandler,
|
|
1082
1078
|
onKeyDown: resumeCompletionHandler,
|
|
@@ -1086,7 +1082,11 @@ const SetupCellComponent = ({
|
|
|
1086
1082
|
tabIndex={-1}
|
|
1087
1083
|
data-setup-cell={true}
|
|
1088
1084
|
>
|
|
1089
|
-
<div
|
|
1085
|
+
<div
|
|
1086
|
+
className={cn("tray")}
|
|
1087
|
+
data-has-output-above={false}
|
|
1088
|
+
data-hidden={!isCellCodeShown}
|
|
1089
|
+
>
|
|
1090
1090
|
<StagedAICellBackground
|
|
1091
1091
|
cellId={cellId}
|
|
1092
1092
|
className="mo-ai-setup-cell"
|
package/src/core/MarimoApp.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import { getInitialAppMode } from "@/core/mode";
|
|
|
12
12
|
import { CssVariables } from "@/theme/ThemeProvider";
|
|
13
13
|
import { reactLazyWithPreload } from "@/utils/lazy";
|
|
14
14
|
import { ErrorBoundary } from "../components/editor/boundary/ErrorBoundary";
|
|
15
|
+
import { KernelStartupErrorModal } from "../components/editor/KernelStartupErrorModal";
|
|
15
16
|
import { ModalProvider } from "../components/modal/ImperativeModal";
|
|
16
17
|
import { Toaster } from "../components/ui/toaster";
|
|
17
18
|
import { TooltipProvider } from "../components/ui/tooltip";
|
|
@@ -91,6 +92,7 @@ const Providers = memo(({ children }: PropsWithChildren) => {
|
|
|
91
92
|
{children}
|
|
92
93
|
<Toaster />
|
|
93
94
|
<TailwindIndicator />
|
|
95
|
+
<KernelStartupErrorModal />
|
|
94
96
|
</ModalProvider>
|
|
95
97
|
</LocaleProvider>
|
|
96
98
|
</SlotzProvider>
|
package/src/core/cells/cells.ts
CHANGED
|
@@ -1325,10 +1325,7 @@ const {
|
|
|
1325
1325
|
};
|
|
1326
1326
|
},
|
|
1327
1327
|
addSetupCellIfDoesntExist: (state, action: { code?: string }) => {
|
|
1328
|
-
|
|
1329
|
-
if (code == null) {
|
|
1330
|
-
code = "# Initialization code that runs before all other cells";
|
|
1331
|
-
}
|
|
1328
|
+
const { code } = action;
|
|
1332
1329
|
|
|
1333
1330
|
if (state.cellIds.setupCellExists()) {
|
|
1334
1331
|
// Just focus on the existing setup cell
|
package/src/core/errors/state.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { useAtomValue } from "jotai";
|
|
3
|
+
import { atom, useAtomValue } from "jotai";
|
|
4
4
|
import { createReducerAndAtoms } from "@/utils/createReducer";
|
|
5
5
|
import type { Identified } from "@/utils/typed";
|
|
6
6
|
import { generateUUID } from "@/utils/uuid";
|
|
7
7
|
import type { Banner } from "../kernel/messages";
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Atom for storing kernel startup error message.
|
|
11
|
+
* When set to a non-null value, shows a modal with the error details.
|
|
12
|
+
*/
|
|
13
|
+
export const kernelStartupErrorAtom = atom<string | null>(null);
|
|
14
|
+
|
|
9
15
|
interface BannerState {
|
|
10
16
|
banners: Identified<Banner>[];
|
|
11
17
|
}
|
package/src/core/islands/main.ts
CHANGED
|
@@ -16,6 +16,7 @@ export const WebSocketClosedReason = {
|
|
|
16
16
|
KERNEL_DISCONNECTED: "KERNEL_DISCONNECTED",
|
|
17
17
|
ALREADY_RUNNING: "ALREADY_RUNNING",
|
|
18
18
|
MALFORMED_QUERY: "MALFORMED_QUERY",
|
|
19
|
+
KERNEL_STARTUP_ERROR: "KERNEL_STARTUP_ERROR",
|
|
19
20
|
} as const;
|
|
20
21
|
|
|
21
22
|
export type WebSocketClosedReason =
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
} from "../datasets/request-registry";
|
|
41
41
|
import { useDatasetsActions } from "../datasets/state";
|
|
42
42
|
import { UI_ELEMENT_REGISTRY } from "../dom/uiregistry";
|
|
43
|
-
import { useBannersActions } from "../errors/state";
|
|
43
|
+
import { kernelStartupErrorAtom, useBannersActions } from "../errors/state";
|
|
44
44
|
import { FUNCTIONS_REGISTRY } from "../functions/FunctionRegistry";
|
|
45
45
|
import {
|
|
46
46
|
handleCellNotificationeration,
|
|
@@ -105,6 +105,7 @@ export function useMarimoKernelConnection(opts: {
|
|
|
105
105
|
const setCapabilities = useSetAtom(capabilitiesAtom);
|
|
106
106
|
const runtimeManager = useRuntimeManager();
|
|
107
107
|
const setCacheInfo = useSetAtom(cacheInfoAtom);
|
|
108
|
+
const setKernelStartupError = useSetAtom(kernelStartupErrorAtom);
|
|
108
109
|
|
|
109
110
|
const handleMessage = (e: MessageEvent<JsonString<NotificationPayload>>) => {
|
|
110
111
|
const msg = jsonParseWithSpecialChar(e.data);
|
|
@@ -134,6 +135,11 @@ export function useMarimoKernelConnection(opts: {
|
|
|
134
135
|
case "interrupted":
|
|
135
136
|
return;
|
|
136
137
|
|
|
138
|
+
case "kernel-startup-error":
|
|
139
|
+
// Full error received via message before websocket close
|
|
140
|
+
setKernelStartupError(msg.data.error);
|
|
141
|
+
return;
|
|
142
|
+
|
|
137
143
|
case "send-ui-element-message": {
|
|
138
144
|
const modelId = msg.data.model_id;
|
|
139
145
|
const uiElement = msg.data.ui_element;
|
|
@@ -413,6 +419,17 @@ export function useMarimoKernelConnection(opts: {
|
|
|
413
419
|
return;
|
|
414
420
|
|
|
415
421
|
default:
|
|
422
|
+
// Check for kernel startup error (full error already received via message)
|
|
423
|
+
if (e.reason === "MARIMO_KERNEL_STARTUP_ERROR") {
|
|
424
|
+
setConnection({
|
|
425
|
+
state: WebSocketState.CLOSED,
|
|
426
|
+
code: WebSocketClosedReason.KERNEL_STARTUP_ERROR,
|
|
427
|
+
reason: "Failed to start kernel sandbox",
|
|
428
|
+
});
|
|
429
|
+
ws.close(); // prevent reconnecting
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
416
433
|
// Session should be valid
|
|
417
434
|
// - browser tab might have been closed or re-opened
|
|
418
435
|
// - computer might have just woken from sleep
|
package/src/css/app/Cell.css
CHANGED