@marimo-team/frontend 0.25.3-dev101 → 0.25.3-dev102
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/package.json +1 -1
- package/src/core/document/__tests__/model.test.ts +6 -6
- package/src/core/document/__tests__/state.test.ts +5 -13
- package/src/core/document/__tests__/submission.test.ts +2 -0
- package/src/core/document/__tests__/user.ts +3 -3
- package/src/core/document/index.ts +3 -1
- package/src/core/document/model.ts +7 -8
- package/src/core/document/select.ts +32 -0
- package/src/core/document/state.ts +3 -30
- package/src/core/document/sync.ts +29 -25
package/package.json
CHANGED
|
@@ -67,8 +67,8 @@ function setup() {
|
|
|
67
67
|
unmounted,
|
|
68
68
|
edit: (code: string) =>
|
|
69
69
|
store.set(model.editAtom, (tx) => tx.setCode("c1", code)),
|
|
70
|
-
|
|
71
|
-
value: () => store.get(model.
|
|
70
|
+
status: () => store.get(model.syncStatusAtom),
|
|
71
|
+
value: () => store.get(model.documentAtom)?.cells.get("c1"),
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -102,7 +102,7 @@ describe("document synchronization", () => {
|
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
it("fails without repeating a request when the response names another notebook", async () => {
|
|
105
|
-
const { sent, replies, edit, model,
|
|
105
|
+
const { sent, replies, edit, model, status } = setup();
|
|
106
106
|
edit("local");
|
|
107
107
|
const rejected = expectFailure(model.flush(), "another notebook");
|
|
108
108
|
await vi.advanceTimersByTimeAsync(0);
|
|
@@ -110,7 +110,7 @@ describe("document synchronization", () => {
|
|
|
110
110
|
await rejected;
|
|
111
111
|
await vi.advanceTimersByTimeAsync(5000);
|
|
112
112
|
expect(sent).toHaveBeenCalledOnce();
|
|
113
|
-
expect(
|
|
113
|
+
expect(status()).toMatchObject({ status: "error" });
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
it("ignores a late response and source update after its lifetime ends", async () => {
|
|
@@ -145,7 +145,7 @@ describe("document synchronization", () => {
|
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
it("aborts an in-flight edit when notebook identity changes", async () => {
|
|
148
|
-
const { store, source, sent, edit, model,
|
|
148
|
+
const { store, source, sent, edit, model, status } = setup();
|
|
149
149
|
edit("local");
|
|
150
150
|
const flushed = model.flush();
|
|
151
151
|
const rejected = expectFailure(flushed, "notebook changed");
|
|
@@ -153,6 +153,6 @@ describe("document synchronization", () => {
|
|
|
153
153
|
store.set(source, document("n2"));
|
|
154
154
|
await rejected;
|
|
155
155
|
expect(sent.mock.calls[0][0].signal.aborted).toBe(true);
|
|
156
|
-
expect(
|
|
156
|
+
expect(status()).toMatchObject({ status: "error", retryable: false });
|
|
157
157
|
});
|
|
158
158
|
});
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type DocumentEdit,
|
|
9
9
|
type DocumentEditor,
|
|
10
10
|
} from "../state";
|
|
11
|
+
import { selectCell, selectOrder } from "../select";
|
|
11
12
|
import { cell, document, withCells } from "./fixtures";
|
|
12
13
|
|
|
13
14
|
const lifetimes: AbortController[] = [];
|
|
@@ -29,6 +30,8 @@ function setup(initial: Document | null = document()) {
|
|
|
29
30
|
store,
|
|
30
31
|
source,
|
|
31
32
|
...model,
|
|
33
|
+
cellAtom: (id: string) => selectCell(model.documentAtom, id),
|
|
34
|
+
orderAtom: selectOrder(model.documentAtom),
|
|
32
35
|
abort: () => controller.abort(),
|
|
33
36
|
edit: (callback: DocumentEdit) => store.set(model.editAtom, callback),
|
|
34
37
|
};
|
|
@@ -252,10 +255,9 @@ describe("document model", () => {
|
|
|
252
255
|
unsubscribeOrder();
|
|
253
256
|
});
|
|
254
257
|
|
|
255
|
-
it("isolates cell
|
|
258
|
+
it("isolates cell and order subscriptions across local and host updates", () => {
|
|
256
259
|
const initial = document();
|
|
257
|
-
const { store, source, cellAtom, orderAtom,
|
|
258
|
-
setup(initial);
|
|
260
|
+
const { store, source, cellAtom, orderAtom, edit } = setup(initial);
|
|
259
261
|
edit((tx) => {
|
|
260
262
|
tx.setCode("c1", "local");
|
|
261
263
|
tx.createCell({ id: "c3", code: "new" });
|
|
@@ -263,16 +265,13 @@ describe("document model", () => {
|
|
|
263
265
|
const c1 = cellAtom("c1");
|
|
264
266
|
const first = store.get(c1);
|
|
265
267
|
const order = store.get(orderAtom);
|
|
266
|
-
const config = store.get(appConfigAtom);
|
|
267
268
|
const onFirst = vi.fn();
|
|
268
269
|
const onSecond = vi.fn();
|
|
269
270
|
const onOrder = vi.fn();
|
|
270
|
-
const onConfig = vi.fn();
|
|
271
271
|
const unsubscribe = [
|
|
272
272
|
store.sub(c1, onFirst),
|
|
273
273
|
store.sub(cellAtom("c2"), onSecond),
|
|
274
274
|
store.sub(orderAtom, onOrder),
|
|
275
|
-
store.sub(appConfigAtom, onConfig),
|
|
276
275
|
];
|
|
277
276
|
|
|
278
277
|
edit((tx) => tx.setCode("c2", "other local"));
|
|
@@ -289,11 +288,9 @@ describe("document model", () => {
|
|
|
289
288
|
});
|
|
290
289
|
expect(store.get(c1)).toBe(first);
|
|
291
290
|
expect(store.get(orderAtom)).toBe(order);
|
|
292
|
-
expect(store.get(appConfigAtom)).toBe(config);
|
|
293
291
|
expect(onFirst).not.toHaveBeenCalled();
|
|
294
292
|
expect(onSecond).toHaveBeenCalledOnce();
|
|
295
293
|
expect(onOrder).not.toHaveBeenCalled();
|
|
296
|
-
expect(onConfig).not.toHaveBeenCalled();
|
|
297
294
|
|
|
298
295
|
store.set(
|
|
299
296
|
source,
|
|
@@ -311,11 +308,6 @@ describe("document model", () => {
|
|
|
311
308
|
edit((tx) => tx.setOrder(["c3", "c2", "c1"]));
|
|
312
309
|
expect(onOrder).toHaveBeenCalledOnce();
|
|
313
310
|
expect(onFirst).toHaveBeenCalledOnce();
|
|
314
|
-
store.set(source, {
|
|
315
|
-
...remote,
|
|
316
|
-
revision: { ...remote.revision, app_config: { width: "full" } },
|
|
317
|
-
});
|
|
318
|
-
expect(onConfig).toHaveBeenCalledOnce();
|
|
319
311
|
unsubscribe.forEach((fn) => fn());
|
|
320
312
|
});
|
|
321
313
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { atom, createStore } from "jotai/vanilla";
|
|
4
4
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
import type { Document } from "../../host/resources/document";
|
|
6
|
+
import { selectCell } from "../select";
|
|
6
7
|
import { createDocumentState, type DocumentSubmission } from "../state";
|
|
7
8
|
import { cell, document, withCells } from "./fixtures";
|
|
8
9
|
|
|
@@ -25,6 +26,7 @@ function setup() {
|
|
|
25
26
|
store,
|
|
26
27
|
source,
|
|
27
28
|
...model,
|
|
29
|
+
cellAtom: (id: string) => selectCell(model.documentAtom, id),
|
|
28
30
|
submit() {
|
|
29
31
|
const submission = store.set(model.submitAtom);
|
|
30
32
|
if (submission === null) {
|
|
@@ -43,7 +43,7 @@ export function createUser(
|
|
|
43
43
|
store.set(model.editAtom, (tx) => tx.deleteCell(id));
|
|
44
44
|
},
|
|
45
45
|
cell(id: Protocol.CellId) {
|
|
46
|
-
const cell = store.get(model.
|
|
46
|
+
const cell = store.get(model.documentAtom)?.cells.get(id);
|
|
47
47
|
return cell === undefined
|
|
48
48
|
? undefined
|
|
49
49
|
: {
|
|
@@ -53,8 +53,8 @@ export function createUser(
|
|
|
53
53
|
config: cell.config,
|
|
54
54
|
};
|
|
55
55
|
},
|
|
56
|
-
order: () => store.get(model.
|
|
57
|
-
status: () => store.get(model.
|
|
56
|
+
order: () => store.get(model.documentAtom)?.order ?? null,
|
|
57
|
+
status: () => store.get(model.syncStatusAtom),
|
|
58
58
|
sync: () => observe(model.flush()),
|
|
59
59
|
retry: () => observe(model.retry()),
|
|
60
60
|
};
|
|
@@ -6,18 +6,19 @@ import { createDocumentState, type DocumentState } from "./state";
|
|
|
6
6
|
import {
|
|
7
7
|
createDocumentSync,
|
|
8
8
|
type DocumentHost,
|
|
9
|
-
type
|
|
9
|
+
type DocumentSyncStatus,
|
|
10
10
|
} from "./sync";
|
|
11
11
|
|
|
12
12
|
export type { DocumentEdit, DocumentEditor } from "./state";
|
|
13
|
-
export type {
|
|
13
|
+
export type { EditableCell, EditableDocument } from "./draft";
|
|
14
|
+
export type { DocumentHost, DocumentSyncStatus } from "./sync";
|
|
14
15
|
|
|
15
16
|
/** The editable document and its synchronization with the host. */
|
|
16
17
|
export interface DocumentModel extends Pick<
|
|
17
18
|
DocumentState,
|
|
18
|
-
"
|
|
19
|
+
"documentAtom" | "editAtom"
|
|
19
20
|
> {
|
|
20
|
-
readonly
|
|
21
|
+
readonly syncStatusAtom: Atom<DocumentSyncStatus>;
|
|
21
22
|
/** Drain local edits and return the confirmed revision. */
|
|
22
23
|
flush(): Promise<Protocol.Document>;
|
|
23
24
|
/** Retry a rejection against fresh state, or repeat an uncertain request exactly. */
|
|
@@ -42,11 +43,9 @@ export function createDocumentModel({
|
|
|
42
43
|
});
|
|
43
44
|
const sync = createDocumentSync({ store, notebook, state, signal });
|
|
44
45
|
return {
|
|
45
|
-
|
|
46
|
-
orderAtom: state.orderAtom,
|
|
47
|
-
appConfigAtom: state.appConfigAtom,
|
|
46
|
+
documentAtom: state.documentAtom,
|
|
48
47
|
editAtom: state.editAtom,
|
|
49
|
-
|
|
48
|
+
syncStatusAtom: sync.statusAtom,
|
|
50
49
|
flush: sync.flush,
|
|
51
50
|
retry: sync.retry,
|
|
52
51
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type * as Protocol from "@marimo-team/client/protocol";
|
|
4
|
+
import { dequal } from "dequal";
|
|
5
|
+
import type { Atom } from "jotai/vanilla";
|
|
6
|
+
import { selectAtom } from "jotai/vanilla/utils";
|
|
7
|
+
import { type EditableCell, type EditableDocument, sameCell } from "./draft";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Select one cell of a document.
|
|
11
|
+
*
|
|
12
|
+
* The atom notifies when the cell's code, name, or config changes. A new host
|
|
13
|
+
* record with the same values leaves it unchanged. Each call creates an atom,
|
|
14
|
+
* so keep it for as long as its subscription.
|
|
15
|
+
*/
|
|
16
|
+
export function selectCell(
|
|
17
|
+
document: Atom<EditableDocument | null>,
|
|
18
|
+
id: Protocol.CellId,
|
|
19
|
+
): Atom<EditableCell | undefined> {
|
|
20
|
+
return selectAtom(
|
|
21
|
+
document,
|
|
22
|
+
(value) => value?.cells.get(id),
|
|
23
|
+
(a, b) => a === b || (a !== undefined && b !== undefined && sameCell(a, b)),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Select a document's cell order, null while the document is unavailable. */
|
|
28
|
+
export function selectOrder(
|
|
29
|
+
document: Atom<EditableDocument | null>,
|
|
30
|
+
): Atom<readonly Protocol.CellId[] | null> {
|
|
31
|
+
return selectAtom(document, (value) => value?.order ?? null, dequal);
|
|
32
|
+
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { dequal } from "dequal";
|
|
4
3
|
import {
|
|
5
4
|
atom,
|
|
6
5
|
type Atom,
|
|
7
6
|
type createStore,
|
|
8
7
|
type WritableAtom,
|
|
9
8
|
} from "jotai/vanilla";
|
|
10
|
-
import { selectAtom } from "jotai/vanilla/utils";
|
|
11
9
|
import type { Document } from "../host/resources/document";
|
|
12
10
|
import * as Protocol from "@marimo-team/client/protocol";
|
|
13
11
|
import {
|
|
@@ -16,14 +14,12 @@ import {
|
|
|
16
14
|
createCell,
|
|
17
15
|
deleteCell,
|
|
18
16
|
type Draft,
|
|
19
|
-
type EditableCell,
|
|
20
17
|
type EditableDocument,
|
|
21
18
|
editCell,
|
|
22
19
|
EMPTY_DRAFT,
|
|
23
20
|
isEmptyDraft,
|
|
24
21
|
type Placement,
|
|
25
22
|
prune,
|
|
26
|
-
sameCell,
|
|
27
23
|
setOrder,
|
|
28
24
|
toChanges,
|
|
29
25
|
viewDocument,
|
|
@@ -70,12 +66,8 @@ interface ModelState {
|
|
|
70
66
|
* Switching notebook identity invalidates it, retaining each notebook's draft.
|
|
71
67
|
*/
|
|
72
68
|
export interface DocumentState {
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
readonly orderAtom: Atom<readonly Protocol.CellId[] | null>;
|
|
76
|
-
readonly appConfigAtom: Atom<Readonly<
|
|
77
|
-
Protocol.Document["app_config"]
|
|
78
|
-
> | null>;
|
|
69
|
+
/** Confirmed host state with local drafts applied, null while unavailable. */
|
|
70
|
+
readonly documentAtom: Atom<EditableDocument | null>;
|
|
79
71
|
/** Null when the document or source needed by a put is unavailable. */
|
|
80
72
|
readonly changesAtom: Atom<readonly Protocol.Change[] | null>;
|
|
81
73
|
/** Remains pending until both the response and its streamed revision arrive. */
|
|
@@ -158,23 +150,6 @@ export function createDocumentState({
|
|
|
158
150
|
? null
|
|
159
151
|
: viewDocument(state.confirmed, draftOf(state));
|
|
160
152
|
});
|
|
161
|
-
const cellAtom = (id: Protocol.CellId) =>
|
|
162
|
-
selectAtom(
|
|
163
|
-
documentAtom,
|
|
164
|
-
(document) => document?.cells.get(id),
|
|
165
|
-
(a, b) =>
|
|
166
|
-
a === b || (a !== undefined && b !== undefined && sameCell(a, b)),
|
|
167
|
-
);
|
|
168
|
-
const orderAtom = selectAtom(
|
|
169
|
-
documentAtom,
|
|
170
|
-
(document) => document?.order ?? null,
|
|
171
|
-
dequal,
|
|
172
|
-
);
|
|
173
|
-
const appConfigAtom = selectAtom(
|
|
174
|
-
stateAtom,
|
|
175
|
-
(state) => state.confirmed?.revision.app_config ?? null,
|
|
176
|
-
dequal,
|
|
177
|
-
);
|
|
178
153
|
const changesAtom = atom((get): readonly Protocol.Change[] | null => {
|
|
179
154
|
const state = get(stateAtom);
|
|
180
155
|
return state.confirmed === null
|
|
@@ -304,9 +279,7 @@ export function createDocumentState({
|
|
|
304
279
|
}
|
|
305
280
|
|
|
306
281
|
return {
|
|
307
|
-
|
|
308
|
-
orderAtom,
|
|
309
|
-
appConfigAtom,
|
|
282
|
+
documentAtom,
|
|
310
283
|
changesAtom,
|
|
311
284
|
pendingAtom,
|
|
312
285
|
editAtom,
|
|
@@ -9,7 +9,7 @@ import type { DocumentState, DocumentSubmission } from "./state";
|
|
|
9
9
|
/** The notebook state and operation needed by document editing. */
|
|
10
10
|
export type DocumentHost = Pick<NotebookClient, "documentAtom" | "edit">;
|
|
11
11
|
|
|
12
|
-
export type
|
|
12
|
+
export type DocumentSyncStatus =
|
|
13
13
|
| {
|
|
14
14
|
readonly status:
|
|
15
15
|
| "idle"
|
|
@@ -26,7 +26,7 @@ export type DocumentSyncState =
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
interface Submission {
|
|
29
|
-
readonly
|
|
29
|
+
readonly submission: DocumentSubmission;
|
|
30
30
|
readonly idempotencyKey: string;
|
|
31
31
|
readonly startedAt: number;
|
|
32
32
|
readonly controller: AbortController;
|
|
@@ -85,7 +85,7 @@ export function createDocumentSync({
|
|
|
85
85
|
}) {
|
|
86
86
|
signal.throwIfAborted();
|
|
87
87
|
const source = notebook.documentAtom;
|
|
88
|
-
const
|
|
88
|
+
const statusAtom = atom<DocumentSyncStatus>({ status: "idle" });
|
|
89
89
|
const observed = atom((get) => ({
|
|
90
90
|
document: get(source),
|
|
91
91
|
pending: get(state.pendingAtom),
|
|
@@ -93,7 +93,7 @@ export function createDocumentSync({
|
|
|
93
93
|
}));
|
|
94
94
|
let active: Submission | null = null;
|
|
95
95
|
let running: Promise<Protocol.Document> | null = null;
|
|
96
|
-
let failure: Extract<
|
|
96
|
+
let failure: Extract<DocumentSyncStatus, { status: "error" }> | null = null;
|
|
97
97
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
98
98
|
let refreshing = false;
|
|
99
99
|
|
|
@@ -118,7 +118,7 @@ export function createDocumentSync({
|
|
|
118
118
|
if (
|
|
119
119
|
active &&
|
|
120
120
|
current &&
|
|
121
|
-
current.revision.notebook_id !== active.
|
|
121
|
+
current.revision.notebook_id !== active.submission.notebookId
|
|
122
122
|
) {
|
|
123
123
|
active.controller.abort(
|
|
124
124
|
new Error("The notebook changed during synchronization"),
|
|
@@ -130,13 +130,13 @@ export function createDocumentSync({
|
|
|
130
130
|
clearTimer();
|
|
131
131
|
const changes = store.get(state.changesAtom);
|
|
132
132
|
if (changes === null) {
|
|
133
|
-
store.set(
|
|
133
|
+
store.set(statusAtom, { status: "unavailable" });
|
|
134
134
|
} else if (changes.length === 0) {
|
|
135
135
|
// Release unsent reversals instead of keeping an invisible override.
|
|
136
136
|
store.set(state.submitAtom);
|
|
137
|
-
store.set(
|
|
137
|
+
store.set(statusAtom, { status: "idle" });
|
|
138
138
|
} else {
|
|
139
|
-
store.set(
|
|
139
|
+
store.set(statusAtom, { status: "pending" });
|
|
140
140
|
timer = setTimeout(() => {
|
|
141
141
|
void flush().catch(() => undefined);
|
|
142
142
|
}, DEBOUNCE_MS);
|
|
@@ -155,7 +155,7 @@ export function createDocumentSync({
|
|
|
155
155
|
"The edit's retry window expired; its outcome is unknown",
|
|
156
156
|
);
|
|
157
157
|
}
|
|
158
|
-
store.set(
|
|
158
|
+
store.set(statusAtom, { status: "sending" });
|
|
159
159
|
const controller = new AbortController();
|
|
160
160
|
const abort = () => controller.abort(request.controller.signal.reason);
|
|
161
161
|
request.controller.signal.addEventListener("abort", abort, {
|
|
@@ -170,10 +170,14 @@ export function createDocumentSync({
|
|
|
170
170
|
signal.throwIfAborted();
|
|
171
171
|
request.controller.signal.throwIfAborted();
|
|
172
172
|
result = await abortable(
|
|
173
|
-
notebook.edit(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
notebook.edit(
|
|
174
|
+
request.submission.notebookId,
|
|
175
|
+
request.submission.changes,
|
|
176
|
+
{
|
|
177
|
+
idempotencyKey: request.idempotencyKey,
|
|
178
|
+
signal: controller.signal,
|
|
179
|
+
},
|
|
180
|
+
),
|
|
177
181
|
controller.signal,
|
|
178
182
|
);
|
|
179
183
|
} catch (error) {
|
|
@@ -198,17 +202,17 @@ export function createDocumentSync({
|
|
|
198
202
|
cause: result.problem,
|
|
199
203
|
});
|
|
200
204
|
}
|
|
201
|
-
if (result.data.document.notebook_id !== request.
|
|
205
|
+
if (result.data.document.notebook_id !== request.submission.notebookId) {
|
|
202
206
|
throw new Error("Edit response belongs to another notebook");
|
|
203
207
|
}
|
|
204
208
|
request.response = result.data.document;
|
|
205
|
-
store.set(state.settleAtom, request.
|
|
209
|
+
store.set(state.settleAtom, request.submission, request.response);
|
|
206
210
|
return;
|
|
207
211
|
}
|
|
208
212
|
}
|
|
209
213
|
|
|
210
214
|
function confirm(request: Submission): Promise<void> {
|
|
211
|
-
store.set(
|
|
215
|
+
store.set(statusAtom, { status: "confirming" });
|
|
212
216
|
return new Promise((resolve, reject) => {
|
|
213
217
|
const check = () => {
|
|
214
218
|
if (signal.aborted || request.controller.signal.aborted) {
|
|
@@ -216,7 +220,7 @@ export function createDocumentSync({
|
|
|
216
220
|
reject(
|
|
217
221
|
signal.aborted ? signal.reason : request.controller.signal.reason,
|
|
218
222
|
);
|
|
219
|
-
} else if (store.get(state.pendingAtom) !== request.
|
|
223
|
+
} else if (store.get(state.pendingAtom) !== request.submission) {
|
|
220
224
|
cleanup();
|
|
221
225
|
resolve();
|
|
222
226
|
}
|
|
@@ -247,12 +251,12 @@ export function createDocumentSync({
|
|
|
247
251
|
if (current === null || store.get(state.changesAtom) === null) {
|
|
248
252
|
throw unavailable();
|
|
249
253
|
}
|
|
250
|
-
const
|
|
251
|
-
if (
|
|
254
|
+
const submission = store.set(state.submitAtom);
|
|
255
|
+
if (submission === null) {
|
|
252
256
|
return current.revision;
|
|
253
257
|
}
|
|
254
258
|
active = {
|
|
255
|
-
|
|
259
|
+
submission,
|
|
256
260
|
idempotencyKey: idempotencyKey(),
|
|
257
261
|
startedAt: Date.now(),
|
|
258
262
|
controller: new AbortController(),
|
|
@@ -282,7 +286,7 @@ export function createDocumentSync({
|
|
|
282
286
|
Date.now() - active.startedAt < RETRY_WINDOW_MS),
|
|
283
287
|
};
|
|
284
288
|
if (!signal.aborted) {
|
|
285
|
-
store.set(
|
|
289
|
+
store.set(statusAtom, failure);
|
|
286
290
|
}
|
|
287
291
|
throw error;
|
|
288
292
|
} finally {
|
|
@@ -305,7 +309,7 @@ export function createDocumentSync({
|
|
|
305
309
|
return running;
|
|
306
310
|
}
|
|
307
311
|
const notebookId =
|
|
308
|
-
active?.
|
|
312
|
+
active?.submission.notebookId ?? store.get(source)?.revision.notebook_id;
|
|
309
313
|
if (notebookId === undefined) {
|
|
310
314
|
return Promise.reject(unavailable());
|
|
311
315
|
}
|
|
@@ -329,14 +333,14 @@ export function createDocumentSync({
|
|
|
329
333
|
const rejectedAt = active.problem.edit?.document.version;
|
|
330
334
|
if (
|
|
331
335
|
!current ||
|
|
332
|
-
current.revision.notebook_id !== active.
|
|
336
|
+
current.revision.notebook_id !== active.submission.notebookId ||
|
|
333
337
|
(rejectedAt !== undefined && current.revision.version < rejectedAt)
|
|
334
338
|
) {
|
|
335
339
|
return Promise.reject(
|
|
336
340
|
new Error("Wait for the host's current document before retrying"),
|
|
337
341
|
);
|
|
338
342
|
}
|
|
339
|
-
store.set(state.settleAtom, active.
|
|
343
|
+
store.set(state.settleAtom, active.submission, null);
|
|
340
344
|
active = null;
|
|
341
345
|
}
|
|
342
346
|
failure = null;
|
|
@@ -355,5 +359,5 @@ export function createDocumentSync({
|
|
|
355
359
|
stop();
|
|
356
360
|
}
|
|
357
361
|
refresh();
|
|
358
|
-
return {
|
|
362
|
+
return { statusAtom, flush, retry };
|
|
359
363
|
}
|