@marimo-team/islands 0.25.3-dev191 → 0.25.3-dev193

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 (154) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BBbOElk3.js → ConnectedDataExplorerComponent-BRuwLOIx.js} +9 -9
  2. package/dist/{ErrorBoundary--X0z83Yx.js → ErrorBoundary-BVu_xT-p.js} +2 -2
  3. package/dist/{abnfDiagram-VCTEODGH-DhMT6Uhd.js → abnfDiagram-VCTEODGH-LlJTOOU_.js} +2 -2
  4. package/dist/{architectureDiagram-5GKGNRK7-BVbEiJLq.js → architectureDiagram-5GKGNRK7-9PQtGLLZ.js} +1 -1
  5. package/dist/{arrays-R6JsImiT.js → arrays-DA6KkLfq.js} +6 -12
  6. package/dist/assets/__vite-browser-external-BCJsx8QS.js +1 -0
  7. package/dist/assets/{worker-C6e9ERyd.js → worker-DodjWT_Z.js} +2 -2
  8. package/dist/{capabilities-CI4xUPsp.js → capabilities-CvWU-ey_.js} +1 -1
  9. package/dist/{chat-ui-BguxTQEL.js → chat-ui-Bl2euMBD.js} +93 -93
  10. package/dist/{chunk-IMKFNOWR-By5hSbJJ.js → chunk-IMKFNOWR-DcXv-BD5.js} +1 -1
  11. package/dist/{chunk-SHT3W25Y-DFrEYG54.js → chunk-SHT3W25Y-VwnELN23.js} +1 -1
  12. package/dist/{chunk-SVP7TREG-DFXGc9ew.js → chunk-SVP7TREG-DgQpI5Zi.js} +1 -1
  13. package/dist/{chunk-TICWLB2K-DZdNIpoZ.js → chunk-TICWLB2K-CTg6VTJT.js} +1 -1
  14. package/dist/{classDiagram-ZZMXUADV-Cgj_eEQA.js → classDiagram-ZZMXUADV-UzmcFrnW.js} +2 -2
  15. package/dist/{classDiagram-v2-VYDZK3BY-DLAUrSqj.js → classDiagram-v2-VYDZK3BY-DhjiI6IQ.js} +2 -2
  16. package/dist/{code-visibility-uDyLo9_i.js → code-visibility-BTVimCUw.js} +38 -42
  17. package/dist/{common-DWKY45Md.js → common-fTgq7gO-.js} +849 -849
  18. package/dist/{cynefinDiagram-5FMLGOSQ-Cikf9AKa.js → cynefinDiagram-5FMLGOSQ-O4ShIbJX.js} +1 -1
  19. package/dist/{dagre-GXQ25YYZ-C5KEi5_l.js → dagre-GXQ25YYZ-dtHhhcPu.js} +1 -1
  20. package/dist/{data-grid-overlay-editor-CRnYWD1L.js → data-grid-overlay-editor-DAZZkLWg.js} +1 -1
  21. package/dist/{diagram-S7CK7UJ4-BaoBTQjR.js → diagram-S7CK7UJ4-ppPYVZ6W.js} +1 -1
  22. package/dist/{diagram-UQ7AKVKN-DDufBsUA.js → diagram-UQ7AKVKN-CMZDf--k.js} +1 -1
  23. package/dist/{diagram-VX7I27RA-CY6RALJ8.js → diagram-VX7I27RA-CFvJdx-C.js} +1 -1
  24. package/dist/{diagram-Z3DM3KII-Bc-LHsD6.js → diagram-Z3DM3KII-Dt6_uFbv.js} +1 -1
  25. package/dist/{dropdown-menu-drDjxJyV.js → dropdown-menu-CeaOsjWZ.js} +2 -2
  26. package/dist/{ebnfDiagram-PWID7BFC-Cz0J-tcC.js → ebnfDiagram-PWID7BFC-DpRFrKK-.js} +2 -2
  27. package/dist/{erDiagram-RLTQ6QDP-DTXTDgFu.js → erDiagram-RLTQ6QDP-CqAU4b0u.js} +1 -1
  28. package/dist/{error-entries-CZ1NQ6Kv.js → error-entries-CYGwr5-O.js} +5596 -6012
  29. package/dist/feedback-button-DzFFn-n4.js +374 -0
  30. package/dist/{flowDiagram-HODETNUW-RbwWf2zc.js → flowDiagram-HODETNUW-i8-Gw8HO.js} +2 -2
  31. package/dist/{formats-DbXnijtH.js → formats-BtoHHy42.js} +1 -1
  32. package/dist/{glide-data-editor-CvRiA49N.js → glide-data-editor-DFsqVbWR.js} +5 -5
  33. package/dist/{globals-BR4CmGOS.js → globals-Dr0MRANu.js} +2 -2
  34. package/dist/{html-to-image-D_4_hQ8U.js → html-to-image-Cy5UDpQJ.js} +56 -56
  35. package/dist/{infoDiagram-27XIBGKW-DlmzZjmI.js → infoDiagram-27XIBGKW-DpfOKdLL.js} +1 -1
  36. package/dist/{ishikawaDiagram-5VMMS53U-EN3TYP_q.js → ishikawaDiagram-5VMMS53U-Dg7mrMG7.js} +1 -1
  37. package/dist/{kanban-definition-UXKFOSKX-CK_-Ru7Q.js → kanban-definition-UXKFOSKX-QB6e3Jrj.js} +1 -1
  38. package/dist/main.js +5875 -5876
  39. package/dist/{mermaid-DU9ZRmkG.js → mermaid-CeWG4Egr.js} +48 -48
  40. package/dist/{mindmap-definition-YA3MSWOX-C-hqgUvF.js → mindmap-definition-YA3MSWOX-BTz3uTGe.js} +1 -1
  41. package/dist/{number-overlay-editor-DiYGbxNa.js → number-overlay-editor-DupOSYHx.js} +1 -1
  42. package/dist/{once-eG8Vmvjd.js → once-DWGO2mQ2.js} +1 -1
  43. package/dist/{pegDiagram-XKGWAZYB-BiMejYPB.js → pegDiagram-XKGWAZYB-3ZIEa3mB.js} +2 -2
  44. package/dist/{pieDiagram-E7YTZNPT-B1zCZ1eq.js → pieDiagram-E7YTZNPT-BJAlk046.js} +1 -1
  45. package/dist/{process-output-DxSCHBWp.js → process-output-q9MTAIMX.js} +2 -2
  46. package/dist/{railroadDiagram-O6MQD6OU-BLcfaASl.js → railroadDiagram-O6MQD6OU-Dn3bsSRa.js} +2 -2
  47. package/dist/{requirementDiagram-BXWQKSXE-DtBOf52-.js → requirementDiagram-BXWQKSXE-wcOmQQCL.js} +1 -1
  48. package/dist/{reveal-component-D3DsXPIm.js → reveal-component-Bg8rP_Uo.js} +17 -17
  49. package/dist/{sequenceDiagram-WJ2MYXX4-DIwF19_n.js → sequenceDiagram-WJ2MYXX4-DSh_qxoF.js} +1 -1
  50. package/dist/{spec-DZDnnlgT.js → spec-C7Fos-If.js} +1 -1
  51. package/dist/{stateDiagram-D77RDMKH-sWk1AdYL.js → stateDiagram-D77RDMKH-DH_H8NMH.js} +2 -2
  52. package/dist/{stateDiagram-v2-MP3YSRHH-CiNo-OI7.js → stateDiagram-v2-MP3YSRHH-D_JM7MEK.js} +2 -2
  53. package/dist/style.css +1 -1
  54. package/dist/{swimlanes-42K2YHIH-Bj3hiOY5.js → swimlanes-42K2YHIH-CfoTJO-9.js} +1 -1
  55. package/dist/{swimlanesDiagram-VR7AAH4N-BzEo2r_N.js → swimlanesDiagram-VR7AAH4N-Z_QhvK0G.js} +2 -2
  56. package/dist/{swiper-component-BkCm-kLg.js → swiper-component-D2geL1Ku.js} +1 -1
  57. package/dist/{timeline-definition-24CTP7MA-CrPQk42H.js → timeline-definition-24CTP7MA-BFtdScaL.js} +1 -1
  58. package/dist/{toDate-Cu2pIZcC.js → toDate-CQAFSwos.js} +24 -24
  59. package/dist/{useAsyncData-DEhLdRIu.js → useAsyncData-BeyZyjso.js} +1 -1
  60. package/dist/{useDeepCompareMemoize-RjZGVhBV.js → useDeepCompareMemoize-DOQgae0W.js} +1 -1
  61. package/dist/{useIframeCapabilities-CSdWn6NP.js → useIframeCapabilities-DNvaj9wM.js} +1 -1
  62. package/dist/{useLifecycle-DtcB2s1z.js → useLifecycle-n2lnCFS8.js} +1 -1
  63. package/dist/{useTheme-CoEtr4mK.js → useTheme-CY777LXN.js} +303 -99
  64. package/dist/{vega-component-BhjTrP9-.js → vega-component-0nQzA-I8.js} +6 -6
  65. package/dist/{vennDiagram-4TSXK5OY-BaYMoIpj.js → vennDiagram-4TSXK5OY-BXY8IGCA.js} +1 -1
  66. package/dist/{wardleyDiagram-VM6X3IG4-DjHeuj8p.js → wardleyDiagram-VM6X3IG4-D0tFe6eC.js} +1 -1
  67. package/dist/{xychartDiagram-S5SC5T6Z-Bc_hcuXS.js → xychartDiagram-S5SC5T6Z-Dx8wFM-y.js} +1 -1
  68. package/package.json +1 -1
  69. package/src/__mocks__/notebook.ts +0 -1
  70. package/src/__mocks__/partial.ts +26 -0
  71. package/src/__tests__/main.test.tsx +24 -1
  72. package/src/components/app-config/app-config-form.tsx +5 -9
  73. package/src/components/editor/__tests__/data-attributes.test.tsx +0 -1
  74. package/src/components/editor/__tests__/output-persistence.test.tsx +0 -1
  75. package/src/components/editor/actions/useCellActionButton.tsx +6 -5
  76. package/src/components/editor/actions/useConfigActions.tsx +3 -4
  77. package/src/components/editor/actions/useNotebookActions.tsx +5 -4
  78. package/src/components/editor/cell/code/cell-editor.tsx +13 -6
  79. package/src/components/editor/cell/collapse.tsx +6 -1
  80. package/src/components/editor/chrome/components/feedback-button.tsx +1 -7
  81. package/src/components/editor/controls/Controls.tsx +1 -1
  82. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +8 -6
  83. package/src/components/editor/navigation/__tests__/navigation.test.ts +15 -15
  84. package/src/components/editor/navigation/__tests__/selection.test.ts +44 -21
  85. package/src/components/editor/navigation/focus-utils.ts +19 -0
  86. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +6 -13
  87. package/src/components/editor/navigation/navigation.ts +52 -43
  88. package/src/components/editor/navigation/selection.ts +30 -29
  89. package/src/components/editor/notebook-cell.tsx +10 -18
  90. package/src/components/editor/renderers/cell-array.tsx +6 -5
  91. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -1
  92. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +1 -1
  93. package/src/components/pages/edit-page.tsx +0 -13
  94. package/src/components/scratchpad/scratchpad.tsx +0 -1
  95. package/src/components/slides/reveal-component.tsx +1 -1
  96. package/src/components/slides/slide-cell-view.tsx +0 -1
  97. package/src/components/sort/SortableCellsProvider.tsx +6 -5
  98. package/src/core/MarimoApp.tsx +1 -1
  99. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +0 -37
  100. package/src/core/cells/__tests__/actions.test.ts +190 -80
  101. package/src/core/cells/__tests__/arrange.test.ts +263 -0
  102. package/src/core/cells/__tests__/cells.test.ts +11 -1101
  103. package/src/core/cells/__tests__/document.test.ts +3 -32
  104. package/src/core/cells/__tests__/editor.test.ts +57 -0
  105. package/src/core/cells/__tests__/harness.ts +82 -0
  106. package/src/core/cells/__tests__/layout.test.ts +189 -0
  107. package/src/core/cells/__tests__/notebook-outline.test.ts +7 -1
  108. package/src/core/cells/__tests__/session.test.ts +329 -174
  109. package/src/core/cells/__tests__/utils.test.ts +0 -1
  110. package/src/core/cells/actions.ts +247 -82
  111. package/src/core/cells/arrange.ts +341 -0
  112. package/src/core/cells/cells.ts +27 -516
  113. package/src/core/cells/document.ts +6 -5
  114. package/src/core/cells/edit.ts +30 -0
  115. package/src/core/cells/editor.ts +82 -0
  116. package/src/core/cells/layout.ts +152 -0
  117. package/src/core/cells/session.ts +75 -52
  118. package/src/core/cells/types.ts +0 -5
  119. package/src/core/cells/utils.ts +6 -4
  120. package/src/core/config/__tests__/app-config.test.ts +64 -0
  121. package/src/core/config/__tests__/config-schema.test.ts +1 -1
  122. package/src/core/config/config-schema.ts +6 -2
  123. package/src/core/config/config.ts +26 -12
  124. package/src/core/islands/bootstrap.ts +0 -1
  125. package/src/core/kernel/handlers.ts +1 -13
  126. package/src/core/meta/__tests__/code-visibility.test.tsx +78 -70
  127. package/src/core/meta/code-visibility.ts +18 -9
  128. package/src/core/notebook/__tests__/operations.test.ts +51 -0
  129. package/src/core/notebook/document/__tests__/codemirror.test.ts +15 -0
  130. package/src/core/notebook/index.ts +2 -1
  131. package/src/core/notebook/operations.ts +30 -0
  132. package/src/core/saving/__tests__/state.test.ts +111 -0
  133. package/src/core/saving/save-component.tsx +19 -33
  134. package/src/core/saving/state.ts +38 -39
  135. package/src/core/saving/useAutoSave.ts +8 -35
  136. package/src/core/session/__tests__/backend.test.ts +1 -0
  137. package/src/core/session/__tests__/useNotebookReplica.hook.test.tsx +1 -0
  138. package/src/core/session/active-notebook.ts +11 -0
  139. package/src/core/session/backend.ts +17 -19
  140. package/src/core/session/embedded.ts +7 -0
  141. package/src/core/session/useNotebookReplica.ts +3 -1
  142. package/src/core/session/useNotebookSession.tsx +7 -8
  143. package/src/core/transports/__tests__/static.test.ts +139 -8
  144. package/src/core/transports/static.ts +98 -16
  145. package/src/core/wasm/__tests__/bridge.test.ts +1 -0
  146. package/src/mount.tsx +19 -10
  147. package/src/stories/cell.stories.tsx +0 -2
  148. package/src/stories/layout/vertical/one-column.stories.tsx +0 -2
  149. package/src/utils/__tests__/id-tree.test.ts +20 -1548
  150. package/src/utils/id-tree.tsx +12 -537
  151. package/dist/assets/__vite-browser-external-C8gQho07.js +0 -1
  152. package/dist/feedback-button-CDf1yRm_.js +0 -379
  153. package/src/components/editor/navigation/__tests__/state.test.ts +0 -81
  154. package/src/components/editor/navigation/state.ts +0 -65
@@ -6,11 +6,11 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
6
6
  import { Mocks } from "@/__mocks__/common";
7
7
  import { cellId } from "@/__tests__/branded";
8
8
  import { parseOutline } from "@/core/dom/outline";
9
- import { MultiColumn, visibleForTesting } from "@/utils/id-tree";
9
+ import { visibleForTesting } from "@/utils/id-tree";
10
10
  import { invariant } from "@/utils/invariant";
11
11
  import { Logger } from "@/utils/Logger";
12
12
  import { SETUP_CELL_ID } from "../ids";
13
- import { notebookStateFromSession } from "../session";
13
+ import { readExport } from "../session";
14
14
 
15
15
  // Mock dependencies
16
16
  vi.mock("@/utils/Logger", () => ({ Logger: Mocks.quietLogger() }));
@@ -25,7 +25,14 @@ type NotebookCell = api.Notebook["NotebookV1"]["cells"][0];
25
25
  // Test constants
26
26
  const CELL_1 = cellId("cell-1");
27
27
 
28
- describe("notebookStateFromSession", () => {
28
+ /** The snapshot's record of a cell. */
29
+ function cellIn(result: ReturnType<typeof readExport>, id: string) {
30
+ const cell = result.snapshot?.cells.find((cell) => cell.id === id);
31
+ invariant(cell, `no cell ${id}`);
32
+ return cell;
33
+ }
34
+
35
+ describe("readExport", () => {
29
36
  beforeEach(() => {
30
37
  vi.clearAllMocks();
31
38
  visibleForTesting.reset();
@@ -88,72 +95,113 @@ describe("notebookStateFromSession", () => {
88
95
 
89
96
  describe("validation", () => {
90
97
  it("logs error for both session and notebook null/undefined", () => {
91
- const result = notebookStateFromSession(null, null);
92
- expect(result).toBeNull();
98
+ const result = readExport({
99
+ session: null,
100
+ notebook: null,
101
+ filename: null,
102
+ appConfig: {},
103
+ });
104
+ expect(result.snapshot).toBeNull();
93
105
  });
94
106
 
95
107
  it("logs error when session and notebook have different cells", () => {
96
108
  const session = createSession([createSessionCell("cell-1")]);
97
109
  const notebook = createNotebook([createNotebookCell("cell-2", {})]);
98
110
 
99
- const result = notebookStateFromSession(session, notebook);
111
+ const result = readExport({
112
+ session: session,
113
+ notebook: notebook,
114
+ filename: null,
115
+ appConfig: {},
116
+ });
100
117
 
101
118
  expect(Logger.warn).toHaveBeenCalledWith(
102
119
  "Session and notebook have different cells, attempted merge.",
103
120
  );
104
121
  // Should have the same cell structure as notebook
105
- expect(result).not.toBeNull();
106
- invariant(result, "result is null");
107
- expect(result.cellIds.inOrderIds).toEqual(
108
- MultiColumn.from([["cell-2"]]).inOrderIds,
109
- );
122
+ invariant(result.snapshot, "no snapshot");
123
+ expect(result.snapshot.document.order).toEqual(["cell-2"]);
110
124
  });
111
125
  });
112
126
 
113
127
  describe("null/undefined inputs", () => {
114
128
  it("returns null when both session and notebook are null", () => {
115
- const result = notebookStateFromSession(null, null);
116
- expect(result).toBeNull();
129
+ const result = readExport({
130
+ session: null,
131
+ notebook: null,
132
+ filename: null,
133
+ appConfig: {},
134
+ });
135
+ expect(result.snapshot).toBeNull();
117
136
  });
118
137
 
119
138
  it("returns null when both session and notebook are undefined", () => {
120
- const result = notebookStateFromSession(undefined, undefined);
121
- expect(result).toBeNull();
139
+ const result = readExport({
140
+ session: undefined,
141
+ notebook: undefined,
142
+ filename: null,
143
+ appConfig: {},
144
+ });
145
+ expect(result.snapshot).toBeNull();
122
146
  });
123
147
 
124
148
  it("returns null when session is null and notebook is undefined", () => {
125
- const result = notebookStateFromSession(null, undefined);
126
- expect(result).toBeNull();
149
+ const result = readExport({
150
+ session: null,
151
+ notebook: undefined,
152
+ filename: null,
153
+ appConfig: {},
154
+ });
155
+ expect(result.snapshot).toBeNull();
127
156
  });
128
157
  });
129
158
 
130
159
  describe("session only scenarios", () => {
131
160
  it("creates state from session with single cell", () => {
132
161
  const session = createSession([createSessionCell("cell-1")]);
133
- const result = notebookStateFromSession(session, null);
162
+ const result = readExport({
163
+ session: session,
164
+ notebook: null,
165
+ filename: null,
166
+ appConfig: {},
167
+ });
134
168
 
135
- expect(result).not.toBeNull();
136
- invariant(result, "result is null");
137
- expect(result.cellIds.inOrderIds).toEqual(
138
- MultiColumn.from([[CELL_1]]).inOrderIds,
139
- );
140
- expect(result.cellData[CELL_1].code).toBe("");
169
+ invariant(result.snapshot, "no snapshot");
170
+ expect(result.snapshot.document.order).toEqual([CELL_1]);
171
+ // The session knows outputs, not source; the host withholds it as null.
172
+ expect(cellIn(result, CELL_1).code).toBeNull();
141
173
  expect(result.cellRuntime[CELL_1]).toBeDefined();
142
174
  });
143
175
 
176
+ it("carries the page's filename and app config into the snapshot", () => {
177
+ const session = createSession([createSessionCell("cell-1")]);
178
+ const result = readExport({
179
+ session,
180
+ notebook: null,
181
+ filename: "notebook.py",
182
+ appConfig: { width: "full" },
183
+ });
184
+
185
+ invariant(result.snapshot, "no snapshot");
186
+ expect(result.snapshot.notebook.name).toBe("notebook.py");
187
+ expect(result.snapshot.document.app_config).toEqual({ width: "full" });
188
+ });
189
+
144
190
  it("creates state from session with multiple cells", () => {
145
191
  const session = createSession([
146
192
  createSessionCell("cell-1"),
147
193
  createSessionCell("cell-2"),
148
194
  ]);
149
- const result = notebookStateFromSession(session, null);
195
+ const result = readExport({
196
+ session: session,
197
+ notebook: null,
198
+ filename: null,
199
+ appConfig: {},
200
+ });
150
201
 
151
- expect(result).not.toBeNull();
152
- invariant(result, "result is null");
153
- expect(result.cellIds.inOrderIds).toEqual(
154
- MultiColumn.from([["cell-1", "cell-2"]]).inOrderIds,
155
- );
156
- expect(Object.keys((result as any).cellData)).toEqual([
202
+ invariant(result.snapshot, "no snapshot");
203
+ expect(result.snapshot.document.order).toEqual(["cell-1", "cell-2"]);
204
+ expect(result.snapshot.cells.map((cell) => cell.id)).toEqual([
157
205
  "cell-1",
158
206
  "cell-2",
159
207
  ]);
@@ -173,10 +221,14 @@ describe("notebookStateFromSession", () => {
173
221
  const session = createSession([
174
222
  createSessionCell("cell-1", [errorOutput]),
175
223
  ]);
176
- const result = notebookStateFromSession(session, null);
224
+ const result = readExport({
225
+ session: session,
226
+ notebook: null,
227
+ filename: null,
228
+ appConfig: {},
229
+ });
177
230
 
178
- expect(result).not.toBeNull();
179
- invariant(result, "result is null");
231
+ invariant(result.snapshot, "no snapshot");
180
232
  expect(result.cellRuntime[CELL_1].output).toEqual({
181
233
  channel: "marimo-error",
182
234
  data: [
@@ -219,8 +271,13 @@ describe("notebookStateFromSession", () => {
219
271
  ],
220
272
  ),
221
273
  ]);
222
- const state = notebookStateFromSession(session, null);
223
- invariant(state, "state is null");
274
+ const state = readExport({
275
+ session: session,
276
+ notebook: null,
277
+ filename: null,
278
+ appConfig: {},
279
+ });
280
+ invariant(state.snapshot, "no snapshot");
224
281
  expect(state.cellRuntime[CELL_1].output).toMatchObject({
225
282
  code,
226
283
  data: [
@@ -249,10 +306,14 @@ describe("notebookStateFromSession", () => {
249
306
  const session = createSession([
250
307
  createSessionCell("cell-1", [dataOutput]),
251
308
  ]);
252
- const result = notebookStateFromSession(session, null);
309
+ const result = readExport({
310
+ session: session,
311
+ notebook: null,
312
+ filename: null,
313
+ appConfig: {},
314
+ });
253
315
 
254
- expect(result).not.toBeNull();
255
- invariant(result, "result is null");
316
+ invariant(result.snapshot, "no snapshot");
256
317
  expect(result.cellRuntime[CELL_1].output).toEqual({
257
318
  channel: "output",
258
319
  data: "Hello World",
@@ -279,10 +340,14 @@ describe("notebookStateFromSession", () => {
279
340
  const session = createSession([
280
341
  createSessionCell("cell-1", [], consoleOutputs),
281
342
  ]);
282
- const result = notebookStateFromSession(session, null);
343
+ const result = readExport({
344
+ session: session,
345
+ notebook: null,
346
+ filename: null,
347
+ appConfig: {},
348
+ });
283
349
 
284
- expect(result).not.toBeNull();
285
- invariant(result, "result is null");
350
+ invariant(result.snapshot, "no snapshot");
286
351
  expect(result.cellRuntime[CELL_1].consoleOutputs).toEqual([
287
352
  {
288
353
  channel: "stdout",
@@ -310,7 +375,12 @@ describe("notebookStateFromSession", () => {
310
375
  const session = createSession([
311
376
  createSessionCell("cell-1", [dataOutput]),
312
377
  ]);
313
- const result = notebookStateFromSession(session, null);
378
+ const result = readExport({
379
+ session: session,
380
+ notebook: null,
381
+ filename: null,
382
+ appConfig: {},
383
+ });
314
384
 
315
385
  expect(parseOutline).toHaveBeenCalledWith({
316
386
  channel: "output",
@@ -318,16 +388,21 @@ describe("notebookStateFromSession", () => {
318
388
  mimetype: "text/html",
319
389
  timestamp: 0,
320
390
  });
321
- invariant(result, "result is null");
391
+ invariant(result.snapshot, "no snapshot");
322
392
  expect(result.cellRuntime[CELL_1].outline).toBe(mockOutline);
323
393
  });
324
394
 
325
395
  it("sets outline to null when no output exists", () => {
326
396
  const session = createSession([createSessionCell("cell-1")]);
327
- const result = notebookStateFromSession(session, null);
397
+ const result = readExport({
398
+ session: session,
399
+ notebook: null,
400
+ filename: null,
401
+ appConfig: {},
402
+ });
328
403
 
329
404
  expect(parseOutline).not.toHaveBeenCalled();
330
- invariant(result, "result is null");
405
+ invariant(result.snapshot, "no snapshot");
331
406
  expect(result.cellRuntime[CELL_1].outline).toBeNull();
332
407
  });
333
408
  });
@@ -340,27 +415,29 @@ describe("notebookStateFromSession", () => {
340
415
  name: "my_cell",
341
416
  }),
342
417
  ]);
343
- const result = notebookStateFromSession(null, notebook);
418
+ const result = readExport({
419
+ session: null,
420
+ notebook: notebook,
421
+ filename: null,
422
+ appConfig: {},
423
+ });
344
424
 
345
- expect(result).not.toBeNull();
346
- invariant(result, "result is null");
347
- expect(result.cellIds.inOrderIds).toEqual(
348
- MultiColumn.from([[CELL_1]]).inOrderIds,
349
- );
350
- expect(result.cellData[CELL_1]).toEqual({
351
- id: "cell-1",
425
+ invariant(result.snapshot, "no snapshot");
426
+ expect(result.snapshot.document.order).toEqual([CELL_1]);
427
+ expect(cellIn(result, CELL_1)).toEqual({
428
+ id: CELL_1,
429
+ notebook_id: "embedded",
352
430
  name: "my_cell",
353
431
  code: "print('hello')",
354
- edited: false,
355
- lastCodeRun: null,
356
- lastExecutionTime: null,
357
432
  config: {
358
433
  hide_code: false,
359
434
  disabled: false,
360
435
  column: null,
361
436
  expand_output: false,
362
437
  },
363
- serializedEditorState: null,
438
+ etag: `embedded:${CELL_1}`,
439
+ code_hash: `embedded:${CELL_1}:code`,
440
+ version: 1,
364
441
  });
365
442
  });
366
443
 
@@ -377,11 +454,15 @@ describe("notebookStateFromSession", () => {
377
454
  },
378
455
  }),
379
456
  ]);
380
- const result = notebookStateFromSession(null, notebook);
457
+ const result = readExport({
458
+ session: null,
459
+ notebook: notebook,
460
+ filename: null,
461
+ appConfig: {},
462
+ });
381
463
 
382
- expect(result).not.toBeNull();
383
- invariant(result, "result is null");
384
- expect(result.cellData[CELL_1].config).toEqual({
464
+ invariant(result.snapshot, "no snapshot");
465
+ expect(cellIn(result, CELL_1).config).toEqual({
385
466
  hide_code: true,
386
467
  disabled: true,
387
468
  column: 1,
@@ -402,24 +483,28 @@ describe("notebookStateFromSession", () => {
402
483
  },
403
484
  };
404
485
  const notebook = createNotebook([notebookCell]);
405
- const result = notebookStateFromSession(null, notebook);
486
+ const result = readExport({
487
+ session: null,
488
+ notebook: notebook,
489
+ filename: null,
490
+ appConfig: {},
491
+ });
406
492
 
407
- expect(result).not.toBeNull();
408
- invariant(result, "result is null");
409
- expect(result.cellData[CELL_1]).toEqual({
410
- id: "cell-1",
411
- name: "",
412
- code: "",
413
- edited: false,
414
- lastCodeRun: null,
415
- lastExecutionTime: null,
493
+ invariant(result.snapshot, "no snapshot");
494
+ expect(cellIn(result, CELL_1)).toEqual({
495
+ id: CELL_1,
496
+ notebook_id: "embedded",
497
+ name: "_",
498
+ code: null,
416
499
  config: {
417
500
  hide_code: false,
418
501
  disabled: false,
419
502
  column: null,
420
503
  expand_output: false,
421
504
  },
422
- serializedEditorState: null,
505
+ etag: `embedded:${CELL_1}`,
506
+ code_hash: `embedded:${CELL_1}:code`,
507
+ version: 1,
423
508
  });
424
509
  });
425
510
 
@@ -436,27 +521,29 @@ describe("notebookStateFromSession", () => {
436
521
  },
437
522
  };
438
523
  const notebook = createNotebook([notebookCell as any]);
439
- const result = notebookStateFromSession(null, notebook);
524
+ const result = readExport({
525
+ session: null,
526
+ notebook: notebook,
527
+ filename: null,
528
+ appConfig: {},
529
+ });
440
530
 
441
- expect(result).not.toBeNull();
442
- invariant(result, "result is null");
443
- expect(result.cellIds.inOrderIds).toEqual(
444
- MultiColumn.from([[SETUP_CELL_ID]]).inOrderIds,
445
- );
446
- expect(result.cellData[SETUP_CELL_ID]).toEqual({
531
+ invariant(result.snapshot, "no snapshot");
532
+ expect(result.snapshot.document.order).toEqual([SETUP_CELL_ID]);
533
+ expect(cellIn(result, SETUP_CELL_ID)).toEqual({
447
534
  id: SETUP_CELL_ID,
535
+ notebook_id: "embedded",
448
536
  name: "setup",
449
537
  code: "import marimo as mo",
450
- edited: false,
451
- lastCodeRun: null,
452
- lastExecutionTime: null,
453
538
  config: {
454
539
  hide_code: false,
455
540
  disabled: false,
456
541
  column: null,
457
542
  expand_output: false,
458
543
  },
459
- serializedEditorState: null,
544
+ etag: `embedded:${SETUP_CELL_ID}`,
545
+ code_hash: `embedded:${SETUP_CELL_ID}:code`,
546
+ version: 1,
460
547
  });
461
548
  });
462
549
  });
@@ -483,24 +570,28 @@ describe("notebookStateFromSession", () => {
483
570
  name: "my_cell",
484
571
  }),
485
572
  ]);
486
- const result = notebookStateFromSession(session, notebook);
573
+ const result = readExport({
574
+ session: session,
575
+ notebook: notebook,
576
+ filename: null,
577
+ appConfig: {},
578
+ });
487
579
 
488
- expect(result).not.toBeNull();
489
- invariant(result, "result is null");
490
- expect(result.cellData[CELL_1]).toEqual({
491
- id: "cell-1",
580
+ invariant(result.snapshot, "no snapshot");
581
+ expect(cellIn(result, CELL_1)).toEqual({
582
+ id: CELL_1,
583
+ notebook_id: "embedded",
492
584
  name: "my_cell",
493
585
  code: "print('hello')",
494
- edited: false,
495
- lastCodeRun: null,
496
- lastExecutionTime: null,
497
586
  config: {
498
587
  hide_code: false,
499
588
  disabled: false,
500
589
  column: null,
501
590
  expand_output: false,
502
591
  },
503
- serializedEditorState: null,
592
+ etag: `embedded:${CELL_1}`,
593
+ code_hash: `embedded:${CELL_1}:code`,
594
+ version: 1,
504
595
  });
505
596
  // Null result means no output.
506
597
  expect(result.cellRuntime[CELL_1].consoleOutputs).toEqual([]);
@@ -516,13 +607,15 @@ describe("notebookStateFromSession", () => {
516
607
  createNotebookCell("cell-1", {}),
517
608
  ]);
518
609
 
519
- const result = notebookStateFromSession(session, notebook);
610
+ const result = readExport({
611
+ session: session,
612
+ notebook: notebook,
613
+ filename: null,
614
+ appConfig: {},
615
+ });
520
616
 
521
- expect(result).not.toBeNull();
522
- invariant(result, "result is null");
523
- expect(result.cellIds.inOrderIds).toEqual(
524
- MultiColumn.from([["cell-2", "cell-1"]]).inOrderIds,
525
- );
617
+ invariant(result.snapshot, "no snapshot");
618
+ expect(result.snapshot.document.order).toEqual(["cell-2", "cell-1"]);
526
619
  });
527
620
 
528
621
  it("creates state when session and notebook have identical cell arrays", () => {
@@ -535,48 +628,69 @@ describe("notebookStateFromSession", () => {
535
628
  createNotebookCell("cell-2", {}),
536
629
  ]);
537
630
 
538
- const result = notebookStateFromSession(session, notebook);
631
+ const result = readExport({
632
+ session: session,
633
+ notebook: notebook,
634
+ filename: null,
635
+ appConfig: {},
636
+ });
539
637
 
540
638
  expect(Logger.error).not.toHaveBeenCalled();
541
- expect(result).not.toBeNull();
542
- invariant(result, "result is null");
543
- expect(result.cellIds.inOrderIds).toEqual(
544
- MultiColumn.from([["cell-1", "cell-2"]]).inOrderIds,
545
- );
639
+ invariant(result.snapshot, "no snapshot");
640
+ expect(result.snapshot.document.order).toEqual(["cell-1", "cell-2"]);
546
641
  });
547
642
  });
548
643
 
549
644
  describe("empty cells scenarios", () => {
550
645
  it("warns and returns null when session has empty cells array", () => {
551
646
  const session = createSession([]);
552
- const result = notebookStateFromSession(session, null);
647
+ const result = readExport({
648
+ session: session,
649
+ notebook: null,
650
+ filename: null,
651
+ appConfig: {},
652
+ });
553
653
 
554
- expect(result).toBeNull();
654
+ expect(result.snapshot).toBeNull();
555
655
  });
556
656
 
557
657
  it("warns and returns null when notebook has empty cells array", () => {
558
658
  const notebook = createNotebook([]);
559
- const result = notebookStateFromSession(null, notebook);
659
+ const result = readExport({
660
+ session: null,
661
+ notebook: notebook,
662
+ filename: null,
663
+ appConfig: {},
664
+ });
560
665
 
561
- expect(result).toBeNull();
666
+ expect(result.snapshot).toBeNull();
562
667
  });
563
668
 
564
669
  it("warns and returns null when both have empty cells arrays", () => {
565
670
  const session = createSession([]);
566
671
  const notebook = createNotebook([]);
567
- const result = notebookStateFromSession(session, notebook);
672
+ const result = readExport({
673
+ session: session,
674
+ notebook: notebook,
675
+ filename: null,
676
+ appConfig: {},
677
+ });
568
678
 
569
- expect(result).toBeNull();
679
+ expect(result.snapshot).toBeNull();
570
680
  });
571
681
  });
572
682
 
573
683
  describe("edge cases", () => {
574
684
  it("handles session cell with no outputs", () => {
575
685
  const session = createSession([createSessionCell("cell-1", [])]);
576
- const result = notebookStateFromSession(session, null);
686
+ const result = readExport({
687
+ session: session,
688
+ notebook: null,
689
+ filename: null,
690
+ appConfig: {},
691
+ });
577
692
 
578
- expect(result).not.toBeNull();
579
- invariant(result, "result is null");
693
+ invariant(result.snapshot, "no snapshot");
580
694
  expect(result.cellRuntime[CELL_1].output).toBeNull();
581
695
  expect(result.cellRuntime[CELL_1].consoleOutputs).toEqual([]);
582
696
  expect(result.cellRuntime[CELL_1].outline).toBeNull();
@@ -584,10 +698,14 @@ describe("notebookStateFromSession", () => {
584
698
 
585
699
  it("handles session cell with empty console array", () => {
586
700
  const session = createSession([createSessionCell("cell-1", [], [])]);
587
- const result = notebookStateFromSession(session, null);
701
+ const result = readExport({
702
+ session: session,
703
+ notebook: null,
704
+ filename: null,
705
+ appConfig: {},
706
+ });
588
707
 
589
- expect(result).not.toBeNull();
590
- invariant(result, "result is null");
708
+ invariant(result.snapshot, "no snapshot");
591
709
  expect(result.cellRuntime[CELL_1].consoleOutputs).toEqual([]);
592
710
  });
593
711
 
@@ -597,10 +715,14 @@ describe("notebookStateFromSession", () => {
597
715
  { type: "data" as const, data: { "text/plain": "Second" } },
598
716
  ];
599
717
  const session = createSession([createSessionCell("cell-1", outputs)]);
600
- const result = notebookStateFromSession(session, null);
718
+ const result = readExport({
719
+ session: session,
720
+ notebook: null,
721
+ filename: null,
722
+ appConfig: {},
723
+ });
601
724
 
602
- expect(result).not.toBeNull();
603
- invariant(result, "result is null");
725
+ invariant(result.snapshot, "no snapshot");
604
726
  expect(result.cellRuntime[CELL_1].output).toEqual({
605
727
  channel: "output",
606
728
  data: "First",
@@ -620,10 +742,14 @@ describe("notebookStateFromSession", () => {
620
742
  const session = createSession([
621
743
  createSessionCell("cell-1", [dataOutput]),
622
744
  ]);
623
- const result = notebookStateFromSession(session, null);
745
+ const result = readExport({
746
+ session: session,
747
+ notebook: null,
748
+ filename: null,
749
+ appConfig: {},
750
+ });
624
751
 
625
- expect(result).not.toBeNull();
626
- invariant(result, "result is null");
752
+ invariant(result.snapshot, "no snapshot");
627
753
  expect(result.cellRuntime[CELL_1].output).toEqual({
628
754
  channel: "output",
629
755
  data: "Plain text",
@@ -640,10 +766,14 @@ describe("notebookStateFromSession", () => {
640
766
  const session = createSession([
641
767
  createSessionCell("cell-1", [dataOutput]),
642
768
  ]);
643
- const result = notebookStateFromSession(session, null);
769
+ const result = readExport({
770
+ session: session,
771
+ notebook: null,
772
+ filename: null,
773
+ appConfig: {},
774
+ });
644
775
 
645
- expect(result).not.toBeNull();
646
- invariant(result, "result is null");
776
+ invariant(result.snapshot, "no snapshot");
647
777
  expect(result.cellRuntime[CELL_1].output).toEqual({
648
778
  channel: "output",
649
779
  data: undefined,
@@ -654,22 +784,29 @@ describe("notebookStateFromSession", () => {
654
784
 
655
785
  it("returns correct structure with all expected properties", () => {
656
786
  const session = createSession([createSessionCell("cell-1")]);
657
- const result = notebookStateFromSession(session, null);
658
-
659
- expect(result).not.toBeNull();
660
- invariant(result, "result is null");
661
- expect(result).toHaveProperty("cellIds");
662
- expect(result).toHaveProperty("cellData");
663
- expect(result).toHaveProperty("cellRuntime");
664
- expect(result).toHaveProperty("cellHandles");
665
- expect(result).toHaveProperty("history");
666
- expect(result).toHaveProperty("scrollKey");
667
- expect(result).toHaveProperty("cellLogs");
668
-
669
- expect(result.cellHandles).toEqual({});
670
- expect(result.history).toEqual([]);
671
- expect(result.scrollKey).toBeNull();
672
- expect(result.cellLogs).toEqual([]);
787
+ const result = readExport({
788
+ session: session,
789
+ notebook: null,
790
+ filename: null,
791
+ appConfig: {},
792
+ });
793
+
794
+ invariant(result.snapshot, "no snapshot");
795
+ expect(result.snapshot.notebook).toEqual({
796
+ id: "embedded",
797
+ project_id: "embedded",
798
+ path: null,
799
+ name: "",
800
+ updated_at: null,
801
+ });
802
+ expect(result.snapshot.document).toMatchObject({
803
+ notebook_id: "embedded",
804
+ order: result.snapshot.cells.map((cell) => cell.id),
805
+ version: 1,
806
+ });
807
+ expect(Object.keys(result.cellRuntime)).toEqual(
808
+ result.snapshot.document.order,
809
+ );
673
810
  });
674
811
  });
675
812
 
@@ -764,19 +901,26 @@ describe("notebookStateFromSession", () => {
764
901
  ];
765
902
  const session = createSession(sessionCells);
766
903
  const notebook = createNotebook(notebookCells);
767
- const result = notebookStateFromSession(session, notebook);
768
- expect(result).not.toBeNull();
769
- invariant(result, "result is null");
904
+ const result = readExport({
905
+ session: session,
906
+ notebook: notebook,
907
+ filename: null,
908
+ appConfig: {},
909
+ });
910
+ invariant(result.snapshot, "no snapshot");
770
911
  // Should have all cell IDs in order
771
- expect(result.cellIds.inOrderIds).toEqual(
772
- MultiColumn.from([
773
- ["cell-a", "cell-b", "cell-c", "cell-d", "cell-e", "cell-f"],
774
- ]).inOrderIds,
775
- );
912
+ expect(result.snapshot.document.order).toEqual([
913
+ "cell-a",
914
+ "cell-b",
915
+ "cell-c",
916
+ "cell-d",
917
+ "cell-e",
918
+ "cell-f",
919
+ ]);
776
920
  // Should have correct code and output for each cell
777
921
  for (const code of ["a", "b", "c", "d", "e", "f"]) {
778
922
  const cid = cellId(`cell-${code}`);
779
- expect(result.cellData[cid].code).toBe(code);
923
+ expect(cellIn(result, cid).code).toBe(code);
780
924
  expect(result.cellRuntime[cid].output).toEqual({
781
925
  channel: "output",
782
926
  data: `${code.toUpperCase()}!`,
@@ -868,23 +1012,30 @@ describe("notebookStateFromSession", () => {
868
1012
 
869
1013
  const session = createSession(sessionCells);
870
1014
  const notebook = createNotebook(notebookCells);
871
- const result = notebookStateFromSession(session, notebook);
1015
+ const result = readExport({
1016
+ session: session,
1017
+ notebook: notebook,
1018
+ filename: null,
1019
+ appConfig: {},
1020
+ });
872
1021
 
873
- expect(result).not.toBeNull();
874
- invariant(result, "result is null");
1022
+ invariant(result.snapshot, "no snapshot");
875
1023
 
876
1024
  // Should have notebook cell IDs in order (notebook is canonical)
877
- expect(result.cellIds.inOrderIds).toEqual(
878
- MultiColumn.from([["cell-a", "cell-c", "cell-z", "cell-e", "cell-g"]])
879
- .inOrderIds,
880
- );
1025
+ expect(result.snapshot.document.order).toEqual([
1026
+ "cell-a",
1027
+ "cell-c",
1028
+ "cell-z",
1029
+ "cell-e",
1030
+ "cell-g",
1031
+ ]);
881
1032
 
882
1033
  // Should have correct code for each cell
883
- expect(result.cellData[cellId("cell-a")].code).toBe("a");
884
- expect(result.cellData[cellId("cell-c")].code).toBe("c");
885
- expect(result.cellData[cellId("cell-z")].code).toBe("z");
886
- expect(result.cellData[cellId("cell-e")].code).toBe("e");
887
- expect(result.cellData[cellId("cell-g")].code).toBe("g");
1034
+ expect(cellIn(result, "cell-a").code).toBe("a");
1035
+ expect(cellIn(result, "cell-c").code).toBe("c");
1036
+ expect(cellIn(result, "cell-z").code).toBe("z");
1037
+ expect(cellIn(result, "cell-e").code).toBe("e");
1038
+ expect(cellIn(result, "cell-g").code).toBe("g");
888
1039
 
889
1040
  // Should have session outputs for matching cells (a, c, e)
890
1041
  expect(result.cellRuntime[cellId("cell-a")].output).toEqual({
@@ -1002,22 +1153,26 @@ describe("notebookStateFromSession", () => {
1002
1153
 
1003
1154
  const session = createSession(sessionCells);
1004
1155
  const notebook = createNotebook(notebookCells);
1005
- const result = notebookStateFromSession(session, notebook);
1156
+ const result = readExport({
1157
+ session: session,
1158
+ notebook: notebook,
1159
+ filename: null,
1160
+ appConfig: {},
1161
+ });
1006
1162
 
1007
- expect(result).not.toBeNull();
1008
- invariant(result, "result is null");
1163
+ invariant(result.snapshot, "no snapshot");
1009
1164
 
1010
1165
  // Should have notebook cell IDs in order (notebook is canonical)
1011
- expect(result.cellIds.inOrderIds).toEqual(
1012
- MultiColumn.from([["cell-1", "cell-2", "cell-3"]]).inOrderIds,
1013
- );
1166
+ expect(result.snapshot.document.order).toEqual([
1167
+ "cell-1",
1168
+ "cell-2",
1169
+ "cell-3",
1170
+ ]);
1014
1171
 
1015
1172
  // Should have correct code from notebook
1016
- expect(result.cellData[cellId("cell-1")].code).toBe("1 / 0");
1017
- expect(result.cellData[cellId("cell-2")].code).toBe('mo.md("Hello")');
1018
- expect(result.cellData[cellId("cell-3")].code).toBe(
1019
- "x = mo.ui.slider(0, 10)",
1020
- );
1173
+ expect(cellIn(result, "cell-1").code).toBe("1 / 0");
1174
+ expect(cellIn(result, "cell-2").code).toBe('mo.md("Hello")');
1175
+ expect(cellIn(result, "cell-3").code).toBe("x = mo.ui.slider(0, 10)");
1021
1176
 
1022
1177
  // cell-1: No matching session cell (hash is null), gets stub session cell
1023
1178
  expect(result.cellRuntime[cellId("cell-1")].output).toBeNull();