@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
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
import type * as Protocol from "@marimo-team/client/protocol";
|
|
12
12
|
import { dequal as isEqual } from "dequal";
|
|
13
|
+
import type { createStore } from "jotai";
|
|
13
14
|
import { createRef, type RefObject } from "react";
|
|
14
15
|
import type { CellHandle } from "@/components/editor/notebook-cell";
|
|
15
16
|
import type {
|
|
16
17
|
DocumentEdit,
|
|
17
18
|
DocumentEditor,
|
|
19
|
+
DocumentModel,
|
|
18
20
|
EditableCell,
|
|
19
21
|
EditableDocument,
|
|
20
22
|
} from "@/core/notebook/document";
|
|
@@ -25,7 +27,7 @@ import type { CellConfig } from "../network/types";
|
|
|
25
27
|
import { canUseRtc, isRtcEnabled } from "../rtc/state";
|
|
26
28
|
import { store } from "../state/jotai";
|
|
27
29
|
import { activeDocumentModelAtom } from "../session/active-notebook";
|
|
28
|
-
import type {
|
|
30
|
+
import type { NotebookState, ReducerCellActions } from "./cells";
|
|
29
31
|
import { groupByColumn } from "./columns";
|
|
30
32
|
import type { CellId } from "./ids";
|
|
31
33
|
import {
|
|
@@ -36,7 +38,7 @@ import {
|
|
|
36
38
|
isEdited,
|
|
37
39
|
} from "./types";
|
|
38
40
|
|
|
39
|
-
type CellAction = DispatchedActionOf<
|
|
41
|
+
type CellAction = DispatchedActionOf<ReducerCellActions>;
|
|
40
42
|
|
|
41
43
|
// Following the document ----------------------------------------------------
|
|
42
44
|
|
|
@@ -124,14 +126,17 @@ export function applyDocument(
|
|
|
124
126
|
continue;
|
|
125
127
|
}
|
|
126
128
|
if (current === undefined) {
|
|
127
|
-
// 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 ?? "";
|
|
128
132
|
cellData = {
|
|
129
133
|
...cellData,
|
|
130
134
|
[id]: createCell({
|
|
131
135
|
id,
|
|
132
|
-
code
|
|
136
|
+
code,
|
|
133
137
|
name: cell.name,
|
|
134
138
|
config: createCellConfig(cell.config),
|
|
139
|
+
edited: code.trim().length > 0,
|
|
135
140
|
}),
|
|
136
141
|
};
|
|
137
142
|
cellRuntime = { ...cellRuntime, [id]: createCellRuntimeState() };
|
|
@@ -165,6 +170,33 @@ export function applyDocument(
|
|
|
165
170
|
return { ...state, cellIds, cellData, cellRuntime, cellHandles };
|
|
166
171
|
}
|
|
167
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Reconcile the model's document now and on each change, until the signal aborts.
|
|
175
|
+
*
|
|
176
|
+
* TODO(MO-8028): Delete with `applyDocument`.
|
|
177
|
+
*/
|
|
178
|
+
export function followDocument({
|
|
179
|
+
store,
|
|
180
|
+
model,
|
|
181
|
+
reconcile,
|
|
182
|
+
signal,
|
|
183
|
+
}: {
|
|
184
|
+
store: ReturnType<typeof createStore>;
|
|
185
|
+
model: Pick<DocumentModel, "documentAtom">;
|
|
186
|
+
reconcile: (document: EditableDocument) => void;
|
|
187
|
+
signal: AbortSignal;
|
|
188
|
+
}): void {
|
|
189
|
+
const follow = () => {
|
|
190
|
+
const document = store.get(model.documentAtom);
|
|
191
|
+
if (document !== null) {
|
|
192
|
+
reconcile(document);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const unsubscribe = store.sub(model.documentAtom, follow);
|
|
196
|
+
signal.addEventListener("abort", unsubscribe, { once: true });
|
|
197
|
+
follow();
|
|
198
|
+
}
|
|
199
|
+
|
|
168
200
|
// Editing the document ------------------------------------------------------
|
|
169
201
|
|
|
170
202
|
const CONFIG_FIELDS = ["disabled", "hide_code", "expand_output"] as const;
|
|
@@ -291,8 +323,8 @@ export function documentEditMiddleware(
|
|
|
291
323
|
next: NotebookState,
|
|
292
324
|
action: CellAction,
|
|
293
325
|
): void {
|
|
294
|
-
// The
|
|
295
|
-
if (action.type === "
|
|
326
|
+
// The document replaces the notebook's cells, so following it is no edit.
|
|
327
|
+
if (action.type === "reconcileDocument") {
|
|
296
328
|
return;
|
|
297
329
|
}
|
|
298
330
|
if (prev.cellIds === next.cellIds && prev.cellData === next.cellData) {
|
|
@@ -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";
|
|
@@ -31,20 +31,21 @@ import {
|
|
|
31
31
|
hasCellsAtom,
|
|
32
32
|
notebookIsRunningAtom,
|
|
33
33
|
numColumnsAtom,
|
|
34
|
-
useCellActions,
|
|
35
34
|
} from "./cells/cells";
|
|
35
|
+
import { useCellActions } from "./cells/actions";
|
|
36
36
|
import type { AppConfig, UserConfig } from "./config/config-schema";
|
|
37
|
-
import { DocumentProvider } from "./notebook/document";
|
|
38
37
|
import { RuntimeState } from "./kernel/RuntimeState";
|
|
39
|
-
import { getSessionId } from "./kernel/session";
|
|
40
38
|
import { useTogglePresenting } from "./layout/useTogglePresenting";
|
|
41
39
|
import { viewStateAtom } from "./mode";
|
|
42
40
|
import { useRequestClient } from "./network/requests";
|
|
43
41
|
import { useFilename } from "./saving/filename";
|
|
44
|
-
import {
|
|
45
|
-
import { useNotebookSession } from "./session/useNotebookSession";
|
|
42
|
+
import type { ConnectionStatus } from "./session/types";
|
|
46
43
|
|
|
47
|
-
|
|
44
|
+
const LazyCommandPalette = lazy(
|
|
45
|
+
() => import("../components/editor/controls/command-palette"),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export interface EditAppProps {
|
|
48
49
|
/**
|
|
49
50
|
* The user config.
|
|
50
51
|
*/
|
|
@@ -57,19 +58,24 @@ interface AppProps {
|
|
|
57
58
|
* If true, the floating controls will be hidden.
|
|
58
59
|
*/
|
|
59
60
|
hideControls?: boolean;
|
|
61
|
+
/** The session's connection, which the page owns with the document model. */
|
|
62
|
+
connection: ConnectionStatus;
|
|
63
|
+
reconnect: () => Promise<void>;
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
/** The notebook editor. Rendered inside the page's `DocumentProvider`. */
|
|
67
|
+
export const EditApp: React.FC<EditAppProps> = ({
|
|
63
68
|
userConfig,
|
|
64
69
|
appConfig,
|
|
65
70
|
hideControls = false,
|
|
71
|
+
connection,
|
|
72
|
+
reconnect,
|
|
66
73
|
}) => {
|
|
67
|
-
const {
|
|
74
|
+
const { mergeAllColumns, collapseAllCells, expandAllCells } =
|
|
68
75
|
useCellActions();
|
|
69
76
|
const viewState = useAtomValue(viewStateAtom);
|
|
70
77
|
const numColumns = useAtomValue(numColumnsAtom);
|
|
71
78
|
const filename = useFilename();
|
|
72
|
-
const setLastSavedNotebook = useSetAtom(lastSavedNotebookAtom);
|
|
73
79
|
const { sendComponentValues, sendInterrupt } = useRequestClient();
|
|
74
80
|
|
|
75
81
|
const isEditing = viewState.mode === "edit";
|
|
@@ -84,18 +90,6 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
84
90
|
};
|
|
85
91
|
}, [sendComponentValues]);
|
|
86
92
|
|
|
87
|
-
const { connection, reconnect, documentModel } = useNotebookSession({
|
|
88
|
-
autoInstantiate: userConfig.runtime.auto_instantiate,
|
|
89
|
-
setCells: (cells, layout) => {
|
|
90
|
-
setCells(cells);
|
|
91
|
-
const names = cells.map((cell) => cell.name);
|
|
92
|
-
const codes = cells.map((cell) => cell.code);
|
|
93
|
-
const configs = cells.map((cell) => cell.config);
|
|
94
|
-
setLastSavedNotebook({ names, codes, configs, layout });
|
|
95
|
-
},
|
|
96
|
-
sessionId: getSessionId(),
|
|
97
|
-
});
|
|
98
|
-
|
|
99
93
|
// Update document title whenever filename or app_title changes
|
|
100
94
|
useEffect(() => {
|
|
101
95
|
// Set document title: app_title takes precedence, then filename, then default
|
|
@@ -175,7 +169,7 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
175
169
|
);
|
|
176
170
|
|
|
177
171
|
return (
|
|
178
|
-
|
|
172
|
+
<>
|
|
179
173
|
<SandboxController onReconnect={reconnect} />
|
|
180
174
|
<AppContainer
|
|
181
175
|
connection={connection}
|
|
@@ -229,6 +223,7 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
229
223
|
/>
|
|
230
224
|
</TooltipProvider>
|
|
231
225
|
)}
|
|
232
|
-
|
|
226
|
+
<LazyCommandPalette />
|
|
227
|
+
</>
|
|
233
228
|
);
|
|
234
229
|
};
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
+
import type { NotebookTransport } from "@marimo-team/client";
|
|
3
4
|
import { createStore } from "jotai";
|
|
4
5
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { cellId } from "@/__tests__/branded";
|
|
7
|
+
import { notebookAtom } from "@/core/cells/cells";
|
|
8
|
+
import {
|
|
9
|
+
fakeBackend,
|
|
10
|
+
fixtureMessages,
|
|
11
|
+
} from "@/core/notebook/replica/__tests__/fixtures";
|
|
5
12
|
import { RuntimeState } from "@/core/kernel/RuntimeState";
|
|
6
13
|
import { initializeIslands } from "../bootstrap";
|
|
7
14
|
import type { IslandsPyodideBridge } from "../bridge";
|
|
@@ -32,6 +39,7 @@ describe("initializeIslands", () => {
|
|
|
32
39
|
consumeMessage = consumer;
|
|
33
40
|
},
|
|
34
41
|
sendComponentValues: vi.fn(),
|
|
42
|
+
notebookBackend: fakeBackend(),
|
|
35
43
|
} as unknown as IslandsPyodideBridge;
|
|
36
44
|
const store = createStore();
|
|
37
45
|
vi.spyOn(RuntimeState.INSTANCE, "start").mockImplementation(
|
|
@@ -59,3 +67,51 @@ describe("initializeIslands", () => {
|
|
|
59
67
|
expect(store.get(islandsHydratedAtom)).toBe(true);
|
|
60
68
|
});
|
|
61
69
|
});
|
|
70
|
+
|
|
71
|
+
describe("initializeIslands document", () => {
|
|
72
|
+
afterEach(vi.restoreAllMocks);
|
|
73
|
+
|
|
74
|
+
it("fills the notebook's cells from the managed session's host document", async () => {
|
|
75
|
+
const root = document.createElement("div");
|
|
76
|
+
root.innerHTML =
|
|
77
|
+
'<marimo-island data-app-id="app-1" data-reactive="true"></marimo-island>';
|
|
78
|
+
let handlers: Parameters<NotebookTransport>[1] | undefined;
|
|
79
|
+
const transport: NotebookTransport = (_attempt, opened) =>
|
|
80
|
+
new Promise(() => {
|
|
81
|
+
handlers = opened;
|
|
82
|
+
opened.open();
|
|
83
|
+
});
|
|
84
|
+
const bridge = {
|
|
85
|
+
consumeNotifications: vi.fn(),
|
|
86
|
+
sendComponentValues: vi.fn(),
|
|
87
|
+
notebookBackend: fakeBackend({ transport }),
|
|
88
|
+
} as unknown as IslandsPyodideBridge;
|
|
89
|
+
const store = createStore();
|
|
90
|
+
vi.spyOn(RuntimeState.INSTANCE, "start").mockImplementation(
|
|
91
|
+
() => undefined,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
await initializeIslands({
|
|
95
|
+
bridge,
|
|
96
|
+
store,
|
|
97
|
+
root,
|
|
98
|
+
autoInitializePlugins: false,
|
|
99
|
+
});
|
|
100
|
+
await vi.waitFor(() => expect(handlers).toBeDefined());
|
|
101
|
+
for (const message of fixtureMessages("notebook-events.sse")) {
|
|
102
|
+
handlers?.message(message);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const notebook = store.get(notebookAtom);
|
|
106
|
+
expect(notebook.cellIds.inOrderIds).toEqual(["c1", "c3", "k7f2"]);
|
|
107
|
+
expect(notebook.cellData[cellId("c3")].code).toBe("x = 21");
|
|
108
|
+
expect(notebook.cellRuntime[cellId("c3")]).toBeDefined();
|
|
109
|
+
|
|
110
|
+
handlers?.message({ event: "reset", data: {} });
|
|
111
|
+
expect(store.get(notebookAtom).cellIds.inOrderIds).toEqual([
|
|
112
|
+
"c1",
|
|
113
|
+
"c3",
|
|
114
|
+
"k7f2",
|
|
115
|
+
]);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -396,6 +396,48 @@ describe("IslandsPyodideBridge", () => {
|
|
|
396
396
|
expect(consumeMessage.mock.calls).toEqual([notification]);
|
|
397
397
|
});
|
|
398
398
|
|
|
399
|
+
it("forwards the managed session's host events to the notebook stream", async () => {
|
|
400
|
+
await initializeSingleApp();
|
|
401
|
+
const handlers = { open: vi.fn(), message: vi.fn() };
|
|
402
|
+
void bridge.notebookBackend.transport(
|
|
403
|
+
{ signal: new AbortController().signal, cursor: null },
|
|
404
|
+
handlers,
|
|
405
|
+
);
|
|
406
|
+
expect(handlers.open).toHaveBeenCalledOnce();
|
|
407
|
+
|
|
408
|
+
const cell = { event: "cell", id: "1", data: { cell: { id: "c1" } } };
|
|
409
|
+
sendNotebookEvent(1, cell);
|
|
410
|
+
sendNotebookEvent(2, { event: "ready", id: "2", data: {} });
|
|
411
|
+
sendKernelMessage(1);
|
|
412
|
+
|
|
413
|
+
expect(handlers.message.mock.calls).toEqual([[cell]]);
|
|
414
|
+
expect(() => bridge.notebookBackend.notebook("n1")).toThrow(
|
|
415
|
+
"Islands do not edit",
|
|
416
|
+
);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("resets the notebook stream when the managed session is replaced or stopped", async () => {
|
|
420
|
+
await initializeSingleApp();
|
|
421
|
+
const handlers = { open: vi.fn(), message: vi.fn() };
|
|
422
|
+
void bridge.notebookBackend.transport(
|
|
423
|
+
{ signal: new AbortController().signal, cursor: null },
|
|
424
|
+
handlers,
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
mockSingleApp("generated app 2");
|
|
428
|
+
await bridge.initializeApps();
|
|
429
|
+
const ready = { event: "ready", id: "2", data: {} };
|
|
430
|
+
sendNotebookEvent(1, { event: "ready", id: "1", data: {} });
|
|
431
|
+
sendNotebookEvent(2, ready);
|
|
432
|
+
await bridge.stopSession("app-1");
|
|
433
|
+
|
|
434
|
+
expect(handlers.message.mock.calls).toEqual([
|
|
435
|
+
[{ event: "reset", data: {} }],
|
|
436
|
+
[ready],
|
|
437
|
+
[{ event: "reset", data: {} }],
|
|
438
|
+
]);
|
|
439
|
+
});
|
|
440
|
+
|
|
399
441
|
it("holds controls until the first app session is ready", async () => {
|
|
400
442
|
setSingleApp();
|
|
401
443
|
|
|
@@ -17,10 +17,14 @@ import {
|
|
|
17
17
|
notebookAtom,
|
|
18
18
|
notebookReducer,
|
|
19
19
|
} from "../cells/cells";
|
|
20
|
+
import { followDocument } from "../cells/document";
|
|
21
|
+
import { notebookCells } from "../cells/utils";
|
|
22
|
+
import { createDocumentModel } from "../notebook/document";
|
|
20
23
|
import { defineCustomElement } from "../dom/defineCustomElement";
|
|
21
24
|
import { MarimoValueInputEvent } from "../dom/events";
|
|
22
25
|
import { UI_ELEMENT_REGISTRY } from "../dom/uiregistry";
|
|
23
26
|
import { FUNCTIONS_REGISTRY } from "../functions/FunctionRegistry";
|
|
27
|
+
import { createNotebookReplica } from "../notebook/replica";
|
|
24
28
|
import {
|
|
25
29
|
handleCellNotificationeration,
|
|
26
30
|
handleKernelReady,
|
|
@@ -124,6 +128,23 @@ export async function initializeIslands(
|
|
|
124
128
|
handleNotification(notification, actions, store, sessionGeneration);
|
|
125
129
|
});
|
|
126
130
|
|
|
131
|
+
// The notebook's cells follow the managed session's host document.
|
|
132
|
+
const replica = createNotebookReplica(store);
|
|
133
|
+
replica.connect(bridge.notebookBackend, { onNotification: Functions.NOOP });
|
|
134
|
+
const documentModel = createDocumentModel({
|
|
135
|
+
store,
|
|
136
|
+
documentAtom: replica.documentAtom,
|
|
137
|
+
backendAtom: replica.backendAtom,
|
|
138
|
+
});
|
|
139
|
+
const lifetime = new AbortController();
|
|
140
|
+
documentModel.start({ signal: lifetime.signal });
|
|
141
|
+
followDocument({
|
|
142
|
+
store,
|
|
143
|
+
model: documentModel,
|
|
144
|
+
reconcile: actions.reconcileDocument,
|
|
145
|
+
signal: lifetime.signal,
|
|
146
|
+
});
|
|
147
|
+
|
|
127
148
|
// Track first user interaction before initialization completes
|
|
128
149
|
document.addEventListener(
|
|
129
150
|
MarimoValueInputEvent.TYPE,
|
|
@@ -196,7 +217,9 @@ function handleNotification(
|
|
|
196
217
|
case "kernel-ready":
|
|
197
218
|
handleKernelReady(msg, {
|
|
198
219
|
autoInstantiate: true,
|
|
199
|
-
|
|
220
|
+
cells: notebookCells(store.get(notebookAtom)),
|
|
221
|
+
setLastRun: actions.setLastRun,
|
|
222
|
+
resetCellRuntimes: actions.resetCellRuntimes,
|
|
200
223
|
setLayoutData: Functions.NOOP,
|
|
201
224
|
setAppConfig: Functions.NOOP,
|
|
202
225
|
setCapabilities: Functions.NOOP,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
/* oxlint-disable typescript/no-explicit-any */
|
|
3
3
|
|
|
4
|
+
import type { NotebookTransport } from "@marimo-team/client";
|
|
4
5
|
import { getWorkerRPC } from "@/core/wasm/rpc";
|
|
5
6
|
import { Deferred } from "@/utils/Deferred";
|
|
6
7
|
import { throwNotImplemented } from "@/utils/functions";
|
|
@@ -8,6 +9,7 @@ import { Logger } from "@/utils/Logger";
|
|
|
8
9
|
import { jsonParseWithSpecialChar } from "@/utils/json/json-parser";
|
|
9
10
|
import { generateUUID } from "@/utils/uuid";
|
|
10
11
|
import { initialNotebookState, notebookAtom } from "../cells/cells";
|
|
12
|
+
import type { NotebookBackend } from "../notebook/replica";
|
|
11
13
|
import type { ClientCommand, NotificationMessage } from "../kernel/messages";
|
|
12
14
|
import type { EditRequests, RunRequests } from "../network/types";
|
|
13
15
|
import { store as defaultStore } from "../state/jotai";
|
|
@@ -70,9 +72,37 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
70
72
|
private readonly activeSessionGenerations = new Set<number>();
|
|
71
73
|
private appTransition = Promise.resolve();
|
|
72
74
|
private workerReady = new Deferred<void>();
|
|
75
|
+
/** Where the managed session's host events go while a replica follows them. */
|
|
76
|
+
private stream: Parameters<NotebookTransport>[1] | undefined;
|
|
73
77
|
|
|
74
78
|
public initialized = new Deferred<void>();
|
|
75
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The managed session's host stream, for a replica to follow. Replacing
|
|
82
|
+
* or stopping the session resets the stream, and the next session's
|
|
83
|
+
* snapshot follows.
|
|
84
|
+
*/
|
|
85
|
+
public readonly notebookBackend: NotebookBackend = {
|
|
86
|
+
transport: ({ signal }, handlers) =>
|
|
87
|
+
new Promise((resolve) => {
|
|
88
|
+
this.stream = handlers;
|
|
89
|
+
handlers.open();
|
|
90
|
+
signal.addEventListener(
|
|
91
|
+
"abort",
|
|
92
|
+
() => {
|
|
93
|
+
if (this.stream === handlers) {
|
|
94
|
+
this.stream = undefined;
|
|
95
|
+
}
|
|
96
|
+
resolve();
|
|
97
|
+
},
|
|
98
|
+
{ once: true },
|
|
99
|
+
);
|
|
100
|
+
}),
|
|
101
|
+
notebook: () => {
|
|
102
|
+
throw new Error("Islands do not edit notebooks");
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
76
106
|
public constructor(config: IslandsBridgeConfig = {}) {
|
|
77
107
|
this.store = config.store || defaultStore;
|
|
78
108
|
this.root = config.root || document;
|
|
@@ -119,18 +149,29 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
119
149
|
if (!this.activeSessionGenerations.has(sessionGeneration)) {
|
|
120
150
|
return;
|
|
121
151
|
}
|
|
122
|
-
// Islands follow only the notebook's marimo notifications.
|
|
123
152
|
const frame = jsonParseWithSpecialChar(event) as {
|
|
124
153
|
event: string;
|
|
125
154
|
data: { marimo?: NotificationMessage };
|
|
126
155
|
};
|
|
127
|
-
if (frame.event === "marimo"
|
|
128
|
-
|
|
156
|
+
if (frame.event === "marimo") {
|
|
157
|
+
if (frame.data.marimo) {
|
|
158
|
+
this.notificationConsumer?.(frame.data.marimo, sessionGeneration);
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// The replica follows the managed session's host document.
|
|
163
|
+
if (sessionGeneration === this.session?.sessionGeneration) {
|
|
164
|
+
this.stream?.message(frame);
|
|
129
165
|
}
|
|
130
166
|
},
|
|
131
167
|
);
|
|
132
168
|
}
|
|
133
169
|
|
|
170
|
+
/** The managed session is gone, so its stream starts over. */
|
|
171
|
+
private resetNotebookStream(): void {
|
|
172
|
+
this.stream?.message({ event: "reset", data: {} });
|
|
173
|
+
}
|
|
174
|
+
|
|
134
175
|
public async initializeApps(): Promise<void> {
|
|
135
176
|
await this.enqueueAppTransition(async () => {
|
|
136
177
|
await this.workerReady.promise;
|
|
@@ -185,6 +226,7 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
185
226
|
const replacesSession = managesSingleApp && previousSession !== undefined;
|
|
186
227
|
this.activeSessionGenerations.add(request.sessionGeneration);
|
|
187
228
|
if (replacesSession) {
|
|
229
|
+
this.resetNotebookStream();
|
|
188
230
|
this.store.set(notebookAtom, initialNotebookState());
|
|
189
231
|
}
|
|
190
232
|
if (managesSingleApp || !previousSession) {
|
|
@@ -236,6 +278,7 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
236
278
|
this.store.set(islandsPendingInitialRunsAtom, null);
|
|
237
279
|
this.session = undefined;
|
|
238
280
|
this.sessionReady = new Deferred<AppSession>();
|
|
281
|
+
this.resetNotebookStream();
|
|
239
282
|
this.store.set(notebookAtom, initialNotebookState());
|
|
240
283
|
});
|
|
241
284
|
}
|
|
@@ -340,12 +383,10 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
340
383
|
public sendInterrupt = throwNotImplemented;
|
|
341
384
|
public sendShutdown = throwNotImplemented;
|
|
342
385
|
public sendFormat = throwNotImplemented;
|
|
343
|
-
public sendDeleteCell = throwNotImplemented;
|
|
344
386
|
public sendInstallMissingPackages = throwNotImplemented;
|
|
345
387
|
public sendCodeCompletionRequest = throwNotImplemented;
|
|
346
388
|
public saveUserConfig = throwNotImplemented;
|
|
347
389
|
public saveAppConfig = throwNotImplemented;
|
|
348
|
-
public saveCellConfig = throwNotImplemented;
|
|
349
390
|
public sendRestart = throwNotImplemented;
|
|
350
391
|
public readCode = throwNotImplemented;
|
|
351
392
|
public readSnippets = throwNotImplemented;
|