@agent-native/toolkit 0.22.2 → 0.22.3-nightly-20260927133429
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/composer/ComposerContextMenu.d.ts.map +1 -1
- package/dist/composer/ComposerContextMenu.js +12 -3
- package/dist/composer/ComposerContextMenu.js.map +1 -1
- package/dist/composer/MentionItemMedia.d.ts +1 -1
- package/dist/composer/MentionItemMedia.d.ts.map +1 -1
- package/dist/composer/MentionItemMedia.js +10 -5
- package/dist/composer/MentionItemMedia.js.map +1 -1
- package/dist/composer/MentionPopover.d.ts +5 -0
- package/dist/composer/MentionPopover.d.ts.map +1 -1
- package/dist/composer/MentionPopover.js +17 -6
- package/dist/composer/MentionPopover.js.map +1 -1
- package/dist/composer/PromptComposer.d.ts +11 -2
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +7 -6
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts +26 -2
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +262 -27
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/dist/composer/TiptapComposer.spec.js +21 -1
- package/dist/composer/TiptapComposer.spec.js.map +1 -1
- package/dist/composer/extensions/MentionReference.d.ts.map +1 -1
- package/dist/composer/extensions/MentionReference.js +1 -1
- package/dist/composer/extensions/MentionReference.js.map +1 -1
- package/dist/composer/index.d.ts +1 -1
- package/dist/composer/index.d.ts.map +1 -1
- package/dist/composer/index.js +1 -1
- package/dist/composer/index.js.map +1 -1
- package/dist/composer/types.d.ts +6 -0
- package/dist/composer/types.d.ts.map +1 -1
- package/dist/editor/useCollabReconcile.concurrent.spec.js +106 -0
- package/dist/editor/useCollabReconcile.concurrent.spec.js.map +1 -1
- package/dist/editor/useCollabReconcile.d.ts.map +1 -1
- package/dist/editor/useCollabReconcile.js +42 -24
- package/dist/editor/useCollabReconcile.js.map +1 -1
- package/package.json +1 -1
- package/src/composer/ComposerContextMenu.tsx +15 -3
- package/src/composer/MentionItemMedia.tsx +13 -13
- package/src/composer/MentionPopover.tsx +52 -10
- package/src/composer/PromptComposer.tsx +34 -5
- package/src/composer/TiptapComposer.spec.ts +24 -0
- package/src/composer/TiptapComposer.tsx +346 -27
- package/src/composer/extensions/MentionReference.tsx +3 -0
- package/src/composer/index.ts +2 -0
- package/src/composer/types.ts +6 -0
- package/src/editor/useCollabReconcile.concurrent.spec.ts +124 -0
- package/src/editor/useCollabReconcile.ts +43 -24
|
@@ -2276,6 +2276,130 @@ describe("useCollabReconcile — concurrent edit / lost-update guards", () => {
|
|
|
2276
2276
|
expect(getEditorMarkdown(captured.editor!)).toBe("# Doc updated by agent");
|
|
2277
2277
|
});
|
|
2278
2278
|
|
|
2279
|
+
it("merges typing that lands after an external reconcile decision before adopting the save baseline", async () => {
|
|
2280
|
+
const { captured, Harness } = makeHarness();
|
|
2281
|
+
vi.useFakeTimers();
|
|
2282
|
+
const base = "Alpha base.\n\nOmega base.";
|
|
2283
|
+
const external = "Alpha from server.\n\nOmega base.";
|
|
2284
|
+
const initial = {
|
|
2285
|
+
value: base,
|
|
2286
|
+
contentUpdatedAt: "2024-01-01T00:00:01.000Z",
|
|
2287
|
+
contentRevision: "revision-1",
|
|
2288
|
+
};
|
|
2289
|
+
|
|
2290
|
+
render(root, Harness, initial);
|
|
2291
|
+
await act(async () => vi.advanceTimersByTimeAsync(0));
|
|
2292
|
+
render(root, Harness, {
|
|
2293
|
+
value: external,
|
|
2294
|
+
contentUpdatedAt: "2024-01-01T00:00:02.000Z",
|
|
2295
|
+
contentRevision: "revision-2",
|
|
2296
|
+
});
|
|
2297
|
+
|
|
2298
|
+
act(() => captured.editor!.commands.insertContentAt(base.length, " LOCAL"));
|
|
2299
|
+
render(root, Harness, {
|
|
2300
|
+
value: external,
|
|
2301
|
+
contentUpdatedAt: "2024-01-01T00:00:02.000Z",
|
|
2302
|
+
contentRevision: "revision-2",
|
|
2303
|
+
});
|
|
2304
|
+
await act(async () => vi.advanceTimersByTimeAsync(0));
|
|
2305
|
+
|
|
2306
|
+
const merged = "Alpha from server.\n\nOmega base LOCAL.";
|
|
2307
|
+
expect(getEditorMarkdown(captured.editor!)).toBe(merged);
|
|
2308
|
+
expect(captured.reconciled).toEqual([
|
|
2309
|
+
{ status: "merged", content: merged },
|
|
2310
|
+
]);
|
|
2311
|
+
|
|
2312
|
+
render(root, Harness, {
|
|
2313
|
+
value: external,
|
|
2314
|
+
contentUpdatedAt: "2024-01-01T00:00:02.000Z",
|
|
2315
|
+
contentRevision: "revision-2",
|
|
2316
|
+
acknowledgedLocalSnapshot: {
|
|
2317
|
+
value: merged,
|
|
2318
|
+
revision: "revision-3",
|
|
2319
|
+
updatedAt: "2024-01-01T00:00:03.000Z",
|
|
2320
|
+
sequence: 1,
|
|
2321
|
+
},
|
|
2322
|
+
});
|
|
2323
|
+
await act(async () => vi.advanceTimersByTimeAsync(5000));
|
|
2324
|
+
expect(getEditorMarkdown(captured.editor!)).toBe(merged);
|
|
2325
|
+
expect(captured.reconciled).toHaveLength(1);
|
|
2326
|
+
});
|
|
2327
|
+
|
|
2328
|
+
it("supersedes a pending decision with the newest revision and preserves later typing", async () => {
|
|
2329
|
+
const { captured, Harness } = makeHarness();
|
|
2330
|
+
vi.useFakeTimers();
|
|
2331
|
+
const base = "Alpha base.\n\nOmega base.";
|
|
2332
|
+
render(root, Harness, {
|
|
2333
|
+
value: base,
|
|
2334
|
+
contentUpdatedAt: "2024-01-01T00:00:01.000Z",
|
|
2335
|
+
contentRevision: "revision-1",
|
|
2336
|
+
});
|
|
2337
|
+
await act(async () => vi.advanceTimersByTimeAsync(0));
|
|
2338
|
+
|
|
2339
|
+
render(root, Harness, {
|
|
2340
|
+
value: "Alpha revision 2.\n\nOmega base.",
|
|
2341
|
+
contentUpdatedAt: "2024-01-01T00:00:02.000Z",
|
|
2342
|
+
contentRevision: "revision-2",
|
|
2343
|
+
});
|
|
2344
|
+
render(root, Harness, {
|
|
2345
|
+
value: "Alpha revision 3.\n\nOmega base.",
|
|
2346
|
+
contentUpdatedAt: "2024-01-01T00:00:03.000Z",
|
|
2347
|
+
contentRevision: "revision-3",
|
|
2348
|
+
});
|
|
2349
|
+
act(() => captured.editor!.commands.insertContentAt(base.length, " LOCAL"));
|
|
2350
|
+
render(root, Harness, {
|
|
2351
|
+
value: "Alpha revision 3.\n\nOmega base.",
|
|
2352
|
+
contentUpdatedAt: "2024-01-01T00:00:03.000Z",
|
|
2353
|
+
contentRevision: "revision-3",
|
|
2354
|
+
});
|
|
2355
|
+
await act(async () => vi.advanceTimersByTimeAsync(0));
|
|
2356
|
+
|
|
2357
|
+
expect(getEditorMarkdown(captured.editor!)).toBe(
|
|
2358
|
+
"Alpha revision 3.\n\nOmega base LOCAL.",
|
|
2359
|
+
);
|
|
2360
|
+
expect(captured.reconciled).toEqual([
|
|
2361
|
+
{
|
|
2362
|
+
status: "merged",
|
|
2363
|
+
content: "Alpha revision 3.\n\nOmega base LOCAL.",
|
|
2364
|
+
},
|
|
2365
|
+
]);
|
|
2366
|
+
});
|
|
2367
|
+
|
|
2368
|
+
it("reports an overlap after the reconcile decision without replacing local typing", async () => {
|
|
2369
|
+
const { captured, Harness } = makeHarness();
|
|
2370
|
+
vi.useFakeTimers();
|
|
2371
|
+
const base = "Alpha base.\n\nOmega base.";
|
|
2372
|
+
render(root, Harness, {
|
|
2373
|
+
value: base,
|
|
2374
|
+
contentUpdatedAt: "2024-01-01T00:00:01.000Z",
|
|
2375
|
+
contentRevision: "revision-1",
|
|
2376
|
+
});
|
|
2377
|
+
await act(async () => vi.advanceTimersByTimeAsync(0));
|
|
2378
|
+
render(root, Harness, {
|
|
2379
|
+
value: "Alpha from server.\n\nOmega base.",
|
|
2380
|
+
contentUpdatedAt: "2024-01-01T00:00:02.000Z",
|
|
2381
|
+
contentRevision: "revision-2",
|
|
2382
|
+
});
|
|
2383
|
+
act(() =>
|
|
2384
|
+
captured.editor!.commands.setContent(
|
|
2385
|
+
"Alpha locally typed.\n\nOmega base.",
|
|
2386
|
+
),
|
|
2387
|
+
);
|
|
2388
|
+
render(root, Harness, {
|
|
2389
|
+
value: "Alpha from server.\n\nOmega base.",
|
|
2390
|
+
contentUpdatedAt: "2024-01-01T00:00:02.000Z",
|
|
2391
|
+
contentRevision: "revision-2",
|
|
2392
|
+
});
|
|
2393
|
+
await act(async () => vi.advanceTimersByTimeAsync(0));
|
|
2394
|
+
|
|
2395
|
+
expect(getEditorMarkdown(captured.editor!)).toBe(
|
|
2396
|
+
"Alpha locally typed.\n\nOmega base.",
|
|
2397
|
+
);
|
|
2398
|
+
expect(captured.reconciled).toEqual([
|
|
2399
|
+
{ status: "conflict", content: "Alpha locally typed.\n\nOmega base." },
|
|
2400
|
+
]);
|
|
2401
|
+
});
|
|
2402
|
+
|
|
2279
2403
|
it("does not roll back a blurred local mark while its controlled echo is queued", async () => {
|
|
2280
2404
|
const { captured, Harness } = makeHarness();
|
|
2281
2405
|
const canonical = "[Link](https://example.com/first) sample.";
|
|
@@ -203,6 +203,24 @@ export function useCollabReconcile({
|
|
|
203
203
|
revision: string;
|
|
204
204
|
} | null>(contentRevision ? { value, revision: contentRevision } : null);
|
|
205
205
|
const reportedConflictRevisionRef = useRef<string | null>(null);
|
|
206
|
+
const reconcileCallbacksRef = useRef({
|
|
207
|
+
getMarkdown,
|
|
208
|
+
setContent,
|
|
209
|
+
parseValue,
|
|
210
|
+
normalizeValue,
|
|
211
|
+
isEditorFocused,
|
|
212
|
+
onBaseAwareReconcile,
|
|
213
|
+
onRemoteSnapshotChange,
|
|
214
|
+
});
|
|
215
|
+
reconcileCallbacksRef.current = {
|
|
216
|
+
getMarkdown,
|
|
217
|
+
setContent,
|
|
218
|
+
parseValue,
|
|
219
|
+
normalizeValue,
|
|
220
|
+
isEditorFocused,
|
|
221
|
+
onBaseAwareReconcile,
|
|
222
|
+
onRemoteSnapshotChange,
|
|
223
|
+
};
|
|
206
224
|
const acknowledgedLocalSnapshotRef = useRef<{
|
|
207
225
|
value: string;
|
|
208
226
|
revision: string;
|
|
@@ -531,6 +549,7 @@ export function useCollabReconcile({
|
|
|
531
549
|
let retry: ReturnType<typeof setTimeout> | null = null;
|
|
532
550
|
const apply = (deferred = false) => {
|
|
533
551
|
if (cancelled || editor.isDestroyed) return;
|
|
552
|
+
const callbacks = reconcileCallbacksRef.current;
|
|
534
553
|
if (contentUpdatedAt) {
|
|
535
554
|
const rollback = acknowledgementBaseRollbackRef.current;
|
|
536
555
|
const conflictsWithAcceptedAcknowledgement =
|
|
@@ -701,12 +720,12 @@ export function useCollabReconcile({
|
|
|
701
720
|
peerWait.deadline = null;
|
|
702
721
|
return;
|
|
703
722
|
}
|
|
704
|
-
const currentMarkdown = getMarkdown(editor);
|
|
705
|
-
const normalizedValue = normalizeValue(value);
|
|
723
|
+
const currentMarkdown = callbacks.getMarkdown(editor);
|
|
724
|
+
const normalizedValue = callbacks.normalizeValue(value);
|
|
706
725
|
const editorUnchangedSinceApply =
|
|
707
726
|
lastAppliedSerializedRef.current !== null &&
|
|
708
727
|
currentMarkdown === lastAppliedSerializedRef.current;
|
|
709
|
-
const editorFocused = isEditorFocused(editor);
|
|
728
|
+
const editorFocused = callbacks.isEditorFocused(editor);
|
|
710
729
|
const typingRecently =
|
|
711
730
|
editorFocused && Date.now() - lastTypedAtRef.current < 1500;
|
|
712
731
|
|
|
@@ -789,9 +808,10 @@ export function useCollabReconcile({
|
|
|
789
808
|
|
|
790
809
|
const applyTimer = setTimeout(() => {
|
|
791
810
|
if (cancelled || editor.isDestroyed) return;
|
|
811
|
+
const scheduledCallbacks = reconcileCallbacksRef.current;
|
|
792
812
|
peerWait.deadline = null;
|
|
793
|
-
const beforeMarkdown = getMarkdown(editor);
|
|
794
|
-
const normalized = normalizeValue(value);
|
|
813
|
+
const beforeMarkdown = scheduledCallbacks.getMarkdown(editor);
|
|
814
|
+
const normalized = scheduledCallbacks.normalizeValue(value);
|
|
795
815
|
const unchangedSinceApply =
|
|
796
816
|
lastAppliedSerializedRef.current !== null &&
|
|
797
817
|
beforeMarkdown === lastAppliedSerializedRef.current;
|
|
@@ -815,19 +835,21 @@ export function useCollabReconcile({
|
|
|
815
835
|
const authoritativeBase = authoritativeBaseRef.current;
|
|
816
836
|
if (
|
|
817
837
|
contentRevision &&
|
|
818
|
-
onBaseAwareReconcile &&
|
|
838
|
+
scheduledCallbacks.onBaseAwareReconcile &&
|
|
819
839
|
authoritativeBase &&
|
|
820
840
|
authoritativeBase.revision !== contentRevision
|
|
821
841
|
) {
|
|
822
842
|
const parse =
|
|
823
|
-
parseValue === false
|
|
843
|
+
scheduledCallbacks.parseValue === false
|
|
844
|
+
? null
|
|
845
|
+
: (scheduledCallbacks.parseValue ?? defaultParseValue);
|
|
824
846
|
const baseDoc = parse?.(editor, authoritativeBase.value) ?? null;
|
|
825
847
|
const serverDoc = parse?.(editor, value) ?? null;
|
|
826
848
|
if (!baseDoc || !serverDoc) {
|
|
827
849
|
isSettingContentRef.current = false;
|
|
828
850
|
if (reportedConflictRevisionRef.current !== contentRevision) {
|
|
829
851
|
reportedConflictRevisionRef.current = contentRevision;
|
|
830
|
-
onBaseAwareReconcile({
|
|
852
|
+
scheduledCallbacks.onBaseAwareReconcile({
|
|
831
853
|
status: "failed",
|
|
832
854
|
content: beforeMarkdown,
|
|
833
855
|
serverContent: value,
|
|
@@ -850,7 +872,7 @@ export function useCollabReconcile({
|
|
|
850
872
|
isSettingContentRef.current = false;
|
|
851
873
|
if (reportedConflictRevisionRef.current !== contentRevision) {
|
|
852
874
|
reportedConflictRevisionRef.current = contentRevision;
|
|
853
|
-
onBaseAwareReconcile({
|
|
875
|
+
scheduledCallbacks.onBaseAwareReconcile({
|
|
854
876
|
status: reconciled.status,
|
|
855
877
|
content: beforeMarkdown,
|
|
856
878
|
serverContent: value,
|
|
@@ -860,7 +882,7 @@ export function useCollabReconcile({
|
|
|
860
882
|
}
|
|
861
883
|
return;
|
|
862
884
|
}
|
|
863
|
-
const merged = getMarkdown(editor);
|
|
885
|
+
const merged = scheduledCallbacks.getMarkdown(editor);
|
|
864
886
|
isSettingContentRef.current = false;
|
|
865
887
|
authoritativeBaseRef.current = { value, revision: contentRevision };
|
|
866
888
|
reportedConflictRevisionRef.current = null;
|
|
@@ -870,9 +892,10 @@ export function useCollabReconcile({
|
|
|
870
892
|
lastAppliedSerializedRef.current = merged;
|
|
871
893
|
if (contentUpdatedAt)
|
|
872
894
|
lastAppliedUpdatedAtRef.current = contentUpdatedAt;
|
|
873
|
-
if (merged !== beforeMarkdown)
|
|
895
|
+
if (merged !== beforeMarkdown)
|
|
896
|
+
scheduledCallbacks.onRemoteSnapshotChange?.(merged);
|
|
874
897
|
if (merged !== normalized) {
|
|
875
|
-
onBaseAwareReconcile({
|
|
898
|
+
scheduledCallbacks.onBaseAwareReconcile({
|
|
876
899
|
status: "merged",
|
|
877
900
|
content: merged,
|
|
878
901
|
serverContent: value,
|
|
@@ -883,8 +906,8 @@ export function useCollabReconcile({
|
|
|
883
906
|
return;
|
|
884
907
|
}
|
|
885
908
|
let appliedSurgically = false;
|
|
886
|
-
if (parseValue !== false) {
|
|
887
|
-
const parse = parseValue ?? defaultParseValue;
|
|
909
|
+
if (scheduledCallbacks.parseValue !== false) {
|
|
910
|
+
const parse = scheduledCallbacks.parseValue ?? defaultParseValue;
|
|
888
911
|
const parsedDoc = parse(editor, value);
|
|
889
912
|
if (parsedDoc) {
|
|
890
913
|
const result = applyDocSurgically(editor, parsedDoc);
|
|
@@ -892,10 +915,13 @@ export function useCollabReconcile({
|
|
|
892
915
|
}
|
|
893
916
|
}
|
|
894
917
|
if (!appliedSurgically) {
|
|
895
|
-
setContent(editor, value, {
|
|
918
|
+
scheduledCallbacks.setContent(editor, value, {
|
|
919
|
+
emitUpdate: false,
|
|
920
|
+
addToHistory: false,
|
|
921
|
+
});
|
|
896
922
|
}
|
|
897
923
|
isSettingContentRef.current = false;
|
|
898
|
-
const serialized = getMarkdown(editor);
|
|
924
|
+
const serialized = scheduledCallbacks.getMarkdown(editor);
|
|
899
925
|
lastEmittedRef.current = serialized;
|
|
900
926
|
pushEmittedRing(recentEmittedRef.current, serialized);
|
|
901
927
|
lastAppliedValueRef.current = value;
|
|
@@ -908,7 +934,7 @@ export function useCollabReconcile({
|
|
|
908
934
|
lastAppliedUpdatedAtRef.current = contentUpdatedAt;
|
|
909
935
|
}
|
|
910
936
|
if (serialized !== beforeMarkdown) {
|
|
911
|
-
onRemoteSnapshotChange?.(serialized);
|
|
937
|
+
scheduledCallbacks.onRemoteSnapshotChange?.(serialized);
|
|
912
938
|
}
|
|
913
939
|
}, 0);
|
|
914
940
|
retry = applyTimer;
|
|
@@ -931,13 +957,6 @@ export function useCollabReconcile({
|
|
|
931
957
|
collab,
|
|
932
958
|
collabSynced,
|
|
933
959
|
isLeadClient,
|
|
934
|
-
getMarkdown,
|
|
935
|
-
setContent,
|
|
936
|
-
parseValue,
|
|
937
|
-
normalizeValue,
|
|
938
|
-
isEditorFocused,
|
|
939
|
-
onBaseAwareReconcile,
|
|
940
|
-
onRemoteSnapshotChange,
|
|
941
960
|
overlapPolicy,
|
|
942
961
|
collabBackedSnapshot,
|
|
943
962
|
pendingCollabSnapshot,
|