@marimo-team/islands 0.23.16-dev13 → 0.23.16-dev15

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.
@@ -2,7 +2,7 @@ import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { g as Logger, h as cn, m as Events } from "./button-BSm8IXRU.js";
3
3
  import { t as require_react } from "./react-DA-nE2FX.js";
4
4
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
5
- import { c as asRemoteURL, v as CircleQuestionMark } from "./toDate-CmR_xR3P.js";
5
+ import { c as asRemoteURL, w as CircleQuestionMark } from "./toDate-CqhBWjvO.js";
6
6
  import "./react-dom-BTJzcVJ9.js";
7
7
  import { t as require_jsx_runtime } from "./jsx-runtime-DebpN0FN.js";
8
8
  import "./zod-BUcyXSdR.js";
@@ -12,7 +12,7 @@ import { t as debounce_default } from "./debounce-BOD3DbfP.js";
12
12
  import { A as useEvent_default, n as useTheme } from "./useTheme-DYfby65G.js";
13
13
  import { s as uniq } from "./arrays-sEtDRoG4.js";
14
14
  import { t as Objects } from "./objects-De59twF-.js";
15
- import { a as isValid, i as AlertTitle, n as Alert, t as arrow } from "./formats-CO8cn5_K.js";
15
+ import { a as isValid, i as AlertTitle, n as Alert, t as arrow } from "./formats-Dwq1THhr.js";
16
16
  import { V as getContainerWidth, n as formats } from "./vega-loader.browser-DmpxCJqx.js";
17
17
  import { a as tooltipHandler, n as vegaLoadData } from "./loader-DX-IDS7n.js";
18
18
  import { t as j } from "./react-vega-BazDALhI.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.23.16-dev13",
3
+ "version": "0.23.16-dev15",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
package/src/core/mode.ts CHANGED
@@ -78,6 +78,16 @@ export const viewStateAtom = atom<ViewState>({
78
78
 
79
79
  export const initialModeAtom = atom<AppMode | undefined>(undefined);
80
80
 
81
+ /**
82
+ * Whether the current page hosts a notebook, and so connects to a kernel.
83
+ * False for non-notebook pages (home, gallery), which are served without a
84
+ * session.
85
+ */
86
+ export function isNotebookPage(): boolean {
87
+ const mode = store.get(initialModeAtom);
88
+ return mode !== "home" && mode !== "gallery";
89
+ }
90
+
81
91
  export const kioskModeAtom = atom<boolean>(false);
82
92
 
83
93
  /**
@@ -0,0 +1,45 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+ import { initialModeAtom } from "@/core/mode";
5
+ import { store } from "@/core/state/jotai";
6
+ import { WebSocketState } from "../../websocket/types";
7
+ import { connectionAtom, waitForConnectionOpenIfNotebook } from "../connection";
8
+
9
+ const NEVER = Symbol("never");
10
+
11
+ /**
12
+ * Resolves to NEVER if the promise does not settle within a macrotask.
13
+ */
14
+ function settledOrNever(promise: Promise<unknown>) {
15
+ return Promise.race([
16
+ promise,
17
+ new Promise((resolve) => setTimeout(() => resolve(NEVER), 0)),
18
+ ]);
19
+ }
20
+
21
+ describe("waitForConnectionOpenIfNotebook", () => {
22
+ afterEach(() => {
23
+ store.set(connectionAtom, { state: WebSocketState.NOT_STARTED });
24
+ store.set(initialModeAtom, undefined);
25
+ });
26
+
27
+ it.each(["home", "gallery"] as const)(
28
+ "resolves without a connection in %s mode",
29
+ async (mode) => {
30
+ store.set(initialModeAtom, mode);
31
+ await expect(
32
+ settledOrNever(waitForConnectionOpenIfNotebook()),
33
+ ).resolves.not.toBe(NEVER);
34
+ },
35
+ );
36
+
37
+ it("waits for the connection to open in edit mode", async () => {
38
+ store.set(initialModeAtom, "edit");
39
+ const promise = waitForConnectionOpenIfNotebook();
40
+ await expect(settledOrNever(promise)).resolves.toBe(NEVER);
41
+
42
+ store.set(connectionAtom, { state: WebSocketState.OPEN });
43
+ await expect(settledOrNever(promise)).resolves.not.toBe(NEVER);
44
+ });
45
+ });
@@ -32,6 +32,7 @@ vi.mock("../api", async () => {
32
32
  vi.mock("../connection", () => ({
33
33
  isConnectedAtom: { read: vi.fn(() => true) },
34
34
  waitForConnectionOpen: vi.fn().mockResolvedValue(undefined),
35
+ waitForConnectionOpenIfNotebook: vi.fn().mockResolvedValue(undefined),
35
36
  }));
36
37
 
37
38
  describe("createNetworkRequests", () => {
@@ -121,6 +122,18 @@ describe("createNetworkRequests", () => {
121
122
  expect(mockClient.GET).toHaveBeenCalledWith("/api/environment");
122
123
  });
123
124
 
125
+ it("getPackageList should not require a kernel connection", async () => {
126
+ const { waitForConnectionOpen, waitForConnectionOpenIfNotebook } =
127
+ await import("../connection");
128
+
129
+ const requests = createNetworkRequests();
130
+ await requests.getPackageList();
131
+
132
+ expect(mockClient.GET).toHaveBeenCalledWith("/api/packages/list");
133
+ expect(waitForConnectionOpenIfNotebook).toHaveBeenCalled();
134
+ expect(waitForConnectionOpen).not.toHaveBeenCalled();
135
+ });
136
+
124
137
  it("exportAsIPYNB should call the new endpoint as text", async () => {
125
138
  const requests = createNetworkRequests();
126
139
  await requests.exportAsIPYNB({
@@ -1,5 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { atom } from "jotai";
3
+ import { isNotebookPage } from "../mode";
3
4
  import { waitFor } from "../state/jotai";
4
5
  import { type ConnectionStatus, WebSocketState } from "../websocket/types";
5
6
 
@@ -17,6 +18,20 @@ export function waitForConnectionOpen() {
17
18
  });
18
19
  }
19
20
 
21
+ /**
22
+ * Waits for the kernel connection, but only on pages that open one. Non-notebook
23
+ * pages (home, gallery) never connect, so waiting there would hang forever.
24
+ *
25
+ * Use for requests that the marimo server can serve without a session, but that
26
+ * should still wait for the kernel when a notebook is open.
27
+ */
28
+ export function waitForConnectionOpenIfNotebook() {
29
+ if (!isNotebookPage()) {
30
+ return Promise.resolve();
31
+ }
32
+ return waitForConnectionOpen();
33
+ }
34
+
20
35
  export const isConnectingAtom = atom((get) => {
21
36
  const connection = get(connectionAtom);
22
37
  return connection.state === WebSocketState.CONNECTING;
@@ -3,7 +3,10 @@
3
3
  import { once } from "@/utils/once";
4
4
  import { getRuntimeManager } from "../runtime/config";
5
5
  import { API, createClientWithRuntimeManager } from "./api";
6
- import { waitForConnectionOpen } from "./connection";
6
+ import {
7
+ waitForConnectionOpen,
8
+ waitForConnectionOpenIfNotebook,
9
+ } from "./connection";
7
10
  import type { EditRequests, RunRequests } from "./types";
8
11
 
9
12
  /**
@@ -489,12 +492,12 @@ export function createNetworkRequests(): EditRequests & RunRequests {
489
492
  },
490
493
  getPackageList: async () => {
491
494
  // If the sidebar is already open, it may try to load before the session has been initialized
492
- await waitForConnectionOpen();
495
+ await waitForConnectionOpenIfNotebook();
493
496
  return getClient().GET("/api/packages/list").then(handleResponse);
494
497
  },
495
498
  getDependencyTree: async () => {
496
499
  // If the sidebar is already open, it may try to load before the session has been initialized
497
- await waitForConnectionOpen();
500
+ await waitForConnectionOpenIfNotebook();
498
501
  return getClient().GET("/api/packages/tree").then(handleResponse);
499
502
  },
500
503
  listSecretKeys: async (request) => {