@marimo-team/islands 0.25.3-dev170 → 0.25.3-dev173
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-wT3f3iJ1.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-B92WMzoC.js} +2 -2
- package/dist/{architectureDiagram-5GKGNRK7-D6CjRib5.js → architectureDiagram-5GKGNRK7-xlnbTG0T.js} +1 -1
- package/dist/{chat-ui-CC7GekpG.js → chat-ui-CuORr6fL.js} +70 -70
- 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-ZReaeMaF.js} +5 -5
- package/dist/{common-Ciw4zkh2.js → common-DCevHg1m.js} +231 -231
- 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-CTC-bJus.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/{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-CmWMEsY_.js} +984 -961
- package/dist/{feedback-button-8a6SETmT.js → feedback-button-DwXkmewo.js} +29 -29
- 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-BlmcP-lw.js} +10 -10
- package/dist/{glide-utils-Dz_F4pAY.js → glide-utils-D98laidb.js} +2 -2
- package/dist/{globals-Cxd5hNlI.js → globals-BdRitZ4p.js} +3 -3
- package/dist/{html-to-image-D4yG5Ru0.js → html-to-image-bpiQh7ND.js} +55 -55
- 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 +8159 -6508
- 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-DQG-hFHT.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-BzloOI7F.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-D3r3vSTK.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/core/cells/__tests__/cells.test.ts +80 -48
- package/src/core/cells/__tests__/document.test.ts +37 -3
- package/src/core/cells/cells.ts +38 -34
- package/src/core/cells/document.ts +31 -2
- package/src/core/edit-app.tsx +7 -4
- 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 -3
- package/src/core/kernel/__tests__/handlers.test.ts +7 -153
- package/src/core/kernel/handlers.ts +18 -59
- 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 +24 -35
|
@@ -9,7 +9,7 @@ import { n as s } from "./chunk-Y2CYZVJY-DKKS1VyV.js";
|
|
|
9
9
|
import { D as c, H as l, K as u, U as d, a as f, b as p, c as m, f as h, v as g, w as _, y as v, z as y } from "./chunk-DU6HZSFF-BwiyaNpg.js";
|
|
10
10
|
import "./chunk-PWAF6VOD-CQw5qATZ.js";
|
|
11
11
|
import { t as b } from "./chunk-GMAD6QVW-BhaJx0X3.js";
|
|
12
|
-
import { p as x } from "./mermaid-
|
|
12
|
+
import { p as x } from "./mermaid-CZ6FrClS.js";
|
|
13
13
|
//#region ../node_modules/.pnpm/d3-scale@4.0.2/node_modules/d3-scale/src/band.js
|
|
14
14
|
function S() {
|
|
15
15
|
var e = t().unknown(void 0), r = e.domain, i = e.range, o = 0, s = 1, c, l, u = !1, d = 0, f = 0, p = .5;
|
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ import { createStore } from "jotai";
|
|
|
7
7
|
import {
|
|
8
8
|
afterAll,
|
|
9
9
|
beforeAll,
|
|
10
|
+
afterEach,
|
|
10
11
|
beforeEach,
|
|
11
12
|
describe,
|
|
12
13
|
expect,
|
|
@@ -3591,31 +3592,9 @@ describe("createTracebackInfoAtom", () => {
|
|
|
3591
3592
|
});
|
|
3592
3593
|
});
|
|
3593
3594
|
|
|
3594
|
-
describe("
|
|
3595
|
+
describe("kernel-ready", () => {
|
|
3595
3596
|
const CELL_A = cellId("A");
|
|
3596
3597
|
const CELL_B = cellId("B");
|
|
3597
|
-
const newCells: CellData[] = [
|
|
3598
|
-
{
|
|
3599
|
-
id: CELL_A,
|
|
3600
|
-
name: "a",
|
|
3601
|
-
code: "1",
|
|
3602
|
-
edited: false,
|
|
3603
|
-
lastCodeRun: null,
|
|
3604
|
-
lastExecutionTime: null,
|
|
3605
|
-
config: { hide_code: false, disabled: false, column: null },
|
|
3606
|
-
serializedEditorState: null,
|
|
3607
|
-
},
|
|
3608
|
-
{
|
|
3609
|
-
id: CELL_B,
|
|
3610
|
-
name: "b",
|
|
3611
|
-
code: "2",
|
|
3612
|
-
edited: false,
|
|
3613
|
-
lastCodeRun: null,
|
|
3614
|
-
lastExecutionTime: null,
|
|
3615
|
-
config: { hide_code: false, disabled: false, column: null },
|
|
3616
|
-
serializedEditorState: null,
|
|
3617
|
-
},
|
|
3618
|
-
];
|
|
3619
3598
|
|
|
3620
3599
|
const hydratedState = () =>
|
|
3621
3600
|
MockNotebook.notebookState({
|
|
@@ -3645,44 +3624,97 @@ describe("setCells snapshot preservation", () => {
|
|
|
3645
3624
|
},
|
|
3646
3625
|
});
|
|
3647
3626
|
|
|
3648
|
-
|
|
3649
|
-
const
|
|
3650
|
-
|
|
3627
|
+
it("setLastRun records what the kernel last ran", () => {
|
|
3628
|
+
const state = hydratedState();
|
|
3629
|
+
const next = exportedForTesting.reducer(state, {
|
|
3630
|
+
type: "setLastRun",
|
|
3631
|
+
payload: {
|
|
3632
|
+
lastCodeRun: { [CELL_A]: "0" },
|
|
3633
|
+
lastExecutionTime: { [CELL_A]: 5 },
|
|
3634
|
+
},
|
|
3635
|
+
});
|
|
3636
|
+
|
|
3637
|
+
expect(next.cellData[CELL_A]).toMatchObject({
|
|
3638
|
+
lastCodeRun: "0",
|
|
3639
|
+
lastExecutionTime: 5,
|
|
3640
|
+
edited: true,
|
|
3641
|
+
});
|
|
3642
|
+
expect(next.cellData[CELL_B]).toMatchObject({
|
|
3643
|
+
lastCodeRun: null,
|
|
3644
|
+
lastExecutionTime: null,
|
|
3645
|
+
edited: false,
|
|
3646
|
+
});
|
|
3647
|
+
expect(next.cellRuntime).toBe(state.cellRuntime);
|
|
3651
3648
|
});
|
|
3652
3649
|
|
|
3653
|
-
it("
|
|
3650
|
+
it("resetCellRuntimes starts every cell's runtime over", () => {
|
|
3654
3651
|
const next = exportedForTesting.reducer(hydratedState(), {
|
|
3655
|
-
type: "
|
|
3656
|
-
payload:
|
|
3652
|
+
type: "resetCellRuntimes",
|
|
3653
|
+
payload: undefined,
|
|
3657
3654
|
});
|
|
3658
3655
|
|
|
3659
|
-
expect(next.cellRuntime[CELL_A].output).
|
|
3660
|
-
|
|
3656
|
+
expect(next.cellRuntime[CELL_A].output).toBeNull();
|
|
3657
|
+
expect(next.cellRuntime[CELL_B].consoleOutputs).toEqual([]);
|
|
3658
|
+
});
|
|
3659
|
+
|
|
3660
|
+
it("resetCellRuntimes starts the scratch cell over too", () => {
|
|
3661
|
+
const state = MockNotebook.notebookState({
|
|
3662
|
+
cellData: { [CELL_A]: { id: CELL_A, code: "1" } },
|
|
3663
|
+
cellRuntime: {
|
|
3664
|
+
[SCRATCH_CELL_ID]: {
|
|
3665
|
+
output: {
|
|
3666
|
+
channel: "output",
|
|
3667
|
+
mimetype: "text/plain",
|
|
3668
|
+
data: "scratch",
|
|
3669
|
+
timestamp: 0,
|
|
3670
|
+
},
|
|
3671
|
+
},
|
|
3672
|
+
},
|
|
3673
|
+
});
|
|
3674
|
+
const next = exportedForTesting.reducer(state, {
|
|
3675
|
+
type: "resetCellRuntimes",
|
|
3676
|
+
payload: undefined,
|
|
3661
3677
|
});
|
|
3678
|
+
|
|
3679
|
+
expect(next.cellRuntime[SCRATCH_CELL_ID].output).toBeNull();
|
|
3662
3680
|
});
|
|
3663
3681
|
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3682
|
+
describe("resetCellRuntimes in WASM", () => {
|
|
3683
|
+
beforeEach(async () => {
|
|
3684
|
+
const { isWasm } = await import("@/core/wasm/utils");
|
|
3685
|
+
vi.mocked(isWasm).mockReturnValue(true);
|
|
3668
3686
|
});
|
|
3669
3687
|
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3688
|
+
afterEach(async () => {
|
|
3689
|
+
const { isWasm } = await import("@/core/wasm/utils");
|
|
3690
|
+
vi.mocked(isWasm).mockReturnValue(false);
|
|
3673
3691
|
});
|
|
3674
|
-
});
|
|
3675
3692
|
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3693
|
+
it("keeps hydrated outputs and console", () => {
|
|
3694
|
+
const next = exportedForTesting.reducer(hydratedState(), {
|
|
3695
|
+
type: "resetCellRuntimes",
|
|
3696
|
+
payload: undefined,
|
|
3697
|
+
});
|
|
3698
|
+
|
|
3699
|
+
expect(next.cellRuntime[CELL_A].output).toMatchObject({
|
|
3700
|
+
data: "hydrated-A",
|
|
3701
|
+
});
|
|
3702
|
+
expect(next.cellRuntime[CELL_B].consoleOutputs[0]).toMatchObject({
|
|
3703
|
+
data: "hydrated-B-stdout",
|
|
3704
|
+
});
|
|
3681
3705
|
});
|
|
3682
3706
|
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3707
|
+
it("starts cells with no prior runtime over", () => {
|
|
3708
|
+
const state = MockNotebook.notebookState({
|
|
3709
|
+
cellData: { [CELL_A]: { id: CELL_A, code: "1" } },
|
|
3710
|
+
});
|
|
3711
|
+
const next = exportedForTesting.reducer(state, {
|
|
3712
|
+
type: "resetCellRuntimes",
|
|
3713
|
+
payload: undefined,
|
|
3714
|
+
});
|
|
3715
|
+
|
|
3716
|
+
expect(next.cellRuntime[CELL_A].output).toBeNull();
|
|
3717
|
+
expect(next.cellRuntime[CELL_A].consoleOutputs).toEqual([]);
|
|
3718
|
+
});
|
|
3687
3719
|
});
|
|
3688
3720
|
});
|
|
@@ -29,7 +29,12 @@ import {
|
|
|
29
29
|
type NotebookState,
|
|
30
30
|
notebookReducer,
|
|
31
31
|
} from "../cells";
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
applyDocument,
|
|
34
|
+
flushDocument,
|
|
35
|
+
followDocument,
|
|
36
|
+
withDocumentSave,
|
|
37
|
+
} from "../document";
|
|
33
38
|
import { type CellId, SCRATCH_CELL_ID } from "../ids";
|
|
34
39
|
|
|
35
40
|
vi.mock("../../rtc/state", async (importOriginal) => ({
|
|
@@ -349,9 +354,14 @@ describe("documentEditMiddleware", () => {
|
|
|
349
354
|
expect(calls).toEqual([["updateConfig", c, { column: null }]]);
|
|
350
355
|
});
|
|
351
356
|
|
|
352
|
-
it("ignores the kernel's
|
|
357
|
+
it("ignores the document and the kernel's run state", () => {
|
|
353
358
|
actions.prepareForRun({ cellId: a });
|
|
354
|
-
actions.
|
|
359
|
+
actions.setLastRun({
|
|
360
|
+
lastCodeRun: { [a]: "a = 0" },
|
|
361
|
+
lastExecutionTime: {},
|
|
362
|
+
});
|
|
363
|
+
actions.resetCellRuntimes();
|
|
364
|
+
actions.reconcileDocument(documentOf([cell(a)]));
|
|
355
365
|
|
|
356
366
|
expect(calls).toEqual([]);
|
|
357
367
|
});
|
|
@@ -367,6 +377,30 @@ describe("documentEditMiddleware", () => {
|
|
|
367
377
|
});
|
|
368
378
|
});
|
|
369
379
|
|
|
380
|
+
describe("followDocument", () => {
|
|
381
|
+
it("reconciles the document now and on each change, until aborted", () => {
|
|
382
|
+
const documentAtom = atom<EditableDocument | null>(null);
|
|
383
|
+
const reconcile = vi.fn();
|
|
384
|
+
const controller = new AbortController();
|
|
385
|
+
|
|
386
|
+
followDocument({
|
|
387
|
+
store,
|
|
388
|
+
model: { documentAtom },
|
|
389
|
+
reconcile,
|
|
390
|
+
signal: controller.signal,
|
|
391
|
+
});
|
|
392
|
+
expect(reconcile).not.toHaveBeenCalled();
|
|
393
|
+
|
|
394
|
+
const first = documentOf([cell(a)]);
|
|
395
|
+
store.set(documentAtom, first);
|
|
396
|
+
expect(reconcile).toHaveBeenCalledWith(first);
|
|
397
|
+
|
|
398
|
+
controller.abort();
|
|
399
|
+
store.set(documentAtom, documentOf([cell(b)]));
|
|
400
|
+
expect(reconcile).toHaveBeenCalledOnce();
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
370
404
|
describe("flushing before host commands", () => {
|
|
371
405
|
const model = {
|
|
372
406
|
syncStatusAtom: atom<DocumentSyncStatus>({ status: "idle" }),
|
package/src/core/cells/cells.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
createCell,
|
|
45
45
|
createCellConfig,
|
|
46
46
|
createCellRuntimeState,
|
|
47
|
+
isEdited,
|
|
47
48
|
} from "./types";
|
|
48
49
|
import {
|
|
49
50
|
canUndoDeletes,
|
|
@@ -936,12 +937,7 @@ const {
|
|
|
936
937
|
cellLogs: [...state.cellLogs, ...action.logs],
|
|
937
938
|
};
|
|
938
939
|
},
|
|
939
|
-
/**
|
|
940
|
-
* Follow the host document's cells and order.
|
|
941
|
-
*
|
|
942
|
-
* Dispatched without middleware, since the document already has these
|
|
943
|
-
* changes.
|
|
944
|
-
*/
|
|
940
|
+
/** Follow the host document's cells and order. */
|
|
945
941
|
reconcileDocument: (state, document: EditableDocument) => {
|
|
946
942
|
const nextState = applyDocument(state, document);
|
|
947
943
|
for (const cellId of state.cellIds.inOrderIds) {
|
|
@@ -985,36 +981,44 @@ const {
|
|
|
985
981
|
},
|
|
986
982
|
});
|
|
987
983
|
},
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
984
|
+
/** Record what the kernel last ran for each cell. */
|
|
985
|
+
setLastRun: (
|
|
986
|
+
state,
|
|
987
|
+
action: {
|
|
988
|
+
lastCodeRun: Record<CellId, string>;
|
|
989
|
+
lastExecutionTime: Record<CellId, number>;
|
|
990
|
+
},
|
|
991
|
+
) => {
|
|
992
|
+
const cellData = { ...state.cellData };
|
|
993
|
+
for (const cellId of state.cellIds.inOrderIds) {
|
|
994
|
+
const cell = state.cellData[cellId];
|
|
995
|
+
const lastCodeRun = action.lastCodeRun[cellId] ?? null;
|
|
996
|
+
cellData[cellId] = {
|
|
997
|
+
...cell,
|
|
998
|
+
lastCodeRun,
|
|
999
|
+
lastExecutionTime: action.lastExecutionTime[cellId] ?? null,
|
|
1000
|
+
// A cell the kernel never ran is not stale on load.
|
|
1001
|
+
edited: lastCodeRun !== null && isEdited(lastCodeRun, cell.code),
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
return { ...state, cellData };
|
|
1005
|
+
},
|
|
1006
|
+
/**
|
|
1007
|
+
* Start each cell's runtime over for a new kernel. WASM keeps the outputs
|
|
1008
|
+
* its mount-time snapshot gave it.
|
|
1009
|
+
*/
|
|
1010
|
+
resetCellRuntimes: (state) => {
|
|
993
1011
|
const preserveSnapshot = isWasm();
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
}
|
|
998
|
-
const prev = state.cellRuntime[cellId];
|
|
1012
|
+
const cellRuntime = { ...state.cellRuntime };
|
|
1013
|
+
for (const cellId of [...state.cellIds.inOrderIds, SCRATCH_CELL_ID]) {
|
|
1014
|
+
const previous = state.cellRuntime[cellId];
|
|
999
1015
|
const hasSnapshot =
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
return withScratchCell({
|
|
1008
|
-
...state,
|
|
1009
|
-
cellIds: MultiColumn.fromIdsAndColumns(
|
|
1010
|
-
cells.map((cell) => [cell.id, cell.config.column]),
|
|
1011
|
-
),
|
|
1012
|
-
cellData: cellData,
|
|
1013
|
-
cellRuntime: cellRuntime,
|
|
1014
|
-
cellHandles: Object.fromEntries(
|
|
1015
|
-
cells.map((cell) => [cell.id, createRef()]),
|
|
1016
|
-
),
|
|
1017
|
-
});
|
|
1016
|
+
preserveSnapshot &&
|
|
1017
|
+
previous !== undefined &&
|
|
1018
|
+
(previous.output != null || previous.consoleOutputs.length > 0);
|
|
1019
|
+
cellRuntime[cellId] = hasSnapshot ? previous : createCellRuntimeState();
|
|
1020
|
+
}
|
|
1021
|
+
return { ...state, cellRuntime };
|
|
1018
1022
|
},
|
|
1019
1023
|
/**
|
|
1020
1024
|
* Move focus to next cell
|
|
@@ -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";
|
|
@@ -165,6 +167,33 @@ export function applyDocument(
|
|
|
165
167
|
return { ...state, cellIds, cellData, cellRuntime, cellHandles };
|
|
166
168
|
}
|
|
167
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Reconcile the model's document now and on each change, until the signal aborts.
|
|
172
|
+
*
|
|
173
|
+
* TODO(MO-8028): Delete with `applyDocument`.
|
|
174
|
+
*/
|
|
175
|
+
export function followDocument({
|
|
176
|
+
store,
|
|
177
|
+
model,
|
|
178
|
+
reconcile,
|
|
179
|
+
signal,
|
|
180
|
+
}: {
|
|
181
|
+
store: ReturnType<typeof createStore>;
|
|
182
|
+
model: Pick<DocumentModel, "documentAtom">;
|
|
183
|
+
reconcile: (document: EditableDocument) => void;
|
|
184
|
+
signal: AbortSignal;
|
|
185
|
+
}): void {
|
|
186
|
+
const follow = () => {
|
|
187
|
+
const document = store.get(model.documentAtom);
|
|
188
|
+
if (document !== null) {
|
|
189
|
+
reconcile(document);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const unsubscribe = store.sub(model.documentAtom, follow);
|
|
193
|
+
signal.addEventListener("abort", unsubscribe, { once: true });
|
|
194
|
+
follow();
|
|
195
|
+
}
|
|
196
|
+
|
|
168
197
|
// Editing the document ------------------------------------------------------
|
|
169
198
|
|
|
170
199
|
const CONFIG_FIELDS = ["disabled", "hide_code", "expand_output"] as const;
|
|
@@ -291,8 +320,8 @@ export function documentEditMiddleware(
|
|
|
291
320
|
next: NotebookState,
|
|
292
321
|
action: CellAction,
|
|
293
322
|
): void {
|
|
294
|
-
// The
|
|
295
|
-
if (action.type === "
|
|
323
|
+
// The document replaces the notebook's cells, so following it is no edit.
|
|
324
|
+
if (action.type === "reconcileDocument") {
|
|
296
325
|
return;
|
|
297
326
|
}
|
|
298
327
|
if (prev.cellIds === next.cellIds && prev.cellData === next.cellData) {
|
package/src/core/edit-app.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import { Tooltip } from "radix-ui";
|
|
|
5
5
|
|
|
6
6
|
const TooltipProvider = Tooltip.Provider;
|
|
7
7
|
|
|
8
|
-
import { useAtomValue, useSetAtom } from "jotai";
|
|
8
|
+
import { useAtomValue, useSetAtom, useStore } from "jotai";
|
|
9
9
|
import { useEffect } from "react";
|
|
10
10
|
import { NotStartedConnectionAlert } from "@/components/editor/alerts/connecting-alert";
|
|
11
11
|
import { Controls } from "@/components/editor/controls/Controls";
|
|
@@ -29,10 +29,12 @@ import { CellsRenderer } from "../components/editor/renderers/cells-renderer";
|
|
|
29
29
|
import { useHotkey } from "../hooks/useHotkey";
|
|
30
30
|
import {
|
|
31
31
|
hasCellsAtom,
|
|
32
|
+
notebookAtom,
|
|
32
33
|
notebookIsRunningAtom,
|
|
33
34
|
numColumnsAtom,
|
|
34
35
|
useCellActions,
|
|
35
36
|
} from "./cells/cells";
|
|
37
|
+
import { notebookCells } from "./cells/utils";
|
|
36
38
|
import type { AppConfig, UserConfig } from "./config/config-schema";
|
|
37
39
|
import { DocumentProvider } from "./notebook/document";
|
|
38
40
|
import { RuntimeState } from "./kernel/RuntimeState";
|
|
@@ -64,8 +66,9 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
64
66
|
appConfig,
|
|
65
67
|
hideControls = false,
|
|
66
68
|
}) => {
|
|
67
|
-
const {
|
|
69
|
+
const { mergeAllColumns, collapseAllCells, expandAllCells } =
|
|
68
70
|
useCellActions();
|
|
71
|
+
const store = useStore();
|
|
69
72
|
const viewState = useAtomValue(viewStateAtom);
|
|
70
73
|
const numColumns = useAtomValue(numColumnsAtom);
|
|
71
74
|
const filename = useFilename();
|
|
@@ -86,8 +89,8 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
86
89
|
|
|
87
90
|
const { connection, reconnect, documentModel } = useNotebookSession({
|
|
88
91
|
autoInstantiate: userConfig.runtime.auto_instantiate,
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
onKernelReady: (layout) => {
|
|
93
|
+
const cells = notebookCells(store.get(notebookAtom));
|
|
91
94
|
const names = cells.map((cell) => cell.name);
|
|
92
95
|
const codes = cells.map((cell) => cell.code);
|
|
93
96
|
const configs = cells.map((cell) => cell.config);
|
|
@@ -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,
|