@marimo-team/islands 0.24.3-dev64 → 0.24.3-dev69

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.
Files changed (32) hide show
  1. package/dist/{ErrorBoundary-A-mTqnGP.js → ErrorBoundary-BwREXvnV.js} +1 -1
  2. package/dist/{cells-BSgNCBZ8.js → cells-BLjghRgH.js} +4 -7
  3. package/dist/{chat-ui-CygzVJ9s.js → chat-ui-ZVLuVQRR.js} +4 -4
  4. package/dist/{code-visibility-BKSYKzYN.js → code-visibility-kB7JdfSm.js} +2 -2
  5. package/dist/{common-Dyo2DjUU.js → common-aCry8UNj.js} +3 -3
  6. package/dist/{data-grid-overlay-editor-WZUSaLCR.js → data-grid-overlay-editor-x8iQJOw5.js} +1 -1
  7. package/dist/{error-entries-iJn-Nryx.js → error-entries-CJR74llu.js} +1 -1
  8. package/dist/{feedback-button-BHfX6PJR.js → feedback-button-DvHr2Ndh.js} +4 -4
  9. package/dist/{glide-data-editor-yX5MjvNo.js → glide-data-editor-yboPkXCU.js} +3 -3
  10. package/dist/{globals-CjGj4P9q.js → globals-D6yacf4y.js} +1 -1
  11. package/dist/{html-to-image-kpuRfdfZ.js → html-to-image-DYuNuZZd.js} +1 -1
  12. package/dist/main.js +10 -10
  13. package/dist/{number-overlay-editor-YhB8oBoI.js → number-overlay-editor-Dh9Q0MlF.js} +1 -1
  14. package/dist/{process-output-DFe2tVIY.js → process-output-BurYfV57.js} +2 -2
  15. package/dist/{reveal-component-DAp51sRP.js → reveal-component-BVyB4-pF.js} +3 -3
  16. package/package.json +1 -1
  17. package/src/components/chat/acp/__tests__/agent-panel-initialization.test.tsx +383 -0
  18. package/src/components/chat/acp/__tests__/agent-panel.test.tsx +10 -8
  19. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  20. package/src/components/chat/acp/agent-panel.tsx +70 -30
  21. package/src/components/chat/acp/state.ts +1 -1
  22. package/src/core/cells/__tests__/cells.test.ts +144 -0
  23. package/src/core/cells/cells.ts +7 -17
  24. package/src/core/wasm/__tests__/bridge.test.ts +5 -4
  25. package/src/core/wasm/__tests__/runtime-config.test.ts +8 -1
  26. package/src/core/wasm/bridge.ts +7 -8
  27. package/src/core/wasm/runtime-config.ts +4 -0
  28. package/src/core/wasm/worker/constants.ts +1 -0
  29. package/src/core/wasm/worker/getController.ts +3 -1
  30. package/src/core/wasm/worker/save-worker.ts +4 -10
  31. package/src/core/wasm/worker/types.ts +0 -1
  32. package/src/core/wasm/worker/worker.ts +4 -10
@@ -257,7 +257,7 @@ interface AgentConfig {
257
257
  const AGENT_CONFIG: Record<ExternalAgentId, AgentConfig> = {
258
258
  claude: {
259
259
  port: 3017,
260
- command: "npx @zed-industries/claude-code-acp",
260
+ command: "npx @agentclientprotocol/claude-agent-acp",
261
261
  sessionSupport: "single",
262
262
  },
263
263
  gemini: {
@@ -2037,6 +2037,150 @@ describe("cell reducer", () => {
2037
2037
  );
2038
2038
  });
2039
2039
 
2040
+ it("can collapse all cells when nested heading ranges end at the same cell", () => {
2041
+ actions.createNewCell({ cellId: firstCellId, before: false });
2042
+ actions.createNewCell({
2043
+ cellId: cellId("1"),
2044
+ before: false,
2045
+ code: "# Header",
2046
+ });
2047
+ actions.createNewCell({
2048
+ cellId: cellId("2"),
2049
+ before: false,
2050
+ code: "## Subheader",
2051
+ });
2052
+ actions.createNewCell({
2053
+ cellId: cellId("3"),
2054
+ before: false,
2055
+ code: "### Subsubheader",
2056
+ });
2057
+
2058
+ const headerId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2059
+ state.cellRuntime[headerId] = {
2060
+ ...state.cellRuntime[headerId],
2061
+ outline: {
2062
+ items: [{ name: "Header", level: 1, by: { id: "header" } }],
2063
+ },
2064
+ };
2065
+
2066
+ const subheaderId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2);
2067
+ state.cellRuntime[subheaderId] = {
2068
+ ...state.cellRuntime[subheaderId],
2069
+ outline: {
2070
+ items: [{ name: "Subheader", level: 2, by: { id: "subheader" } }],
2071
+ },
2072
+ };
2073
+
2074
+ const subsubheaderId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(3);
2075
+ state.cellRuntime[subsubheaderId] = {
2076
+ ...state.cellRuntime[subsubheaderId],
2077
+ outline: {
2078
+ items: [{ name: "Subsubheader", level: 3, by: { id: "subsubheader" } }],
2079
+ },
2080
+ };
2081
+
2082
+ // The header, subheader and subsubheader ranges all end at the last
2083
+ // cell. Collapsing all used to throw "Node ... not found in tree" and
2084
+ // leave the whole notebook unchanged.
2085
+ actions.collapseAllCells();
2086
+ expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2087
+ true,
2088
+ );
2089
+ expect(state.cellIds.atOrThrow(FIRST_COLUMN).topLevelIds).toEqual([
2090
+ firstCellId,
2091
+ headerId,
2092
+ ]);
2093
+
2094
+ // Each level is nested under its parent, collapsed
2095
+ actions.expandCell({ cellId: headerId });
2096
+ expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subheaderId)).toBe(
2097
+ true,
2098
+ );
2099
+ actions.expandCell({ cellId: subheaderId });
2100
+ expect(
2101
+ state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subsubheaderId),
2102
+ ).toBe(true);
2103
+ });
2104
+
2105
+ it("can collapse all cells when a deep chain is followed by a later section", () => {
2106
+ actions.createNewCell({ cellId: firstCellId, before: false });
2107
+ actions.createNewCell({
2108
+ cellId: cellId("1"),
2109
+ before: false,
2110
+ code: "# Header",
2111
+ });
2112
+ actions.createNewCell({
2113
+ cellId: cellId("2"),
2114
+ before: false,
2115
+ code: "## Section A",
2116
+ });
2117
+ actions.createNewCell({
2118
+ cellId: cellId("3"),
2119
+ before: false,
2120
+ code: "### Sub of A",
2121
+ });
2122
+ actions.createNewCell({
2123
+ cellId: cellId("4"),
2124
+ before: false,
2125
+ code: "## Section B",
2126
+ });
2127
+
2128
+ const headerId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2129
+ state.cellRuntime[headerId] = {
2130
+ ...state.cellRuntime[headerId],
2131
+ outline: {
2132
+ items: [{ name: "Header", level: 1, by: { id: "header" } }],
2133
+ },
2134
+ };
2135
+
2136
+ const sectionAId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2);
2137
+ state.cellRuntime[sectionAId] = {
2138
+ ...state.cellRuntime[sectionAId],
2139
+ outline: {
2140
+ items: [{ name: "Section A", level: 2, by: { id: "section-a" } }],
2141
+ },
2142
+ };
2143
+
2144
+ const subsectionId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(3);
2145
+ state.cellRuntime[subsectionId] = {
2146
+ ...state.cellRuntime[subsectionId],
2147
+ outline: {
2148
+ items: [{ name: "Sub of A", level: 3, by: { id: "sub-of-a" } }],
2149
+ },
2150
+ };
2151
+
2152
+ const sectionBId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(4);
2153
+ state.cellRuntime[sectionBId] = {
2154
+ ...state.cellRuntime[sectionBId],
2155
+ outline: {
2156
+ items: [{ name: "Section B", level: 2, by: { id: "section-b" } }],
2157
+ },
2158
+ };
2159
+
2160
+ actions.collapseAllCells();
2161
+ expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2162
+ true,
2163
+ );
2164
+ expect(state.cellIds.atOrThrow(FIRST_COLUMN).topLevelIds).toEqual([
2165
+ firstCellId,
2166
+ headerId,
2167
+ ]);
2168
+
2169
+ // Both sections sit under the header, collapsed, with the subsection
2170
+ // nested under section A
2171
+ actions.expandCell({ cellId: headerId });
2172
+ expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(sectionAId)).toBe(
2173
+ true,
2174
+ );
2175
+ expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(sectionBId)).toBe(
2176
+ true,
2177
+ );
2178
+ actions.expandCell({ cellId: sectionAId });
2179
+ expect(
2180
+ state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subsectionId),
2181
+ ).toBe(true);
2182
+ });
2183
+
2040
2184
  it("can show hidden cells", () => {
2041
2185
  actions.createNewCell({ cellId: firstCellId, before: false });
2042
2186
  actions.createNewCell({ cellId: cellId("1"), before: false });
@@ -1365,10 +1365,7 @@ const {
1365
1365
 
1366
1366
  // Find the start/end of the collapsed ranges
1367
1367
  const nodes = [...column.nodes];
1368
- const rangeIndexes: {
1369
- start: CellIndex;
1370
- end: CellIndex;
1371
- }[] = [];
1368
+ const visibleEndByOriginalEnd = new Map<CellIndex, CellIndex>();
1372
1369
  const reversedCollapseRanges = [];
1373
1370
 
1374
1371
  // Iterate in reverse order (bottom-up) to process children first
@@ -1377,20 +1374,13 @@ const {
1377
1374
  const range = findCollapseRange(i, outlines);
1378
1375
  if (range) {
1379
1376
  const startIndex = i;
1380
- let endIndex = range[1];
1377
+ const originalEnd = range[1];
1378
+ const endIndex =
1379
+ visibleEndByOriginalEnd.get(originalEnd) ?? originalEnd;
1381
1380
 
1382
- // Check if the parent's end point is inside any already-collapsed child range
1383
- const parentEndInChild = rangeIndexes.find(
1384
- (child) => child.start <= endIndex && child.end === endIndex,
1385
- );
1386
-
1387
- if (parentEndInChild) {
1388
- // Adjust the new endIndex to the child's start
1389
- endIndex = parentEndInChild.start;
1390
- }
1391
-
1392
- // Store this range for future child checks
1393
- rangeIndexes.push({ start: startIndex, end: endIndex });
1381
+ // After this section collapses, its heading represents this
1382
+ // endpoint for any enclosing section that shares it.
1383
+ visibleEndByOriginalEnd.set(originalEnd, startIndex);
1394
1384
 
1395
1385
  // Add the range to the list of ranges
1396
1386
  const cellId = column.atOrThrow(startIndex);
@@ -126,6 +126,7 @@ describe("PyodideBridge.readCode", () => {
126
126
  await PyodideBridge.INSTANCE.readCode();
127
127
 
128
128
  expect(mockBootstrap).toHaveBeenCalledWith({
129
+ version: "0.0.0-test",
129
130
  pyodideIndexUrl: undefined,
130
131
  pyodideLockfileUrl: undefined,
131
132
  pypiIndexUrl: undefined,
@@ -223,21 +224,21 @@ describe("getWasmWorkerName", () => {
223
224
  .__MARIMO_HAS_WASM_CONTROLLER__;
224
225
  });
225
226
 
226
- it("returns the version without suffix by default", () => {
227
- expect(getWasmWorkerName()).toBe("0.0.0-test");
227
+ it("returns the default worker name without suffix", () => {
228
+ expect(getWasmWorkerName()).toBe("marimo");
228
229
  });
229
230
 
230
231
  it("appends ::controller when the host opts in", () => {
231
232
  (
232
233
  window as unknown as { __MARIMO_HAS_WASM_CONTROLLER__?: boolean }
233
234
  ).__MARIMO_HAS_WASM_CONTROLLER__ = true;
234
- expect(getWasmWorkerName()).toBe("0.0.0-test::controller");
235
+ expect(getWasmWorkerName()).toBe("marimo::controller");
235
236
  });
236
237
 
237
238
  it("does not append the suffix for non-true values", () => {
238
239
  (
239
240
  window as unknown as { __MARIMO_HAS_WASM_CONTROLLER__?: unknown }
240
241
  ).__MARIMO_HAS_WASM_CONTROLLER__ = "true";
241
- expect(getWasmWorkerName()).toBe("0.0.0-test");
242
+ expect(getWasmWorkerName()).toBe("marimo");
242
243
  });
243
244
  });
@@ -1,8 +1,12 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { afterEach, assert, describe, expect, it } from "vitest";
3
+ import { afterEach, assert, describe, expect, it, vi } from "vitest";
4
4
  import { getWasmRuntimeConfig } from "../runtime-config";
5
5
 
6
+ vi.mock("@/core/meta/globals", () => ({
7
+ getMarimoVersion: () => "0.0.0-test",
8
+ }));
9
+
6
10
  afterEach(() => {
7
11
  document.head.innerHTML = "";
8
12
  });
@@ -10,6 +14,7 @@ afterEach(() => {
10
14
  describe("getWasmRuntimeConfig", () => {
11
15
  it("uses runtime defaults when no URLs are configured", () => {
12
16
  expect(getWasmRuntimeConfig()).toEqual({
17
+ version: "0.0.0-test",
13
18
  pyodideIndexUrl: undefined,
14
19
  pyodideLockfileUrl: undefined,
15
20
  pypiIndexUrl: undefined,
@@ -25,6 +30,7 @@ describe("getWasmRuntimeConfig", () => {
25
30
  data-pypi-index-url="https://packages.example.com/{package_name}/json"
26
31
  ></marimo-wasm>`;
27
32
  expect(getWasmRuntimeConfig()).toEqual({
33
+ version: "0.0.0-test",
28
34
  pyodideIndexUrl: "https://example.com/blog/runtime/",
29
35
  pyodideLockfileUrl: "https://example.com/locks/pyodide.json?v=1&x=2",
30
36
  pypiIndexUrl: "https://packages.example.com/{package_name}/json",
@@ -46,6 +52,7 @@ describe("getWasmRuntimeConfig", () => {
46
52
  element.dataset[key] = "https://invalid host/";
47
53
 
48
54
  expect(getWasmRuntimeConfig()).toEqual({
55
+ version: "0.0.0-test",
49
56
  pyodideIndexUrl: "https://example.com/notebook/pyodide/",
50
57
  pyodideLockfileUrl:
51
58
  "https://example.com/notebook/lockfile/pyodide.json",
@@ -12,7 +12,6 @@ import { generateUUID } from "@/utils/uuid";
12
12
  import { createModuleWorker } from "@/utils/worker";
13
13
  import { notebookIsRunningAtom } from "../cells/cells";
14
14
  import type { CommandMessage } from "../kernel/messages";
15
- import { getMarimoVersion } from "../meta/globals";
16
15
  import { getInitialAppMode } from "../mode";
17
16
  import { API } from "../network/api";
18
17
  import type {
@@ -49,6 +48,7 @@ import { fallbackFileStore, notebookFileStore } from "./store";
49
48
  import { isWasm } from "./utils";
50
49
  import saveWorkerUrl from "./worker/save-worker.ts?worker&url";
51
50
  import workerUrl from "./worker/worker.ts?worker&url";
51
+ import { CUSTOM_CONTROLLER_SUFFIX } from "./worker/constants";
52
52
  import type { SaveWorkerSchema } from "./worker/save-worker";
53
53
  import type { WorkerSchema } from "./worker/worker";
54
54
 
@@ -91,7 +91,7 @@ export class PyodideBridge implements RunRequests, EditRequests {
91
91
  const saveWorker = createModuleWorker(
92
92
  new URL(saveWorkerUrl, import.meta.url),
93
93
  {
94
- // Pass the version (and optional capability suffix) to the worker
94
+ // Pass the optional custom-controller capability to the worker.
95
95
  name: getWasmWorkerName(),
96
96
  },
97
97
  );
@@ -110,7 +110,7 @@ export class PyodideBridge implements RunRequests, EditRequests {
110
110
 
111
111
  // Create a worker
112
112
  const worker = createModuleWorker(new URL(workerUrl, import.meta.url), {
113
- // Pass the version (and optional capability suffix) to the worker
113
+ // Pass the optional custom-controller capability to the worker.
114
114
  name: getWasmWorkerName(),
115
115
  });
116
116
 
@@ -726,10 +726,9 @@ export function createPyodideConnection(): IConnectionTransport {
726
726
  });
727
727
  }
728
728
 
729
- // Compose the worker name. The version prefix is read by getMarimoVersion()
730
- // in the worker; the optional "::controller" suffix tells getController.ts
731
- // that the host page provides a custom /wasm/controller.js and that the
732
- // dynamic import should be attempted. Hosts opt in by setting
729
+ // Compose the worker name. The optional "::controller" suffix tells
730
+ // getController.ts that the host page provides a custom /wasm/controller.js
731
+ // and that the dynamic import should be attempted. Hosts opt in by setting
733
732
  // `window.__MARIMO_HAS_WASM_CONTROLLER__ = true` before
734
733
  // PyodideBridge/worker initialization.
735
734
  export function getWasmWorkerName(): string {
@@ -737,5 +736,5 @@ export function getWasmWorkerName(): string {
737
736
  typeof window !== "undefined" &&
738
737
  (window as unknown as { __MARIMO_HAS_WASM_CONTROLLER__?: boolean })
739
738
  .__MARIMO_HAS_WASM_CONTROLLER__ === true;
740
- return getMarimoVersion() + (hasCustomController ? "::controller" : "");
739
+ return `marimo${hasCustomController ? CUSTOM_CONTROLLER_SUFFIX : ""}`;
741
740
  }
@@ -1,6 +1,9 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
+ import { getMarimoVersion } from "@/core/meta/globals";
4
+
3
5
  export interface WasmRuntimeConfig {
6
+ version: string;
4
7
  pyodideIndexUrl?: string;
5
8
  pyodideLockfileUrl?: string;
6
9
  pypiIndexUrl?: string;
@@ -16,6 +19,7 @@ export function getWasmRuntimeConfig(): WasmRuntimeConfig {
16
19
  }
17
20
  };
18
21
  return {
22
+ version: getMarimoVersion(),
19
23
  pyodideIndexUrl: resolve(element?.dataset.pyodideIndexUrl),
20
24
  pyodideLockfileUrl: resolve(element?.dataset.pyodideLockfileUrl),
21
25
  // URL serialization escapes the placeholder understood by micropip.
@@ -1,2 +1,3 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
+ export const CUSTOM_CONTROLLER_SUFFIX = "::controller";
2
3
  export const TRANSPORT_ID = "marimo-transport";
@@ -1,5 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { DefaultWasmController } from "./bootstrap";
3
+ import { CUSTOM_CONTROLLER_SUFFIX } from "./constants";
3
4
  import type { WasmController } from "./types";
4
5
 
5
6
  // Load the controller
@@ -8,7 +9,8 @@ export async function getController(version: string): Promise<WasmController> {
8
9
  // Hosts that provide a custom /wasm/controller.js opt in via the worker
9
10
  // name (see bridge.ts). Default: skip the dynamic import to avoid a
10
11
  // guaranteed-404 round trip on the standard build.
11
- const hasCustomController = self.name?.includes("::controller") ?? false;
12
+ const hasCustomController =
13
+ self.name?.includes(CUSTOM_CONTROLLER_SUFFIX) ?? false;
12
14
  if (!hasCustomController) {
13
15
  return new DefaultWasmController();
14
16
  }
@@ -33,15 +33,14 @@ const runtimeConfig = new Deferred<WasmRuntimeConfig>();
33
33
  async function loadPyodideAndPackages() {
34
34
  try {
35
35
  // Import pyodide
36
- const marimoVersion = getMarimoVersion();
37
- const pyodideVersion = getPyodideVersion(marimoVersion);
36
+ const config = await runtimeConfig.promise;
37
+ const pyodideVersion = getPyodideVersion(config.version);
38
38
 
39
39
  // Bootstrap the controller
40
- const controller = await getController(marimoVersion);
40
+ const controller = await getController(config.version);
41
41
  self.controller = controller;
42
42
  self.pyodide = await controller.bootstrap({
43
- ...(await runtimeConfig.promise),
44
- version: marimoVersion,
43
+ ...config,
45
44
  pyodideVersion: pyodideVersion,
46
45
  });
47
46
 
@@ -108,8 +107,3 @@ const rpc = createRPC<SaveWorkerSchema, ParentSchema>({
108
107
  rpc.addMessageListener("bootstrap", (config) => runtimeConfig.resolve(config));
109
108
 
110
109
  rpc.send("ready", {});
111
-
112
- function getMarimoVersion() {
113
- // Worker name is "<version>" or "<version>::<capability>" — see bridge.ts.
114
- return self.name.split("::")[0];
115
- }
@@ -46,7 +46,6 @@ export interface WasmController {
46
46
  */
47
47
  bootstrap(
48
48
  opts: WasmRuntimeConfig & {
49
- version: string;
50
49
  pyodideVersion: string;
51
50
  },
52
51
  ): Promise<PyodideInterface>;
@@ -53,16 +53,15 @@ const runtimeConfig = new Deferred<WasmRuntimeConfig>();
53
53
  // Initialize pyodide
54
54
  async function loadPyodideAndPackages() {
55
55
  try {
56
- const marimoVersion = getMarimoVersion();
57
- const pyodideVersion = getPyodideVersion(marimoVersion);
58
- const controller = await t.wrapAsync(getController)(marimoVersion);
56
+ const config = await runtimeConfig.promise;
57
+ const pyodideVersion = getPyodideVersion(config.version);
58
+ const controller = await t.wrapAsync(getController)(config.version);
59
59
  self.controller = controller;
60
60
  rpc.send.initializingMessage({
61
61
  message: "Loading marimo...",
62
62
  });
63
63
  self.pyodide = await t.wrapAsync(controller.bootstrap.bind(controller))({
64
- ...(await runtimeConfig.promise),
65
- version: marimoVersion,
64
+ ...config,
66
65
  pyodideVersion: pyodideVersion,
67
66
  });
68
67
  } catch (error) {
@@ -380,9 +379,4 @@ const namesThatRequireSync = new Set<keyof RawBridge>([
380
379
  "update_file",
381
380
  ]);
382
381
 
383
- function getMarimoVersion() {
384
- // Worker name is "<version>" or "<version>::<capability>" — see bridge.ts.
385
- return self.name.split("::")[0];
386
- }
387
-
388
382
  const pyodideReadyPromise = t.wrapAsync(loadPyodideAndPackages)();