@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.
- package/dist/{ConnectedDataExplorerComponent-BbSXV-Xa.js → ConnectedDataExplorerComponent-Le_OBfmt.js} +9 -9
- package/dist/{ErrorBoundary-NE86qEWZ.js → ErrorBoundary-DNvcAIsP.js} +8 -8
- package/dist/{abnfDiagram-VCTEODGH-D7n07nXf.js → abnfDiagram-VCTEODGH-Tf1ghyQQ.js} +2 -2
- package/dist/{any-language-editor-3E24SnCV.js → any-language-editor-BVFHDDQ3.js} +4 -4
- package/dist/{architectureDiagram-5GKGNRK7-D6CjRib5.js → architectureDiagram-5GKGNRK7-xlnbTG0T.js} +1 -1
- package/dist/{chat-ui-CC7GekpG.js → chat-ui-X-cWrL9u.js} +1218 -1218
- package/dist/{check-SpOMoCLP.js → check-C9Mp9eCK.js} +1 -1
- package/dist/{chunk-IMKFNOWR-6dtPz7uS.js → chunk-IMKFNOWR-Cmd4a5jy.js} +1 -1
- package/dist/{chunk-SHT3W25Y-BwnFNKDA.js → chunk-SHT3W25Y-mSNp-WpO.js} +1 -1
- package/dist/{chunk-SVP7TREG-D2eV4vBJ.js → chunk-SVP7TREG-CWC49D8X.js} +1 -1
- package/dist/{chunk-TICWLB2K-BDs4-_-r.js → chunk-TICWLB2K-De9FINtG.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-DCJnf_8v.js → classDiagram-ZZMXUADV-DweTkYdp.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-BOmSzPBD.js → classDiagram-v2-VYDZK3BY-A6MR2TiE.js} +2 -2
- package/dist/{code-visibility-Bzvc6ajl.js → code-visibility-CH1lmM1h.js} +5 -5
- package/dist/{common-Ciw4zkh2.js → common-DIrS1UR9.js} +1081 -1085
- package/dist/{copy-icon-C1trdvVt.js → copy-icon-De584pZ4.js} +2 -2
- package/dist/{cynefinDiagram-5FMLGOSQ-Bln_Soky.js → cynefinDiagram-5FMLGOSQ-BSvo5le-.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-D-nf7WzW.js → dagre-GXQ25YYZ-tXRQslO6.js} +1 -1
- package/dist/{data-grid-overlay-editor-AQREmxI7.js → data-grid-overlay-editor-DFLZvFK0.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-PsgSMkY2.js → diagram-S7CK7UJ4-r3t5VtwI.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-DfwfUuSw.js → diagram-UQ7AKVKN-qgbC8YnT.js} +1 -1
- package/dist/{diagram-VX7I27RA-C9_e-oMc.js → diagram-VX7I27RA-CiHxEwtp.js} +1 -1
- package/dist/{diagram-Z3DM3KII-CWLa5tuY.js → diagram-Z3DM3KII-D95u15Ev.js} +1 -1
- package/dist/{dialog-DgeqyrEZ.js → dialog-DVRfQuhD.js} +24 -24
- package/dist/{dist-F8Ep36rk.js → dist-B1M0bGO3.js} +1 -1
- package/dist/{dist-D6d86eNJ.js → dist-BA13RJj5.js} +1 -1
- package/dist/{dist-Bvs24R_E.js → dist-BJtmSgcE.js} +48 -48
- package/dist/{dist-CN_BeG2I.js → dist-Ba2VIn1K.js} +53 -53
- package/dist/{dist-PXYI_yuP.js → dist-Bj4bmwUm.js} +12 -12
- package/dist/{dist-DoE4XJBC.js → dist-BmS-ZGjl.js} +8626 -8626
- package/dist/{dist-BorMD-uX.js → dist-Bzogh7CW.js} +87 -87
- package/dist/{dist-CN8zI-XC.js → dist-CThPVKaP.js} +1385 -1382
- package/dist/{dist-DiDibOYr.js → dist-CTi5FTiV.js} +87 -87
- package/dist/{dist-diIx5qJW.js → dist-Ck_C32ye.js} +2 -2
- package/dist/{dist-BLfKmR-l.js → dist-D1fnFp1J.js} +1 -1
- package/dist/{dist-OR1OnN-y.js → dist-D3x9__Fu.js} +46 -46
- package/dist/{dist-BMQf5zIS.js → dist-D7mrh5sO.js} +36 -36
- package/dist/{dist-CSJ0MDRw.js → dist-D9Q3xjiP.js} +1 -1
- package/dist/{dist-BuaRHxOc.js → dist-DP60Ubh8.js} +2 -2
- package/dist/{dist-CSbh1PNP.js → dist-DTv9KkpW.js} +12 -12
- package/dist/{dist-D-wwqlzJ.js → dist-DVVwOfrD.js} +109 -109
- package/dist/{dist-C8fAKSH1.js → dist-DYXM73-k.js} +36 -36
- package/dist/{dist-DBtSl3sf.js → dist-DjeybPOb.js} +1 -1
- package/dist/{dist-CrSP_H1M.js → dist-DtGn9N4h.js} +197 -197
- package/dist/{dist-CLdpGQVr.js → dist-KN80qWCP.js} +2 -2
- package/dist/{dist-Cg7gzw-a.js → dist-O0qFpEJh.js} +56 -56
- package/dist/{dist-CkTTIdBe.js → dist-OUle2RT7.js} +207 -207
- package/dist/{dist-BK4Oj99o.js → dist-T5rh6JvL.js} +1 -1
- package/dist/{dropdown-menu-B6ftyzOg.js → dropdown-menu-DCMbbQ35.js} +691 -691
- package/dist/{ebnfDiagram-PWID7BFC-B1QRM58v.js → ebnfDiagram-PWID7BFC-P7e4bryK.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-DJK_FqvW.js → erDiagram-RLTQ6QDP-CUHZcmRj.js} +1 -1
- package/dist/{error-banner-D40pmfQQ.js → error-banner-DxJ3s8YO.js} +57 -57
- package/dist/{error-entries-DgK3wUJR.js → error-entries-mpoD5t79.js} +4985 -5006
- package/dist/{esm-D2gTsXEV.js → esm-Bbw_Njjx.js} +575 -575
- package/dist/{esm-AMFssoNp.js → esm-mDQNEFe_.js} +354 -354
- package/dist/{feedback-button-8a6SETmT.js → feedback-button-DL0DJ-LB.js} +30 -30
- package/dist/{flowDiagram-HODETNUW-Uxc25XSJ.js → flowDiagram-HODETNUW-BGZ95AzI.js} +2 -2
- package/dist/{formats-DQCyPd5Z.js → formats-DI7HEVv7.js} +1 -1
- package/dist/{glide-data-editor-B70oke_v.js → glide-data-editor-n4wnx19S.js} +10 -10
- package/dist/{glide-utils-Dz_F4pAY.js → glide-utils-D98laidb.js} +2 -2
- package/dist/{globals-Cxd5hNlI.js → globals-NCsYyjpB.js} +3 -3
- package/dist/{html-to-image-D4yG5Ru0.js → html-to-image-BoZthB4P.js} +467 -458
- package/dist/{infoDiagram-27XIBGKW-DmjxqwL0.js → infoDiagram-27XIBGKW-BF0YX70D.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-NgCLHqQi.js → ishikawaDiagram-5VMMS53U-_itsAAn-.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-CZaG-iam.js → kanban-definition-UXKFOSKX-VNW2Beqh.js} +1 -1
- package/dist/{label-D9_-zSg-.js → label-Qvw1E97W.js} +1 -1
- package/dist/main.js +8257 -6606
- package/dist/{mermaid-CSvw7c71.js → mermaid-CZ6FrClS.js} +35 -35
- package/dist/{mindmap-definition-YA3MSWOX-DJYQBDH_.js → mindmap-definition-YA3MSWOX-DR4ripvd.js} +1 -1
- package/dist/{number-overlay-editor-ChK-SKUk.js → number-overlay-editor-rlvo4mo3.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-CHKO_dAe.js → pegDiagram-XKGWAZYB-CFr3XlGl.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-DVevbMAo.js → pieDiagram-E7YTZNPT-j91XYSLY.js} +1 -1
- package/dist/{process-output-DxvBAeL3.js → process-output-DLS4Mqg_.js} +4 -4
- package/dist/{railroadDiagram-O6MQD6OU-Detj1kcB.js → railroadDiagram-O6MQD6OU-Cd47ZV9E.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-BLFD0T_Z.js → requirementDiagram-BXWQKSXE-eJH6gLtX.js} +1 -1
- package/dist/{reveal-component-CXMXeDZD.js → reveal-component-BJfmRUzb.js} +9 -9
- package/dist/{select-CkF_GBxO.js → select-2KOiReNY.js} +324 -324
- package/dist/{sequenceDiagram-WJ2MYXX4-BZBgatQV.js → sequenceDiagram-WJ2MYXX4-DdfFEIrm.js} +1 -1
- package/dist/{spec-Mnkn3XEr.js → spec-h69nC3SI.js} +4 -4
- package/dist/{stateDiagram-D77RDMKH-4BZW9-6_.js → stateDiagram-D77RDMKH-DkjDvip8.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-C4sKC8Iq.js → stateDiagram-v2-MP3YSRHH-DBHACnOo.js} +2 -2
- package/dist/{swimlanes-42K2YHIH-SL5yeGe2.js → swimlanes-42K2YHIH-ogImlOmp.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-DqPmAYUv.js → swimlanesDiagram-VR7AAH4N-D6x-zsci.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-BfXUMWkq.js → timeline-definition-24CTP7MA-DQpLtsHD.js} +1 -1
- package/dist/{toDate-TMubKIVL.js → toDate-CDzoofJo.js} +2 -2
- package/dist/{toast-Btbve3Lm.js → toast-B5t9o6s_.js} +47 -47
- package/dist/{tooltip-CJ4Rqasp.js → tooltip-s4zZxwKe.js} +67 -67
- package/dist/{use-restore-focus-CmGwRNId.js → use-restore-focus-BgDTd2tc.js} +1 -1
- package/dist/{useAsyncData-DjMGFzFy.js → useAsyncData-Bn8SxUqM.js} +1 -1
- package/dist/{useDateFormatter-eCL_SdeG.js → useDateFormatter-D3Tkfzej.js} +2 -2
- package/dist/{useDeepCompareMemoize-BQR5TJot.js → useDeepCompareMemoize-CC2tnPCk.js} +1 -1
- package/dist/{useLifecycle-C_GvKWbQ.js → useLifecycle-BJlxHqWa.js} +2 -2
- package/dist/{useTheme-Bns2gJZ6.js → useTheme-DMfQYmlG.js} +85 -85
- package/dist/{vega-component-BsVptuhz.js → vega-component-CovWHi60.js} +7 -7
- package/dist/{vennDiagram-4TSXK5OY-CH65znGa.js → vennDiagram-4TSXK5OY-BD-SL_vq.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-z9pWKb2_.js → wardleyDiagram-VM6X3IG4-BDgJV4ah.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-MxyaWjjq.js → xychartDiagram-S5SC5T6Z-1hIHN6Rl.js} +1 -1
- package/package.json +1 -1
- package/src/__mocks__/document.tsx +54 -0
- package/src/__mocks__/notebook.ts +2 -1
- package/src/__mocks__/requests.ts +0 -2
- package/src/components/chat/chat-panel.tsx +3 -3
- package/src/components/datasources/__tests__/column-preview.test.tsx +6 -8
- package/src/components/datasources/column-preview.tsx +4 -8
- package/src/components/datasources/datasources.tsx +4 -3
- package/src/components/dependency-graph/minimap-content.tsx +2 -6
- package/src/components/dependency-graph/panels.tsx +2 -1
- package/src/components/editor/__tests__/Output.test.tsx +50 -32
- package/src/components/editor/__tests__/data-attributes.test.tsx +22 -11
- package/src/components/editor/__tests__/output-persistence.test.tsx +6 -1
- package/src/components/editor/actions/__tests__/name-cell-input.test.tsx +69 -0
- package/src/components/editor/actions/name-cell-input.tsx +4 -3
- package/src/components/editor/actions/useCellActionButton.tsx +20 -26
- package/src/components/editor/actions/useNotebookActions.tsx +13 -18
- package/src/components/editor/actions/useSetCodeVisibility.ts +10 -11
- package/src/components/editor/actions/useSetOutputsExpanded.ts +13 -18
- package/src/components/editor/cell/CreateCellButton.tsx +5 -9
- package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +3 -1
- package/src/components/editor/cell/code/cell-editor.tsx +13 -20
- package/src/components/editor/cell/useAddCell.ts +4 -8
- package/src/components/editor/cell/useDeleteCell.tsx +10 -36
- package/src/components/editor/cell/useRunCells.ts +2 -5
- package/src/components/editor/cell/useSplitCell.tsx +2 -1
- package/src/components/editor/chrome/panels/logs-panel.tsx +2 -1
- package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -11
- package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +3 -1
- package/src/components/editor/columns/sortable-column.tsx +1 -1
- package/src/components/editor/connections/components.tsx +4 -9
- package/src/components/editor/controls/Controls.tsx +1 -1
- package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +9 -3
- package/src/components/editor/documentation.css +34 -11
- package/src/components/editor/errors/auto-fix.tsx +4 -8
- package/src/components/editor/file-tree/file-explorer.tsx +3 -7
- package/src/components/editor/links/__tests__/cell-link.test.tsx +3 -1
- package/src/components/editor/links/cell-link.tsx +2 -1
- package/src/components/editor/navigation/__tests__/clipboard.test.ts +60 -38
- package/src/components/editor/navigation/__tests__/navigation.test.ts +73 -64
- package/src/components/editor/navigation/clipboard.ts +12 -11
- package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +11 -20
- package/src/components/editor/navigation/navigation.ts +20 -28
- package/src/components/editor/notebook-cell.tsx +7 -10
- package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +125 -69
- package/src/components/editor/renderers/cell-array.tsx +10 -17
- package/src/components/markdown/markdown-renderer.tsx +4 -8
- package/src/components/pages/edit-page.tsx +32 -17
- package/src/components/scratchpad/scratchpad.test.tsx +3 -1
- package/src/components/scratchpad/scratchpad.tsx +4 -7
- package/src/components/slides/__tests__/minimap-actions.test.tsx +9 -25
- package/src/components/slides/minimap.tsx +7 -3
- package/src/components/slides/slide-cell-view.tsx +3 -3
- package/src/components/sort/SortableCellsProvider.tsx +2 -1
- package/src/core/ai/__tests__/staged-cells.test.ts +18 -20
- package/src/core/ai/staged-cells.ts +8 -16
- package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +2 -2
- package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +2 -2
- package/src/core/ai/tools/base.ts +2 -2
- package/src/core/ai/tools/edit-notebook-tool.ts +13 -21
- package/src/core/cells/__tests__/actions.test.ts +474 -34
- package/src/core/cells/__tests__/add-missing-import.test.ts +7 -8
- package/src/core/cells/__tests__/cells.test.ts +251 -861
- package/src/core/cells/__tests__/document.test.ts +42 -38
- package/src/core/cells/__tests__/notebook-outline.test.ts +7 -1
- package/src/core/cells/__tests__/outputs.test.tsx +71 -53
- package/src/core/cells/actions.ts +391 -3
- package/src/core/cells/add-missing-import.ts +46 -42
- package/src/core/cells/cells.ts +73 -444
- package/src/core/cells/document.ts +38 -6
- package/src/core/cells/outputs.ts +12 -28
- package/src/core/codemirror/cells/extensions.ts +6 -6
- package/src/core/codemirror/cells/state.ts +1 -1
- package/src/core/codemirror/keymaps/vim.ts +2 -2
- package/src/core/edit-app.tsx +20 -25
- package/src/core/islands/__tests__/bootstrap.test.ts +56 -0
- package/src/core/islands/__tests__/bridge.test.ts +42 -0
- package/src/core/islands/bootstrap.ts +24 -1
- package/src/core/islands/bridge.ts +46 -5
- package/src/core/kernel/__tests__/handlers.test.ts +7 -153
- package/src/core/kernel/handlers.ts +18 -59
- package/src/core/network/__tests__/requests-lazy.test.ts +1 -3
- package/src/core/network/requests-lazy.ts +0 -2
- package/src/core/network/requests-network.ts +0 -16
- package/src/core/network/requests-static.ts +0 -2
- package/src/core/network/requests-toasting.tsx +0 -2
- package/src/core/network/types.ts +0 -7
- package/src/core/notebook/document/__tests__/codemirror.test.ts +5 -0
- package/src/core/notebook/document/__tests__/draft.test.ts +41 -0
- package/src/core/notebook/document/__tests__/state.test.ts +21 -0
- package/src/core/notebook/document/draft.ts +35 -5
- package/src/core/notebook/document/index.ts +1 -0
- package/src/core/notebook/document/state.ts +6 -2
- package/src/core/run-app.tsx +1 -7
- package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +0 -6
- package/src/core/session/useNotebookSession.tsx +28 -35
- package/src/core/wasm/bridge.ts +0 -16
- package/src/css/globals.css +56 -22
- 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 {
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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(
|
|
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
|
-
|
|
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. */
|
|
@@ -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 (
|
|
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 =
|
|
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);
|