@marimo-team/islands 0.25.3-dev170 → 0.25.3-dev175

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 (196) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BbSXV-Xa.js → ConnectedDataExplorerComponent-Le_OBfmt.js} +9 -9
  2. package/dist/{ErrorBoundary-NE86qEWZ.js → ErrorBoundary-DNvcAIsP.js} +8 -8
  3. package/dist/{abnfDiagram-VCTEODGH-D7n07nXf.js → abnfDiagram-VCTEODGH-Tf1ghyQQ.js} +2 -2
  4. package/dist/{any-language-editor-3E24SnCV.js → any-language-editor-BVFHDDQ3.js} +4 -4
  5. package/dist/{architectureDiagram-5GKGNRK7-D6CjRib5.js → architectureDiagram-5GKGNRK7-xlnbTG0T.js} +1 -1
  6. package/dist/{chat-ui-CC7GekpG.js → chat-ui-X-cWrL9u.js} +1218 -1218
  7. package/dist/{check-SpOMoCLP.js → check-C9Mp9eCK.js} +1 -1
  8. package/dist/{chunk-IMKFNOWR-6dtPz7uS.js → chunk-IMKFNOWR-Cmd4a5jy.js} +1 -1
  9. package/dist/{chunk-SHT3W25Y-BwnFNKDA.js → chunk-SHT3W25Y-mSNp-WpO.js} +1 -1
  10. package/dist/{chunk-SVP7TREG-D2eV4vBJ.js → chunk-SVP7TREG-CWC49D8X.js} +1 -1
  11. package/dist/{chunk-TICWLB2K-BDs4-_-r.js → chunk-TICWLB2K-De9FINtG.js} +1 -1
  12. package/dist/{classDiagram-ZZMXUADV-DCJnf_8v.js → classDiagram-ZZMXUADV-DweTkYdp.js} +2 -2
  13. package/dist/{classDiagram-v2-VYDZK3BY-BOmSzPBD.js → classDiagram-v2-VYDZK3BY-A6MR2TiE.js} +2 -2
  14. package/dist/{code-visibility-Bzvc6ajl.js → code-visibility-CH1lmM1h.js} +5 -5
  15. package/dist/{common-Ciw4zkh2.js → common-DIrS1UR9.js} +1081 -1085
  16. package/dist/{copy-icon-C1trdvVt.js → copy-icon-De584pZ4.js} +2 -2
  17. package/dist/{cynefinDiagram-5FMLGOSQ-Bln_Soky.js → cynefinDiagram-5FMLGOSQ-BSvo5le-.js} +1 -1
  18. package/dist/{dagre-GXQ25YYZ-D-nf7WzW.js → dagre-GXQ25YYZ-tXRQslO6.js} +1 -1
  19. package/dist/{data-grid-overlay-editor-AQREmxI7.js → data-grid-overlay-editor-DFLZvFK0.js} +1 -1
  20. package/dist/{diagram-S7CK7UJ4-PsgSMkY2.js → diagram-S7CK7UJ4-r3t5VtwI.js} +1 -1
  21. package/dist/{diagram-UQ7AKVKN-DfwfUuSw.js → diagram-UQ7AKVKN-qgbC8YnT.js} +1 -1
  22. package/dist/{diagram-VX7I27RA-C9_e-oMc.js → diagram-VX7I27RA-CiHxEwtp.js} +1 -1
  23. package/dist/{diagram-Z3DM3KII-CWLa5tuY.js → diagram-Z3DM3KII-D95u15Ev.js} +1 -1
  24. package/dist/{dialog-DgeqyrEZ.js → dialog-DVRfQuhD.js} +24 -24
  25. package/dist/{dist-F8Ep36rk.js → dist-B1M0bGO3.js} +1 -1
  26. package/dist/{dist-D6d86eNJ.js → dist-BA13RJj5.js} +1 -1
  27. package/dist/{dist-Bvs24R_E.js → dist-BJtmSgcE.js} +48 -48
  28. package/dist/{dist-CN_BeG2I.js → dist-Ba2VIn1K.js} +53 -53
  29. package/dist/{dist-PXYI_yuP.js → dist-Bj4bmwUm.js} +12 -12
  30. package/dist/{dist-DoE4XJBC.js → dist-BmS-ZGjl.js} +8626 -8626
  31. package/dist/{dist-BorMD-uX.js → dist-Bzogh7CW.js} +87 -87
  32. package/dist/{dist-CN8zI-XC.js → dist-CThPVKaP.js} +1385 -1382
  33. package/dist/{dist-DiDibOYr.js → dist-CTi5FTiV.js} +87 -87
  34. package/dist/{dist-diIx5qJW.js → dist-Ck_C32ye.js} +2 -2
  35. package/dist/{dist-BLfKmR-l.js → dist-D1fnFp1J.js} +1 -1
  36. package/dist/{dist-OR1OnN-y.js → dist-D3x9__Fu.js} +46 -46
  37. package/dist/{dist-BMQf5zIS.js → dist-D7mrh5sO.js} +36 -36
  38. package/dist/{dist-CSJ0MDRw.js → dist-D9Q3xjiP.js} +1 -1
  39. package/dist/{dist-BuaRHxOc.js → dist-DP60Ubh8.js} +2 -2
  40. package/dist/{dist-CSbh1PNP.js → dist-DTv9KkpW.js} +12 -12
  41. package/dist/{dist-D-wwqlzJ.js → dist-DVVwOfrD.js} +109 -109
  42. package/dist/{dist-C8fAKSH1.js → dist-DYXM73-k.js} +36 -36
  43. package/dist/{dist-DBtSl3sf.js → dist-DjeybPOb.js} +1 -1
  44. package/dist/{dist-CrSP_H1M.js → dist-DtGn9N4h.js} +197 -197
  45. package/dist/{dist-CLdpGQVr.js → dist-KN80qWCP.js} +2 -2
  46. package/dist/{dist-Cg7gzw-a.js → dist-O0qFpEJh.js} +56 -56
  47. package/dist/{dist-CkTTIdBe.js → dist-OUle2RT7.js} +207 -207
  48. package/dist/{dist-BK4Oj99o.js → dist-T5rh6JvL.js} +1 -1
  49. package/dist/{dropdown-menu-B6ftyzOg.js → dropdown-menu-DCMbbQ35.js} +691 -691
  50. package/dist/{ebnfDiagram-PWID7BFC-B1QRM58v.js → ebnfDiagram-PWID7BFC-P7e4bryK.js} +2 -2
  51. package/dist/{erDiagram-RLTQ6QDP-DJK_FqvW.js → erDiagram-RLTQ6QDP-CUHZcmRj.js} +1 -1
  52. package/dist/{error-banner-D40pmfQQ.js → error-banner-DxJ3s8YO.js} +57 -57
  53. package/dist/{error-entries-DgK3wUJR.js → error-entries-mpoD5t79.js} +4985 -5006
  54. package/dist/{esm-D2gTsXEV.js → esm-Bbw_Njjx.js} +575 -575
  55. package/dist/{esm-AMFssoNp.js → esm-mDQNEFe_.js} +354 -354
  56. package/dist/{feedback-button-8a6SETmT.js → feedback-button-DL0DJ-LB.js} +30 -30
  57. package/dist/{flowDiagram-HODETNUW-Uxc25XSJ.js → flowDiagram-HODETNUW-BGZ95AzI.js} +2 -2
  58. package/dist/{formats-DQCyPd5Z.js → formats-DI7HEVv7.js} +1 -1
  59. package/dist/{glide-data-editor-B70oke_v.js → glide-data-editor-n4wnx19S.js} +10 -10
  60. package/dist/{glide-utils-Dz_F4pAY.js → glide-utils-D98laidb.js} +2 -2
  61. package/dist/{globals-Cxd5hNlI.js → globals-NCsYyjpB.js} +3 -3
  62. package/dist/{html-to-image-D4yG5Ru0.js → html-to-image-BoZthB4P.js} +467 -458
  63. package/dist/{infoDiagram-27XIBGKW-DmjxqwL0.js → infoDiagram-27XIBGKW-BF0YX70D.js} +1 -1
  64. package/dist/{ishikawaDiagram-5VMMS53U-NgCLHqQi.js → ishikawaDiagram-5VMMS53U-_itsAAn-.js} +1 -1
  65. package/dist/{kanban-definition-UXKFOSKX-CZaG-iam.js → kanban-definition-UXKFOSKX-VNW2Beqh.js} +1 -1
  66. package/dist/{label-D9_-zSg-.js → label-Qvw1E97W.js} +1 -1
  67. package/dist/main.js +8257 -6606
  68. package/dist/{mermaid-CSvw7c71.js → mermaid-CZ6FrClS.js} +35 -35
  69. package/dist/{mindmap-definition-YA3MSWOX-DJYQBDH_.js → mindmap-definition-YA3MSWOX-DR4ripvd.js} +1 -1
  70. package/dist/{number-overlay-editor-ChK-SKUk.js → number-overlay-editor-rlvo4mo3.js} +1 -1
  71. package/dist/{pegDiagram-XKGWAZYB-CHKO_dAe.js → pegDiagram-XKGWAZYB-CFr3XlGl.js} +2 -2
  72. package/dist/{pieDiagram-E7YTZNPT-DVevbMAo.js → pieDiagram-E7YTZNPT-j91XYSLY.js} +1 -1
  73. package/dist/{process-output-DxvBAeL3.js → process-output-DLS4Mqg_.js} +4 -4
  74. package/dist/{railroadDiagram-O6MQD6OU-Detj1kcB.js → railroadDiagram-O6MQD6OU-Cd47ZV9E.js} +2 -2
  75. package/dist/{requirementDiagram-BXWQKSXE-BLFD0T_Z.js → requirementDiagram-BXWQKSXE-eJH6gLtX.js} +1 -1
  76. package/dist/{reveal-component-CXMXeDZD.js → reveal-component-BJfmRUzb.js} +9 -9
  77. package/dist/{select-CkF_GBxO.js → select-2KOiReNY.js} +324 -324
  78. package/dist/{sequenceDiagram-WJ2MYXX4-BZBgatQV.js → sequenceDiagram-WJ2MYXX4-DdfFEIrm.js} +1 -1
  79. package/dist/{spec-Mnkn3XEr.js → spec-h69nC3SI.js} +4 -4
  80. package/dist/{stateDiagram-D77RDMKH-4BZW9-6_.js → stateDiagram-D77RDMKH-DkjDvip8.js} +2 -2
  81. package/dist/{stateDiagram-v2-MP3YSRHH-C4sKC8Iq.js → stateDiagram-v2-MP3YSRHH-DBHACnOo.js} +2 -2
  82. package/dist/{swimlanes-42K2YHIH-SL5yeGe2.js → swimlanes-42K2YHIH-ogImlOmp.js} +1 -1
  83. package/dist/{swimlanesDiagram-VR7AAH4N-DqPmAYUv.js → swimlanesDiagram-VR7AAH4N-D6x-zsci.js} +2 -2
  84. package/dist/{timeline-definition-24CTP7MA-BfXUMWkq.js → timeline-definition-24CTP7MA-DQpLtsHD.js} +1 -1
  85. package/dist/{toDate-TMubKIVL.js → toDate-CDzoofJo.js} +2 -2
  86. package/dist/{toast-Btbve3Lm.js → toast-B5t9o6s_.js} +47 -47
  87. package/dist/{tooltip-CJ4Rqasp.js → tooltip-s4zZxwKe.js} +67 -67
  88. package/dist/{use-restore-focus-CmGwRNId.js → use-restore-focus-BgDTd2tc.js} +1 -1
  89. package/dist/{useAsyncData-DjMGFzFy.js → useAsyncData-Bn8SxUqM.js} +1 -1
  90. package/dist/{useDateFormatter-eCL_SdeG.js → useDateFormatter-D3Tkfzej.js} +2 -2
  91. package/dist/{useDeepCompareMemoize-BQR5TJot.js → useDeepCompareMemoize-CC2tnPCk.js} +1 -1
  92. package/dist/{useLifecycle-C_GvKWbQ.js → useLifecycle-BJlxHqWa.js} +2 -2
  93. package/dist/{useTheme-Bns2gJZ6.js → useTheme-DMfQYmlG.js} +85 -85
  94. package/dist/{vega-component-BsVptuhz.js → vega-component-CovWHi60.js} +7 -7
  95. package/dist/{vennDiagram-4TSXK5OY-CH65znGa.js → vennDiagram-4TSXK5OY-BD-SL_vq.js} +1 -1
  96. package/dist/{wardleyDiagram-VM6X3IG4-z9pWKb2_.js → wardleyDiagram-VM6X3IG4-BDgJV4ah.js} +1 -1
  97. package/dist/{xychartDiagram-S5SC5T6Z-MxyaWjjq.js → xychartDiagram-S5SC5T6Z-1hIHN6Rl.js} +1 -1
  98. package/package.json +1 -1
  99. package/src/__mocks__/document.tsx +54 -0
  100. package/src/__mocks__/notebook.ts +2 -1
  101. package/src/__mocks__/requests.ts +0 -2
  102. package/src/components/chat/chat-panel.tsx +3 -3
  103. package/src/components/datasources/__tests__/column-preview.test.tsx +6 -8
  104. package/src/components/datasources/column-preview.tsx +4 -8
  105. package/src/components/datasources/datasources.tsx +4 -3
  106. package/src/components/dependency-graph/minimap-content.tsx +2 -6
  107. package/src/components/dependency-graph/panels.tsx +2 -1
  108. package/src/components/editor/__tests__/Output.test.tsx +50 -32
  109. package/src/components/editor/__tests__/data-attributes.test.tsx +22 -11
  110. package/src/components/editor/__tests__/output-persistence.test.tsx +6 -1
  111. package/src/components/editor/actions/__tests__/name-cell-input.test.tsx +69 -0
  112. package/src/components/editor/actions/name-cell-input.tsx +4 -3
  113. package/src/components/editor/actions/useCellActionButton.tsx +20 -26
  114. package/src/components/editor/actions/useNotebookActions.tsx +13 -18
  115. package/src/components/editor/actions/useSetCodeVisibility.ts +10 -11
  116. package/src/components/editor/actions/useSetOutputsExpanded.ts +13 -18
  117. package/src/components/editor/cell/CreateCellButton.tsx +5 -9
  118. package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +3 -1
  119. package/src/components/editor/cell/code/cell-editor.tsx +13 -20
  120. package/src/components/editor/cell/useAddCell.ts +4 -8
  121. package/src/components/editor/cell/useDeleteCell.tsx +10 -36
  122. package/src/components/editor/cell/useRunCells.ts +2 -5
  123. package/src/components/editor/cell/useSplitCell.tsx +2 -1
  124. package/src/components/editor/chrome/panels/logs-panel.tsx +2 -1
  125. package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -11
  126. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +3 -1
  127. package/src/components/editor/columns/sortable-column.tsx +1 -1
  128. package/src/components/editor/connections/components.tsx +4 -9
  129. package/src/components/editor/controls/Controls.tsx +1 -1
  130. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +9 -3
  131. package/src/components/editor/documentation.css +34 -11
  132. package/src/components/editor/errors/auto-fix.tsx +4 -8
  133. package/src/components/editor/file-tree/file-explorer.tsx +3 -7
  134. package/src/components/editor/links/__tests__/cell-link.test.tsx +3 -1
  135. package/src/components/editor/links/cell-link.tsx +2 -1
  136. package/src/components/editor/navigation/__tests__/clipboard.test.ts +60 -38
  137. package/src/components/editor/navigation/__tests__/navigation.test.ts +73 -64
  138. package/src/components/editor/navigation/clipboard.ts +12 -11
  139. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +11 -20
  140. package/src/components/editor/navigation/navigation.ts +20 -28
  141. package/src/components/editor/notebook-cell.tsx +7 -10
  142. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +125 -69
  143. package/src/components/editor/renderers/cell-array.tsx +10 -17
  144. package/src/components/markdown/markdown-renderer.tsx +4 -8
  145. package/src/components/pages/edit-page.tsx +32 -17
  146. package/src/components/scratchpad/scratchpad.test.tsx +3 -1
  147. package/src/components/scratchpad/scratchpad.tsx +4 -7
  148. package/src/components/slides/__tests__/minimap-actions.test.tsx +9 -25
  149. package/src/components/slides/minimap.tsx +7 -3
  150. package/src/components/slides/slide-cell-view.tsx +3 -3
  151. package/src/components/sort/SortableCellsProvider.tsx +2 -1
  152. package/src/core/ai/__tests__/staged-cells.test.ts +18 -20
  153. package/src/core/ai/staged-cells.ts +8 -16
  154. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +2 -2
  155. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +2 -2
  156. package/src/core/ai/tools/base.ts +2 -2
  157. package/src/core/ai/tools/edit-notebook-tool.ts +13 -21
  158. package/src/core/cells/__tests__/actions.test.ts +474 -34
  159. package/src/core/cells/__tests__/add-missing-import.test.ts +7 -8
  160. package/src/core/cells/__tests__/cells.test.ts +251 -861
  161. package/src/core/cells/__tests__/document.test.ts +42 -38
  162. package/src/core/cells/__tests__/notebook-outline.test.ts +7 -1
  163. package/src/core/cells/__tests__/outputs.test.tsx +71 -53
  164. package/src/core/cells/actions.ts +391 -3
  165. package/src/core/cells/add-missing-import.ts +46 -42
  166. package/src/core/cells/cells.ts +73 -444
  167. package/src/core/cells/document.ts +38 -6
  168. package/src/core/cells/outputs.ts +12 -28
  169. package/src/core/codemirror/cells/extensions.ts +6 -6
  170. package/src/core/codemirror/cells/state.ts +1 -1
  171. package/src/core/codemirror/keymaps/vim.ts +2 -2
  172. package/src/core/edit-app.tsx +20 -25
  173. package/src/core/islands/__tests__/bootstrap.test.ts +56 -0
  174. package/src/core/islands/__tests__/bridge.test.ts +42 -0
  175. package/src/core/islands/bootstrap.ts +24 -1
  176. package/src/core/islands/bridge.ts +46 -5
  177. package/src/core/kernel/__tests__/handlers.test.ts +7 -153
  178. package/src/core/kernel/handlers.ts +18 -59
  179. package/src/core/network/__tests__/requests-lazy.test.ts +1 -3
  180. package/src/core/network/requests-lazy.ts +0 -2
  181. package/src/core/network/requests-network.ts +0 -16
  182. package/src/core/network/requests-static.ts +0 -2
  183. package/src/core/network/requests-toasting.tsx +0 -2
  184. package/src/core/network/types.ts +0 -7
  185. package/src/core/notebook/document/__tests__/codemirror.test.ts +5 -0
  186. package/src/core/notebook/document/__tests__/draft.test.ts +41 -0
  187. package/src/core/notebook/document/__tests__/state.test.ts +21 -0
  188. package/src/core/notebook/document/draft.ts +35 -5
  189. package/src/core/notebook/document/index.ts +1 -0
  190. package/src/core/notebook/document/state.ts +6 -2
  191. package/src/core/run-app.tsx +1 -7
  192. package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +0 -6
  193. package/src/core/session/useNotebookSession.tsx +28 -35
  194. package/src/core/wasm/bridge.ts +0 -16
  195. package/src/css/globals.css +56 -22
  196. package/src/stories/runtime-story.tsx +22 -15
@@ -1,7 +1,8 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { describe, expect, it, vi } from "vitest";
3
3
  import { cellId } from "@/__tests__/branded";
4
- import { buildCellData, buildLayoutState } from "../handlers";
4
+ import { createCell } from "@/core/cells/types";
5
+ import { buildLayoutState } from "../handlers";
5
6
  import type { NotificationMessageData } from "../messages";
6
7
  import { queryParamHandlers } from "../queryParamHandlers";
7
8
 
@@ -63,152 +64,9 @@ describe("queryParamHandlers", () => {
63
64
  });
64
65
  });
65
66
 
66
- describe("buildCellData", () => {
67
- it("should build cell data from kernel-ready data", () => {
68
- const kernelReadyData: NotificationMessageData<"kernel-ready"> = {
69
- cell_ids: [cellId("cell1"), cellId("cell2")],
70
- codes: ["x = 1", "y = 2"],
71
- names: ["__", "__"],
72
- configs: [
73
- { disabled: false, hide_code: false },
74
- { disabled: false, hide_code: false },
75
- ],
76
- layout: null,
77
- resumed: false,
78
- ui_values: {},
79
- last_executed_code: {},
80
- last_execution_time: {},
81
- app_config: {
82
- width: "normal",
83
- app_title: null,
84
- layout_file: null,
85
- css_file: null,
86
- auto_download: [],
87
- },
88
- kiosk: false,
89
- capabilities: {
90
- terminal: false,
91
- },
92
- auto_instantiated: false,
93
- consumer_capabilities: { edit: true, interact: true },
94
- };
95
-
96
- const cells = buildCellData(kernelReadyData);
97
-
98
- expect(cells).toMatchInlineSnapshot(`
99
- [
100
- {
101
- "code": "x = 1",
102
- "config": {
103
- "disabled": false,
104
- "hide_code": false,
105
- },
106
- "edited": false,
107
- "id": "cell1",
108
- "lastCodeRun": null,
109
- "lastExecutionTime": null,
110
- "name": "__",
111
- "serializedEditorState": null,
112
- },
113
- {
114
- "code": "y = 2",
115
- "config": {
116
- "disabled": false,
117
- "hide_code": false,
118
- },
119
- "edited": false,
120
- "id": "cell2",
121
- "lastCodeRun": null,
122
- "lastExecutionTime": null,
123
- "name": "__",
124
- "serializedEditorState": null,
125
- },
126
- ]
127
- `);
128
- });
129
-
130
- it("should mark cells as edited when code differs from last executed code", () => {
131
- const kernelReadyData: NotificationMessageData<"kernel-ready"> = {
132
- cell_ids: [cellId("cell1"), cellId("cell2")],
133
- codes: ["x = 1", "y = 3"],
134
- names: ["__", "__"],
135
- configs: [
136
- { disabled: false, hide_code: false },
137
- { disabled: false, hide_code: false },
138
- ],
139
- layout: null,
140
- resumed: false,
141
- ui_values: {},
142
- last_executed_code: {
143
- cell1: "x = 1",
144
- cell2: "y = 2",
145
- },
146
- last_execution_time: {
147
- cell1: 1_234_567_890,
148
- cell2: 1_234_567_891,
149
- },
150
- app_config: {
151
- width: "normal",
152
- app_title: null,
153
- layout_file: null,
154
- css_file: null,
155
- auto_download: [],
156
- },
157
- kiosk: false,
158
- capabilities: {
159
- terminal: false,
160
- },
161
- auto_instantiated: false,
162
- consumer_capabilities: { edit: true, interact: true },
163
- };
164
-
165
- const cells = buildCellData(kernelReadyData);
166
-
167
- expect(cells[0].edited).toBe(false);
168
- expect(cells[1].edited).toBe(true);
169
- expect(cells[0].lastCodeRun).toBe("x = 1");
170
- expect(cells[1].lastCodeRun).toBe("y = 2");
171
- });
172
-
173
- it("should handle empty cell data", () => {
174
- const kernelReadyData: NotificationMessageData<"kernel-ready"> = {
175
- cell_ids: [],
176
- codes: [],
177
- names: [],
178
- configs: [],
179
- layout: null,
180
- resumed: false,
181
- ui_values: {},
182
- last_executed_code: {},
183
- last_execution_time: {},
184
- app_config: {
185
- width: "normal",
186
- app_title: null,
187
- layout_file: null,
188
- css_file: null,
189
- auto_download: [],
190
- },
191
- kiosk: false,
192
- capabilities: {
193
- terminal: false,
194
- },
195
- auto_instantiated: false,
196
- consumer_capabilities: { edit: true, interact: true },
197
- };
198
-
199
- const cells = buildCellData(kernelReadyData);
200
-
201
- expect(cells).toMatchInlineSnapshot("[]");
202
- });
203
- });
204
-
205
67
  describe("buildLayoutState", () => {
206
68
  it("should build default layout state when no layout is provided", () => {
207
69
  const kernelReadyData: NotificationMessageData<"kernel-ready"> = {
208
- cell_ids: [cellId("cell1")],
209
- codes: ["x = 1"],
210
- names: ["__"],
211
- configs: [{ disabled: false, hide_code: false }],
212
70
  layout: null,
213
71
  resumed: false,
214
72
  ui_values: {},
@@ -229,7 +87,7 @@ describe("buildLayoutState", () => {
229
87
  consumer_capabilities: { edit: true, interact: true },
230
88
  };
231
89
 
232
- const cells = buildCellData(kernelReadyData);
90
+ const cells = [createCell({ id: cellId("cell1") })];
233
91
  const mockSetLayoutData = vi.fn();
234
92
  const layoutState = buildLayoutState(
235
93
  kernelReadyData,
@@ -248,13 +106,6 @@ describe("buildLayoutState", () => {
248
106
 
249
107
  it("should build layout state with vertical layout", () => {
250
108
  const kernelReadyData: NotificationMessageData<"kernel-ready"> = {
251
- cell_ids: [cellId("cell1"), cellId("cell2")],
252
- codes: ["x = 1", "y = 2"],
253
- names: ["__", "__"],
254
- configs: [
255
- { disabled: false, hide_code: false },
256
- { disabled: false, hide_code: false },
257
- ],
258
109
  layout: {
259
110
  type: "vertical",
260
111
  data: [],
@@ -278,7 +129,10 @@ describe("buildLayoutState", () => {
278
129
  consumer_capabilities: { edit: true, interact: true },
279
130
  };
280
131
 
281
- const cells = buildCellData(kernelReadyData);
132
+ const cells = [
133
+ createCell({ id: cellId("cell1") }),
134
+ createCell({ id: cellId("cell2") }),
135
+ ];
282
136
  const mockSetLayoutData = vi.fn();
283
137
  const layoutState = buildLayoutState(
284
138
  kernelReadyData,
@@ -3,7 +3,8 @@ import type { LayoutType } from "@/components/editor/renderers/types";
3
3
  import { Logger } from "@/utils/Logger";
4
4
  import { Objects } from "@/utils/objects";
5
5
  import type { UIElementId } from "../cells/ids";
6
- import { type CellData, createCell } from "../cells/types";
6
+ import type { ReducerCellActions } from "../cells/cells";
7
+ import type { CellData } from "../cells/types";
7
8
  import { type AppConfig, AppConfigSchema } from "../config/config-schema";
8
9
  import { UI_ELEMENT_REGISTRY } from "../dom/uiregistry";
9
10
  import { deserializeLayoutState } from "../layout/layout";
@@ -19,41 +20,6 @@ import type { KernelState } from "./state";
19
20
 
20
21
  type KernelReadyData = NotificationMessageData<"kernel-ready">;
21
22
 
22
- /**
23
- * Build cells from kernel-ready data.
24
- */
25
- export function buildCellData(data: KernelReadyData): CellData[] {
26
- const {
27
- codes,
28
- names,
29
- configs,
30
- cell_ids,
31
- last_executed_code,
32
- last_execution_time,
33
- } = data;
34
-
35
- const lastExecutedCode = last_executed_code || {};
36
- const lastExecutionTime = last_execution_time || {};
37
-
38
- return codes.map((code, i) => {
39
- const cellId = cell_ids[i];
40
- const lastCodeRun = lastExecutedCode[cellId];
41
-
42
- // A cell is edited if the code has changed since the last time it was run
43
- const edited = lastCodeRun ? lastCodeRun !== code : false;
44
-
45
- return createCell({
46
- id: cellId,
47
- code,
48
- edited,
49
- name: names[i],
50
- lastCodeRun: lastExecutedCode[cellId] ?? null,
51
- lastExecutionTime: lastExecutionTime[cellId] ?? null,
52
- config: configs[i],
53
- });
54
- });
55
- }
56
-
57
23
  /**
58
24
  * Build layout state from kernel-ready data.
59
25
  */
@@ -95,7 +61,10 @@ export function handleKernelReady(
95
61
  data: KernelReadyData,
96
62
  opts: {
97
63
  autoInstantiate: boolean;
98
- setCells: (cells: CellData[], layout: LayoutState) => void;
64
+ /** The notebook's cells, as the host document provided them. */
65
+ cells: CellData[];
66
+ setLastRun: ReducerCellActions["setLastRun"];
67
+ resetCellRuntimes: ReducerCellActions["resetCellRuntimes"];
99
68
  setLayoutData: (payload: {
100
69
  layoutView: LayoutType;
101
70
  data: LayoutData;
@@ -104,17 +73,13 @@ export function handleKernelReady(
104
73
  setKernelState: (state: KernelState) => void;
105
74
  setAppConfig: (config: AppConfig) => void;
106
75
  onError: (error: Error) => void;
107
- /**
108
- * If provided, these cells will be used instead of the cells from
109
- * kernel-ready. This allows preserving local edits made before connecting.
110
- */
111
- existingCells?: CellData[];
112
76
  },
113
- ) {
77
+ ): LayoutState {
114
78
  const {
115
- existingCells,
116
79
  autoInstantiate,
117
- setCells,
80
+ cells,
81
+ setLastRun,
82
+ resetCellRuntimes,
118
83
  setLayoutData,
119
84
  setAppConfig,
120
85
  setCapabilities,
@@ -124,15 +89,12 @@ export function handleKernelReady(
124
89
  const { resumed, ui_values, app_config, capabilities, auto_instantiated } =
125
90
  data;
126
91
 
127
- // Use existing cells if provided (local pre-connect edits), otherwise build from kernel-ready
128
- // If the kernel was resumed, we don't want to use the existing cells because they may be stale.
129
- const hasExistingCells = existingCells && existingCells.length > 0;
130
- const cells =
131
- hasExistingCells && !resumed ? existingCells : buildCellData(data);
132
-
133
- // Set up layout and cells
92
+ resetCellRuntimes();
93
+ setLastRun({
94
+ lastCodeRun: data.last_executed_code ?? {},
95
+ lastExecutionTime: data.last_execution_time ?? {},
96
+ });
134
97
  const layoutState = buildLayoutState(data, cells, setLayoutData);
135
- setCells(cells, layoutState);
136
98
 
137
99
  // Set app config and capabilities
138
100
  const parsedAppConfig = AppConfigSchema.safeParse(app_config);
@@ -145,7 +107,7 @@ export function handleKernelReady(
145
107
 
146
108
  // If the kernel was already instantiated server-side (e.g. run mode), we're done
147
109
  if (auto_instantiated) {
148
- return;
110
+ return layoutState;
149
111
  }
150
112
 
151
113
  // If resumed, restore UI element values from kernel and we're done
@@ -156,16 +118,13 @@ export function handleKernelReady(
156
118
  const uiObjectId: UIElementId = objectId;
157
119
  UI_ELEMENT_REGISTRY.set(uiObjectId, value);
158
120
  }
159
- return;
121
+ return layoutState;
160
122
  }
161
123
 
162
124
  // If we already have values for some objects, we should
163
125
  // send them to the kernel. This may happen after re-connecting
164
126
  // to the kernel after the computer wakes from sleep.
165
127
  const { objectIds, values } = collectUIElementValues();
166
- const codesToSend = hasExistingCells
167
- ? Object.fromEntries(existingCells.map((c) => [c.id, c.code]))
168
- : undefined;
169
128
 
170
129
  // Send instantiate request to kernel
171
130
  void getRequestClient()
@@ -173,7 +132,6 @@ export function handleKernelReady(
173
132
  objectIds,
174
133
  values,
175
134
  autoRun: autoInstantiate,
176
- codes: codesToSend,
177
135
  })
178
136
  .then(() => {
179
137
  setKernelState({ isInstantiated: true, error: null });
@@ -182,6 +140,7 @@ export function handleKernelReady(
182
140
  setKernelState({ isInstantiated: false, error: error });
183
141
  onError(new Error("Failed to instantiate", { cause: error }));
184
142
  });
143
+ return layoutState;
185
144
  }
186
145
 
187
146
  export function handleRemoveUIElements(
@@ -53,7 +53,6 @@ describe("createLazyRequests", () => {
53
53
  sendInstantiate: vi.fn().mockResolvedValue({ instantiated: true }),
54
54
  sendFunctionRequest: vi.fn().mockResolvedValue({ result: "data" }),
55
55
  sendRestart: vi.fn().mockResolvedValue({ restarted: true }),
56
- sendDeleteCell: vi.fn().mockResolvedValue({ deleted: true }),
57
56
  sendInterrupt: vi.fn().mockResolvedValue({ interrupted: true }),
58
57
  sendPdb: vi.fn().mockResolvedValue({ pdb: true }),
59
58
  sendRunScratchpad: vi.fn().mockResolvedValue({ run: true }),
@@ -320,7 +319,6 @@ describe("createLazyRequests", () => {
320
319
  cellIds: [cellId("cell1")],
321
320
  codes: ["code"],
322
321
  });
323
- await lazyRequests.sendDeleteCell({ cellId: cellId("cell2") });
324
322
  await lazyRequests.sendInstantiate({
325
323
  objectIds: [uiElementId("obj1")],
326
324
  values: [],
@@ -328,7 +326,7 @@ describe("createLazyRequests", () => {
328
326
 
329
327
  // Init should only be called once
330
328
  expect(mockInit).toHaveBeenCalledTimes(1);
331
- expect(mockGetRuntimeManager).toHaveBeenCalledTimes(3);
329
+ expect(mockGetRuntimeManager).toHaveBeenCalledTimes(2);
332
330
  });
333
331
  });
334
332
  });
@@ -52,10 +52,8 @@ const ACTIONS: Record<keyof AllRequests, Action> = {
52
52
  sendModelValue: "startConnection",
53
53
  sendInstantiate: "startConnectionWithoutKernel",
54
54
  sendRun: "startConnection",
55
- sendDeleteCell: "startConnection",
56
55
  sendRunScratchpad: "startConnection",
57
56
  saveAppConfig: "startConnection",
58
- saveCellConfig: "startConnection",
59
57
  discoverDataSources: "startConnection",
60
58
 
61
59
  // Export operations start a connection
@@ -142,14 +142,6 @@ export function createNetworkRequests(): EditRequests & RunRequests {
142
142
  })
143
143
  .then(handleResponseReturnNull);
144
144
  },
145
- sendDeleteCell: (request) => {
146
- return getClient()
147
- .POST("/api/kernel/delete", {
148
- body: request,
149
- params: getParams(),
150
- })
151
- .then(handleResponseReturnNull);
152
- },
153
145
  sendCodeCompletionRequest: async (request) => {
154
146
  await waitForConnectionOpen();
155
147
  return getClient()
@@ -175,14 +167,6 @@ export function createNetworkRequests(): EditRequests & RunRequests {
175
167
  })
176
168
  .then(handleResponseReturnNull);
177
169
  },
178
- saveCellConfig: (request) => {
179
- return getClient()
180
- .POST("/api/kernel/set_cell_config", {
181
- body: request,
182
- params: getParams(),
183
- })
184
- .then(handleResponseReturnNull);
185
- },
186
170
  sendFunctionRequest: (request) => {
187
171
  return getClient()
188
172
  .POST("/api/kernel/function_call", {
@@ -48,11 +48,9 @@ export function createStaticRequests(): EditRequests & RunRequests {
48
48
  sendInterrupt: throwNotInEditMode,
49
49
  sendShutdown: throwNotInEditMode,
50
50
  sendFormat: throwNotInEditMode,
51
- sendDeleteCell: throwNotInEditMode,
52
51
  sendCodeCompletionRequest: throwNotInEditMode,
53
52
  saveUserConfig: throwNotInEditMode,
54
53
  saveAppConfig: throwNotInEditMode,
55
- saveCellConfig: throwNotInEditMode,
56
54
  sendStdin: throwNotInEditMode,
57
55
  readCode: throwNotInEditMode,
58
56
  readSnippets: throwNotInEditMode,
@@ -29,11 +29,9 @@ export function createErrorToastingRequests(
29
29
  sendInterrupt: "Failed to interrupt",
30
30
  sendShutdown: "Failed to shutdown",
31
31
  sendFormat: "Failed to format",
32
- sendDeleteCell: "Failed to delete cell",
33
32
  sendCodeCompletionRequest: "Failed to complete code",
34
33
  saveUserConfig: "Failed to save user config",
35
34
  saveAppConfig: "Failed to save app config",
36
- saveCellConfig: "Failed to save cell config",
37
35
  sendStdin: "Failed to send stdin",
38
36
  readCode: "Failed to read code",
39
37
  readSnippets: "Failed to fetch snippets",
@@ -1,6 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import type { components, paths } from "@marimo-team/marimo-api";
3
- import type { CellId } from "../cells/ids";
4
3
 
5
4
  export type schemas = components["schemas"];
6
5
  export type AiCompletionRequest = schemas["AiCompletionRequest"];
@@ -16,7 +15,6 @@ export type CellConfig = schemas["CellConfig"];
16
15
  */
17
16
  export type RuntimeState = schemas["CellNotification"]["status"];
18
17
  export type CodeCompletionRequest = schemas["CodeCompletionRequest"];
19
- export type DeleteCellRequest = schemas["DeleteCellRequest"];
20
18
  export type AutoExportAsIPYNBRequest = schemas["AutoExportAsIPYNBRequest"];
21
19
  export type AutoExportAsMarkdownRequest =
22
20
  schemas["AutoExportAsMarkdownRequest"];
@@ -101,9 +99,6 @@ export type SaveNotebookRequest = schemas["SaveNotebookRequest"];
101
99
  export type CopyNotebookRequest = schemas["CopyNotebookRequest"];
102
100
  export type SaveUserConfigurationRequest =
103
101
  schemas["SaveUserConfigurationRequest"];
104
- export interface SetCellConfigRequest {
105
- configs: Record<CellId, Partial<CellConfig>>;
106
- }
107
102
  /**
108
103
  * Client-side shape for creating a file/directory/notebook. The HTTP
109
104
  * transport sends this as multipart/form-data; the WASM bridge base64-encodes
@@ -171,11 +166,9 @@ export interface EditRequests {
171
166
  sendInterrupt: () => Promise<null>;
172
167
  sendShutdown: () => Promise<null>;
173
168
  sendFormat: (request: FormatCellsRequest) => Promise<FormatResponse>;
174
- sendDeleteCell: (request: DeleteCellRequest) => Promise<null>;
175
169
  sendCodeCompletionRequest: (request: CodeCompletionRequest) => Promise<null>;
176
170
  saveUserConfig: (request: SaveUserConfigurationRequest) => Promise<null>;
177
171
  saveAppConfig: (request: SaveAppConfigurationRequest) => Promise<null>;
178
- saveCellConfig: (request: SetCellConfigRequest) => Promise<null>;
179
172
  sendRestart: () => Promise<null>;
180
173
  sendInstallMissingPackages: (
181
174
  request: InstallPackagesRequest,
@@ -60,6 +60,11 @@ function languages(id: CellId) {
60
60
  aiCellCompletion: () => false,
61
61
  createManyBelow: vi.fn(),
62
62
  addOrAppendSetupCell: vi.fn(),
63
+ createCell: vi.fn(),
64
+ moveToNextCell: vi.fn(),
65
+ addSetupCell: vi.fn(),
66
+ deleteCells: vi.fn(),
67
+ undoDeleteCell: vi.fn(),
63
68
  onRun: vi.fn(),
64
69
  deleteCell: vi.fn(),
65
70
  afterToggleMarkdown: vi.fn(),
@@ -9,6 +9,7 @@ import {
9
9
  type EditableCell,
10
10
  editCell,
11
11
  EMPTY_DRAFT,
12
+ restoreCell,
12
13
  setOrder,
13
14
  toChanges,
14
15
  viewDocument,
@@ -99,6 +100,46 @@ describe("createCell and deleteCell", () => {
99
100
  expect(toChanges(confirmed, draft)).toEqual([]);
100
101
  expect(viewDocument(confirmed, draft).order).toEqual(["c1", "c2"]);
101
102
  });
103
+
104
+ it("restores a deleted cell as an edit at the requested place", () => {
105
+ const deleted = deleteCell(EMPTY_DRAFT, "c1");
106
+ const back = { id: "c1", code: "back" };
107
+ const put = {
108
+ kind: "put_cell",
109
+ base_etag: "c1-etag",
110
+ cell: { ...back, name: "_", config: CONFIG },
111
+ };
112
+ const inPlace = restoreCell(confirmed, deleted, back, { before: "c2" });
113
+ const moved = restoreCell(confirmed, deleted, back, { after: "c2" });
114
+ expect(viewDocument(confirmed, inPlace).cells.get("c1")?.code).toBe("back");
115
+ expect(viewDocument(confirmed, inPlace).order).toEqual(["c1", "c2"]);
116
+ expect(toChanges(confirmed, inPlace)).toEqual([put]);
117
+ expect(viewDocument(confirmed, moved).order).toEqual(["c2", "c1"]);
118
+ expect(toChanges(confirmed, moved)).toEqual([
119
+ put,
120
+ { kind: "set_order", base_etag: "order-1", order: ["c2", "c1"] },
121
+ ]);
122
+ });
123
+
124
+ it("recreates a cell whose deletion is in flight once the host removes it", () => {
125
+ const sent = deleteCell(EMPTY_DRAFT, "c1");
126
+ const back = { id: "c1", code: "back" };
127
+ const draft = restoreCell(confirmed, sent, back, { before: "c2" }, sent);
128
+ expect(viewDocument(confirmed, draft).cells.get("c1")?.code).toBe("back");
129
+ expect(viewDocument(confirmed, draft).order).toEqual(["c1", "c2"]);
130
+
131
+ const removed = withCells(confirmed, { c1: null });
132
+ const settled = acknowledge(draft, sent);
133
+ expect(viewDocument(removed, settled).order).toEqual(["c1", "c2"]);
134
+ expect(toChanges(removed, settled)).toEqual([
135
+ {
136
+ kind: "put_cell",
137
+ base_etag: null,
138
+ cell: { ...back, name: "_", config: CONFIG },
139
+ before: "c2",
140
+ },
141
+ ]);
142
+ });
102
143
  });
103
144
 
104
145
  describe("viewDocument", () => {
@@ -124,6 +124,27 @@ describe("document model", () => {
124
124
  },
125
125
  );
126
126
 
127
+ it("brings back a cell deleted earlier in the draft with new content", () => {
128
+ const { store, cellAtom, orderAtom, changesAtom, edit } = setup();
129
+ edit((tx) => tx.deleteCell("c1"));
130
+ edit((tx) => tx.createCell({ id: "c1", code: "back" }, { after: "c2" }));
131
+ expect(store.get(cellAtom("c1"))?.code).toBe("back");
132
+ expect(store.get(orderAtom)).toEqual(["c2", "c1"]);
133
+ expect(store.get(changesAtom)).toEqual([
134
+ {
135
+ kind: "put_cell",
136
+ base_etag: "c1-etag",
137
+ cell: {
138
+ id: "c1",
139
+ code: "back",
140
+ name: "_",
141
+ config: cell("c1", "").config,
142
+ },
143
+ },
144
+ { kind: "set_order", base_etag: "order-1", order: ["c2", "c1"] },
145
+ ]);
146
+ });
147
+
127
148
  it("publishes the visible order when an anchor is deleted later in the draft", () => {
128
149
  const { store, orderAtom, changesAtom, edit } = setup();
129
150
  edit((tx) => {
@@ -253,17 +253,47 @@ export function createCell(
253
253
  input: Protocol.CellInput,
254
254
  placement: Placement = {},
255
255
  ): Draft {
256
+ const cell = parseCell(input);
257
+ const next = withCell(draft, cell.id, { kind: "create", cell, placement });
258
+ return draft.order === null
259
+ ? next
260
+ : { ...next, order: insertAt(draft.order, cell.id, placement) };
261
+ }
262
+
263
+ /**
264
+ * Bring back a cell this draft deletes, with the given content.
265
+ *
266
+ * A deletion not yet sent is withdrawn and the cell edited in place. One in
267
+ * flight is superseded by a creation, so the cell returns once the host has
268
+ * removed it. Either way the cell takes the requested placement.
269
+ */
270
+ export function restoreCell(
271
+ confirmed: Document,
272
+ draft: Draft,
273
+ input: Protocol.CellInput,
274
+ placement: Placement = {},
275
+ sent: Draft = EMPTY_DRAFT,
276
+ ): Draft {
277
+ const { id, code, name, config } = parseCell(input);
278
+ const withdrawn = withCell(draft, id, undefined);
279
+ const next =
280
+ sent.cells.get(id)?.kind === "delete"
281
+ ? createCell(withdrawn, input, placement)
282
+ : editCell(confirmed, withdrawn, id, { code, name, config });
283
+ const { order } = viewDocument(confirmed, next);
284
+ const placed = insertAt(order, id, placement);
285
+ return sameOrder(order, placed) ? next : setOrder(next, placed);
286
+ }
287
+
288
+ /** A cell input with the protocol's defaults filled in. */
289
+ function parseCell(input: Protocol.CellInput): CellWithSource {
256
290
  const parsed = Protocol.CellInput.parse(input);
257
- const cell: CellWithSource = {
291
+ return {
258
292
  id: parsed.id,
259
293
  code: parsed.code,
260
294
  name: parsed.name,
261
295
  config: parsed.config ?? Protocol.CellConfigInput.parse({}),
262
296
  };
263
- const next = withCell(draft, cell.id, { kind: "create", cell, placement });
264
- return draft.order === null
265
- ? next
266
- : { ...next, order: insertAt(draft.order, cell.id, placement) };
267
297
  }
268
298
 
269
299
  /** Forget an unsent creation; otherwise retain the deletion until confirmed. */
@@ -21,3 +21,4 @@ export {
21
21
  type EditableCell,
22
22
  type EditableDocument,
23
23
  } from "./model";
24
+ export type { Placement } from "./draft";
@@ -13,6 +13,7 @@ import {
13
13
  type CellEdit,
14
14
  createCell,
15
15
  deleteCell,
16
+ restoreCell,
16
17
  type Draft,
17
18
  type EditableDocument,
18
19
  editCell,
@@ -30,6 +31,7 @@ export interface DocumentEditor {
30
31
  setCode(id: Protocol.CellId, code: string): void;
31
32
  setName(id: Protocol.CellId, name: string): void;
32
33
  updateConfig(id: Protocol.CellId, patch: Partial<Protocol.CellConfig>): void;
34
+ /** Create a cell, or bring back one this draft deletes with this content. */
33
35
  createCell(cell: Protocol.CellInput, placement?: Placement): void;
34
36
  deleteCell(id: Protocol.CellId): void;
35
37
  /** A permutation of the cells present at this point in the transaction. */
@@ -330,7 +332,7 @@ function editDocument(
330
332
  updateCell(id, { config: Protocol.CellConfig.partial().parse(patch) }),
331
333
  createCell: (cell, placement = {}) => {
332
334
  assertActive();
333
- if (confirmed.cells.has(cell.id) || hasCell(cell.id)) {
335
+ if (hasCell(cell.id)) {
334
336
  throw new Error(`Cell ${cell.id} already exists`);
335
337
  }
336
338
  if (placement.after !== undefined && placement.before !== undefined) {
@@ -340,7 +342,9 @@ function editDocument(
340
342
  if (anchor !== undefined) {
341
343
  requireCell(anchor);
342
344
  }
343
- next = createCell(next, cell, { ...placement });
345
+ next = confirmed.cells.has(cell.id)
346
+ ? restoreCell(confirmed, next, cell, { ...placement }, sent)
347
+ : createCell(next, cell, { ...placement });
344
348
  },
345
349
  deleteCell: (id) => {
346
350
  requireCell(id);