@marimo-team/islands 0.25.3-dev169 → 0.25.3-dev170
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-BbDrlHvG.js → ConnectedDataExplorerComponent-BbSXV-Xa.js} +8 -8
- package/dist/{ErrorBoundary-RaIwGWT3.js → ErrorBoundary-NE86qEWZ.js} +4 -4
- package/dist/{abnfDiagram-VCTEODGH-Dr-fo8_z.js → abnfDiagram-VCTEODGH-D7n07nXf.js} +2 -2
- package/dist/{architectureDiagram-5GKGNRK7-ByKIl9GG.js → architectureDiagram-5GKGNRK7-D6CjRib5.js} +1 -1
- package/dist/{chat-ui-BsV2WH-4.js → chat-ui-CC7GekpG.js} +34 -34
- package/dist/{chunk-IMKFNOWR-IKDP18MQ.js → chunk-IMKFNOWR-6dtPz7uS.js} +1 -1
- package/dist/{chunk-SHT3W25Y-LkIgegwh.js → chunk-SHT3W25Y-BwnFNKDA.js} +1 -1
- package/dist/{chunk-SVP7TREG-DReBYtnB.js → chunk-SVP7TREG-D2eV4vBJ.js} +1 -1
- package/dist/{chunk-TICWLB2K-DQ0A0-1D.js → chunk-TICWLB2K-BDs4-_-r.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-CwvXdCNE.js → classDiagram-ZZMXUADV-DCJnf_8v.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-D0XAxDsc.js → classDiagram-v2-VYDZK3BY-BOmSzPBD.js} +2 -2
- package/dist/{code-visibility-BMi0KLAQ.js → code-visibility-Bzvc6ajl.js} +23 -23
- package/dist/{common-BMkJrZsD.js → common-Ciw4zkh2.js} +3412 -3412
- package/dist/{cynefinDiagram-5FMLGOSQ-BFN68nHr.js → cynefinDiagram-5FMLGOSQ-Bln_Soky.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-CZt4rtLq.js → dagre-GXQ25YYZ-D-nf7WzW.js} +1 -1
- package/dist/{data-grid-overlay-editor-CCc1P42P.js → data-grid-overlay-editor-AQREmxI7.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-D5cOz47_.js → diagram-S7CK7UJ4-PsgSMkY2.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-Qgdppb4a.js → diagram-UQ7AKVKN-DfwfUuSw.js} +1 -1
- package/dist/{diagram-VX7I27RA-BsDCvily.js → diagram-VX7I27RA-C9_e-oMc.js} +1 -1
- package/dist/{diagram-Z3DM3KII-igTkGjKI.js → diagram-Z3DM3KII-CWLa5tuY.js} +1 -1
- package/dist/{dropdown-menu-BWnnAstC.js → dropdown-menu-B6ftyzOg.js} +1 -1
- package/dist/{ebnfDiagram-PWID7BFC-BHuZGjIr.js → ebnfDiagram-PWID7BFC-B1QRM58v.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-DKObXTVE.js → erDiagram-RLTQ6QDP-DJK_FqvW.js} +1 -1
- package/dist/{error-entries-BpUJSLUZ.js → error-entries-DgK3wUJR.js} +5640 -5591
- package/dist/{feedback-button-CMVkEos_.js → feedback-button-8a6SETmT.js} +24 -24
- package/dist/{flowDiagram-HODETNUW-DZI_kit0.js → flowDiagram-HODETNUW-Uxc25XSJ.js} +2 -2
- package/dist/{formats-CwJHWasD.js → formats-DQCyPd5Z.js} +1 -1
- package/dist/{glide-data-editor-BA2pcHSV.js → glide-data-editor-B70oke_v.js} +5 -5
- package/dist/{globals-B5jjJ0ms.js → globals-Cxd5hNlI.js} +2 -2
- package/dist/{html-to-image-CQr2-IDA.js → html-to-image-D4yG5Ru0.js} +25 -25
- package/dist/{infoDiagram-27XIBGKW-DdPl-7-n.js → infoDiagram-27XIBGKW-DmjxqwL0.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-DbyW6l59.js → ishikawaDiagram-5VMMS53U-NgCLHqQi.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-BMX9SyBS.js → kanban-definition-UXKFOSKX-CZaG-iam.js} +1 -1
- package/dist/main.js +187 -188
- package/dist/{mermaid-C8W8mTbA.js → mermaid-CSvw7c71.js} +34 -34
- package/dist/{mindmap-definition-YA3MSWOX-Zd_tAxWL.js → mindmap-definition-YA3MSWOX-DJYQBDH_.js} +1 -1
- package/dist/{number-overlay-editor-BIWiV-TU.js → number-overlay-editor-ChK-SKUk.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-DjYydPdo.js → pegDiagram-XKGWAZYB-CHKO_dAe.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-R5LKjO2n.js → pieDiagram-E7YTZNPT-DVevbMAo.js} +1 -1
- package/dist/{process-output-Uw-VJE5V.js → process-output-DxvBAeL3.js} +3 -3
- package/dist/{railroadDiagram-O6MQD6OU-C7uNKtfm.js → railroadDiagram-O6MQD6OU-Detj1kcB.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-CxAIzQfl.js → requirementDiagram-BXWQKSXE-BLFD0T_Z.js} +1 -1
- package/dist/{reveal-component-XiecZ3Ua.js → reveal-component-CXMXeDZD.js} +6 -6
- package/dist/{sequenceDiagram-WJ2MYXX4-B5tmlPd5.js → sequenceDiagram-WJ2MYXX4-BZBgatQV.js} +1 -1
- package/dist/{spec-45FnujvG.js → spec-Mnkn3XEr.js} +1 -1
- package/dist/{stateDiagram-D77RDMKH-C1hc5H7Y.js → stateDiagram-D77RDMKH-4BZW9-6_.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-DRWeqQTn.js → stateDiagram-v2-MP3YSRHH-C4sKC8Iq.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-NTLas--s.js → swimlanes-42K2YHIH-SL5yeGe2.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-ChPggljD.js → swimlanesDiagram-VR7AAH4N-DqPmAYUv.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-B4CZ9srJ.js → timeline-definition-24CTP7MA-BfXUMWkq.js} +1 -1
- package/dist/{toDate-rySyYxqk.js → toDate-TMubKIVL.js} +22 -22
- package/dist/{useAsyncData-B7MiltyV.js → useAsyncData-DjMGFzFy.js} +1 -1
- package/dist/{useDeepCompareMemoize-DZmHBD_1.js → useDeepCompareMemoize-BQR5TJot.js} +1 -1
- package/dist/{useLifecycle-DRbRhzxg.js → useLifecycle-C_GvKWbQ.js} +26 -23
- package/dist/{useTheme-TLkqJsvo.js → useTheme-Bns2gJZ6.js} +179 -178
- package/dist/{vega-component-DC6P127L.js → vega-component-BsVptuhz.js} +5 -5
- package/dist/{vennDiagram-4TSXK5OY-DJIvMt44.js → vennDiagram-4TSXK5OY-CH65znGa.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-45H0zg0u.js → wardleyDiagram-VM6X3IG4-z9pWKb2_.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-ZT-XzcPN.js → xychartDiagram-S5SC5T6Z-MxyaWjjq.js} +1 -1
- package/package.json +1 -1
- package/src/__mocks__/requests.ts +0 -1
- package/src/components/editor/cell/StagedAICell.tsx +10 -2
- package/src/components/editor/cell/code/cell-editor.tsx +7 -14
- package/src/components/editor/chrome/wrapper/__tests__/document-sync-status.test.tsx +92 -0
- package/src/components/editor/chrome/wrapper/footer-items/document-sync-status.tsx +54 -0
- package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
- package/src/core/ai/__tests__/staged-cells.test.ts +8 -5
- package/src/core/ai/staged-cells.ts +7 -2
- package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +3 -15
- package/src/core/ai/tools/edit-notebook-tool.ts +14 -3
- package/src/core/cells/__tests__/cells.test.ts +0 -297
- package/src/core/cells/__tests__/document.test.ts +410 -0
- package/src/core/cells/__tests__/flush-document.test.ts +40 -0
- package/src/core/cells/__tests__/notebook-outline.test.ts +1 -6
- package/src/core/cells/__tests__/with-document-save.test.ts +67 -0
- package/src/core/cells/cells.ts +27 -158
- package/src/core/cells/columns.ts +38 -0
- package/src/core/cells/document.ts +360 -0
- package/src/core/cells/types.ts +12 -0
- package/src/core/codemirror/cells/__tests__/extensions.test.ts +27 -1
- package/src/core/codemirror/cells/extensions.ts +14 -0
- package/src/core/codemirror/format.ts +6 -5
- package/src/core/codemirror/language/__tests__/utils.test.ts +187 -15
- package/src/core/codemirror/language/cell-code.ts +114 -0
- package/src/core/codemirror/language/extension.ts +47 -30
- package/src/core/codemirror/language/languages/sql/sql.ts +7 -17
- package/src/core/codemirror/language/utils.ts +9 -39
- package/src/core/codemirror/lsp/notebook-lsp.ts +6 -5
- package/src/core/codemirror/rtc/extension.ts +0 -1
- package/src/core/edit-app.tsx +4 -3
- package/src/core/export/layout.ts +2 -2
- package/src/core/islands/bridge.ts +0 -1
- package/src/core/network/requests-lazy.ts +0 -1
- package/src/core/network/requests-network.ts +0 -9
- package/src/core/network/requests-static.ts +0 -1
- package/src/core/network/requests-toasting.tsx +0 -1
- package/src/core/network/types.ts +0 -5
- package/src/core/notebook/document/__tests__/codemirror.test.ts +76 -14
- package/src/core/notebook/document/__tests__/hold.test.ts +88 -0
- package/src/core/notebook/document/__tests__/hooks.test.tsx +3 -3
- package/src/core/notebook/document/__tests__/lifecycle-scenarios.test.ts +3 -3
- package/src/core/notebook/document/__tests__/model.test.ts +71 -15
- package/src/core/notebook/document/__tests__/scenario-fixture.ts +1 -1
- package/src/core/notebook/document/codemirror.ts +27 -122
- package/src/core/notebook/document/hooks.tsx +2 -2
- package/src/core/notebook/document/index.ts +9 -1
- package/src/core/notebook/document/model.ts +74 -24
- package/src/core/notebook/document/sync.ts +53 -4
- package/src/core/run-app.tsx +44 -41
- package/src/core/saving/save-component.tsx +2 -5
- package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +63 -3
- package/src/core/session/active-notebook.ts +4 -0
- package/src/core/session/useNotebookSession.tsx +39 -19
- package/src/core/wasm/__tests__/bridge.test.ts +3 -3
- package/src/core/wasm/bridge.ts +5 -12
- package/src/core/wasm/worker/worker.ts +0 -9
- package/src/utils/__tests__/{createReducer.test.ts → createReducer.test.tsx} +28 -0
- package/src/utils/createReducer.ts +42 -21
- package/src/core/cells/__tests__/apply-transaction.test.ts +0 -991
- package/src/core/cells/__tests__/document-changes.test.ts +0 -1140
- package/src/core/cells/__tests__/document-roundtrip.test.ts +0 -392
- package/src/core/cells/document-changes.ts +0 -944
|
@@ -3,20 +3,20 @@ import { g as t, h as n, m as r, o as i } from "./button-BQRdqQi1.js";
|
|
|
3
3
|
import { t as a } from "./react-sHtZdjeP.js";
|
|
4
4
|
import { t as o } from "./compiler-runtime-CldRdSsv.js";
|
|
5
5
|
import { t as s } from "./tooltip-CJ4Rqasp.js";
|
|
6
|
-
import { C as c, a as l } from "./toDate-
|
|
6
|
+
import { C as c, a as l } from "./toDate-TMubKIVL.js";
|
|
7
7
|
import { t as u } from "./jsx-runtime-3BD1kT0s.js";
|
|
8
8
|
import { n as d } from "./error-banner-D40pmfQQ.js";
|
|
9
9
|
import { t as f } from "./debounce-C3jyATpc.js";
|
|
10
|
-
import { A as p, n as ee } from "./useTheme-
|
|
10
|
+
import { A as p, n as ee } from "./useTheme-Bns2gJZ6.js";
|
|
11
11
|
import { c as m, t as h } from "./arrays-R6JsImiT.js";
|
|
12
12
|
import { t as te } from "./objects-DCmMvZtn.js";
|
|
13
|
-
import { t as ne } from "./useAsyncData-
|
|
14
|
-
import { a as g, i as re, n as ie, t as ae } from "./formats-
|
|
13
|
+
import { t as ne } from "./useAsyncData-DjMGFzFy.js";
|
|
14
|
+
import { a as g, i as re, n as ie, t as ae } from "./formats-DQCyPd5Z.js";
|
|
15
15
|
import { t as _ } from "./semaphore-89jsoHtS.js";
|
|
16
16
|
import { D as oe, n as se } from "./vega-loader.browser-B9Pz0EGP.js";
|
|
17
17
|
import { t as ce } from "./react-vega-C4T9Rb_w.js";
|
|
18
18
|
import { a as le, n as v } from "./loader-DOVB0Syl.js";
|
|
19
|
-
import { t as ue } from "./useDeepCompareMemoize-
|
|
19
|
+
import { t as ue } from "./useDeepCompareMemoize-BQR5TJot.js";
|
|
20
20
|
import { t as de } from "./use-vega-container-remeasure-Bz4PZMtt.js";
|
|
21
21
|
//#region src/plugins/impl/vega/fix-relative-url.ts
|
|
22
22
|
var fe = /* @__PURE__ */ e(a(), 1), pe = o();
|
|
@@ -3,7 +3,7 @@ import { t } from "./src-CA_m4ONl.js";
|
|
|
3
3
|
import { n } from "./chunk-Y2CYZVJY-DKKS1VyV.js";
|
|
4
4
|
import { $ as r, H as i, K as a, U as o, a as s, b as c, c as l, et as u, f as d, nt as f, tt as p, v as m, w as h, y as g } from "./chunk-DU6HZSFF-BwiyaNpg.js";
|
|
5
5
|
import { t as _ } from "./rough.esm-CtV9AnV1.js";
|
|
6
|
-
import { p as v } from "./mermaid-
|
|
6
|
+
import { p as v } from "./mermaid-CSvw7c71.js";
|
|
7
7
|
//#region ../node_modules/.pnpm/khroma@2.1.0/node_modules/khroma/dist/methods/transparentize.js
|
|
8
8
|
var y = (e, t) => p(e, "a", -t), b = 1e-10;
|
|
9
9
|
function x(e, t) {
|
|
@@ -2,7 +2,7 @@ import { i as e } from "./chunk-75Z2AOVW-D7gxIs2Q.js";
|
|
|
2
2
|
import { n as t } from "./src-CA_m4ONl.js";
|
|
3
3
|
import { n } from "./chunk-Y2CYZVJY-DKKS1VyV.js";
|
|
4
4
|
import { D as r, H as i, K as a, U as o, a as s, b as c, c as l, v as u, w as d, x as f, y as p } from "./chunk-DU6HZSFF-BwiyaNpg.js";
|
|
5
|
-
import { p as m } from "./mermaid-
|
|
5
|
+
import { p as m } from "./mermaid-CSvw7c71.js";
|
|
6
6
|
import { n as h } from "./mermaid-parser.core-DxuSfYwJ.js";
|
|
7
7
|
import { t as g } from "./chunk-JWPE2WC7-DJvv1t8U.js";
|
|
8
8
|
//#region ../node_modules/.pnpm/mermaid@11.17.2/node_modules/mermaid/dist/chunks/mermaid.core/wardleyDiagram-VM6X3IG4.mjs
|
|
@@ -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-CSvw7c71.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
|
@@ -17,7 +17,6 @@ export const MockRequestClient = {
|
|
|
17
17
|
sendModelValue: vi.fn().mockResolvedValue({}),
|
|
18
18
|
sendRename: vi.fn().mockResolvedValue({}),
|
|
19
19
|
sendRestart: vi.fn().mockResolvedValue({}),
|
|
20
|
-
sendDocumentTransaction: vi.fn().mockResolvedValue({}),
|
|
21
20
|
sendSave: vi.fn().mockResolvedValue({}),
|
|
22
21
|
sendCopy: vi.fn().mockResolvedValue({}),
|
|
23
22
|
sendStdin: vi.fn().mockResolvedValue({}),
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "@/core/ai/staged-cells";
|
|
12
12
|
import { getCellEditorView } from "@/core/cells/cells";
|
|
13
13
|
import type { CellId } from "@/core/cells/ids";
|
|
14
|
-
import {
|
|
14
|
+
import { prepareCellCodeUpdate } from "@/core/codemirror/language/cell-code";
|
|
15
15
|
import { cn } from "@/utils/cn";
|
|
16
16
|
import { Logger } from "@/utils/Logger";
|
|
17
17
|
import {
|
|
@@ -144,7 +144,15 @@ export function rejectStagedCell(
|
|
|
144
144
|
break;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
const update = prepareCellCodeUpdate(
|
|
148
|
+
editorView.state,
|
|
149
|
+
edit.previousCode,
|
|
150
|
+
{ initialize: true },
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (update) {
|
|
154
|
+
editorView.dispatch(update);
|
|
155
|
+
}
|
|
148
156
|
removeStagedCell(cellId);
|
|
149
157
|
break;
|
|
150
158
|
}
|
|
@@ -24,10 +24,8 @@ import {
|
|
|
24
24
|
reconfigureLanguageEffect,
|
|
25
25
|
switchLanguage,
|
|
26
26
|
} from "@/core/codemirror/language/extension";
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
updateEditorCodeFromPython,
|
|
30
|
-
} from "@/core/codemirror/language/utils";
|
|
27
|
+
import { prepareCellCodeUpdate } from "@/core/codemirror/language/cell-code";
|
|
28
|
+
import { getEditorCodeAsPython } from "@/core/codemirror/language/utils";
|
|
31
29
|
import { MARKDOWN_INITIAL_HIDE_CODE } from "@/core/codemirror/language/languages/markdown";
|
|
32
30
|
import type { LanguageAdapterType } from "@/core/codemirror/language/types";
|
|
33
31
|
import {
|
|
@@ -253,7 +251,10 @@ const CellEditorInternal = ({
|
|
|
253
251
|
? `${existing.trimEnd()}\n\n${code}`
|
|
254
252
|
: code;
|
|
255
253
|
if (view) {
|
|
256
|
-
|
|
254
|
+
const update = prepareCellCodeUpdate(view.state, merged);
|
|
255
|
+
if (update) {
|
|
256
|
+
view.dispatch(update);
|
|
257
|
+
}
|
|
257
258
|
}
|
|
258
259
|
cellActions.updateCellCode({
|
|
259
260
|
cellId: SETUP_CELL_ID,
|
|
@@ -363,15 +364,7 @@ const CellEditorInternal = ({
|
|
|
363
364
|
return composeRtcEditorConfig(code, extensions, undefined);
|
|
364
365
|
}
|
|
365
366
|
|
|
366
|
-
const rtc = realTimeCollaboration(
|
|
367
|
-
cellId,
|
|
368
|
-
(code) => {
|
|
369
|
-
// It's not really a formatting change,
|
|
370
|
-
// but this means it won't be marked as stale
|
|
371
|
-
cellActions.updateCellCode({ cellId, code, formattingChange: true });
|
|
372
|
-
},
|
|
373
|
-
initialCode,
|
|
374
|
-
);
|
|
367
|
+
const rtc = realTimeCollaboration(cellId, initialCode);
|
|
375
368
|
return composeRtcEditorConfig(code, extensions, rtc);
|
|
376
369
|
};
|
|
377
370
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { atom, createStore, Provider } from "jotai";
|
|
5
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
7
|
+
import { createDocumentModel } from "@/core/notebook/document";
|
|
8
|
+
import { document } from "@/core/notebook/document/__tests__/fixtures";
|
|
9
|
+
import {
|
|
10
|
+
type FakeEdit,
|
|
11
|
+
fakeBackend,
|
|
12
|
+
} from "@/core/notebook/replica/__tests__/fixtures";
|
|
13
|
+
import { activeDocumentModelAtom } from "@/core/session/active-notebook";
|
|
14
|
+
import { DocumentSyncStatus } from "../footer-items/document-sync-status";
|
|
15
|
+
|
|
16
|
+
const lifetimes: AbortController[] = [];
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
act(() => lifetimes.splice(0).forEach((controller) => controller.abort()));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const conflict = {
|
|
23
|
+
ok: false as const,
|
|
24
|
+
status: 409,
|
|
25
|
+
problem: {
|
|
26
|
+
type: "tag:marimo.io,2026:host/conflict",
|
|
27
|
+
title: "Conflict",
|
|
28
|
+
status: 409,
|
|
29
|
+
detail: "cell c1 has changed since it was read.",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function setup(edit: FakeEdit) {
|
|
34
|
+
const store = createStore();
|
|
35
|
+
const model = createDocumentModel({
|
|
36
|
+
store,
|
|
37
|
+
documentAtom: atom(document()),
|
|
38
|
+
backendAtom: atom(fakeBackend({ edit })),
|
|
39
|
+
});
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
lifetimes.push(controller);
|
|
42
|
+
model.start({ signal: controller.signal });
|
|
43
|
+
store.set(activeDocumentModelAtom, model);
|
|
44
|
+
render(
|
|
45
|
+
<Provider store={store}>
|
|
46
|
+
<TooltipProvider>
|
|
47
|
+
<DocumentSyncStatus />
|
|
48
|
+
</TooltipProvider>
|
|
49
|
+
</Provider>,
|
|
50
|
+
);
|
|
51
|
+
return { store, model };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
describe("DocumentSyncStatus", () => {
|
|
55
|
+
it("renders nothing while edits reach the server", () => {
|
|
56
|
+
setup(vi.fn<FakeEdit>());
|
|
57
|
+
expect(screen.queryByTestId("footer-document-sync")).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("shows a rejected edit and resubmits it on click", async () => {
|
|
61
|
+
const accepted = document().revision;
|
|
62
|
+
const edit = vi
|
|
63
|
+
.fn<FakeEdit>()
|
|
64
|
+
.mockResolvedValueOnce(conflict)
|
|
65
|
+
.mockResolvedValueOnce({
|
|
66
|
+
ok: true,
|
|
67
|
+
status: 200,
|
|
68
|
+
data: {
|
|
69
|
+
document: { ...accepted, version: 2, etag: "document-2" },
|
|
70
|
+
cells: {},
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
const { store, model } = setup(edit);
|
|
74
|
+
|
|
75
|
+
await act(async () => {
|
|
76
|
+
store.set(model.editAtom, (tx) => tx.setCode("c1", "x = 2"));
|
|
77
|
+
await model.flush().catch(() => undefined);
|
|
78
|
+
});
|
|
79
|
+
const item = screen.getByTestId("footer-document-sync");
|
|
80
|
+
expect(item).toHaveAttribute(
|
|
81
|
+
"aria-label",
|
|
82
|
+
"Edits are not reaching the server",
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
await act(async () => {
|
|
86
|
+
fireEvent.click(item);
|
|
87
|
+
await Promise.resolve();
|
|
88
|
+
});
|
|
89
|
+
expect(edit).toHaveBeenCalledTimes(2);
|
|
90
|
+
expect(screen.queryByTestId("footer-document-sync")).toBeNull();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { atom, useAtomValue } from "jotai";
|
|
4
|
+
import { AlertCircleIcon } from "lucide-react";
|
|
5
|
+
import type React from "react";
|
|
6
|
+
import { activeDocumentModelAtom } from "@/core/session/active-notebook";
|
|
7
|
+
import { FooterItem } from "../footer-item";
|
|
8
|
+
|
|
9
|
+
/** The active notebook's document sync status, or null without a model. */
|
|
10
|
+
const syncStatusAtom = atom((get) => {
|
|
11
|
+
const model = get(activeDocumentModelAtom);
|
|
12
|
+
return model === null ? null : get(model.syncStatusAtom);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Shown when this client's edits stop reaching the server.
|
|
17
|
+
*
|
|
18
|
+
* Typing keeps working locally and Run still uses the editor's code, but
|
|
19
|
+
* other clients stop seeing the edits and Save fails until they are
|
|
20
|
+
* through. A rejected edit is retried on click.
|
|
21
|
+
*/
|
|
22
|
+
export const DocumentSyncStatus: React.FC = () => {
|
|
23
|
+
const model = useAtomValue(activeDocumentModelAtom);
|
|
24
|
+
const status = useAtomValue(syncStatusAtom);
|
|
25
|
+
if (model === null || status === null || status.status !== "error") {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const retry = status.retryable
|
|
29
|
+
? () => {
|
|
30
|
+
// The status shows the outcome, so a failed retry needs no handling.
|
|
31
|
+
model.retry().catch(() => undefined);
|
|
32
|
+
}
|
|
33
|
+
: undefined;
|
|
34
|
+
const detail = status.problem?.detail ?? status.error.message;
|
|
35
|
+
return (
|
|
36
|
+
<FooterItem
|
|
37
|
+
tooltip={
|
|
38
|
+
<div className="w-[260px] space-y-1">
|
|
39
|
+
<div>Edits are not reaching the server.</div>
|
|
40
|
+
<div className="text-muted-foreground">{detail}</div>
|
|
41
|
+
<div>
|
|
42
|
+
{retry ? "Click to retry." : "Reload the page to continue."}
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
}
|
|
46
|
+
selected={false}
|
|
47
|
+
onClick={retry}
|
|
48
|
+
aria-label="Edits are not reaching the server"
|
|
49
|
+
data-testid="footer-document-sync"
|
|
50
|
+
>
|
|
51
|
+
<AlertCircleIcon className="w-4 h-4 text-destructive" />
|
|
52
|
+
</FooterItem>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
connectionStatusAtom,
|
|
19
19
|
} from "./footer-items/backend-status";
|
|
20
20
|
import { CopilotStatusIcon } from "./footer-items/copilot-status";
|
|
21
|
+
import { DocumentSyncStatus } from "./footer-items/document-sync-status";
|
|
21
22
|
import { MachineStats } from "./footer-items/machine-stats";
|
|
22
23
|
import { PyodideStatus } from "./footer-items/pyodide-status";
|
|
23
24
|
import { RTCStatus } from "./footer-items/rtc-status";
|
|
@@ -107,6 +108,7 @@ export const Footer: React.FC = () => {
|
|
|
107
108
|
</ShowInKioskMode>
|
|
108
109
|
|
|
109
110
|
<div className="flex items-center shrink-0 min-w-0">
|
|
111
|
+
<DocumentSyncStatus />
|
|
110
112
|
<MachineStats />
|
|
111
113
|
<AttachmentStatus />
|
|
112
114
|
<AIStatusIcon />
|
|
@@ -7,7 +7,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
7
7
|
import { cellId } from "@/__tests__/branded";
|
|
8
8
|
import { CellId } from "@/core/cells/ids";
|
|
9
9
|
import { getCellEditorView } from "../../cells/cells";
|
|
10
|
-
import {
|
|
10
|
+
import { prepareCellCodeUpdate } from "../../codemirror/language/cell-code";
|
|
11
11
|
import {
|
|
12
12
|
stagedAICellsAtom,
|
|
13
13
|
stagedGenerationInProgressAtom,
|
|
@@ -57,8 +57,8 @@ vi.mock("@/core/network/requests", () => ({
|
|
|
57
57
|
getRequestClient: () => ({ sendRun: vi.fn() }),
|
|
58
58
|
}));
|
|
59
59
|
|
|
60
|
-
vi.mock("../../codemirror/language/
|
|
61
|
-
|
|
60
|
+
vi.mock("../../codemirror/language/cell-code", () => ({
|
|
61
|
+
prepareCellCodeUpdate: vi.fn(() => ({ effects: [] })),
|
|
62
62
|
}));
|
|
63
63
|
|
|
64
64
|
// Mock CellId.create
|
|
@@ -402,7 +402,7 @@ describe("staged cell generation", () => {
|
|
|
402
402
|
|
|
403
403
|
// No cell or cell update should have been called
|
|
404
404
|
expect(mockCreateNewCell).not.toHaveBeenCalled();
|
|
405
|
-
expect(
|
|
405
|
+
expect(prepareCellCodeUpdate).not.toHaveBeenCalled();
|
|
406
406
|
expect(store.get(stagedGenerationInProgressAtom)).toBe(true);
|
|
407
407
|
});
|
|
408
408
|
|
|
@@ -686,7 +686,10 @@ describe("staged cell generation", () => {
|
|
|
686
686
|
expect(mockCreateNewCell).toHaveBeenCalledTimes(2);
|
|
687
687
|
expect(store.get(stagedAICellsAtom).has(cellId("first-cell"))).toBe(true);
|
|
688
688
|
expect(store.get(stagedAICellsAtom).has(cellId("second-cell"))).toBe(true);
|
|
689
|
-
expect(
|
|
689
|
+
expect(prepareCellCodeUpdate).toHaveBeenCalledTimes(2);
|
|
690
|
+
expect(
|
|
691
|
+
mockCellHandle.current.editorViewOrNull.dispatch,
|
|
692
|
+
).toHaveBeenCalledTimes(2);
|
|
690
693
|
});
|
|
691
694
|
|
|
692
695
|
it("should update notebook state when the cell editor is not mounted", () => {
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
getCellEditorView,
|
|
20
20
|
useCellActions,
|
|
21
21
|
} from "../cells/cells";
|
|
22
|
-
import {
|
|
22
|
+
import { prepareCellCodeUpdate } from "../codemirror/language/cell-code";
|
|
23
23
|
import type { JotaiStore } from "../state/jotai";
|
|
24
24
|
import type { EditType } from "./tools/edit-notebook-tool";
|
|
25
25
|
|
|
@@ -124,7 +124,12 @@ export function useStagedCells(store: JotaiStore) {
|
|
|
124
124
|
|
|
125
125
|
const editorView = getCellEditorView(cellId);
|
|
126
126
|
if (editorView) {
|
|
127
|
-
|
|
127
|
+
const update = prepareCellCodeUpdate(editorView.state, code, {
|
|
128
|
+
initialize: true,
|
|
129
|
+
});
|
|
130
|
+
if (update) {
|
|
131
|
+
editorView.dispatch(update);
|
|
132
|
+
}
|
|
128
133
|
} else {
|
|
129
134
|
updateCellCode({ cellId, code, formattingChange: false });
|
|
130
135
|
}
|
|
@@ -8,7 +8,6 @@ import { MockNotebook } from "@/__mocks__/notebook";
|
|
|
8
8
|
import { cellId } from "@/__tests__/branded";
|
|
9
9
|
import { notebookAtom } from "@/core/cells/cells";
|
|
10
10
|
import type { CellId } from "@/core/cells/ids";
|
|
11
|
-
import { updateEditorCodeFromPython } from "@/core/codemirror/language/utils";
|
|
12
11
|
import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
|
|
13
12
|
import { MultiColumn } from "@/utils/id-tree";
|
|
14
13
|
import { cellConfigExtension } from "../../../codemirror/config/extension";
|
|
@@ -22,11 +21,6 @@ vi.mock("@/components/editor/links/cell-link", () => ({
|
|
|
22
21
|
scrollAndHighlightCell: vi.fn(),
|
|
23
22
|
}));
|
|
24
23
|
|
|
25
|
-
// Mock updateEditorCodeFromPython
|
|
26
|
-
vi.mock("@/core/codemirror/language/utils", () => ({
|
|
27
|
-
updateEditorCodeFromPython: vi.fn(),
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
24
|
function createMockEditorView(code: string): EditorView {
|
|
31
25
|
return new EditorView({
|
|
32
26
|
state: EditorState.create({
|
|
@@ -140,10 +134,7 @@ describe("EditNotebookTool", () => {
|
|
|
140
134
|
);
|
|
141
135
|
|
|
142
136
|
expect(result.status).toBe("success");
|
|
143
|
-
expect(
|
|
144
|
-
editorView,
|
|
145
|
-
newCode,
|
|
146
|
-
);
|
|
137
|
+
expect(editorView.state.doc.toString()).toBe(newCode);
|
|
147
138
|
|
|
148
139
|
// Check that cell was staged
|
|
149
140
|
const stagedCells = store.get(stagedAICellsAtom);
|
|
@@ -166,10 +157,7 @@ describe("EditNotebookTool", () => {
|
|
|
166
157
|
);
|
|
167
158
|
|
|
168
159
|
expect(result2.status).toBe("success");
|
|
169
|
-
expect(
|
|
170
|
-
editorView,
|
|
171
|
-
"x = 3",
|
|
172
|
-
);
|
|
160
|
+
expect(editorView.state.doc.toString()).toBe("x = 3");
|
|
173
161
|
|
|
174
162
|
const stagedCells2 = store.get(stagedAICellsAtom);
|
|
175
163
|
expect(stagedCells2.has(cellId1)).toBe(true);
|
|
@@ -206,7 +194,7 @@ describe("EditNotebookTool", () => {
|
|
|
206
194
|
|
|
207
195
|
// Should NOT stage the cell or update the editor
|
|
208
196
|
expect(toolContext.addStagedCell).not.toHaveBeenCalled();
|
|
209
|
-
expect(
|
|
197
|
+
expect(editorView.state.doc.toString()).toBe(code);
|
|
210
198
|
|
|
211
199
|
// No staged cells
|
|
212
200
|
const stagedCells = store.get(stagedAICellsAtom);
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
notebookAtom,
|
|
10
10
|
} from "@/core/cells/cells";
|
|
11
11
|
import { CellId } from "@/core/cells/ids";
|
|
12
|
-
import {
|
|
12
|
+
import { prepareCellCodeUpdate } from "@/core/codemirror/language/cell-code";
|
|
13
13
|
import type { CellColumnId } from "@/utils/id-tree";
|
|
14
14
|
import { stagedAICellsAtom } from "../staged-cells";
|
|
15
15
|
import {
|
|
@@ -117,7 +117,12 @@ export class EditNotebookTool implements AiTool<
|
|
|
117
117
|
|
|
118
118
|
// If previous edit was from a new cell, just replace editor code with the new code
|
|
119
119
|
if (existingStagedCell?.type === "add_cell") {
|
|
120
|
-
|
|
120
|
+
const update = prepareCellCodeUpdate(editorView.state, code, {
|
|
121
|
+
initialize: true,
|
|
122
|
+
});
|
|
123
|
+
if (update) {
|
|
124
|
+
editorView.dispatch(update);
|
|
125
|
+
}
|
|
121
126
|
break;
|
|
122
127
|
}
|
|
123
128
|
|
|
@@ -137,7 +142,13 @@ export class EditNotebookTool implements AiTool<
|
|
|
137
142
|
edit: { type: "update_cell", previousCode: previousCode },
|
|
138
143
|
});
|
|
139
144
|
|
|
140
|
-
|
|
145
|
+
const update = prepareCellCodeUpdate(editorView.state, code, {
|
|
146
|
+
initialize: true,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (update) {
|
|
150
|
+
editorView.dispatch(update);
|
|
151
|
+
}
|
|
141
152
|
|
|
142
153
|
break;
|
|
143
154
|
}
|