@marimo-team/islands 0.25.3-dev275 → 0.25.3-dev279
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ErrorBoundary-BuIXiuc5.js → ErrorBoundary-DmTMuDPR.js} +1 -1
- package/dist/{chat-ui-SZ6lSH52.js → chat-ui-v1Fdh0NA.js} +270 -270
- package/dist/{code-visibility-BEAPMrC-.js → code-visibility-Dw4-TXN0.js} +2 -2
- package/dist/{common-CEmzaoJ8.js → common-BXMFi4lr.js} +4947 -4943
- package/dist/{data-grid-overlay-editor-CUo25_W8.js → data-grid-overlay-editor-hZzwX7Ia.js} +1 -1
- package/dist/{error-entries-B-1yUdcH.js → error-entries-C1S-Rmyt.js} +545 -535
- package/dist/{feedback-button-BqiR7zMO.js → feedback-button-6Ui2TDLR.js} +5 -5
- package/dist/{glide-data-editor-B7dguvqk.js → glide-data-editor-PZNlrSDI.js} +3 -3
- package/dist/{globals-CYykXfGI.js → globals-DX6FPZYq.js} +1 -1
- package/dist/{html-to-image-DC_pmZLs.js → html-to-image-CqGUd58T.js} +14452 -14452
- package/dist/main.js +6031 -6036
- package/dist/{number-overlay-editor-C4u7SJAJ.js → number-overlay-editor-D0CsL3_R.js} +1 -1
- package/dist/{process-output-DYu84VfZ.js → process-output-DskX9jLa.js} +2 -2
- package/dist/{reveal-component-DGi-irHV.js → reveal-component-D7xeI1QR.js} +906 -906
- package/package.json +1 -1
- package/src/__mocks__/executions.ts +4 -2
- package/src/__mocks__/notebook.ts +0 -2
- package/src/__tests__/CellStatus.test.tsx +6 -6
- package/src/components/editor/__tests__/data-attributes.test.tsx +0 -2
- package/src/components/editor/__tests__/output-persistence.test.tsx +20 -5
- package/src/components/editor/cell/CellStatus.tsx +6 -5
- package/src/components/editor/cell/__tests__/useRunCells.test.tsx +169 -0
- package/src/components/editor/cell/collapse.tsx +3 -4
- package/src/components/editor/cell/useRunCells.ts +32 -3
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +2 -3
- package/src/components/editor/notebook-cell.tsx +23 -37
- package/src/components/editor/renderers/grid-layout/grid-layout.tsx +4 -10
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -2
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +8 -5
- package/src/components/slides/minimap.tsx +3 -2
- package/src/components/slides/reveal-component.tsx +3 -2
- package/src/components/slides/slide-cell-view.tsx +8 -8
- package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +0 -101
- package/src/core/cells/__tests__/actions.test.ts +0 -1
- package/src/core/cells/__tests__/cell.test.ts +81 -17
- package/src/core/cells/__tests__/cells.test.ts +0 -100
- package/src/core/cells/__tests__/document.test.ts +2 -8
- package/src/core/cells/__tests__/utils.test.ts +93 -24
- package/src/core/cells/actions.ts +0 -1
- package/src/core/cells/cell.ts +37 -14
- package/src/core/cells/cells.ts +10 -99
- package/src/core/cells/document.ts +2 -13
- package/src/core/cells/types.ts +0 -23
- package/src/core/cells/utils.ts +44 -17
- package/src/core/codemirror/cells/extensions.ts +0 -14
- package/src/core/errors/__tests__/error-entries.test.ts +40 -1
- package/src/core/errors/error-entries.ts +2 -3
- package/src/core/executions/__tests__/freshness.test.ts +420 -0
- package/src/core/executions/__tests__/hooks.test.tsx +106 -0
- package/src/core/executions/atoms.ts +22 -0
- package/src/core/executions/freshness.ts +164 -0
- package/src/core/executions/hooks.ts +17 -1
- package/src/core/executions/index.ts +3 -1
- package/src/core/islands/bootstrap.ts +0 -1
- package/src/core/kernel/handlers.ts +0 -4
- package/src/core/session/useNotebookSession.tsx +2 -7
- package/src/stories/cell-status.stories.tsx +12 -14
- package/src/stories/cell.stories.tsx +44 -10
- package/src/stories/layout/vertical/one-column.stories.tsx +0 -10
- package/src/components/editor/cell/__tests__/useRunCells.test.ts +0 -45
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@ import type { CellExecutionInputs } from "@/core/executions";
|
|
|
8
8
|
import {
|
|
9
9
|
ConnectionState,
|
|
10
10
|
type ConnectionStatus,
|
|
11
|
+
type Document,
|
|
11
12
|
type Execution,
|
|
12
13
|
type Executions,
|
|
13
14
|
type NotebookReplica,
|
|
@@ -162,16 +163,16 @@ export const MockExecutions = {
|
|
|
162
163
|
};
|
|
163
164
|
},
|
|
164
165
|
|
|
165
|
-
/** Make the store's active replica report `records` and `
|
|
166
|
+
/** Make the store's active replica report `records`, `runtime`, and `document`. */
|
|
166
167
|
seed(
|
|
167
168
|
store: Store,
|
|
168
169
|
records: Protocol.Execution[],
|
|
169
170
|
runtime: Protocol.Runtime | null = null,
|
|
171
|
+
document: Document | null = null,
|
|
170
172
|
) {
|
|
171
173
|
const current = store.get(activeNotebookReplicaAtom);
|
|
172
174
|
const replica = {
|
|
173
175
|
connectionAtom: atom<ConnectionStatus>({ state: ConnectionState.OPEN }),
|
|
174
|
-
documentAtom: atom(null),
|
|
175
176
|
backendAtom: atom(null),
|
|
176
177
|
attachmentsAtom: atom([]),
|
|
177
178
|
connect: () => () => undefined,
|
|
@@ -185,6 +186,7 @@ export const MockExecutions = {
|
|
|
185
186
|
operations: [],
|
|
186
187
|
updated_at: null,
|
|
187
188
|
}),
|
|
189
|
+
documentAtom: atom<Document | null>(document),
|
|
188
190
|
runtimeAtom: atom(runtime),
|
|
189
191
|
executionsAtom: atom(mirror(records)),
|
|
190
192
|
resultsAtom: atom(results(records)),
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { render } from "@testing-library/react";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
5
5
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
6
|
+
import type { CellFreshness } from "@/core/executions";
|
|
6
7
|
import { formatElapsedTime, type Milliseconds } from "@/utils/time";
|
|
7
8
|
import {
|
|
8
9
|
CellStatusComponent,
|
|
@@ -70,9 +71,8 @@ describe("CellStatusComponent", () => {
|
|
|
70
71
|
// Base props that will be modified for different test cases
|
|
71
72
|
const baseProps = {
|
|
72
73
|
editing: true,
|
|
73
|
-
edited: false,
|
|
74
74
|
disabled: false,
|
|
75
|
-
|
|
75
|
+
freshness: { sourceChanged: false, inputsChanged: false } as CellFreshness,
|
|
76
76
|
status: "idle" as const,
|
|
77
77
|
interrupted: false,
|
|
78
78
|
elapsedTime: null,
|
|
@@ -94,7 +94,7 @@ describe("CellStatusComponent", () => {
|
|
|
94
94
|
const props = {
|
|
95
95
|
...baseProps,
|
|
96
96
|
disabled: true,
|
|
97
|
-
|
|
97
|
+
freshness: { sourceChanged: false, inputsChanged: true } as CellFreshness,
|
|
98
98
|
startedAt: 1_704_096_000_000 as Milliseconds, // Jan 1, 2024 12:00:00
|
|
99
99
|
};
|
|
100
100
|
|
|
@@ -140,7 +140,7 @@ describe("CellStatusComponent", () => {
|
|
|
140
140
|
const props = {
|
|
141
141
|
...baseProps,
|
|
142
142
|
blocked: true,
|
|
143
|
-
|
|
143
|
+
freshness: { sourceChanged: false, inputsChanged: true } as CellFreshness,
|
|
144
144
|
startedAt: 1_704_096_000_000 as Milliseconds,
|
|
145
145
|
};
|
|
146
146
|
|
|
@@ -215,7 +215,7 @@ describe("CellStatusComponent", () => {
|
|
|
215
215
|
test("renders edited state", () => {
|
|
216
216
|
const props = {
|
|
217
217
|
...baseProps,
|
|
218
|
-
|
|
218
|
+
freshness: { sourceChanged: true, inputsChanged: false } as CellFreshness,
|
|
219
219
|
elapsedTime: 1500 as Milliseconds,
|
|
220
220
|
startedAt: 1_704_096_000_000 as Milliseconds,
|
|
221
221
|
};
|
|
@@ -231,7 +231,7 @@ describe("CellStatusComponent", () => {
|
|
|
231
231
|
test("renders stale inputs state", () => {
|
|
232
232
|
const props = {
|
|
233
233
|
...baseProps,
|
|
234
|
-
|
|
234
|
+
freshness: { sourceChanged: false, inputsChanged: true } as CellFreshness,
|
|
235
235
|
elapsedTime: 1500 as Milliseconds,
|
|
236
236
|
startedAt: 1_704_096_000_000 as Milliseconds,
|
|
237
237
|
};
|
|
@@ -93,7 +93,6 @@ describe("Cell data attributes", () => {
|
|
|
93
93
|
[cid]: {
|
|
94
94
|
code: "",
|
|
95
95
|
name: cellName,
|
|
96
|
-
edited: false,
|
|
97
96
|
config: {
|
|
98
97
|
disabled: false,
|
|
99
98
|
hide_code: false,
|
|
@@ -106,7 +105,6 @@ describe("Cell data attributes", () => {
|
|
|
106
105
|
notebook.cellRuntime[cid] = createCellRuntimeState({
|
|
107
106
|
output: null,
|
|
108
107
|
consoleOutputs: [],
|
|
109
|
-
staleInputs: false,
|
|
110
108
|
debuggerActive: false,
|
|
111
109
|
outline: null,
|
|
112
110
|
});
|
|
@@ -11,6 +11,11 @@ import { cellId } from "@/__tests__/branded";
|
|
|
11
11
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
12
12
|
import { notebookAtom } from "@/core/cells/cells";
|
|
13
13
|
import { DocumentProvider } from "@/core/notebook/document";
|
|
14
|
+
import {
|
|
15
|
+
cell,
|
|
16
|
+
document as documentOf,
|
|
17
|
+
withCells,
|
|
18
|
+
} from "@/core/notebook/document/__tests__/fixtures";
|
|
14
19
|
import type { CellRuntimeState } from "@/core/cells/types";
|
|
15
20
|
import { createCellRuntimeState } from "@/core/cells/types";
|
|
16
21
|
import { defaultUserConfig } from "@/core/config/config-schema";
|
|
@@ -41,7 +46,6 @@ function seedNotebook(
|
|
|
41
46
|
[cid]: {
|
|
42
47
|
code: "1 + 1",
|
|
43
48
|
name: "test_cell",
|
|
44
|
-
edited: false,
|
|
45
49
|
config: {
|
|
46
50
|
disabled: false,
|
|
47
51
|
hide_code: false,
|
|
@@ -188,11 +192,22 @@ describe("output persistence across mode changes", () => {
|
|
|
188
192
|
|
|
189
193
|
it("does not dim edited-but-not-run outputs as stale while presenting", () => {
|
|
190
194
|
const { store, wrapper } = createTestWrapper();
|
|
195
|
+
// The host holds newer source than the cell last ran with
|
|
196
|
+
MockExecutions.seed(
|
|
197
|
+
store,
|
|
198
|
+
[MockExecutions.completed(cid, { code_fingerprint: "ran" })],
|
|
199
|
+
null,
|
|
200
|
+
withCells(
|
|
201
|
+
documentOf(),
|
|
202
|
+
{
|
|
203
|
+
c1: null,
|
|
204
|
+
c2: null,
|
|
205
|
+
[cid]: cell(cid, "1 + 1", { code_fingerprint: "edited" }),
|
|
206
|
+
},
|
|
207
|
+
[cid],
|
|
208
|
+
),
|
|
209
|
+
);
|
|
191
210
|
seedNotebook(store);
|
|
192
|
-
// Mark the cell as edited without re-running it
|
|
193
|
-
const notebook = store.get(notebookAtom);
|
|
194
|
-
notebook.cellData[cid] = { ...notebook.cellData[cid], edited: true };
|
|
195
|
-
store.set(notebookAtom, { ...notebook });
|
|
196
211
|
|
|
197
212
|
const { container, rerender } = render(renderCell("edit"), { wrapper });
|
|
198
213
|
expect(container.querySelector(".marimo-output-stale")).toBeTruthy();
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "lucide-react";
|
|
8
8
|
import { useDateFormatter } from "react-aria";
|
|
9
9
|
import { MultiIcon } from "@/components/icons/multi-icon";
|
|
10
|
-
import type { CellRunStatus } from "@/core/executions";
|
|
10
|
+
import type { CellFreshness, CellRunStatus } from "@/core/executions";
|
|
11
11
|
import { Logger } from "@/utils/Logger";
|
|
12
12
|
import { useElapsedTime } from "../../../hooks/useElapsedTime";
|
|
13
13
|
import { Tooltip } from "../../ui/tooltip";
|
|
@@ -18,11 +18,11 @@ import { formatElapsedTime, type Milliseconds, Time } from "@/utils/time";
|
|
|
18
18
|
|
|
19
19
|
export interface CellStatusComponentProps {
|
|
20
20
|
editing: boolean;
|
|
21
|
-
edited: boolean;
|
|
22
21
|
disabled: boolean;
|
|
23
22
|
/** Whether a cell it depends on is disabled. */
|
|
24
23
|
blocked: boolean;
|
|
25
|
-
|
|
24
|
+
/** Whether the cell's result is current. */
|
|
25
|
+
freshness: CellFreshness;
|
|
26
26
|
status: CellRunStatus;
|
|
27
27
|
/** Whether the cell's last run was interrupted. */
|
|
28
28
|
interrupted: boolean;
|
|
@@ -38,8 +38,7 @@ export const CellStatusComponent: React.FC<CellStatusComponentProps> = ({
|
|
|
38
38
|
status,
|
|
39
39
|
disabled,
|
|
40
40
|
blocked,
|
|
41
|
-
|
|
42
|
-
edited,
|
|
41
|
+
freshness,
|
|
43
42
|
interrupted,
|
|
44
43
|
elapsedTime,
|
|
45
44
|
startedAt,
|
|
@@ -48,6 +47,8 @@ export const CellStatusComponent: React.FC<CellStatusComponentProps> = ({
|
|
|
48
47
|
if (!editing) {
|
|
49
48
|
return null;
|
|
50
49
|
}
|
|
50
|
+
const edited = freshness.sourceChanged;
|
|
51
|
+
const staleInputs = freshness.inputsChanged;
|
|
51
52
|
|
|
52
53
|
const lastRanTime = startedAt ? (
|
|
53
54
|
<LastRanTime lastRanTime={startedAt} />
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { act, renderHook } from "@testing-library/react";
|
|
4
|
+
import { Provider } from "jotai";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
import { MockExecutions } from "@/__mocks__/executions";
|
|
8
|
+
import { MockNotebook } from "@/__mocks__/notebook";
|
|
9
|
+
import { MockRequestClient } from "@/__mocks__/requests";
|
|
10
|
+
import { cellId } from "@/__tests__/branded";
|
|
11
|
+
import { notebookAtom } from "@/core/cells/cells";
|
|
12
|
+
import {
|
|
13
|
+
cellExecutionInputsAtom,
|
|
14
|
+
cellExecutionView,
|
|
15
|
+
notebookQueuedOrRunningCountAtom,
|
|
16
|
+
} from "@/core/executions";
|
|
17
|
+
import { requestClientAtom } from "@/core/network/requests";
|
|
18
|
+
import type { DocumentModel } from "@/core/notebook/document";
|
|
19
|
+
import {
|
|
20
|
+
cell,
|
|
21
|
+
document as documentOf,
|
|
22
|
+
withCells,
|
|
23
|
+
} from "@/core/notebook/document/__tests__/fixtures";
|
|
24
|
+
import { activeDocumentModelAtom } from "@/core/session/active-notebook";
|
|
25
|
+
import { store } from "@/core/state/jotai";
|
|
26
|
+
import { runCells, useRunStaleCells } from "../useRunCells";
|
|
27
|
+
|
|
28
|
+
const ENABLED = cellId("enabled");
|
|
29
|
+
const DISABLED = cellId("disabled");
|
|
30
|
+
|
|
31
|
+
describe("runCells", () => {
|
|
32
|
+
it("sends a disabled cell to the kernel without reading it as queued", async () => {
|
|
33
|
+
MockExecutions.seed(store, []);
|
|
34
|
+
const notebook = MockNotebook.notebookState({
|
|
35
|
+
cellData: {
|
|
36
|
+
[ENABLED]: { code: "x = 1" },
|
|
37
|
+
[DISABLED]: { code: "y = 2", config: { disabled: true } },
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const sendRun = vi.fn(async () => null);
|
|
41
|
+
|
|
42
|
+
await runCells({
|
|
43
|
+
cellIds: [ENABLED, DISABLED],
|
|
44
|
+
sendRun,
|
|
45
|
+
prepareForRun: vi.fn(),
|
|
46
|
+
notebook,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(sendRun).toHaveBeenCalledWith({
|
|
50
|
+
cellIds: [ENABLED, DISABLED],
|
|
51
|
+
codes: ["x = 1", "y = 2"],
|
|
52
|
+
});
|
|
53
|
+
const inputs = store.get(cellExecutionInputsAtom);
|
|
54
|
+
expect(cellExecutionView(inputs, ENABLED).status).toBe("queued");
|
|
55
|
+
expect(cellExecutionView(inputs, DISABLED).status).toBe("idle");
|
|
56
|
+
expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(1);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("useRunStaleCells", () => {
|
|
61
|
+
const C1 = cellId("c1");
|
|
62
|
+
const C2 = cellId("c2");
|
|
63
|
+
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
store.set(activeDocumentModelAtom, null);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("flushes pending edits before choosing the stale cells", async () => {
|
|
69
|
+
const document = documentOf();
|
|
70
|
+
const replica = MockExecutions.seed(
|
|
71
|
+
store,
|
|
72
|
+
[
|
|
73
|
+
MockExecutions.completed(C1, { code_fingerprint: "c1-hash" }),
|
|
74
|
+
MockExecutions.completed(C2, { code_fingerprint: "c2-hash" }),
|
|
75
|
+
],
|
|
76
|
+
null,
|
|
77
|
+
document,
|
|
78
|
+
);
|
|
79
|
+
store.set(
|
|
80
|
+
notebookAtom,
|
|
81
|
+
MockNotebook.notebookState({
|
|
82
|
+
cellData: { [C1]: { code: "x = 2" }, [C2]: { code: "y = 2" } },
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
const client = MockRequestClient.create();
|
|
86
|
+
store.set(requestClientAtom, client);
|
|
87
|
+
// The edit to c1 is still in flight: the host confirms it on flush.
|
|
88
|
+
const flush = vi.fn(async () => {
|
|
89
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
90
|
+
store.set(
|
|
91
|
+
replica.documentAtom,
|
|
92
|
+
withCells(document, {
|
|
93
|
+
[C1]: cell(C1, "x = 2", { code_fingerprint: "c1-hash-2" }),
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
store.set(activeDocumentModelAtom, { flush } as unknown as DocumentModel);
|
|
98
|
+
const wrapper = ({ children }: { children: ReactNode }) => (
|
|
99
|
+
<Provider store={store}>{children}</Provider>
|
|
100
|
+
);
|
|
101
|
+
const { result } = renderHook(useRunStaleCells, { wrapper });
|
|
102
|
+
|
|
103
|
+
await act(() => result.current());
|
|
104
|
+
|
|
105
|
+
expect(flush).toHaveBeenCalledOnce();
|
|
106
|
+
expect(client.sendRun).toHaveBeenCalledWith({
|
|
107
|
+
cellIds: [C1],
|
|
108
|
+
codes: ["x = 2"],
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("does nothing when the notebook changed while the flush was pending", async () => {
|
|
113
|
+
MockExecutions.seed(
|
|
114
|
+
store,
|
|
115
|
+
[MockExecutions.completed(C1, { code_fingerprint: "stale" })],
|
|
116
|
+
null,
|
|
117
|
+
documentOf(),
|
|
118
|
+
);
|
|
119
|
+
store.set(
|
|
120
|
+
notebookAtom,
|
|
121
|
+
MockNotebook.notebookState({ cellData: { [C1]: { code: "x = 1" } } }),
|
|
122
|
+
);
|
|
123
|
+
const client = MockRequestClient.create();
|
|
124
|
+
store.set(requestClientAtom, client);
|
|
125
|
+
const flush = vi.fn(async () => {
|
|
126
|
+
store.set(activeDocumentModelAtom, null);
|
|
127
|
+
throw new Error("aborted");
|
|
128
|
+
});
|
|
129
|
+
store.set(activeDocumentModelAtom, { flush } as unknown as DocumentModel);
|
|
130
|
+
const wrapper = ({ children }: { children: ReactNode }) => (
|
|
131
|
+
<Provider store={store}>{children}</Provider>
|
|
132
|
+
);
|
|
133
|
+
const { result } = renderHook(useRunStaleCells, { wrapper });
|
|
134
|
+
|
|
135
|
+
await act(() => result.current());
|
|
136
|
+
|
|
137
|
+
expect(client.sendRun).not.toHaveBeenCalled();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("still runs what is stale when the flush fails", async () => {
|
|
141
|
+
MockExecutions.seed(
|
|
142
|
+
store,
|
|
143
|
+
[MockExecutions.completed(C1, { code_fingerprint: "stale" })],
|
|
144
|
+
null,
|
|
145
|
+
documentOf(),
|
|
146
|
+
);
|
|
147
|
+
store.set(
|
|
148
|
+
notebookAtom,
|
|
149
|
+
MockNotebook.notebookState({ cellData: { [C1]: { code: "x = 1" } } }),
|
|
150
|
+
);
|
|
151
|
+
const client = MockRequestClient.create();
|
|
152
|
+
store.set(requestClientAtom, client);
|
|
153
|
+
const flush = vi.fn(async () => {
|
|
154
|
+
throw new Error("offline");
|
|
155
|
+
});
|
|
156
|
+
store.set(activeDocumentModelAtom, { flush } as unknown as DocumentModel);
|
|
157
|
+
const wrapper = ({ children }: { children: ReactNode }) => (
|
|
158
|
+
<Provider store={store}>{children}</Provider>
|
|
159
|
+
);
|
|
160
|
+
const { result } = renderHook(useRunStaleCells, { wrapper });
|
|
161
|
+
|
|
162
|
+
await act(() => result.current());
|
|
163
|
+
|
|
164
|
+
expect(client.sendRun).toHaveBeenCalledWith({
|
|
165
|
+
cellIds: [C1],
|
|
166
|
+
codes: ["x = 1"],
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -14,11 +14,10 @@ import { memo } from "react";
|
|
|
14
14
|
import { Toolbar } from "@/components/layout/toolbar";
|
|
15
15
|
import { Button } from "@/components/ui/button";
|
|
16
16
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
17
|
-
import { useNotebook } from "@/core/cells/cells";
|
|
18
17
|
import { columnOf, useLayout } from "@/core/cells/layout";
|
|
19
18
|
import type { CellId } from "@/core/cells/ids";
|
|
20
19
|
import { getDescendantsStatus } from "@/core/cells/utils";
|
|
21
|
-
import { cellExecutionInputsAtom } from "@/core/executions";
|
|
20
|
+
import { cellExecutionInputsAtom, cellFreshnessAtom } from "@/core/executions";
|
|
22
21
|
import { cn } from "@/utils/cn";
|
|
23
22
|
|
|
24
23
|
interface Props {
|
|
@@ -66,12 +65,12 @@ export const CollapsedCellBanner: React.FC<{
|
|
|
66
65
|
cellId: CellId;
|
|
67
66
|
count: number;
|
|
68
67
|
}> = memo(({ onClick, count, cellId }) => {
|
|
69
|
-
const notebook = useNotebook();
|
|
70
68
|
const executions = useAtomValue(cellExecutionInputsAtom);
|
|
69
|
+
const freshness = useAtomValue(cellFreshnessAtom);
|
|
71
70
|
const layout = useLayout();
|
|
72
71
|
const states = getDescendantsStatus(
|
|
73
|
-
notebook,
|
|
74
72
|
executions,
|
|
73
|
+
freshness,
|
|
75
74
|
columnOf(layout, cellId).folds.get(cellId) ?? [],
|
|
76
75
|
);
|
|
77
76
|
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
import { closeCompletion } from "@codemirror/autocomplete";
|
|
4
4
|
import { atom, useSetAtom } from "jotai";
|
|
5
5
|
import useEvent from "react-use-event-hook";
|
|
6
|
+
import { toast } from "@/components/ui/use-toast";
|
|
6
7
|
import { getNotebook, type NotebookState } from "@/core/cells/cells";
|
|
7
8
|
import { useCellActions } from "@/core/cells/actions";
|
|
8
9
|
import type { CellId } from "@/core/cells/ids";
|
|
9
10
|
import { enabledCellIds, staleCellIds } from "@/core/cells/utils";
|
|
10
11
|
import {
|
|
11
12
|
cellExecutionInputsAtom,
|
|
13
|
+
cellFreshnessAtom,
|
|
12
14
|
submitCells,
|
|
13
15
|
withdrawCells,
|
|
14
16
|
} from "@/core/executions";
|
|
@@ -16,6 +18,7 @@ import { getCurrentLanguageAdapter } from "@/core/codemirror/language/commands";
|
|
|
16
18
|
import { getEditorCodeAsPython } from "@/core/codemirror/language/utils";
|
|
17
19
|
import { useRequestClient } from "@/core/network/requests";
|
|
18
20
|
import type { ExecuteCellsRequest } from "@/core/network/types";
|
|
21
|
+
import { activeDocumentModelAtom } from "@/core/session/active-notebook";
|
|
19
22
|
import { store } from "@/core/state/jotai";
|
|
20
23
|
import { Logger } from "@/utils/Logger";
|
|
21
24
|
|
|
@@ -26,9 +29,35 @@ export const hasRunAnyCellAtom = atom<boolean>(false);
|
|
|
26
29
|
*/
|
|
27
30
|
export function useRunStaleCells() {
|
|
28
31
|
const runCells = useRunCells();
|
|
29
|
-
const runStaleCells = useEvent(() =>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
const runStaleCells = useEvent(async () => {
|
|
33
|
+
// Freshness reads the confirmed document, so an edit still in flight
|
|
34
|
+
// reaches the host before the stale cells are chosen.
|
|
35
|
+
const model = store.get(activeDocumentModelAtom);
|
|
36
|
+
try {
|
|
37
|
+
await model?.flush();
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (store.get(activeDocumentModelAtom) !== model) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
Logger.warn("Running stale cells with unsynced edits", error);
|
|
43
|
+
toast({
|
|
44
|
+
title: "Edits not yet synced",
|
|
45
|
+
description: "Stale cells were chosen from the last synced source.",
|
|
46
|
+
variant: "danger",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// The request was for the notebook that was open when it was made.
|
|
50
|
+
if (store.get(activeDocumentModelAtom) !== model) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
return runCells(
|
|
54
|
+
staleCellIds(
|
|
55
|
+
getNotebook(),
|
|
56
|
+
store.get(cellExecutionInputsAtom),
|
|
57
|
+
store.get(cellFreshnessAtom),
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
32
61
|
return runStaleCells;
|
|
33
62
|
}
|
|
34
63
|
|
|
@@ -418,7 +418,7 @@ describe("SendErrorReportButton", () => {
|
|
|
418
418
|
store.set(
|
|
419
419
|
notebookAtom,
|
|
420
420
|
MockNotebook.notebookState({
|
|
421
|
-
cellData: { [CELL_ID]: { code: CODE
|
|
421
|
+
cellData: { [CELL_ID]: { code: CODE } },
|
|
422
422
|
cellRuntime: { [CELL_ID]: { output } },
|
|
423
423
|
}),
|
|
424
424
|
);
|
|
@@ -429,7 +429,6 @@ describe("SendErrorReportButton", () => {
|
|
|
429
429
|
[CELL_ID]: {
|
|
430
430
|
...state.cellData[CELL_ID],
|
|
431
431
|
code: "print('edited')",
|
|
432
|
-
edited: true,
|
|
433
432
|
},
|
|
434
433
|
},
|
|
435
434
|
}));
|
|
@@ -466,7 +465,7 @@ describe("SendErrorReportButton", () => {
|
|
|
466
465
|
notebookAtom,
|
|
467
466
|
MockNotebook.notebookState({
|
|
468
467
|
cellData: {
|
|
469
|
-
[CELL_ID]: { code: "unrelated edit"
|
|
468
|
+
[CELL_ID]: { code: "unrelated edit" },
|
|
470
469
|
},
|
|
471
470
|
cellRuntime: {
|
|
472
471
|
[CELL_ID]: {
|