@marimo-team/islands 0.21.2-dev6 → 0.21.2-dev62

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 (152) hide show
  1. package/dist/{ConnectedDataExplorerComponent-D0GoOd_c.js → ConnectedDataExplorerComponent-DrWDbHRV.js} +1 -1
  2. package/dist/{any-language-editor-DlsjUw_l.js → any-language-editor-BRpxklRq.js} +1 -1
  3. package/dist/{copy-DIK6DiIA.js → copy-BjkXCUxP.js} +12 -2
  4. package/dist/{esm-BLobyqMs.js → esm-No_6eSQS.js} +1 -1
  5. package/dist/{glide-data-editor-pZyd9UJ_.js → glide-data-editor-858wsVkd.js} +1 -1
  6. package/dist/main.js +821 -417
  7. package/dist/{spec-Bfvf9Hre.js → spec-oVDndBz4.js} +25 -16
  8. package/dist/style.css +1 -1
  9. package/package.json +1 -1
  10. package/src/__mocks__/notebook.ts +9 -9
  11. package/src/__mocks__/requests.ts +1 -0
  12. package/src/__tests__/branded.ts +20 -0
  13. package/src/components/app-config/user-config-form.tsx +5 -4
  14. package/src/components/data-table/__tests__/utils.test.ts +138 -1
  15. package/src/components/data-table/charts/__tests__/storage.test.ts +7 -7
  16. package/src/components/data-table/context-menu.tsx +9 -5
  17. package/src/components/data-table/data-table.tsx +3 -0
  18. package/src/components/data-table/range-focus/__tests__/atoms.test.ts +8 -2
  19. package/src/components/data-table/range-focus/__tests__/test-utils.ts +2 -0
  20. package/src/components/data-table/range-focus/__tests__/utils.test.ts +82 -8
  21. package/src/components/data-table/range-focus/atoms.ts +2 -2
  22. package/src/components/data-table/range-focus/utils.ts +50 -12
  23. package/src/components/data-table/types.ts +7 -0
  24. package/src/components/data-table/utils.ts +87 -0
  25. package/src/components/editor/__tests__/data-attributes.test.tsx +8 -8
  26. package/src/components/editor/ai/__tests__/completion-utils.test.ts +15 -15
  27. package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +2 -2
  28. package/src/components/editor/connections/storage/as-code.ts +2 -2
  29. package/src/components/editor/file-tree/file-explorer.tsx +16 -2
  30. package/src/components/editor/file-tree/file-viewer.tsx +17 -3
  31. package/src/components/editor/navigation/__tests__/clipboard.test.ts +2 -2
  32. package/src/components/editor/navigation/__tests__/selection.test.ts +7 -6
  33. package/src/components/editor/navigation/__tests__/state.test.ts +8 -7
  34. package/src/components/editor/output/MarimoErrorOutput.tsx +7 -7
  35. package/src/components/editor/output/__tests__/traceback.test.tsx +4 -4
  36. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +4 -4
  37. package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +8 -1
  38. package/src/components/storage/storage-file-viewer.tsx +35 -1
  39. package/src/components/storage/storage-inspector.tsx +9 -4
  40. package/src/components/storage/storage-snippets.ts +3 -3
  41. package/src/components/tracing/tracing.tsx +3 -1
  42. package/src/components/ui/range-slider.tsx +108 -1
  43. package/src/core/ai/__tests__/staged-cells.test.ts +9 -8
  44. package/src/core/ai/context/providers/__tests__/cell-output.test.ts +31 -31
  45. package/src/core/ai/context/providers/__tests__/datasource.test.ts +3 -3
  46. package/src/core/ai/context/providers/__tests__/tables.test.ts +3 -2
  47. package/src/core/ai/context/providers/__tests__/variable.test.ts +84 -63
  48. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +10 -9
  49. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +6 -6
  50. package/src/core/ai/tools/edit-notebook-tool.ts +3 -3
  51. package/src/core/cells/__tests__/add-missing-import.test.ts +3 -3
  52. package/src/core/cells/__tests__/apply-transaction.test.ts +279 -0
  53. package/src/core/cells/__tests__/cells.test.ts +198 -135
  54. package/src/core/cells/__tests__/document-changes.test.ts +575 -0
  55. package/src/core/cells/__tests__/document-roundtrip.test.ts +376 -0
  56. package/src/core/cells/__tests__/focus.test.ts +5 -4
  57. package/src/core/cells/__tests__/logs.test.ts +13 -12
  58. package/src/core/cells/__tests__/pending-delete-service.test.tsx +3 -3
  59. package/src/core/cells/__tests__/runs.test.ts +22 -21
  60. package/src/core/cells/__tests__/scrollCellIntoView.test.ts +8 -7
  61. package/src/core/cells/__tests__/session.test.ts +23 -22
  62. package/src/core/cells/cells.ts +29 -4
  63. package/src/core/cells/document-changes.ts +644 -0
  64. package/src/core/cells/ids.ts +5 -5
  65. package/src/core/cells/logs.ts +2 -2
  66. package/src/core/cells/runs.ts +6 -8
  67. package/src/core/codemirror/__tests__/format.test.ts +34 -36
  68. package/src/core/codemirror/__tests__/setup.test.ts +2 -2
  69. package/src/core/codemirror/cells/__tests__/extensions.test.ts +114 -0
  70. package/src/core/codemirror/cells/__tests__/traceback-decorations.test.ts +33 -32
  71. package/src/core/codemirror/cells/extensions.ts +66 -23
  72. package/src/core/codemirror/completion/__tests__/keymap.test.ts +15 -35
  73. package/src/core/codemirror/completion/keymap.ts +14 -4
  74. package/src/core/codemirror/copilot/__tests__/getCodes.test.ts +12 -13
  75. package/src/core/codemirror/language/__tests__/utils.test.ts +3 -3
  76. package/src/core/codemirror/language/embedded/__tests__/embedded-python.test.ts +7 -8
  77. package/src/core/codemirror/language/languages/python.ts +4 -0
  78. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +4 -3
  79. package/src/core/codemirror/lsp/notebook-lsp.ts +28 -2
  80. package/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts +7 -6
  81. package/src/core/codemirror/reactive-references/analyzer.ts +2 -2
  82. package/src/core/codemirror/rtc/loro/__tests__/sync.test.ts +52 -0
  83. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  84. package/src/core/datasets/__tests__/data-source.test.ts +5 -6
  85. package/src/core/datasets/state.ts +1 -1
  86. package/src/core/errors/__tests__/errors.test.ts +2 -1
  87. package/src/core/export/__tests__/hooks.test.ts +37 -36
  88. package/src/core/islands/bridge.ts +1 -0
  89. package/src/core/islands/main.ts +4 -7
  90. package/src/core/kernel/__tests__/handlers.test.ts +5 -4
  91. package/src/core/kernel/handlers.ts +7 -4
  92. package/src/core/network/DeferredRequestRegistry.ts +2 -2
  93. package/src/core/network/__tests__/CachingRequestRegistry.test.ts +9 -10
  94. package/src/core/network/__tests__/DeferredRequestRegistry.test.ts +4 -6
  95. package/src/core/network/requests-lazy.ts +1 -0
  96. package/src/core/network/requests-network.ts +9 -0
  97. package/src/core/network/requests-static.ts +1 -0
  98. package/src/core/network/requests-toasting.tsx +1 -0
  99. package/src/core/network/types.ts +5 -0
  100. package/src/core/static/__tests__/virtual-file-tracker.test.ts +8 -8
  101. package/src/core/static/virtual-file-tracker.ts +1 -1
  102. package/src/core/storage/__tests__/state.test.ts +31 -21
  103. package/src/core/storage/state.ts +1 -1
  104. package/src/core/variables/__tests__/state.test.ts +6 -6
  105. package/src/core/variables/types.ts +2 -2
  106. package/src/core/wasm/__tests__/state.test.ts +8 -8
  107. package/src/core/wasm/bridge.ts +1 -0
  108. package/src/core/websocket/useMarimoKernelConnection.tsx +31 -16
  109. package/src/css/app/fonts.css +6 -6
  110. package/src/css/md-tooltip.css +4 -39
  111. package/src/css/md.css +7 -0
  112. package/src/fonts/Fira_Mono/FiraMono-Bold.woff2 +0 -0
  113. package/src/fonts/Fira_Mono/FiraMono-Medium.woff2 +0 -0
  114. package/src/fonts/Fira_Mono/FiraMono-Regular.woff2 +0 -0
  115. package/src/fonts/Lora/Lora-VariableFont_wght.woff2 +0 -0
  116. package/src/fonts/PT_Sans/PTSans-Bold.woff2 +0 -0
  117. package/src/fonts/PT_Sans/PTSans-Regular.woff2 +0 -0
  118. package/src/plugins/core/RenderHTML.tsx +17 -0
  119. package/src/plugins/core/__test__/RenderHTML.test.ts +45 -0
  120. package/src/plugins/core/sanitize-html.ts +25 -18
  121. package/src/plugins/impl/DataTablePlugin.tsx +23 -2
  122. package/src/plugins/impl/SliderPlugin.tsx +1 -3
  123. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +120 -0
  124. package/src/plugins/impl/anywidget/model.ts +1 -2
  125. package/src/stories/cell.stories.tsx +8 -8
  126. package/src/stories/layout/vertical/one-column.stories.tsx +9 -8
  127. package/src/stories/log-viewer.stories.tsx +8 -8
  128. package/src/stories/variables.stories.tsx +2 -2
  129. package/src/utils/__tests__/download.test.tsx +21 -20
  130. package/src/utils/copy.ts +18 -5
  131. package/src/utils/createReducer.ts +26 -11
  132. package/src/utils/download.ts +4 -3
  133. package/src/utils/html-to-image.ts +6 -0
  134. package/src/utils/json/base64.ts +3 -3
  135. package/src/utils/traceback.ts +5 -3
  136. package/src/fonts/Fira_Mono/FiraMono-Bold.ttf +0 -0
  137. package/src/fonts/Fira_Mono/FiraMono-Medium.ttf +0 -0
  138. package/src/fonts/Fira_Mono/FiraMono-Regular.ttf +0 -0
  139. package/src/fonts/Lora/Lora-Italic-VariableFont_wght.ttf +0 -0
  140. package/src/fonts/Lora/Lora-VariableFont_wght.ttf +0 -0
  141. package/src/fonts/Lora/static/Lora-Bold.ttf +0 -0
  142. package/src/fonts/Lora/static/Lora-BoldItalic.ttf +0 -0
  143. package/src/fonts/Lora/static/Lora-Italic.ttf +0 -0
  144. package/src/fonts/Lora/static/Lora-Medium.ttf +0 -0
  145. package/src/fonts/Lora/static/Lora-MediumItalic.ttf +0 -0
  146. package/src/fonts/Lora/static/Lora-Regular.ttf +0 -0
  147. package/src/fonts/Lora/static/Lora-SemiBold.ttf +0 -0
  148. package/src/fonts/Lora/static/Lora-SemiBoldItalic.ttf +0 -0
  149. package/src/fonts/PT_Sans/PTSans-Bold.ttf +0 -0
  150. package/src/fonts/PT_Sans/PTSans-BoldItalic.ttf +0 -0
  151. package/src/fonts/PT_Sans/PTSans-Italic.ttf +0 -0
  152. package/src/fonts/PT_Sans/PTSans-Regular.ttf +0 -0
@@ -0,0 +1,376 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ /**
4
+ * Round-trip tests: perform actions on a primary notebook (producing changes
5
+ * via the middleware), then apply those changes to a replica notebook via
6
+ * applyTransactionChanges. The two should converge to identical document state.
7
+ *
8
+ * This catches drift between what the middleware emits and what
9
+ * apply-transaction consumes.
10
+ */
11
+
12
+ import { python } from "@codemirror/lang-python";
13
+ import { EditorState } from "@codemirror/state";
14
+ import { EditorView } from "@codemirror/view";
15
+ import {
16
+ afterAll,
17
+ afterEach,
18
+ beforeAll,
19
+ beforeEach,
20
+ describe,
21
+ expect,
22
+ it,
23
+ } from "vitest";
24
+ import { cellId } from "@/__tests__/branded";
25
+ import type { CellHandle } from "@/components/editor/notebook-cell";
26
+ import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
27
+ import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
28
+ import { MultiColumn } from "@/utils/id-tree";
29
+ import { exportedForTesting, type NotebookState } from "../cells";
30
+ import {
31
+ applyTransactionChanges,
32
+ exportedForTesting as middlewareExports,
33
+ } from "../document-changes";
34
+ import { CellId } from "../ids";
35
+
36
+ const { initialNotebookState, reducer, createActions } = exportedForTesting;
37
+ const { drainChanges } = middlewareExports;
38
+
39
+ function createEditor(code: string) {
40
+ const state = EditorState.create({
41
+ doc: code,
42
+ extensions: [
43
+ python(),
44
+ adaptiveLanguageConfiguration({
45
+ cellId: cellId("cell1"),
46
+ completionConfig: {
47
+ activate_on_typing: true,
48
+ signature_hint_on_typing: false,
49
+ copilot: false,
50
+ codeium_api_key: null,
51
+ },
52
+ hotkeys: new OverridingHotkeyProvider({}),
53
+ placeholderType: "marimo-import",
54
+ lspConfig: {},
55
+ }),
56
+ ],
57
+ });
58
+ return new EditorView({ state, parent: document.body });
59
+ }
60
+
61
+ // --- Primary notebook: performs actions, middleware produces changes ---
62
+
63
+ let primary: NotebookState;
64
+
65
+ const primaryActions = createActions((action) => {
66
+ primary = reducer(primary, action);
67
+ for (const [cellIdString, handle] of Object.entries(primary.cellHandles)) {
68
+ // @ts-expect-error - Object.entries doesn't know keys are CellId
69
+ const cid: CellId = cellIdString;
70
+ if (!handle.current) {
71
+ const view = createEditor(primary.cellData[cid].code);
72
+ const h: CellHandle = { editorView: view, editorViewOrNull: view };
73
+ primary.cellHandles[cid] = { current: h };
74
+ }
75
+ }
76
+ });
77
+
78
+ // --- Replica notebook: receives changes via applyTransactionChanges ---
79
+
80
+ let replica: NotebookState;
81
+
82
+ const replicaActions = createActions((action) => {
83
+ replica = reducer(replica, action);
84
+ for (const [cellIdString, handle] of Object.entries(replica.cellHandles)) {
85
+ // @ts-expect-error - Object.entries doesn't know keys are CellId
86
+ const cid: CellId = cellIdString;
87
+ if (!handle.current) {
88
+ const view = createEditor(replica.cellData[cid].code);
89
+ const h: CellHandle = { editorView: view, editorViewOrNull: view };
90
+ replica.cellHandles[cid] = { current: h };
91
+ }
92
+ }
93
+ });
94
+
95
+ let i = 0;
96
+ const originalCreate = CellId.create.bind(CellId);
97
+
98
+ beforeAll(() => {
99
+ CellId.create = () => cellId(`${i++}`);
100
+ });
101
+
102
+ beforeEach(() => {
103
+ i = 0;
104
+ primary = initialNotebookState();
105
+ primary.cellIds = MultiColumn.from([]);
106
+ drainChanges();
107
+ });
108
+
109
+ afterEach(() => {
110
+ middlewareExports.cancelPendingChanges();
111
+ });
112
+
113
+ afterAll(() => {
114
+ CellId.create = originalCreate;
115
+ });
116
+
117
+ /** Set up both notebooks with the same initial cells. */
118
+ function setup(...codes: string[]) {
119
+ for (const code of codes) {
120
+ primaryActions.createNewCell({
121
+ cellId: "__end__",
122
+ before: false,
123
+ code,
124
+ newCellId: CellId.create(),
125
+ });
126
+ }
127
+
128
+ // Apply the setup changes to the replica so both start identical
129
+ const setupChanges = drainChanges();
130
+ replica = initialNotebookState();
131
+ replica.cellIds = MultiColumn.from([]);
132
+ applyTransactionChanges(
133
+ setupChanges,
134
+ replicaActions,
135
+ () => replica.cellIds.inOrderIds,
136
+ );
137
+ // Drain any changes the replica's middleware produced
138
+ drainChanges();
139
+ }
140
+
141
+ /**
142
+ * Drain changes from the primary's middleware and apply them to the replica.
143
+ */
144
+ function sync() {
145
+ const changes = drainChanges();
146
+ applyTransactionChanges(
147
+ changes,
148
+ replicaActions,
149
+ () => replica.cellIds.inOrderIds,
150
+ );
151
+ // Drain any changes the replica's middleware produced (we don't want those)
152
+ drainChanges();
153
+ }
154
+
155
+ /**
156
+ * Extract the document-relevant state: cell ordering, code, name, config.
157
+ * This is the "NotebookDocument" equivalent — what the Python side tracks.
158
+ *
159
+ * TODO(column-config): config.column is excluded because the column
160
+ * reducers (addColumnBreakpoint, dropOverNewColumn, moveColumn, etc.)
161
+ * update cellIds (MultiColumn structure) but don't sync config.column
162
+ * on affected cells. The middleware correctly emits set-config changes with
163
+ * the new column index, but the primary's config.column stays stale,
164
+ * causing a mismatch with the replica. Fix: have the column reducers
165
+ * update config.column as part of their state transition, then remove
166
+ * the { column: _, ...config } exclusion here.
167
+ */
168
+ function documentSnapshot(state: NotebookState) {
169
+ return state.cellIds.inOrderIds.map((id) => {
170
+ const { column: _, ...config } = state.cellData[id].config;
171
+ return {
172
+ id,
173
+ code: state.cellData[id].code,
174
+ name: state.cellData[id].name,
175
+ config,
176
+ };
177
+ });
178
+ }
179
+
180
+ describe("document round-trip", () => {
181
+ it("initial setup converges", () => {
182
+ setup("a", "b", "c");
183
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
184
+ });
185
+
186
+ it("createNewCell at end", () => {
187
+ setup("a", "b");
188
+ primaryActions.createNewCell({
189
+ cellId: "__end__",
190
+ before: false,
191
+ code: "c",
192
+ newCellId: CellId.create(),
193
+ });
194
+ sync();
195
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
196
+ });
197
+
198
+ it("createNewCell before first cell", () => {
199
+ setup("a", "b");
200
+ const [a] = primary.cellIds.inOrderIds;
201
+ primaryActions.createNewCell({
202
+ cellId: a,
203
+ before: true,
204
+ code: "before-a",
205
+ newCellId: CellId.create(),
206
+ });
207
+ sync();
208
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
209
+ });
210
+
211
+ it("createNewCell between cells", () => {
212
+ setup("a", "b", "c");
213
+ const [a] = primary.cellIds.inOrderIds;
214
+ primaryActions.createNewCell({
215
+ cellId: a,
216
+ before: false,
217
+ code: "between",
218
+ newCellId: CellId.create(),
219
+ });
220
+ sync();
221
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
222
+ });
223
+
224
+ it("createNewCell with hideCode config", () => {
225
+ setup("a");
226
+ primaryActions.createNewCell({
227
+ cellId: "__end__",
228
+ before: false,
229
+ code: "hidden",
230
+ newCellId: CellId.create(),
231
+ hideCode: true,
232
+ });
233
+ sync();
234
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
235
+ });
236
+
237
+ it("deleteCell", () => {
238
+ setup("a", "b", "c");
239
+ const [, b] = primary.cellIds.inOrderIds;
240
+ primaryActions.deleteCell({ cellId: b });
241
+ sync();
242
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
243
+ });
244
+
245
+ it("updateCellCode", () => {
246
+ setup("a", "b");
247
+ const [a] = primary.cellIds.inOrderIds;
248
+ primaryActions.updateCellCode({
249
+ cellId: a,
250
+ code: "updated",
251
+ formattingChange: false,
252
+ });
253
+ sync();
254
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
255
+ });
256
+
257
+ it("updateCellName", () => {
258
+ setup("a");
259
+ const [a] = primary.cellIds.inOrderIds;
260
+ primaryActions.updateCellName({ cellId: a, name: "my_var" });
261
+ sync();
262
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
263
+ });
264
+
265
+ it("updateCellConfig", () => {
266
+ setup("a");
267
+ const [a] = primary.cellIds.inOrderIds;
268
+ primaryActions.updateCellConfig({
269
+ cellId: a,
270
+ config: { hide_code: true, disabled: true },
271
+ });
272
+ sync();
273
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
274
+ });
275
+
276
+ it("moveCell down", () => {
277
+ setup("a", "b", "c");
278
+ const [a] = primary.cellIds.inOrderIds;
279
+ primaryActions.moveCell({ cellId: a, before: false });
280
+ sync();
281
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
282
+ });
283
+
284
+ it("sendToTop", () => {
285
+ setup("a", "b", "c");
286
+ const c = primary.cellIds.inOrderIds[2];
287
+ primaryActions.sendToTop({ cellId: c });
288
+ sync();
289
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
290
+ });
291
+
292
+ it("sendToBottom", () => {
293
+ setup("a", "b", "c");
294
+ const [a] = primary.cellIds.inOrderIds;
295
+ primaryActions.sendToBottom({ cellId: a });
296
+ sync();
297
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
298
+ });
299
+
300
+ it("dropCellOverCell", () => {
301
+ setup("a", "b", "c");
302
+ const [a, , c] = primary.cellIds.inOrderIds;
303
+ primaryActions.dropCellOverCell({ cellId: c, overCellId: a });
304
+ sync();
305
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
306
+ });
307
+
308
+ it("multiple operations in sequence", () => {
309
+ setup("a", "b", "c");
310
+
311
+ // Add a cell
312
+ const [a] = primary.cellIds.inOrderIds;
313
+ primaryActions.createNewCell({
314
+ cellId: a,
315
+ before: false,
316
+ code: "new",
317
+ newCellId: CellId.create(),
318
+ });
319
+ sync();
320
+
321
+ // Rename it
322
+ const newId = primary.cellIds.inOrderIds[1];
323
+ primaryActions.updateCellName({ cellId: newId, name: "inserted" });
324
+ sync();
325
+
326
+ // Move it to top
327
+ primaryActions.sendToTop({ cellId: newId });
328
+ sync();
329
+
330
+ // Update code on another cell
331
+ const last =
332
+ primary.cellIds.inOrderIds[primary.cellIds.inOrderIds.length - 1];
333
+ primaryActions.updateCellCode({
334
+ cellId: last,
335
+ code: "modified",
336
+ formattingChange: false,
337
+ });
338
+ sync();
339
+
340
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
341
+ });
342
+
343
+ it("create then delete", () => {
344
+ setup("a", "b");
345
+ primaryActions.createNewCell({
346
+ cellId: "__end__",
347
+ before: false,
348
+ code: "temporary",
349
+ newCellId: CellId.create(),
350
+ });
351
+ sync();
352
+
353
+ const newId =
354
+ primary.cellIds.inOrderIds[primary.cellIds.inOrderIds.length - 1];
355
+ primaryActions.deleteCell({ cellId: newId });
356
+ sync();
357
+
358
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
359
+ });
360
+
361
+ it("addColumnBreakpoint", () => {
362
+ setup("a", "b", "c");
363
+ const [, b] = primary.cellIds.inOrderIds;
364
+ primaryActions.addColumnBreakpoint({ cellId: b });
365
+ sync();
366
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
367
+ });
368
+
369
+ it("dropOverNewColumn", () => {
370
+ setup("a", "b", "c");
371
+ const [, b] = primary.cellIds.inOrderIds;
372
+ primaryActions.dropOverNewColumn({ cellId: b });
373
+ sync();
374
+ expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
375
+ });
376
+ });
@@ -1,15 +1,16 @@
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 type { CellFocusState } from "../focus";
5
6
  import { exportedForTesting } from "../focus";
6
7
 
7
8
  const { initialState, reducer, createActions } = exportedForTesting;
8
9
 
9
10
  const CellIds = {
10
- a: "a" as CellId,
11
- b: "b" as CellId,
12
- c: "c" as CellId,
11
+ a: cellId("a"),
12
+ b: cellId("b"),
13
+ c: cellId("c"),
13
14
  };
14
15
 
15
16
  describe("cell focus reducer", () => {
@@ -1,6 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
4
+ import { cellId } from "@/__tests__/branded";
4
5
  import type { CellMessage } from "../../kernel/messages";
5
6
  import { formatLogTimestamp, getCellLogsForMessage } from "../logs";
6
7
 
@@ -18,7 +19,7 @@ describe("getCellLogsForMessage", () => {
18
19
 
19
20
  test("handles text/plain MIME type on stdout", () => {
20
21
  const cellMessage: CellMessage = {
21
- cell_id: "cell-1",
22
+ cell_id: cellId("cell-1"),
22
23
  console: [
23
24
  {
24
25
  mimetype: "text/plain",
@@ -46,7 +47,7 @@ describe("getCellLogsForMessage", () => {
46
47
 
47
48
  test("handles text/plain MIME type on stderr", () => {
48
49
  const cellMessage: CellMessage = {
49
- cell_id: "cell-2",
50
+ cell_id: cellId("cell-2"),
50
51
  console: [
51
52
  {
52
53
  mimetype: "text/plain",
@@ -74,7 +75,7 @@ describe("getCellLogsForMessage", () => {
74
75
 
75
76
  test("handles text/html MIME type and strips HTML tags", () => {
76
77
  const cellMessage: CellMessage = {
77
- cell_id: "cell-3",
78
+ cell_id: cellId("cell-3"),
78
79
  console: [
79
80
  {
80
81
  mimetype: "text/html",
@@ -102,7 +103,7 @@ describe("getCellLogsForMessage", () => {
102
103
 
103
104
  test("handles text/html MIME type on stderr", () => {
104
105
  const cellMessage: CellMessage = {
105
- cell_id: "cell-4",
106
+ cell_id: cellId("cell-4"),
106
107
  console: [
107
108
  {
108
109
  mimetype: "text/html",
@@ -130,7 +131,7 @@ describe("getCellLogsForMessage", () => {
130
131
 
131
132
  test("handles application/vnd.marimo+traceback MIME type and strips HTML", () => {
132
133
  const cellMessage: CellMessage = {
133
- cell_id: "cell-5",
134
+ cell_id: cellId("cell-5"),
134
135
  console: [
135
136
  {
136
137
  mimetype: "application/vnd.marimo+traceback",
@@ -156,7 +157,7 @@ describe("getCellLogsForMessage", () => {
156
157
 
157
158
  test("handles multiple console outputs with different MIME types", () => {
158
159
  const cellMessage: CellMessage = {
159
- cell_id: "cell-7",
160
+ cell_id: cellId("cell-7"),
160
161
  console: [
161
162
  {
162
163
  mimetype: "text/plain",
@@ -196,7 +197,7 @@ describe("getCellLogsForMessage", () => {
196
197
  vi.spyOn(Date, "now").mockReturnValue(now);
197
198
 
198
199
  const cellMessage: CellMessage = {
199
- cell_id: "cell-8",
200
+ cell_id: cellId("cell-8"),
200
201
  console: [
201
202
  {
202
203
  mimetype: "text/plain",
@@ -219,7 +220,7 @@ describe("getCellLogsForMessage", () => {
219
220
 
220
221
  test("ignores unsupported MIME types", () => {
221
222
  const cellMessage: CellMessage = {
222
- cell_id: "cell-9",
223
+ cell_id: cellId("cell-9"),
223
224
  console: [
224
225
  {
225
226
  mimetype: "application/json",
@@ -241,7 +242,7 @@ describe("getCellLogsForMessage", () => {
241
242
 
242
243
  test("ignores non-logging channels", () => {
243
244
  const cellMessage: CellMessage = {
244
- cell_id: "cell-10",
245
+ cell_id: cellId("cell-10"),
245
246
  console: [
246
247
  {
247
248
  mimetype: "text/plain",
@@ -263,7 +264,7 @@ describe("getCellLogsForMessage", () => {
263
264
 
264
265
  test("returns empty array when console is null", () => {
265
266
  const cellMessage: CellMessage = {
266
- cell_id: "cell-11",
267
+ cell_id: cellId("cell-11"),
267
268
  console: null as unknown as CellMessage["console"],
268
269
  output: null,
269
270
  status: "idle",
@@ -278,7 +279,7 @@ describe("getCellLogsForMessage", () => {
278
279
 
279
280
  test("handles complex HTML with nested elements in text/html", () => {
280
281
  const cellMessage: CellMessage = {
281
- cell_id: "cell-12",
282
+ cell_id: cellId("cell-12"),
282
283
  console: [
283
284
  {
284
285
  mimetype: "text/html",
@@ -301,7 +302,7 @@ describe("getCellLogsForMessage", () => {
301
302
 
302
303
  test("handles marimo-error channel as stderr level", () => {
303
304
  const cellMessage: CellMessage = {
304
- cell_id: "cell-13",
305
+ cell_id: cellId("cell-13"),
305
306
  console: [
306
307
  {
307
308
  mimetype: "text/plain",
@@ -4,11 +4,11 @@ import { act, renderHook, waitFor } from "@testing-library/react";
4
4
  import { createStore, Provider } from "jotai";
5
5
  import { beforeEach, describe, expect, it, vi } from "vitest";
6
6
  import { MockNotebook } from "@/__mocks__/notebook";
7
+ import { variableName } from "@/__tests__/branded";
7
8
  import { notebookAtom } from "@/core/cells/cells";
8
9
  import { CellId } from "@/core/cells/ids";
9
10
  import { createCellRuntimeState } from "@/core/cells/types";
10
11
  import { variablesAtom } from "@/core/variables/state";
11
- import type { VariableName } from "@/core/variables/types";
12
12
  import type { Milliseconds } from "@/utils/time";
13
13
  import {
14
14
  usePendingDelete,
@@ -138,8 +138,8 @@ describe("pending-delete-service", () => {
138
138
 
139
139
  store.set(notebookAtom, notebook);
140
140
  store.set(variablesAtom, {
141
- ["x" as VariableName]: {
142
- name: "x" as VariableName,
141
+ [variableName("x")]: {
142
+ name: variableName("x"),
143
143
  declaredBy: [cell1Id],
144
144
  usedBy: [cell2Id],
145
145
  },