@marimo-team/islands 0.25.3-dev173 → 0.25.3-dev176
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/{ErrorBoundary-wT3f3iJ1.js → ErrorBoundary-D66f-xAE.js} +1 -1
- package/dist/{any-language-editor-B92WMzoC.js → any-language-editor-BVFHDDQ3.js} +2 -2
- package/dist/{chat-ui-CuORr6fL.js → chat-ui-X-cWrL9u.js} +1222 -1222
- package/dist/{code-visibility-ZReaeMaF.js → code-visibility-CVVeTAwi.js} +2 -2
- package/dist/{common-DCevHg1m.js → common-DNlm9Cdj.js} +1074 -1078
- package/dist/{data-grid-overlay-editor-CTC-bJus.js → data-grid-overlay-editor-BArE3_zi.js} +1 -1
- 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/{error-entries-CmWMEsY_.js → error-entries-mpoD5t79.js} +4939 -4983
- 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-DwXkmewo.js → feedback-button-CyvqdxXN.js} +21 -21
- package/dist/{glide-data-editor-BlmcP-lw.js → glide-data-editor-BxvYRMC3.js} +3 -3
- package/dist/{globals-BdRitZ4p.js → globals-BJla9q8e.js} +1 -1
- package/dist/{html-to-image-bpiQh7ND.js → html-to-image-BoZthB4P.js} +454 -445
- package/dist/main.js +3587 -3587
- package/dist/{number-overlay-editor-DQG-hFHT.js → number-overlay-editor-BWg7xiRF.js} +1 -1
- package/dist/{process-output-BzloOI7F.js → process-output-DLS4Mqg_.js} +2 -2
- package/dist/{reveal-component-D3r3vSTK.js → reveal-component-feAltY24.js} +3 -3
- 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 +171 -813
- package/src/core/cells/__tests__/document.test.ts +5 -35
- 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 +38 -413
- package/src/core/cells/document.ts +7 -4
- 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 +19 -27
- package/src/core/islands/bridge.ts +0 -2
- package/src/core/kernel/handlers.ts +3 -3
- 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/session/useNotebookSession.tsx +6 -2
- package/src/core/wasm/bridge.ts +0 -16
- package/src/css/globals.css +56 -22
- package/src/stories/runtime-story.tsx +22 -15
|
@@ -27,7 +27,7 @@ import type { CellConfig } from "../network/types";
|
|
|
27
27
|
import { canUseRtc, isRtcEnabled } from "../rtc/state";
|
|
28
28
|
import { store } from "../state/jotai";
|
|
29
29
|
import { activeDocumentModelAtom } from "../session/active-notebook";
|
|
30
|
-
import type {
|
|
30
|
+
import type { NotebookState, ReducerCellActions } from "./cells";
|
|
31
31
|
import { groupByColumn } from "./columns";
|
|
32
32
|
import type { CellId } from "./ids";
|
|
33
33
|
import {
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
isEdited,
|
|
39
39
|
} from "./types";
|
|
40
40
|
|
|
41
|
-
type CellAction = DispatchedActionOf<
|
|
41
|
+
type CellAction = DispatchedActionOf<ReducerCellActions>;
|
|
42
42
|
|
|
43
43
|
// Following the document ----------------------------------------------------
|
|
44
44
|
|
|
@@ -126,14 +126,17 @@ export function applyDocument(
|
|
|
126
126
|
continue;
|
|
127
127
|
}
|
|
128
128
|
if (current === undefined) {
|
|
129
|
-
// A new cell
|
|
129
|
+
// A new cell with code is stale until it runs. The kernel's last run
|
|
130
|
+
// settles it on kernel-ready.
|
|
131
|
+
const code = cell.code ?? "";
|
|
130
132
|
cellData = {
|
|
131
133
|
...cellData,
|
|
132
134
|
[id]: createCell({
|
|
133
135
|
id,
|
|
134
|
-
code
|
|
136
|
+
code,
|
|
135
137
|
name: cell.name,
|
|
136
138
|
config: createCellConfig(cell.config),
|
|
139
|
+
edited: code.trim().length > 0,
|
|
137
140
|
}),
|
|
138
141
|
};
|
|
139
142
|
cellRuntime = { ...cellRuntime, [id]: createCellRuntimeState() };
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { useAtomValue } from "jotai";
|
|
4
|
-
import {
|
|
5
|
-
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
6
5
|
import { useEvent } from "@/hooks/useEvent";
|
|
7
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
useNotebookCellConfig,
|
|
8
|
+
useNotebookDocumentEdit,
|
|
9
|
+
} from "../notebook/document";
|
|
8
10
|
import type { OutputMessage } from "../kernel/messages";
|
|
9
11
|
import { viewStateAtom } from "../mode";
|
|
10
|
-
import {
|
|
11
|
-
import { cellDataAtom, useCellActions } from "./cells";
|
|
12
|
-
import type { CellId } from "./ids";
|
|
12
|
+
import { type CellId, SCRATCH_CELL_ID } from "./ids";
|
|
13
13
|
|
|
14
14
|
// This does not need to be overcomplicated. We can just store the expanded
|
|
15
15
|
// state in a global map instead of Jotai since state is not shared between cells.
|
|
@@ -27,20 +27,9 @@ const expandedConsoleOutputs: Record<CellId, boolean> = {};
|
|
|
27
27
|
*/
|
|
28
28
|
export function useExpandedOutput(cellId: CellId) {
|
|
29
29
|
const { mode } = useAtomValue(viewStateAtom);
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const isConfigExpanded = useAtomValue(
|
|
34
|
-
useMemo(
|
|
35
|
-
() =>
|
|
36
|
-
selectAtom(
|
|
37
|
-
cellDataAtom(cellId),
|
|
38
|
-
(data) => data?.config.expand_output ?? false,
|
|
39
|
-
Object.is,
|
|
40
|
-
),
|
|
41
|
-
[cellId],
|
|
42
|
-
),
|
|
43
|
-
);
|
|
30
|
+
const edit = useNotebookDocumentEdit();
|
|
31
|
+
const isConfigExpanded =
|
|
32
|
+
useNotebookCellConfig(cellId)?.expand_output ?? false;
|
|
44
33
|
const [isSessionExpanded, setIsSessionExpanded] = useState(
|
|
45
34
|
() => expandedOutputs[cellId] ?? isConfigExpanded,
|
|
46
35
|
);
|
|
@@ -55,16 +44,11 @@ export function useExpandedOutput(cellId: CellId) {
|
|
|
55
44
|
|
|
56
45
|
const setIsExpanded = useEvent((expanded: boolean) => {
|
|
57
46
|
setIsSessionExpanded(expanded);
|
|
58
|
-
|
|
47
|
+
// The scratchpad is not in the document.
|
|
48
|
+
if (!isEditable || cellId === SCRATCH_CELL_ID) {
|
|
59
49
|
return;
|
|
60
50
|
}
|
|
61
|
-
|
|
62
|
-
updateCellConfig({ cellId, config });
|
|
63
|
-
getRequestClient()
|
|
64
|
-
.saveCellConfig({ configs: { [cellId]: config } })
|
|
65
|
-
.catch((error) => {
|
|
66
|
-
Logger.error("Failed to save expand_output config", error);
|
|
67
|
-
});
|
|
51
|
+
edit((tx) => tx.updateConfig(cellId, { expand_output: expanded }));
|
|
68
52
|
});
|
|
69
53
|
|
|
70
54
|
return [isExpanded, setIsExpanded] as const;
|
|
@@ -190,7 +190,7 @@ function cellKeymaps({
|
|
|
190
190
|
|
|
191
191
|
if (isAtStartOfEditor(ev)) {
|
|
192
192
|
const actions = ev.state.facet(cellActionsState);
|
|
193
|
-
actions.moveToNextCell({ cellId, before: true,
|
|
193
|
+
actions.moveToNextCell({ cellId, before: true, create: false });
|
|
194
194
|
return true;
|
|
195
195
|
}
|
|
196
196
|
return false;
|
|
@@ -207,7 +207,7 @@ function cellKeymaps({
|
|
|
207
207
|
|
|
208
208
|
if (isAtEndOfEditor(ev)) {
|
|
209
209
|
const actions = ev.state.facet(cellActionsState);
|
|
210
|
-
actions.moveToNextCell({ cellId, before: false,
|
|
210
|
+
actions.moveToNextCell({ cellId, before: false, create: false });
|
|
211
211
|
return true;
|
|
212
212
|
}
|
|
213
213
|
return false;
|
|
@@ -219,7 +219,7 @@ function cellKeymaps({
|
|
|
219
219
|
stopPropagation: true,
|
|
220
220
|
run: (ev) => {
|
|
221
221
|
const actions = ev.state.facet(cellActionsState);
|
|
222
|
-
actions.moveToNextCell({ cellId, before: false,
|
|
222
|
+
actions.moveToNextCell({ cellId, before: false, create: false });
|
|
223
223
|
return true;
|
|
224
224
|
},
|
|
225
225
|
},
|
|
@@ -229,7 +229,7 @@ function cellKeymaps({
|
|
|
229
229
|
stopPropagation: true,
|
|
230
230
|
run: (ev) => {
|
|
231
231
|
const actions = ev.state.facet(cellActionsState);
|
|
232
|
-
actions.moveToNextCell({ cellId, before: true,
|
|
232
|
+
actions.moveToNextCell({ cellId, before: true, create: false });
|
|
233
233
|
return true;
|
|
234
234
|
},
|
|
235
235
|
},
|
|
@@ -260,7 +260,7 @@ function cellKeymaps({
|
|
|
260
260
|
run: (ev) => {
|
|
261
261
|
ev.contentDOM.blur();
|
|
262
262
|
const actions = ev.state.facet(cellActionsState);
|
|
263
|
-
actions.
|
|
263
|
+
actions.createCell({ before: cellId });
|
|
264
264
|
return true;
|
|
265
265
|
},
|
|
266
266
|
},
|
|
@@ -271,7 +271,7 @@ function cellKeymaps({
|
|
|
271
271
|
run: (ev) => {
|
|
272
272
|
ev.contentDOM.blur();
|
|
273
273
|
const actions = ev.state.facet(cellActionsState);
|
|
274
|
-
actions.
|
|
274
|
+
actions.createCell({ after: cellId });
|
|
275
275
|
return true;
|
|
276
276
|
},
|
|
277
277
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { Facet } from "@codemirror/state";
|
|
4
|
-
import type { CellActions } from "@/core/cells/
|
|
4
|
+
import type { CellActions } from "@/core/cells/actions";
|
|
5
5
|
import type { CellId } from "@/core/cells/ids";
|
|
6
6
|
|
|
7
7
|
export interface CodemirrorCellActions extends CellActions {
|
|
@@ -37,7 +37,7 @@ export function vimKeymapExtension(): Extension[] {
|
|
|
37
37
|
if (isAtEndOfEditor(ev, true) && isInVimNormalMode(ev)) {
|
|
38
38
|
const actions = ev.state.facet(cellActionsState);
|
|
39
39
|
const cellId = ev.state.facet(cellIdState);
|
|
40
|
-
actions.moveToNextCell({ cellId, before: false,
|
|
40
|
+
actions.moveToNextCell({ cellId, before: false, create: false });
|
|
41
41
|
return true;
|
|
42
42
|
}
|
|
43
43
|
return false;
|
|
@@ -51,7 +51,7 @@ export function vimKeymapExtension(): Extension[] {
|
|
|
51
51
|
if (isAtStartOfEditor(ev) && isInVimNormalMode(ev)) {
|
|
52
52
|
const actions = ev.state.facet(cellActionsState);
|
|
53
53
|
const cellId = ev.state.facet(cellIdState);
|
|
54
|
-
actions.moveToNextCell({ cellId, before: true,
|
|
54
|
+
actions.moveToNextCell({ cellId, before: true, create: false });
|
|
55
55
|
return true;
|
|
56
56
|
}
|
|
57
57
|
return false;
|
package/src/core/edit-app.tsx
CHANGED
|
@@ -5,8 +5,8 @@ import { Tooltip } from "radix-ui";
|
|
|
5
5
|
|
|
6
6
|
const TooltipProvider = Tooltip.Provider;
|
|
7
7
|
|
|
8
|
-
import { useAtomValue
|
|
9
|
-
import { useEffect } from "react";
|
|
8
|
+
import { useAtomValue } from "jotai";
|
|
9
|
+
import { lazy, useEffect } from "react";
|
|
10
10
|
import { NotStartedConnectionAlert } from "@/components/editor/alerts/connecting-alert";
|
|
11
11
|
import { Controls } from "@/components/editor/controls/Controls";
|
|
12
12
|
import { SandboxController } from "@/components/editor/chrome/panels/sandbox-controller";
|
|
@@ -29,24 +29,23 @@ import { CellsRenderer } from "../components/editor/renderers/cells-renderer";
|
|
|
29
29
|
import { useHotkey } from "../hooks/useHotkey";
|
|
30
30
|
import {
|
|
31
31
|
hasCellsAtom,
|
|
32
|
-
notebookAtom,
|
|
33
32
|
notebookIsRunningAtom,
|
|
34
33
|
numColumnsAtom,
|
|
35
|
-
useCellActions,
|
|
36
34
|
} from "./cells/cells";
|
|
37
|
-
import {
|
|
35
|
+
import { useCellActions } from "./cells/actions";
|
|
38
36
|
import type { AppConfig, UserConfig } from "./config/config-schema";
|
|
39
|
-
import { DocumentProvider } from "./notebook/document";
|
|
40
37
|
import { RuntimeState } from "./kernel/RuntimeState";
|
|
41
|
-
import { getSessionId } from "./kernel/session";
|
|
42
38
|
import { useTogglePresenting } from "./layout/useTogglePresenting";
|
|
43
39
|
import { viewStateAtom } from "./mode";
|
|
44
40
|
import { useRequestClient } from "./network/requests";
|
|
45
41
|
import { useFilename } from "./saving/filename";
|
|
46
|
-
import {
|
|
47
|
-
import { useNotebookSession } from "./session/useNotebookSession";
|
|
42
|
+
import type { ConnectionStatus } from "./session/types";
|
|
48
43
|
|
|
49
|
-
|
|
44
|
+
const LazyCommandPalette = lazy(
|
|
45
|
+
() => import("../components/editor/controls/command-palette"),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export interface EditAppProps {
|
|
50
49
|
/**
|
|
51
50
|
* The user config.
|
|
52
51
|
*/
|
|
@@ -59,20 +58,24 @@ interface AppProps {
|
|
|
59
58
|
* If true, the floating controls will be hidden.
|
|
60
59
|
*/
|
|
61
60
|
hideControls?: boolean;
|
|
61
|
+
/** The session's connection, which the page owns with the document model. */
|
|
62
|
+
connection: ConnectionStatus;
|
|
63
|
+
reconnect: () => Promise<void>;
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
/** The notebook editor. Rendered inside the page's `DocumentProvider`. */
|
|
67
|
+
export const EditApp: React.FC<EditAppProps> = ({
|
|
65
68
|
userConfig,
|
|
66
69
|
appConfig,
|
|
67
70
|
hideControls = false,
|
|
71
|
+
connection,
|
|
72
|
+
reconnect,
|
|
68
73
|
}) => {
|
|
69
74
|
const { mergeAllColumns, collapseAllCells, expandAllCells } =
|
|
70
75
|
useCellActions();
|
|
71
|
-
const store = useStore();
|
|
72
76
|
const viewState = useAtomValue(viewStateAtom);
|
|
73
77
|
const numColumns = useAtomValue(numColumnsAtom);
|
|
74
78
|
const filename = useFilename();
|
|
75
|
-
const setLastSavedNotebook = useSetAtom(lastSavedNotebookAtom);
|
|
76
79
|
const { sendComponentValues, sendInterrupt } = useRequestClient();
|
|
77
80
|
|
|
78
81
|
const isEditing = viewState.mode === "edit";
|
|
@@ -87,18 +90,6 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
87
90
|
};
|
|
88
91
|
}, [sendComponentValues]);
|
|
89
92
|
|
|
90
|
-
const { connection, reconnect, documentModel } = useNotebookSession({
|
|
91
|
-
autoInstantiate: userConfig.runtime.auto_instantiate,
|
|
92
|
-
onKernelReady: (layout) => {
|
|
93
|
-
const cells = notebookCells(store.get(notebookAtom));
|
|
94
|
-
const names = cells.map((cell) => cell.name);
|
|
95
|
-
const codes = cells.map((cell) => cell.code);
|
|
96
|
-
const configs = cells.map((cell) => cell.config);
|
|
97
|
-
setLastSavedNotebook({ names, codes, configs, layout });
|
|
98
|
-
},
|
|
99
|
-
sessionId: getSessionId(),
|
|
100
|
-
});
|
|
101
|
-
|
|
102
93
|
// Update document title whenever filename or app_title changes
|
|
103
94
|
useEffect(() => {
|
|
104
95
|
// Set document title: app_title takes precedence, then filename, then default
|
|
@@ -178,7 +169,7 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
178
169
|
);
|
|
179
170
|
|
|
180
171
|
return (
|
|
181
|
-
|
|
172
|
+
<>
|
|
182
173
|
<SandboxController onReconnect={reconnect} />
|
|
183
174
|
<AppContainer
|
|
184
175
|
connection={connection}
|
|
@@ -232,6 +223,7 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
232
223
|
/>
|
|
233
224
|
</TooltipProvider>
|
|
234
225
|
)}
|
|
235
|
-
|
|
226
|
+
<LazyCommandPalette />
|
|
227
|
+
</>
|
|
236
228
|
);
|
|
237
229
|
};
|
|
@@ -383,12 +383,10 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
383
383
|
public sendInterrupt = throwNotImplemented;
|
|
384
384
|
public sendShutdown = throwNotImplemented;
|
|
385
385
|
public sendFormat = throwNotImplemented;
|
|
386
|
-
public sendDeleteCell = throwNotImplemented;
|
|
387
386
|
public sendInstallMissingPackages = throwNotImplemented;
|
|
388
387
|
public sendCodeCompletionRequest = throwNotImplemented;
|
|
389
388
|
public saveUserConfig = throwNotImplemented;
|
|
390
389
|
public saveAppConfig = throwNotImplemented;
|
|
391
|
-
public saveCellConfig = throwNotImplemented;
|
|
392
390
|
public sendRestart = throwNotImplemented;
|
|
393
391
|
public readCode = throwNotImplemented;
|
|
394
392
|
public readSnippets = throwNotImplemented;
|
|
@@ -3,7 +3,7 @@ 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 {
|
|
6
|
+
import type { ReducerCellActions } from "../cells/cells";
|
|
7
7
|
import type { CellData } from "../cells/types";
|
|
8
8
|
import { type AppConfig, AppConfigSchema } from "../config/config-schema";
|
|
9
9
|
import { UI_ELEMENT_REGISTRY } from "../dom/uiregistry";
|
|
@@ -63,8 +63,8 @@ export function handleKernelReady(
|
|
|
63
63
|
autoInstantiate: boolean;
|
|
64
64
|
/** The notebook's cells, as the host document provided them. */
|
|
65
65
|
cells: CellData[];
|
|
66
|
-
setLastRun:
|
|
67
|
-
resetCellRuntimes:
|
|
66
|
+
setLastRun: ReducerCellActions["setLastRun"];
|
|
67
|
+
resetCellRuntimes: ReducerCellActions["resetCellRuntimes"];
|
|
68
68
|
setLayoutData: (payload: {
|
|
69
69
|
layoutView: LayoutType;
|
|
70
70
|
data: LayoutData;
|
|
@@ -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);
|