@marimo-team/islands 0.21.2-dev37 → 0.21.2-dev40

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 (75) hide show
  1. package/dist/main.js +21 -3
  2. package/package.json +1 -1
  3. package/src/__mocks__/notebook.ts +9 -9
  4. package/src/__tests__/branded.ts +20 -0
  5. package/src/components/data-table/charts/__tests__/storage.test.ts +7 -7
  6. package/src/components/editor/__tests__/data-attributes.test.tsx +8 -8
  7. package/src/components/editor/ai/__tests__/completion-utils.test.ts +15 -15
  8. package/src/components/editor/navigation/__tests__/clipboard.test.ts +2 -2
  9. package/src/components/editor/navigation/__tests__/selection.test.ts +7 -6
  10. package/src/components/editor/navigation/__tests__/state.test.ts +8 -7
  11. package/src/components/editor/output/MarimoErrorOutput.tsx +7 -7
  12. package/src/components/editor/output/__tests__/traceback.test.tsx +4 -4
  13. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +4 -4
  14. package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +8 -1
  15. package/src/components/storage/storage-inspector.tsx +1 -2
  16. package/src/components/tracing/tracing.tsx +3 -1
  17. package/src/core/ai/__tests__/staged-cells.test.ts +9 -8
  18. package/src/core/ai/context/providers/__tests__/cell-output.test.ts +31 -31
  19. package/src/core/ai/context/providers/__tests__/datasource.test.ts +3 -3
  20. package/src/core/ai/context/providers/__tests__/tables.test.ts +3 -2
  21. package/src/core/ai/context/providers/__tests__/variable.test.ts +84 -63
  22. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +10 -9
  23. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +6 -6
  24. package/src/core/ai/tools/edit-notebook-tool.ts +3 -3
  25. package/src/core/cells/__tests__/add-missing-import.test.ts +3 -3
  26. package/src/core/cells/__tests__/cells.test.ts +192 -135
  27. package/src/core/cells/__tests__/focus.test.ts +5 -4
  28. package/src/core/cells/__tests__/logs.test.ts +13 -12
  29. package/src/core/cells/__tests__/pending-delete-service.test.tsx +3 -3
  30. package/src/core/cells/__tests__/runs.test.ts +22 -21
  31. package/src/core/cells/__tests__/scrollCellIntoView.test.ts +8 -7
  32. package/src/core/cells/__tests__/session.test.ts +23 -22
  33. package/src/core/cells/cells.ts +1 -1
  34. package/src/core/cells/ids.ts +5 -5
  35. package/src/core/cells/logs.ts +2 -2
  36. package/src/core/cells/runs.ts +6 -8
  37. package/src/core/codemirror/__tests__/format.test.ts +34 -36
  38. package/src/core/codemirror/__tests__/setup.test.ts +2 -2
  39. package/src/core/codemirror/cells/__tests__/extensions.test.ts +114 -0
  40. package/src/core/codemirror/cells/__tests__/traceback-decorations.test.ts +33 -32
  41. package/src/core/codemirror/cells/extensions.ts +66 -23
  42. package/src/core/codemirror/copilot/__tests__/getCodes.test.ts +12 -13
  43. package/src/core/codemirror/language/__tests__/utils.test.ts +3 -3
  44. package/src/core/codemirror/language/embedded/__tests__/embedded-python.test.ts +7 -8
  45. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +4 -3
  46. package/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts +7 -6
  47. package/src/core/codemirror/reactive-references/analyzer.ts +2 -2
  48. package/src/core/codemirror/rtc/loro/__tests__/sync.test.ts +52 -0
  49. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  50. package/src/core/datasets/__tests__/data-source.test.ts +5 -6
  51. package/src/core/datasets/state.ts +1 -1
  52. package/src/core/errors/__tests__/errors.test.ts +2 -1
  53. package/src/core/export/__tests__/hooks.test.ts +37 -36
  54. package/src/core/islands/main.ts +2 -7
  55. package/src/core/kernel/__tests__/handlers.test.ts +5 -4
  56. package/src/core/kernel/handlers.ts +7 -4
  57. package/src/core/network/DeferredRequestRegistry.ts +2 -2
  58. package/src/core/network/__tests__/CachingRequestRegistry.test.ts +9 -10
  59. package/src/core/network/__tests__/DeferredRequestRegistry.test.ts +4 -6
  60. package/src/core/static/__tests__/virtual-file-tracker.test.ts +8 -8
  61. package/src/core/static/virtual-file-tracker.ts +1 -1
  62. package/src/core/storage/__tests__/state.test.ts +31 -21
  63. package/src/core/storage/state.ts +1 -1
  64. package/src/core/variables/__tests__/state.test.ts +6 -6
  65. package/src/core/variables/types.ts +2 -2
  66. package/src/core/wasm/__tests__/state.test.ts +8 -8
  67. package/src/core/websocket/useMarimoKernelConnection.tsx +12 -15
  68. package/src/plugins/impl/anywidget/model.ts +1 -2
  69. package/src/stories/cell.stories.tsx +8 -8
  70. package/src/stories/layout/vertical/one-column.stories.tsx +9 -8
  71. package/src/stories/log-viewer.stories.tsx +8 -8
  72. package/src/stories/variables.stories.tsx +2 -2
  73. package/src/utils/__tests__/download.test.tsx +19 -18
  74. package/src/utils/json/base64.ts +3 -3
  75. package/src/utils/traceback.ts +5 -3
package/dist/main.js CHANGED
@@ -27014,6 +27014,7 @@ ${c.sqlString}
27014
27014
  function indentOneTab(e) {
27015
27015
  return e.split("\n").map((e2) => (e2 == null ? void 0 : e2.trim()) ? ` ${e2}` : e2).join("\n");
27016
27016
  }
27017
+ const loroSyncAnnotation = Annotation.define();
27017
27018
  function initialState$4() {
27018
27019
  return {};
27019
27020
  }
@@ -27609,9 +27610,23 @@ ${c.sqlString}
27609
27610
  }
27610
27611
  });
27611
27612
  }
27613
+ var MARKDOWN_AUTORUN_USER_EVENTS = [
27614
+ "input",
27615
+ "delete",
27616
+ "undo",
27617
+ "redo"
27618
+ ];
27619
+ function shouldAutorunMarkdownUpdate({ docChanged: e, transactions: r, predicate: c = () => true, hasFocus: d = false }) {
27620
+ return !e || !c() || r.some((e2) => e2.effects.some((e3) => e3.is(formattingChangeEffect))) ? false : r.some((e2) => e2.annotation(loroSyncAnnotation) === void 0 ? MARKDOWN_AUTORUN_USER_EVENTS.some((r2) => e2.isUserEvent(r2)) || d : false);
27621
+ }
27612
27622
  function markdownAutoRunExtension({ predicate: e }) {
27613
27623
  return EditorView.updateListener.of((r) => {
27614
- r.docChanged && r.view.hasFocus && e() && (r.transactions.some((e2) => e2.effects.some((e3) => e3.is(formattingChangeEffect))) || r.view.state.facet(cellActionsState).onRun());
27624
+ shouldAutorunMarkdownUpdate({
27625
+ docChanged: r.docChanged,
27626
+ transactions: r.transactions,
27627
+ predicate: e,
27628
+ hasFocus: r.view.hasFocus
27629
+ }) && r.view.state.facet(cellActionsState).onRun();
27615
27630
  });
27616
27631
  }
27617
27632
  const pythonCompletionSource = async (e) => {
@@ -70858,7 +70873,7 @@ Image URL: ${r.imageUrl}`)), contextToXml({
70858
70873
  return Logger.warn("Failed to get version from mount config"), null;
70859
70874
  }
70860
70875
  }
70861
- const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.21.2-dev37"), showCodeInRunModeAtom = atom(true);
70876
+ const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.21.2-dev40"), showCodeInRunModeAtom = atom(true);
70862
70877
  atom(null);
70863
70878
  var import_compiler_runtime$89 = require_compiler_runtime();
70864
70879
  function useKeydownOnElement(e, r) {
@@ -101164,7 +101179,10 @@ ${c}
101164
101179
  let IY = AppConfigSchema.safeParse(M);
101165
101180
  if (IY.success ? v(IY.data) : Logger.error("Failed to parse app config", IY.error), y(I), z) return;
101166
101181
  if (E) {
101167
- for (let [e2, r2] of Objects.entries(O || {})) UI_ELEMENT_REGISTRY.set(e2, r2);
101182
+ for (let [e2, r2] of Objects.entries(O || {})) {
101183
+ let c2 = e2;
101184
+ UI_ELEMENT_REGISTRY.set(c2, r2);
101185
+ }
101168
101186
  return;
101169
101187
  }
101170
101188
  let { objectIds: LY, values: RY } = collectUIElementValues(), zY = G ? Object.fromEntries(c.map((e2) => [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.21.2-dev37",
3
+ "version": "0.21.2-dev40",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -22,16 +22,16 @@ export const MockNotebook = {
22
22
  },
23
23
 
24
24
  notebookState: (opts?: {
25
- cellData: Record<string, Partial<CellData>>;
26
- cellRuntime?: Record<string, Partial<CellRuntimeState>>;
25
+ cellData: Record<CellId, Partial<CellData>>;
26
+ cellRuntime?: Record<CellId, Partial<CellRuntimeState>>;
27
27
  }): NotebookState => {
28
- const cellData = opts?.cellData || {};
29
- const cellRuntime = opts?.cellRuntime || {};
28
+ const cellData = opts?.cellData ?? {};
29
+ const cellRuntime = opts?.cellRuntime ?? {};
30
30
  return {
31
- cellData: Objects.mapValues(cellData, (data, cellId) => ({
32
- id: cellId as CellId,
31
+ cellData: Objects.mapValues(cellData, (data, cid) => ({
32
+ id: cid,
33
33
  code: "",
34
- name: `cell-${cellId}`,
34
+ name: `cell-${cid}`,
35
35
  config: {
36
36
  hide_code: false,
37
37
  disabled: false,
@@ -45,8 +45,8 @@ export const MockNotebook = {
45
45
  ...data,
46
46
  })),
47
47
  cellIds: MultiColumn.from([Object.keys(cellData) as CellId[]]),
48
- cellRuntime: Objects.mapValues(cellData, (_data, cellId) =>
49
- createCellRuntimeState({ ...cellRuntime[cellId] }),
48
+ cellRuntime: Objects.mapValues(cellData, (_data, cid) =>
49
+ createCellRuntimeState({ ...cellRuntime[cid] }),
50
50
  ),
51
51
  cellHandles: Objects.mapValues(cellData, (_data) => createRef()),
52
52
  cellLogs: [],
@@ -0,0 +1,20 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ /**
4
+ * Test-only helpers for constructing branded ID types from plain strings.
5
+ *
6
+ * In production code, branded types flow from the API (via codegen) or
7
+ * from designated creation points (e.g. CellId.create()). Tests need to
8
+ * construct these from string literals, which requires a cast. These
9
+ * helpers centralise that cast so test files don't scatter `as CellId`
10
+ * everywhere.
11
+ */
12
+
13
+ import type { CellId, UIElementId } from "@/core/cells/ids";
14
+ import type { RequestId } from "@/core/network/DeferredRequestRegistry";
15
+ import type { VariableName } from "@/core/variables/types";
16
+
17
+ export const cellId = (s: string) => s as CellId;
18
+ export const variableName = (s: string) => s as VariableName;
19
+ export const requestId = (s: string) => s as RequestId;
20
+ export const uiElementId = (s: string) => s as UIElementId;
@@ -12,7 +12,7 @@ vi.mock("@/utils/storage/storage", () => ({
12
12
  },
13
13
  }));
14
14
 
15
- import type { CellId } from "@/core/cells/ids";
15
+ import { cellId } from "@/__tests__/branded";
16
16
  import { availableStorage } from "@/utils/storage/storage";
17
17
  import { ChartSchema } from "../schemas";
18
18
  import type { TabName } from "../storage";
@@ -37,7 +37,7 @@ describe("Chart Transforms Storage", () => {
37
37
 
38
38
  it("should store and retrieve tab data", () => {
39
39
  const store = getDefaultStore();
40
- const cellId = "cell-1" as CellId;
40
+ const cid = cellId("cell-1");
41
41
  const tabData = {
42
42
  tabName: "Tab 1" as TabName,
43
43
  chartType: ChartType.LINE,
@@ -51,17 +51,17 @@ describe("Chart Transforms Storage", () => {
51
51
 
52
52
  // Set the atom value
53
53
  const newMap = new Map();
54
- newMap.set(cellId, [tabData]);
54
+ newMap.set(cid, [tabData]);
55
55
  store.set(tabsStorageAtom, newMap);
56
56
 
57
57
  // Verify the value was set
58
58
  const retrievedValue = store.get(tabsStorageAtom);
59
- expect(retrievedValue.get(cellId)).toEqual([tabData]);
59
+ expect(retrievedValue.get(cid)).toEqual([tabData]);
60
60
  });
61
61
 
62
62
  it("should handle multiple tabs for the same cell", () => {
63
63
  const store = getDefaultStore();
64
- const cellId = "cell-1" as CellId;
64
+ const cid = cellId("cell-1");
65
65
  const tabData1 = {
66
66
  tabName: "Tab 1" as TabName,
67
67
  chartType: ChartType.LINE,
@@ -85,12 +85,12 @@ describe("Chart Transforms Storage", () => {
85
85
 
86
86
  // Set the atom value
87
87
  const newMap = new Map();
88
- newMap.set(cellId, [tabData1, tabData2]);
88
+ newMap.set(cid, [tabData1, tabData2]);
89
89
  store.set(tabsStorageAtom, newMap);
90
90
 
91
91
  // Verify the value was set
92
92
  const retrievedValue = store.get(tabsStorageAtom);
93
- expect(retrievedValue.get(cellId)).toEqual([tabData1, tabData2]);
93
+ expect(retrievedValue.get(cid)).toEqual([tabData1, tabData2]);
94
94
  });
95
95
  });
96
96
 
@@ -5,9 +5,9 @@ import { beforeAll, describe, expect, it } from "vitest";
5
5
  import { SetupMocks } from "@/__mocks__/common";
6
6
  import { MockNotebook } from "@/__mocks__/notebook";
7
7
  import { MockRequestClient } from "@/__mocks__/requests";
8
+ import { cellId } from "@/__tests__/branded";
8
9
  import { TooltipProvider } from "@/components/ui/tooltip";
9
10
  import { notebookAtom } from "@/core/cells/cells";
10
- import type { CellId } from "@/core/cells/ids";
11
11
  import { createCellRuntimeState } from "@/core/cells/types";
12
12
  import type { UserConfig } from "@/core/config/config-schema";
13
13
  import type { OutputMessage } from "@/core/kernel/messages";
@@ -39,7 +39,7 @@ describe("Cell data attributes", () => {
39
39
  "present",
40
40
  ])("should render cell with data-cell-id and data-cell-name in %s mode", (mode) => {
41
41
  const { store, wrapper } = createTestWrapper();
42
- const cellId = "test" as CellId;
42
+ const cid = cellId("test");
43
43
  const cellName = "test_cell";
44
44
 
45
45
  const userConfig: UserConfig = {
@@ -83,7 +83,7 @@ describe("Cell data attributes", () => {
83
83
 
84
84
  const notebook = MockNotebook.notebookState({
85
85
  cellData: {
86
- [cellId]: {
86
+ [cid]: {
87
87
  code: "",
88
88
  name: cellName,
89
89
  edited: false,
@@ -97,7 +97,7 @@ describe("Cell data attributes", () => {
97
97
  },
98
98
  });
99
99
 
100
- notebook.cellRuntime[cellId] = createCellRuntimeState({
100
+ notebook.cellRuntime[cid] = createCellRuntimeState({
101
101
  status: "idle",
102
102
  output: null,
103
103
  consoleOutputs: [],
@@ -117,7 +117,7 @@ describe("Cell data attributes", () => {
117
117
  const { container } = render(
118
118
  <TooltipProvider>
119
119
  <Cell
120
- cellId={cellId}
120
+ cellId={cid}
121
121
  mode={mode as AppMode}
122
122
  canDelete={true}
123
123
  userConfig={userConfig}
@@ -131,7 +131,7 @@ describe("Cell data attributes", () => {
131
131
  { wrapper },
132
132
  );
133
133
 
134
- const cellElement = container.querySelector(`[data-cell-id="${cellId}"]`);
134
+ const cellElement = container.querySelector(`[data-cell-id="${cid}"]`);
135
135
  expect(cellElement).toBeTruthy();
136
136
  expect(cellElement?.getAttribute("data-cell-name")).toBe(cellName);
137
137
  });
@@ -139,7 +139,7 @@ describe("Cell data attributes", () => {
139
139
 
140
140
  describe("Output data attributes", () => {
141
141
  it("should render output with data-cell-role", () => {
142
- const cellId = "test" as CellId;
142
+ const cid = cellId("test");
143
143
  const output: OutputMessage = {
144
144
  channel: "output",
145
145
  mimetype: "text/plain",
@@ -151,7 +151,7 @@ describe("Output data attributes", () => {
151
151
  <TooltipProvider>
152
152
  <OutputArea
153
153
  output={output}
154
- cellId={cellId}
154
+ cellId={cid}
155
155
  stale={false}
156
156
  loading={false}
157
157
  allowExpand={true}
@@ -1,5 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { beforeEach, describe, expect, it, type Mock, vi } from "vitest";
3
+ import { variableName } from "@/__tests__/branded";
3
4
  import { getCodes } from "@/core/codemirror/copilot/getCodes";
4
5
  import { dataSourceConnectionsAtom } from "@/core/datasets/data-source-connections";
5
6
  import { DUCKDB_ENGINE } from "@/core/datasets/engines";
@@ -7,7 +8,6 @@ import { datasetsAtom } from "@/core/datasets/state";
7
8
  import type { DatasetsState } from "@/core/datasets/types";
8
9
  import { store } from "@/core/state/jotai";
9
10
  import { variablesAtom } from "@/core/variables/state";
10
- import type { Variable, VariableName } from "@/core/variables/types";
11
11
  import { codeToCells, getAICompletionBody } from "../completion-utils";
12
12
 
13
13
  // Mock getCodes function
@@ -205,16 +205,16 @@ describe("getAICompletionBody", () => {
205
205
 
206
206
  it("should return the correct completion body with mentioned variables", () => {
207
207
  // Set up test data in the Jotai store
208
- const testVariables: Record<VariableName, Variable> = {
209
- ["var1" as VariableName]: {
210
- name: "var1" as VariableName,
208
+ const testVariables = {
209
+ [variableName("var1")]: {
210
+ name: variableName("var1"),
211
211
  value: "string value",
212
212
  dataType: "string",
213
213
  declaredBy: [],
214
214
  usedBy: [],
215
215
  },
216
- ["var2" as VariableName]: {
217
- name: "var2" as VariableName,
216
+ [variableName("var2")]: {
217
+ name: variableName("var2"),
218
218
  value: "42",
219
219
  dataType: "number",
220
220
  declaredBy: [],
@@ -253,9 +253,9 @@ describe("getAICompletionBody", () => {
253
253
  ];
254
254
  store.set(datasetsAtom, { tables: testDatasets } as DatasetsState);
255
255
 
256
- const testVariables: Record<VariableName, Variable> = {
257
- ["var1" as VariableName]: {
258
- name: "var1" as VariableName,
256
+ const testVariables = {
257
+ [variableName("var1")]: {
258
+ name: variableName("var1"),
259
259
  value: "string value",
260
260
  dataType: "string",
261
261
  declaredBy: [],
@@ -285,9 +285,9 @@ describe("getAICompletionBody", () => {
285
285
 
286
286
  it("should handle non-existent variables", () => {
287
287
  // Set up test data in the Jotai store
288
- const testVariables: Record<VariableName, Variable> = {
289
- ["existingVar" as VariableName]: {
290
- name: "existingVar" as VariableName,
288
+ const testVariables = {
289
+ [variableName("existingVar")]: {
290
+ name: variableName("existingVar"),
291
291
  value: "string value",
292
292
  dataType: "string",
293
293
  declaredBy: [],
@@ -322,9 +322,9 @@ describe("getAICompletionBody", () => {
322
322
  ];
323
323
  store.set(datasetsAtom, { tables: testDatasets } as DatasetsState);
324
324
 
325
- const testVariables: Record<VariableName, Variable> = {
326
- ["conflict" as VariableName]: {
327
- name: "conflict" as VariableName,
325
+ const testVariables = {
326
+ [variableName("conflict")]: {
327
+ name: variableName("conflict"),
328
328
  value: "string value",
329
329
  dataType: "string",
330
330
  declaredBy: [],
@@ -4,8 +4,8 @@
4
4
  import { act, renderHook } from "@testing-library/react";
5
5
  import { beforeEach, describe, expect, it, vi } from "vitest";
6
6
  import { asMock, MockModules, Mocks, SetupMocks } from "@/__mocks__/common";
7
+ import { cellId } from "@/__tests__/branded";
7
8
  import type { CellActions, NotebookState } from "@/core/cells/cells";
8
- import type { CellId } from "@/core/cells/ids";
9
9
  import { useCellClipboard } from "../clipboard";
10
10
 
11
11
  // Mock dependencies
@@ -179,7 +179,7 @@ describe("useCellClipboard", () => {
179
179
  });
180
180
 
181
181
  it("should filter out non-existent cells", async () => {
182
- const nonExistentCellId = "non-existent" as CellId;
182
+ const nonExistentCellId = cellId("non-existent");
183
183
 
184
184
  const { result } = renderHook(() => useCellClipboard());
185
185
 
@@ -1,6 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
+
2
3
  import { beforeEach, describe, expect, it } from "vitest";
3
- import type { CellId } from "@/core/cells/ids";
4
+ import { cellId } from "@/__tests__/branded";
4
5
  import { MultiColumn } from "@/utils/id-tree";
5
6
  import type { CellSelectionState } from "../selection";
6
7
  import { exportedForTesting } from "../selection";
@@ -8,11 +9,11 @@ import { exportedForTesting } from "../selection";
8
9
  const { initialState, reducer, createActions } = exportedForTesting;
9
10
 
10
11
  const CellIds = {
11
- a: "a" as CellId,
12
- b: "b" as CellId,
13
- c: "c" as CellId,
14
- d: "d" as CellId,
15
- e: "e" as CellId,
12
+ a: cellId("a"),
13
+ b: cellId("b"),
14
+ c: cellId("c"),
15
+ d: cellId("d"),
16
+ e: cellId("e"),
16
17
  };
17
18
 
18
19
  describe("cell selection reducer", () => {
@@ -1,6 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { cellId } from "@/__tests__/branded";
4
5
  import { type CellId, HTMLCellId } from "@/core/cells/ids";
5
6
 
6
7
  const mockScrollCellIntoView = vi.fn();
@@ -14,12 +15,12 @@ vi.mock("../focus-utils", () => ({
14
15
  type TemporarilyShownCodeState = Set<CellId>;
15
16
 
16
17
  describe("temporarilyShownCodeActions", () => {
17
- const cellId = "cell-1" as CellId;
18
+ const cid = cellId("cell-1");
18
19
  let cellElement: HTMLElement;
19
20
 
20
21
  beforeEach(() => {
21
22
  cellElement = document.createElement("div");
22
- cellElement.id = HTMLCellId.create(cellId);
23
+ cellElement.id = HTMLCellId.create(cid);
23
24
  document.body.append(cellElement);
24
25
  cellElement.focus();
25
26
 
@@ -37,17 +38,17 @@ describe("temporarilyShownCodeActions", () => {
37
38
  });
38
39
 
39
40
  it("should scroll cell into view when removing cell causes layout shift", () => {
40
- const state = new Set<CellId>([cellId]);
41
- removeCell(state, cellId);
41
+ const state = new Set<CellId>([cid]);
42
+ removeCell(state, cid);
42
43
 
43
- expect(mockScrollCellIntoView).toHaveBeenCalledWith(cellId);
44
+ expect(mockScrollCellIntoView).toHaveBeenCalledWith(cid);
44
45
  });
45
46
 
46
47
  it("should not scroll when focused cell is not found", () => {
47
48
  vi.spyOn(HTMLCellId, "findElementThroughShadowDOMs").mockReturnValue(null);
48
49
 
49
- const state = new Set<CellId>([cellId]);
50
- removeCell(state, cellId);
50
+ const state = new Set<CellId>([cid]);
51
+ removeCell(state, cid);
51
52
 
52
53
  expect(mockScrollCellIntoView).not.toHaveBeenCalled();
53
54
  });
@@ -316,7 +316,7 @@ export const MarimoErrorOutput = ({
316
316
  <ul className="list-disc">
317
317
  {error.cells.map((cid, cidIdx) => (
318
318
  <li className={liStyle} key={`cell-${cidIdx}`}>
319
- <CellLinkError cellId={cid as CellId} />
319
+ <CellLinkError cellId={cid} />
320
320
  </li>
321
321
  ))}
322
322
  </ul>
@@ -491,7 +491,7 @@ export const MarimoErrorOutput = ({
491
491
  ) : (
492
492
  <div>
493
493
  {processTextForUrls(error.msg, `exception-${idx}`)}
494
- <CellLinkError cellId={error.raising_cell as CellId} />
494
+ <CellLinkError cellId={error.raising_cell} />
495
495
  </div>
496
496
  )}
497
497
  </li>
@@ -518,7 +518,7 @@ export const MarimoErrorOutput = ({
518
518
  ) : (
519
519
  <div>
520
520
  {error.msg}
521
- <CellLinkError cellId={error.blamed_cell as CellId} />
521
+ <CellLinkError cellId={error.blamed_cell} />
522
522
  </div>
523
523
  )}
524
524
  </li>
@@ -554,13 +554,13 @@ export const MarimoErrorOutput = ({
554
554
  {error.msg}
555
555
  {error.blamed_cell == null ? (
556
556
  <span>
557
- (<CellLinkError cellId={error.raising_cell as CellId} />)
557
+ (<CellLinkError cellId={error.raising_cell} />)
558
558
  </span>
559
559
  ) : (
560
560
  <span>
561
- (<CellLinkError cellId={error.raising_cell as CellId} />
561
+ (<CellLinkError cellId={error.raising_cell} />
562
562
  &nbsp;blames&nbsp;
563
- <CellLinkError cellId={error.blamed_cell as CellId} />)
563
+ <CellLinkError cellId={error.blamed_cell} />)
564
564
  </span>
565
565
  )}
566
566
  </div>
@@ -578,7 +578,7 @@ export const MarimoErrorOutput = ({
578
578
  {ancestorStoppedErrors.map((error, idx) => (
579
579
  <div key={`ancestor-stopped-${idx}`}>
580
580
  {error.msg}
581
- <CellLinkError cellId={error.raising_cell as CellId} />
581
+ <CellLinkError cellId={error.raising_cell} />
582
582
  </div>
583
583
  ))}
584
584
  {cellId && (
@@ -3,8 +3,8 @@
3
3
  import { render } from "@testing-library/react";
4
4
  import { beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
5
5
  import { Tracebacks } from "@/__mocks__/tracebacks";
6
+ import { cellId } from "@/__tests__/branded";
6
7
  import { TooltipProvider } from "@/components/ui/tooltip";
7
- import type { CellId } from "@/core/cells/ids";
8
8
  import { initialModeAtom } from "@/core/mode";
9
9
  import { store } from "@/core/state/jotai";
10
10
  import { renderHTML } from "@/plugins/core/RenderHTML";
@@ -14,7 +14,7 @@ import {
14
14
  replaceTracebackPrefix,
15
15
  } from "../MarimoTracebackOutput";
16
16
 
17
- const cellId = "1" as CellId;
17
+ const cid = cellId("1");
18
18
 
19
19
  describe("traceback component", () => {
20
20
  beforeEach(() => {
@@ -25,7 +25,7 @@ describe("traceback component", () => {
25
25
  test("extracts cell-link", () => {
26
26
  const traceback = (
27
27
  <TooltipProvider>
28
- <MarimoTracebackOutput traceback={Tracebacks.raw} cellId={cellId} />
28
+ <MarimoTracebackOutput traceback={Tracebacks.raw} cellId={cid} />
29
29
  </TooltipProvider>
30
30
  );
31
31
  const { unmount, getAllByRole } = render(traceback);
@@ -45,7 +45,7 @@ describe("traceback component", () => {
45
45
  test("renames File to Cell for relevant lines", () => {
46
46
  const traceback = (
47
47
  <TooltipProvider>
48
- <MarimoTracebackOutput traceback={Tracebacks.raw} cellId={cellId} />
48
+ <MarimoTracebackOutput traceback={Tracebacks.raw} cellId={cid} />
49
49
  </TooltipProvider>
50
50
  );
51
51
  const { unmount, container } = render(traceback);
@@ -3,8 +3,8 @@
3
3
  import { act, fireEvent, render, screen } from "@testing-library/react";
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
5
  import { SetupMocks } from "@/__mocks__/common";
6
+ import { cellId } from "@/__tests__/branded";
6
7
  import { TooltipProvider } from "@/components/ui/tooltip";
7
- import type { CellId } from "@/core/cells/ids";
8
8
  import type { WithResponse } from "@/core/cells/types";
9
9
  import type { OutputMessage } from "@/core/kernel/messages";
10
10
  import { CONSOLE_CLEAR_DEBOUNCE_MS, ConsoleOutput } from "../ConsoleOutput";
@@ -24,7 +24,7 @@ describe("ConsoleOutput integration", () => {
24
24
  });
25
25
 
26
26
  const defaultProps = {
27
- cellId: "cell-1" as CellId,
27
+ cellId: cellId("cell-1"),
28
28
  cellName: "test_cell",
29
29
  consoleOutputs: [] as WithResponse<OutputMessage>[],
30
30
  stale: false,
@@ -55,7 +55,7 @@ describe("ConsoleOutput integration", () => {
55
55
 
56
56
  describe("ConsoleOutput pdb history", () => {
57
57
  const defaultProps = {
58
- cellId: "cell-1" as CellId,
58
+ cellId: cellId("cell-1"),
59
59
  cellName: "test_cell",
60
60
  consoleOutputs: [] as WithResponse<OutputMessage>[],
61
61
  stale: false,
@@ -215,7 +215,7 @@ describe("ConsoleOutput debounced clearing", () => {
215
215
  });
216
216
 
217
217
  const defaultProps = {
218
- cellId: "cell-1" as CellId,
218
+ cellId: cellId("cell-1"),
219
219
  cellName: "test_cell",
220
220
  consoleOutputs: [] as WithResponse<OutputMessage>[],
221
221
  stale: false,
@@ -91,7 +91,7 @@ function focusCellByName(cellName: string) {
91
91
 
92
92
  // Look for an editor to focus
93
93
  const { cellHandles } = getNotebook();
94
- const cellId = cellElement.dataset.cellId as CellId | undefined;
94
+ const cellId = extractCellIdFromDomElement(cellElement);
95
95
 
96
96
  if (!cellId) {
97
97
  Logger.error(`Missing cellId for cell with name ${cellName}`);
@@ -111,3 +111,10 @@ function focusCellByName(cellName: string) {
111
111
  focusFirstEditor();
112
112
  }
113
113
  }
114
+
115
+ function extractCellIdFromDomElement(
116
+ cellElement: HTMLElement,
117
+ ): CellId | undefined {
118
+ const cellIdStr = cellElement.dataset.cellId ?? undefined;
119
+ return cellIdStr as CellId | undefined;
120
+ }
@@ -50,7 +50,6 @@ import type {
50
50
  StoragePathKey,
51
51
  } from "@/core/storage/types";
52
52
  import { storagePathKey } from "@/core/storage/types";
53
- import type { VariableName } from "@/core/variables/types";
54
53
  import { cn } from "@/utils/cn";
55
54
  import { copyToClipboard } from "@/utils/copy";
56
55
  import { downloadByURL } from "@/utils/download";
@@ -474,7 +473,7 @@ const StorageNamespaceSection: React.FC<{
474
473
  <span>{namespace.displayName}</span>
475
474
  {namespace.name && (
476
475
  <span className="text-xs text-muted-foreground font-normal">
477
- (<EngineVariable variableName={namespace.name as VariableName} />)
476
+ (<EngineVariable variableName={namespace.name} />)
478
477
  </span>
479
478
  )}
480
479
  <RefreshIconButton
@@ -243,7 +243,9 @@ const TraceBlockBody: React.FC<{
243
243
  signalName: VEGA_HOVER_SIGNAL,
244
244
  handler: (_name: string, value: unknown) => {
245
245
  const signalValue = value as VegaHoverCellSignal;
246
- const hoveredCell = signalValue.cell?.[0] as CellId | undefined;
246
+ const hoveredCell = (signalValue.cell?.[0] ?? undefined) as
247
+ | CellId
248
+ | undefined;
247
249
  setHoveredCellId(hoveredCell ?? null);
248
250
  },
249
251
  },
@@ -3,6 +3,7 @@
3
3
  import { renderHook } from "@testing-library/react";
4
4
  import { getDefaultStore } from "jotai";
5
5
  import { beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { cellId } from "@/__tests__/branded";
6
7
  import { CellId } from "@/core/cells/ids";
7
8
  import { updateEditorCodeFromPython } from "../../codemirror/language/utils";
8
9
  import {
@@ -61,8 +62,8 @@ describe("staged-cells", () => {
61
62
 
62
63
  beforeEach(() => {
63
64
  store = getDefaultStore();
64
- cellId1 = "cell-1" as CellId;
65
- cellId2 = "cell-2" as CellId;
65
+ cellId1 = cellId("cell-1");
66
+ cellId2 = cellId("cell-2");
66
67
 
67
68
  // Reset mocks
68
69
  vi.clearAllMocks();
@@ -213,7 +214,7 @@ describe("staged-cells", () => {
213
214
  const testCode = "print('hello world')";
214
215
 
215
216
  // Mock CellId.create to return a predictable ID
216
- const mockCellId = "mock-cell-id" as CellId;
217
+ const mockCellId = cellId("mock-cell-id");
217
218
  vi.mocked(CellId.create).mockReturnValue(mockCellId);
218
219
 
219
220
  const returnedCellId = result.current.createStagedCell(testCode);
@@ -229,7 +230,7 @@ describe("staged-cells", () => {
229
230
 
230
231
  it("should delete a staged cell", () => {
231
232
  const { result } = renderHook(() => useStagedCells(store));
232
- const testCellId = "test-cell-id" as CellId;
233
+ const testCellId = cellId("test-cell-id");
233
234
 
234
235
  result.current.deleteStagedCell(testCellId);
235
236
 
@@ -330,7 +331,7 @@ describe("staged-cells", () => {
330
331
  const { result } = renderHook(() => useStagedCells(store));
331
332
 
332
333
  // Create a staged cell
333
- const mockCellId = "mock-cell-id" as CellId;
334
+ const mockCellId = cellId("mock-cell-id");
334
335
  vi.mocked(CellId.create).mockReturnValue(mockCellId);
335
336
 
336
337
  const createdCellId = result.current.createStagedCell("test code");
@@ -341,7 +342,7 @@ describe("staged-cells", () => {
341
342
 
342
343
  let state = store.get(stagedAICellsAtom);
343
344
  expect(state.has(mockCellId)).toBe(true);
344
- expect(state.get(mockCellId)).toEqual({ type: "add_cell" });
345
+ expect(state.get(mockCellId)).toEqual({ type: cellId("add_cell") });
345
346
 
346
347
  // Delete the staged cell
347
348
  result.current.deleteStagedCell(mockCellId);
@@ -423,7 +424,7 @@ describe("onStream", () => {
423
424
  result.current.onStream({ type: "text-start", id: "test-id" });
424
425
 
425
426
  // Mock CellId.create to return a predictable ID
426
- const mockCellId = "mock-cell-id" as CellId;
427
+ const mockCellId = cellId("mock-cell-id");
427
428
  vi.mocked(CellId.create).mockReturnValue(mockCellId);
428
429
 
429
430
  result.current.onStream({
@@ -444,7 +445,7 @@ describe("onStream", () => {
444
445
  const { result } = renderHook(() => useStagedCells(store));
445
446
  result.current.onStream({ type: "text-start", id: "test-id" });
446
447
 
447
- const mockCellId = "mock-cell-id" as CellId;
448
+ const mockCellId = cellId("mock-cell-id");
448
449
  vi.mocked(CellId.create).mockReturnValue(mockCellId);
449
450
 
450
451
  result.current.onStream({