@marimo-team/islands 0.19.3-dev6 → 0.19.3-dev8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.19.3-dev6",
3
+ "version": "0.19.3-dev8",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -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
+ };
@@ -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>
@@ -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
  }
@@ -192,6 +192,8 @@ export async function initialize() {
192
192
  return;
193
193
  case "cache-info":
194
194
  return;
195
+ case "kernel-startup-error":
196
+ return;
195
197
  default:
196
198
  logNever(msg.data);
197
199
  }
@@ -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