@agent-native/toolkit 0.22.2 → 0.22.3-nightly-20260927152031

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.
Files changed (47) hide show
  1. package/dist/composer/ComposerContextMenu.d.ts.map +1 -1
  2. package/dist/composer/ComposerContextMenu.js +12 -3
  3. package/dist/composer/ComposerContextMenu.js.map +1 -1
  4. package/dist/composer/MentionItemMedia.d.ts +1 -1
  5. package/dist/composer/MentionItemMedia.d.ts.map +1 -1
  6. package/dist/composer/MentionItemMedia.js +10 -5
  7. package/dist/composer/MentionItemMedia.js.map +1 -1
  8. package/dist/composer/MentionPopover.d.ts +5 -0
  9. package/dist/composer/MentionPopover.d.ts.map +1 -1
  10. package/dist/composer/MentionPopover.js +17 -6
  11. package/dist/composer/MentionPopover.js.map +1 -1
  12. package/dist/composer/PromptComposer.d.ts +11 -2
  13. package/dist/composer/PromptComposer.d.ts.map +1 -1
  14. package/dist/composer/PromptComposer.js +7 -6
  15. package/dist/composer/PromptComposer.js.map +1 -1
  16. package/dist/composer/TiptapComposer.d.ts +26 -2
  17. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  18. package/dist/composer/TiptapComposer.js +262 -27
  19. package/dist/composer/TiptapComposer.js.map +1 -1
  20. package/dist/composer/TiptapComposer.spec.js +21 -1
  21. package/dist/composer/TiptapComposer.spec.js.map +1 -1
  22. package/dist/composer/extensions/MentionReference.d.ts.map +1 -1
  23. package/dist/composer/extensions/MentionReference.js +1 -1
  24. package/dist/composer/extensions/MentionReference.js.map +1 -1
  25. package/dist/composer/index.d.ts +1 -1
  26. package/dist/composer/index.d.ts.map +1 -1
  27. package/dist/composer/index.js +1 -1
  28. package/dist/composer/index.js.map +1 -1
  29. package/dist/composer/types.d.ts +6 -0
  30. package/dist/composer/types.d.ts.map +1 -1
  31. package/dist/editor/useCollabReconcile.concurrent.spec.js +106 -0
  32. package/dist/editor/useCollabReconcile.concurrent.spec.js.map +1 -1
  33. package/dist/editor/useCollabReconcile.d.ts.map +1 -1
  34. package/dist/editor/useCollabReconcile.js +42 -24
  35. package/dist/editor/useCollabReconcile.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/composer/ComposerContextMenu.tsx +15 -3
  38. package/src/composer/MentionItemMedia.tsx +13 -13
  39. package/src/composer/MentionPopover.tsx +52 -10
  40. package/src/composer/PromptComposer.tsx +34 -5
  41. package/src/composer/TiptapComposer.spec.ts +24 -0
  42. package/src/composer/TiptapComposer.tsx +346 -27
  43. package/src/composer/extensions/MentionReference.tsx +3 -0
  44. package/src/composer/index.ts +2 -0
  45. package/src/composer/types.ts +6 -0
  46. package/src/editor/useCollabReconcile.concurrent.spec.ts +124 -0
  47. 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 ? null : (parseValue ?? defaultParseValue);
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) onRemoteSnapshotChange?.(merged);
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, { emitUpdate: false, addToHistory: false });
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,