@marimo-team/islands 0.24.3-dev81 → 0.24.3-dev83
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/{ErrorBoundary-CCi-VkF7.js → ErrorBoundary-Uf_LvjvY.js} +1 -1
- package/dist/{cells-BIfOnGBK.js → cells-DYIkSxs6.js} +4 -0
- package/dist/{chat-ui-LWkjhObe.js → chat-ui-BXj3uYjy.js} +4 -4
- package/dist/{code-visibility-Bsfqbe2z.js → code-visibility-DxgQWu1j.js} +2 -2
- package/dist/{common-BnKC1tWB.js → common-ByAtncLr.js} +3 -3
- package/dist/{data-grid-overlay-editor-CJnlWykO.js → data-grid-overlay-editor-DL_blFyL.js} +1 -1
- package/dist/{error-entries-CR0oClSL.js → error-entries-C0NsUqXl.js} +1 -1
- package/dist/{feedback-button-MWISfIIf.js → feedback-button-AZcBqfoV.js} +4 -4
- package/dist/{glide-data-editor-dcmP8mKS.js → glide-data-editor-BJdZncJk.js} +3 -3
- package/dist/{globals-fCMBgbAD.js → globals-yWarpyN4.js} +1 -1
- package/dist/{html-to-image-_i03yR4q.js → html-to-image-CeYYvQNb.js} +1 -1
- package/dist/main.js +12 -11
- package/dist/{number-overlay-editor-DhNE6pBe.js → number-overlay-editor-BatBVYxr.js} +1 -1
- package/dist/{process-output-D3zQyTAS.js → process-output-BCTjlyuH.js} +2 -2
- package/dist/{reveal-component-Cdcgngkc.js → reveal-component-D02jcpiJ.js} +3 -3
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/editor/alerts/__tests__/connection-notice.test.tsx +186 -0
- package/src/components/editor/alerts/__tests__/startup-progress.test.tsx +185 -0
- package/src/components/editor/alerts/connection-notice.tsx +100 -54
- package/src/components/editor/alerts/startup-output.tsx +173 -0
- package/src/components/editor/alerts/startup-progress.tsx +204 -0
- package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +53 -4
- package/src/components/editor/chrome/panels/__tests__/sandbox-details.test.tsx +358 -0
- package/src/components/editor/chrome/panels/__tests__/sandbox-recovery.test.tsx +5 -2
- package/src/components/editor/chrome/panels/packages-panel.tsx +32 -21
- package/src/components/editor/chrome/panels/sandbox-details-state.ts +38 -0
- package/src/components/editor/chrome/panels/sandbox-panel.tsx +188 -131
- package/src/components/editor/chrome/panels/sandbox-toggle.tsx +52 -0
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +7 -0
- package/src/components/editor/package-alert.tsx +112 -66
- package/src/core/alerts/__tests__/environment.test.ts +183 -0
- package/src/core/alerts/__tests__/state.test.tsx +240 -0
- package/src/core/alerts/environment.ts +57 -0
- package/src/core/alerts/state.ts +155 -99
- package/src/core/islands/bootstrap.ts +2 -1
- package/src/core/kernel/messages.ts +2 -2
- package/src/core/network/connection-notice.ts +45 -12
- package/src/core/network/connection.ts +19 -0
- package/src/core/network/useConnectionNotice.ts +53 -0
- package/src/core/packages/__tests__/useSandboxController.test.tsx +86 -5
- package/src/core/packages/sandbox-state.ts +56 -4
- package/src/core/packages/useSandboxController.ts +27 -7
- package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +199 -8
- package/src/core/websocket/types.ts +1 -1
- package/src/core/websocket/useMarimoKernelConnection.tsx +31 -19
- package/src/stories/sandbox.stories.tsx +230 -50
|
@@ -42,6 +42,7 @@ vi.mock("@/core/runtime/config", async () => {
|
|
|
42
42
|
|
|
43
43
|
import { MockNotebook } from "@/__mocks__/notebook";
|
|
44
44
|
import { cellId } from "@/__tests__/branded";
|
|
45
|
+
import { alertAtom, getPackageAlert } from "@/core/alerts/state";
|
|
45
46
|
import { notebookAtom } from "@/core/cells/cells";
|
|
46
47
|
import { AppConfigSchema } from "@/core/config/config-schema";
|
|
47
48
|
import { ConnectionNotice } from "@/components/editor/alerts/connection-notice";
|
|
@@ -49,7 +50,7 @@ import { kernelStartupErrorAtom } from "@/core/errors/state";
|
|
|
49
50
|
import type { NotificationPayload } from "@/core/kernel/messages";
|
|
50
51
|
import { useRuntimeManager } from "@/core/runtime/config";
|
|
51
52
|
import { initialRunCompletedAtom } from "../../kernel/state";
|
|
52
|
-
import { connectionAtom } from "../../network/connection";
|
|
53
|
+
import { connectionAtom, startupProgressAtom } from "../../network/connection";
|
|
53
54
|
import type { SessionId } from "../../kernel/session";
|
|
54
55
|
import { WebSocketClosedReason, WebSocketState } from "../types";
|
|
55
56
|
import type { IConnectionTransport } from "../transports/transport";
|
|
@@ -306,7 +307,12 @@ describe("connection notice", () => {
|
|
|
306
307
|
|
|
307
308
|
it("keeps elapsed time across startup phases and leaves reconnection to the footer once cells are available", () => {
|
|
308
309
|
const { store, options, send } = renderNotice();
|
|
309
|
-
send({
|
|
310
|
+
send({
|
|
311
|
+
op: "startup-progress",
|
|
312
|
+
phase: "preparing-environment",
|
|
313
|
+
logs: "",
|
|
314
|
+
log_mode: "replace",
|
|
315
|
+
});
|
|
310
316
|
expect(
|
|
311
317
|
screen.queryByRole("region", { name: "Notebook startup" }),
|
|
312
318
|
).not.toBeInTheDocument();
|
|
@@ -324,8 +330,13 @@ describe("connection notice", () => {
|
|
|
324
330
|
}),
|
|
325
331
|
),
|
|
326
332
|
);
|
|
327
|
-
send({
|
|
328
|
-
|
|
333
|
+
send({
|
|
334
|
+
op: "startup-progress",
|
|
335
|
+
phase: "starting-kernel",
|
|
336
|
+
logs: "",
|
|
337
|
+
log_mode: "replace",
|
|
338
|
+
});
|
|
339
|
+
expect(screen.getByRole("status")).toHaveTextContent("Starting kernel");
|
|
329
340
|
expect(screen.getByText("Elapsed 35s")).toBeInTheDocument();
|
|
330
341
|
send({ op: "reconnected" });
|
|
331
342
|
expect(
|
|
@@ -342,11 +353,71 @@ describe("connection notice", () => {
|
|
|
342
353
|
).not.toBeInTheDocument();
|
|
343
354
|
});
|
|
344
355
|
|
|
356
|
+
it("restores startup output before appending live chunks and resets it for a new attempt", () => {
|
|
357
|
+
const { store, send } = renderNotice();
|
|
358
|
+
const snapshot = {
|
|
359
|
+
op: "startup-progress",
|
|
360
|
+
phase: "starting-kernel",
|
|
361
|
+
logs: "Downloading runtime\n",
|
|
362
|
+
log_mode: "replace",
|
|
363
|
+
} as const;
|
|
364
|
+
send(snapshot);
|
|
365
|
+
send(snapshot);
|
|
366
|
+
const connection = store.get(connectionAtom);
|
|
367
|
+
send({ ...snapshot, logs: "Loading kernel\n", log_mode: "append" });
|
|
368
|
+
expect(store.get(startupProgressAtom)).toEqual({
|
|
369
|
+
phase: "starting-kernel",
|
|
370
|
+
logs: "Downloading runtime\nLoading kernel\n",
|
|
371
|
+
});
|
|
372
|
+
expect(store.get(connectionAtom)).toBe(connection);
|
|
373
|
+
expect(getPackageAlert(store.get(alertAtom))).toBeNull();
|
|
374
|
+
|
|
375
|
+
send({ ...snapshot, phase: "preparing-environment", logs: "" });
|
|
376
|
+
expect(store.get(startupProgressAtom)).toEqual({
|
|
377
|
+
phase: "preparing-environment",
|
|
378
|
+
logs: "",
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("restores completed startup output without putting a connected kernel back into startup", async () => {
|
|
383
|
+
const { store, options, send } = renderNotice();
|
|
384
|
+
send({ op: "reconnected" });
|
|
385
|
+
send({
|
|
386
|
+
op: "startup-progress",
|
|
387
|
+
phase: "starting-kernel",
|
|
388
|
+
logs: "Kernel startup output\n",
|
|
389
|
+
log_mode: "replace",
|
|
390
|
+
});
|
|
391
|
+
expect(store.get(connectionAtom)).toEqual({ state: WebSocketState.OPEN });
|
|
392
|
+
expect(store.get(startupProgressAtom)).toEqual({
|
|
393
|
+
phase: "starting-kernel",
|
|
394
|
+
logs: "Kernel startup output\n",
|
|
395
|
+
});
|
|
396
|
+
act(() => vi.advanceTimersByTime(500));
|
|
397
|
+
expect(
|
|
398
|
+
screen.queryByRole("region", { name: "Notebook startup" }),
|
|
399
|
+
).not.toBeInTheDocument();
|
|
400
|
+
|
|
401
|
+
// A new transport starts from the next session's authoritative snapshot.
|
|
402
|
+
await act(async () => options.onOpen(new Event("open")));
|
|
403
|
+
expect(store.get(startupProgressAtom)).toBeNull();
|
|
404
|
+
});
|
|
405
|
+
|
|
345
406
|
it("does not flash a notice when startup finishes within the delay", () => {
|
|
346
407
|
const { send } = renderNotice();
|
|
347
|
-
send({
|
|
408
|
+
send({
|
|
409
|
+
op: "startup-progress",
|
|
410
|
+
phase: "preparing-environment",
|
|
411
|
+
logs: "",
|
|
412
|
+
log_mode: "replace",
|
|
413
|
+
});
|
|
348
414
|
act(() => vi.advanceTimersByTime(200));
|
|
349
|
-
send({
|
|
415
|
+
send({
|
|
416
|
+
op: "startup-progress",
|
|
417
|
+
phase: "starting-kernel",
|
|
418
|
+
logs: "",
|
|
419
|
+
log_mode: "replace",
|
|
420
|
+
});
|
|
350
421
|
act(() => vi.advanceTimersByTime(200));
|
|
351
422
|
expect(
|
|
352
423
|
screen.queryByRole("region", { name: "Notebook startup" }),
|
|
@@ -365,7 +436,7 @@ describe("connection notice", () => {
|
|
|
365
436
|
"keeps a %s failure available until the user retries",
|
|
366
437
|
async (phase, title) => {
|
|
367
438
|
const { store, transport, options, send } = renderNotice();
|
|
368
|
-
send({ op: "startup-progress", phase });
|
|
439
|
+
send({ op: "startup-progress", phase, logs: "", log_mode: "replace" });
|
|
369
440
|
const error = "A full diagnostic\nwith <stderr> details";
|
|
370
441
|
send({ op: "kernel-startup-error", error });
|
|
371
442
|
transport.readyState = WebSocket.CLOSED;
|
|
@@ -389,7 +460,12 @@ describe("connection notice", () => {
|
|
|
389
460
|
expect(
|
|
390
461
|
screen.queryByRole("button", { name: "Try again" }),
|
|
391
462
|
).not.toBeInTheDocument();
|
|
392
|
-
send({
|
|
463
|
+
send({
|
|
464
|
+
op: "startup-progress",
|
|
465
|
+
phase: "preparing-environment",
|
|
466
|
+
logs: "",
|
|
467
|
+
log_mode: "replace",
|
|
468
|
+
});
|
|
393
469
|
act(() => vi.advanceTimersByTime(500));
|
|
394
470
|
expect(screen.getByRole("status")).toHaveTextContent(
|
|
395
471
|
"Preparing environment",
|
|
@@ -398,3 +474,118 @@ describe("connection notice", () => {
|
|
|
398
474
|
},
|
|
399
475
|
);
|
|
400
476
|
});
|
|
477
|
+
|
|
478
|
+
it("replaces environment state from snapshots, then continues live progress", () => {
|
|
479
|
+
const store = createStore();
|
|
480
|
+
vi.mocked(useConnectionTransport).mockClear();
|
|
481
|
+
vi.mocked(useConnectionTransport).mockReturnValue(
|
|
482
|
+
makeTransport(WebSocket.OPEN),
|
|
483
|
+
);
|
|
484
|
+
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
485
|
+
makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
|
|
486
|
+
);
|
|
487
|
+
renderConnectionHook(store);
|
|
488
|
+
const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)?.[0];
|
|
489
|
+
function receive(data: NotificationPayload["data"]) {
|
|
490
|
+
act(() =>
|
|
491
|
+
options?.onMessage(
|
|
492
|
+
new MessageEvent("message", {
|
|
493
|
+
data: JSON.stringify({ op: data.op, data }),
|
|
494
|
+
}),
|
|
495
|
+
),
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
receive({
|
|
499
|
+
op: "environment-operation",
|
|
500
|
+
source: "kernel",
|
|
501
|
+
operation_id: "old",
|
|
502
|
+
action: "install",
|
|
503
|
+
status: { kind: "running" },
|
|
504
|
+
packages: { pandas: "running" },
|
|
505
|
+
logs: { pandas: "stale" },
|
|
506
|
+
log_mode: "append",
|
|
507
|
+
});
|
|
508
|
+
const operation = {
|
|
509
|
+
operation_id: "new",
|
|
510
|
+
action: "install",
|
|
511
|
+
source: "kernel",
|
|
512
|
+
status: { kind: "running" },
|
|
513
|
+
packages: { numpy: "running" },
|
|
514
|
+
logs: { numpy: "Downloading\n" },
|
|
515
|
+
} as const;
|
|
516
|
+
const serverOperation = {
|
|
517
|
+
...operation,
|
|
518
|
+
operation_id: "server",
|
|
519
|
+
action: "install",
|
|
520
|
+
source: "server",
|
|
521
|
+
status: { kind: "succeeded" },
|
|
522
|
+
packages: { numpy: "succeeded" },
|
|
523
|
+
logs: { numpy: "Server logs\n" },
|
|
524
|
+
} as const;
|
|
525
|
+
receive({
|
|
526
|
+
op: "environment-state",
|
|
527
|
+
source: "server",
|
|
528
|
+
state: {
|
|
529
|
+
restart_required: false,
|
|
530
|
+
operations: [serverOperation],
|
|
531
|
+
},
|
|
532
|
+
});
|
|
533
|
+
const snapshot = {
|
|
534
|
+
op: "environment-state",
|
|
535
|
+
source: "kernel",
|
|
536
|
+
state: { restart_required: false, operations: [operation] },
|
|
537
|
+
} as const;
|
|
538
|
+
// Receiving a snapshot twice must not duplicate logs.
|
|
539
|
+
receive({
|
|
540
|
+
...snapshot,
|
|
541
|
+
state: { ...snapshot.state, operations: [operation] },
|
|
542
|
+
});
|
|
543
|
+
receive({
|
|
544
|
+
...snapshot,
|
|
545
|
+
state: { ...snapshot.state, operations: [operation] },
|
|
546
|
+
});
|
|
547
|
+
receive({
|
|
548
|
+
op: "environment-operation",
|
|
549
|
+
operation_id: "new",
|
|
550
|
+
action: "install",
|
|
551
|
+
source: "kernel",
|
|
552
|
+
status: { kind: "failed", error: "Network unavailable" },
|
|
553
|
+
packages: { numpy: "running" },
|
|
554
|
+
logs: { numpy: "Failed\n" },
|
|
555
|
+
log_mode: "append",
|
|
556
|
+
});
|
|
557
|
+
expect(getPackageAlert(store.get(alertAtom))).toEqual({
|
|
558
|
+
...operation,
|
|
559
|
+
id: "new",
|
|
560
|
+
kind: "environment",
|
|
561
|
+
restartRequired: false,
|
|
562
|
+
status: { kind: "failed", error: "Network unavailable" },
|
|
563
|
+
logs: { numpy: "Downloading\nFailed\n" },
|
|
564
|
+
});
|
|
565
|
+
receive({
|
|
566
|
+
op: "environment-state",
|
|
567
|
+
source: "kernel",
|
|
568
|
+
state: {
|
|
569
|
+
restart_required: false,
|
|
570
|
+
operations: [],
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
// Clearing the active environment must not promote an old server success.
|
|
574
|
+
expect(getPackageAlert(store.get(alertAtom))).toBeNull();
|
|
575
|
+
expect(store.get(alertAtom).environments.server.operations).toEqual([
|
|
576
|
+
serverOperation,
|
|
577
|
+
]);
|
|
578
|
+
receive({
|
|
579
|
+
op: "environment-state",
|
|
580
|
+
source: "server",
|
|
581
|
+
state: {
|
|
582
|
+
restart_required: false,
|
|
583
|
+
operations: [],
|
|
584
|
+
},
|
|
585
|
+
});
|
|
586
|
+
expect(getPackageAlert(store.get(alertAtom))).toBeNull();
|
|
587
|
+
expect(store.get(alertAtom).environments).toEqual({
|
|
588
|
+
kernel: { restart_required: false, operations: [] },
|
|
589
|
+
server: { restart_required: false, operations: [] },
|
|
590
|
+
});
|
|
591
|
+
});
|
|
@@ -64,7 +64,7 @@ import type { SessionId } from "../kernel/session";
|
|
|
64
64
|
import { initialRunCompletedAtom, kernelStateAtom } from "../kernel/state";
|
|
65
65
|
import { type LayoutState, useLayoutActions } from "../layout/state";
|
|
66
66
|
import { kioskModeAtom } from "../mode";
|
|
67
|
-
import { connectionAtom } from "../network/connection";
|
|
67
|
+
import { connectionAtom, startupProgressAtom } from "../network/connection";
|
|
68
68
|
import type { RequestId } from "../network/DeferredRequestRegistry";
|
|
69
69
|
import { useRuntimeManager } from "../runtime/config";
|
|
70
70
|
import { SECRETS_REGISTRY } from "../secrets/request-registry";
|
|
@@ -222,8 +222,14 @@ export function useMarimoKernelConnection(opts: {
|
|
|
222
222
|
useDataSourceActions();
|
|
223
223
|
const { setLayoutData } = useLayoutActions();
|
|
224
224
|
const [connection, setConnection] = useAtom(connectionAtom);
|
|
225
|
+
const updateStartupProgress = useSetAtom(startupProgressAtom);
|
|
225
226
|
const { addBanner } = useBannersActions();
|
|
226
|
-
const {
|
|
227
|
+
const {
|
|
228
|
+
addMissingPackageAlert,
|
|
229
|
+
updateEnvironment,
|
|
230
|
+
setEnvironment,
|
|
231
|
+
addStartupLog,
|
|
232
|
+
} = useAlertActions();
|
|
227
233
|
const setKioskMode = useSetAtom(kioskModeAtom);
|
|
228
234
|
const setCapabilities = useSetAtom(capabilitiesAtom);
|
|
229
235
|
const runtimeManager = useRuntimeManager();
|
|
@@ -241,12 +247,18 @@ export function useMarimoKernelConnection(opts: {
|
|
|
241
247
|
case "reload":
|
|
242
248
|
reloadSafe();
|
|
243
249
|
return;
|
|
244
|
-
case "startup-progress":
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
250
|
+
case "startup-progress": {
|
|
251
|
+
const { phase } = msg.data;
|
|
252
|
+
updateStartupProgress(msg.data);
|
|
253
|
+
setConnection((previous) =>
|
|
254
|
+
previous.state === WebSocketState.OPEN ||
|
|
255
|
+
(previous.state === WebSocketState.CONNECTING &&
|
|
256
|
+
previous.phase === phase)
|
|
257
|
+
? previous
|
|
258
|
+
: { state: WebSocketState.CONNECTING, phase },
|
|
259
|
+
);
|
|
249
260
|
return;
|
|
261
|
+
}
|
|
250
262
|
case "kernel-ready": {
|
|
251
263
|
setKernelStartupError(null);
|
|
252
264
|
setConnection({ state: WebSocketState.OPEN });
|
|
@@ -366,26 +378,25 @@ export function useMarimoKernelConnection(opts: {
|
|
|
366
378
|
addBanner(msg.data);
|
|
367
379
|
return;
|
|
368
380
|
case "missing-package-alert":
|
|
369
|
-
|
|
381
|
+
addMissingPackageAlert({
|
|
370
382
|
...msg.data,
|
|
371
383
|
kind: "missing",
|
|
372
384
|
});
|
|
373
385
|
return;
|
|
374
|
-
case "
|
|
386
|
+
case "environment-operation":
|
|
375
387
|
if (
|
|
376
|
-
msg.data.source
|
|
377
|
-
msg.data.
|
|
378
|
-
msg.data.log_status !== "start" &&
|
|
379
|
-
Object.values(msg.data.packages).some(
|
|
380
|
-
(status) => status === "installed" || status === "restart-required",
|
|
381
|
-
)
|
|
388
|
+
msg.data.source === "kernel" &&
|
|
389
|
+
msg.data.status.kind !== "running"
|
|
382
390
|
) {
|
|
383
391
|
invalidatePackageData();
|
|
384
392
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
393
|
+
updateEnvironment(msg.data);
|
|
394
|
+
return;
|
|
395
|
+
case "environment-state":
|
|
396
|
+
setEnvironment(msg.data);
|
|
397
|
+
if (msg.data.source === "kernel") {
|
|
398
|
+
invalidatePackageData();
|
|
399
|
+
}
|
|
389
400
|
return;
|
|
390
401
|
case "startup-logs":
|
|
391
402
|
addStartupLog({
|
|
@@ -538,6 +549,7 @@ export function useMarimoKernelConnection(opts: {
|
|
|
538
549
|
* The transport is open; kernel-ready establishes session readiness.
|
|
539
550
|
*/
|
|
540
551
|
onOpen: async () => {
|
|
552
|
+
updateStartupProgress(null);
|
|
541
553
|
// If we are open, we can reset our reconnecting flag.
|
|
542
554
|
shouldTryReconnecting.current = true;
|
|
543
555
|
},
|