superdoc 2.4.0-next.20 → 2.4.0-next.22

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 (45) hide show
  1. package/dist/chunks/{create-super-doc-ui-BbNbi0cj.es.js → create-super-doc-ui-CmOSA_lN.es.js} +253 -657
  2. package/dist/chunks/{create-super-doc-ui-CPuOlTWb.cjs → create-super-doc-ui-CqGmoMMA.cjs} +252 -662
  3. package/dist/collaboration-upgrade-engine.cjs +1 -1
  4. package/dist/collaboration-upgrade-engine.es.js +1 -1
  5. package/dist/document-api/src/citations/citations.types.d.ts +9 -0
  6. package/dist/document-api/src/types/receipt.d.ts +9 -5
  7. package/dist/layout-engine/contracts/src/incremental-dependency.d.ts +22 -2
  8. package/dist/layout-engine/contracts/src/index.d.ts +74 -2
  9. package/dist/layout-engine/contracts/src/inline-box.d.ts +19 -0
  10. package/dist/layout-engine/contracts/src/paragraph-visible-text.d.ts +19 -0
  11. package/dist/layout-engine/layout-bridge/src/incrementalLayout.d.ts +2 -1
  12. package/dist/layout-engine/layout-bridge/src/inline-box-key.d.ts +3 -0
  13. package/dist/layout-engine/layout-bridge/src/run-visual-marks.d.ts +2 -0
  14. package/dist/layout-engine/layout-bridge/src/text-measurement.d.ts +0 -12
  15. package/dist/layout-engine/painters/dom/src/runs/inline-box.d.ts +8 -0
  16. package/dist/public/ui-react.cjs +1 -1
  17. package/dist/public/ui-react.es.js +1 -1
  18. package/dist/public/ui.cjs +1 -1
  19. package/dist/public/ui.es.js +1 -1
  20. package/dist/style.css +93 -92
  21. package/dist/style.layered.css +93 -92
  22. package/dist/superdoc/src/components/CommentsLayer/CommentDialog.vue.d.ts +2 -0
  23. package/dist/superdoc/src/components/CommentsLayer/commentsList/ReviewDirectoryListItem.vue.d.ts +16 -0
  24. package/dist/superdoc/src/components/CommentsLayer/commentsList/commentsList.vue.d.ts +3 -1
  25. package/dist/superdoc/src/components/CommentsLayer/commentsList/super-comments-list.d.ts +12 -0
  26. package/dist/superdoc/src/core/SuperDoc.d.ts +6 -7
  27. package/dist/superdoc/src/core/extensions/index.d.ts +1 -1
  28. package/dist/superdoc/src/core/extensions/types.d.ts +63 -5
  29. package/dist/superdoc/src/core/types/index.d.ts +19 -0
  30. package/dist/superdoc/src/core/v2-integration/v2-integration.d.ts +3 -3
  31. package/dist/superdoc/src/helpers/v2-review-mutation-impact.d.ts +3 -5
  32. package/dist/superdoc/src/public/index.d.cts +6 -0
  33. package/dist/superdoc/src/public/index.d.ts +3 -0
  34. package/dist/superdoc/src/public/ui/types.d.ts +5 -5
  35. package/dist/superdoc/src/stores/comments-store.d.ts +81 -78
  36. package/dist/superdoc/src/stores/superdoc-store.d.ts +243 -234
  37. package/dist/superdoc.cjs +660 -857
  38. package/dist/superdoc.es.js +648 -846
  39. package/dist-cdn/style.layered.css +1 -1
  40. package/dist-cdn/superdoc.min.css +1 -1
  41. package/dist-cdn/superdoc.min.js +36 -36
  42. package/package.json +2 -2
  43. package/dist/superdoc/src/helpers/v2-remote-review-hydration.d.ts +0 -12
  44. package/dist/superdoc/src/helpers/v2-review-mutation-reconciler.d.ts +0 -19
  45. package/dist/superdoc/src/helpers/v2-typing-review-hydration.d.ts +0 -16
@@ -423,6 +423,7 @@ Object.freeze([
423
423
  "furniture-page-tokens",
424
424
  "non-balanceable-multi-column-sections",
425
425
  "body-anchored-objects",
426
+ "non-flowing-page-relative-body-anchors",
426
427
  "footnotes",
427
428
  "page-references",
428
429
  "keep-constraints",
@@ -435,11 +436,9 @@ Object.freeze([
435
436
  * Classify the tracked-change identities carried by a v2 mutation event.
436
437
  *
437
438
  * Receipt entities are the durable invalidation contract between the document
438
- * kernel and derived review UI. Consumers can refresh `upsertIds` narrowly via
439
- * `trackChanges.get(...)`, drop `removedIds` immediately, and leave a bounded
440
- * full-list reconciliation as background verification. History results are
441
- * different: undo/redo can restore many identities at once, so consumers use
442
- * one authoritative catalog after render instead of issuing one read per id.
439
+ * kernel and derived review UI. Consumers apply removals/remaps locally; the
440
+ * next committed window supplies surviving or restored rows. History follows
441
+ * the same bounded path and never enumerates invisible review identities.
443
442
  */
444
443
  function getV2TrackedChangeMutationImpact(event) {
445
444
  if (event?.type !== "mutation:committed") return null;
@@ -479,7 +478,7 @@ function getV2TrackedChangeMutationImpact(event) {
479
478
  upsertIds,
480
479
  removedIds,
481
480
  remappedPairs,
482
- reconcileMode: event.origin === "history" ? "authoritative" : "targeted",
481
+ reconcileMode: "targeted",
483
482
  ...allResolved ? { allResolved } : {}
484
483
  };
485
484
  }
@@ -489,280 +488,6 @@ function readAllResolvedFact(event, receipt) {
489
488
  return fact;
490
489
  }
491
490
  //#endregion
492
- //#region src/helpers/v2-review-mutation-reconciler.js
493
- var normalizeIds = (values) => new Set(Array.from(values ?? []).filter((value) => typeof value === "string" && value.length > 0));
494
- var itemId = (item) => {
495
- if (typeof item?.id === "string" && item.id.length > 0) return item.id;
496
- if (typeof item?.changeId === "string" && item.changeId.length > 0) return item.changeId;
497
- if (typeof item?.address?.entityId === "string" && item.address.entityId.length > 0) return item.address.entityId;
498
- return null;
499
- };
500
- var contextMatches = (left, right) => left?.adapter === right?.adapter && left?.documentId === right?.documentId && left?.editor === right?.editor && left?.reconcileToken === right?.reconcileToken;
501
- /**
502
- * Reconcile tracked changes only after their canonical render has made the
503
- * read model observable. Command receipts use narrow reads; history results
504
- * use one authoritative catalog because undo/redo can restore hundreds of
505
- * identities in one transaction. An absent history identity only converges
506
- * when both catalog and source coverage are explicitly complete.
507
- */
508
- function createV2ReviewMutationReconciler({ getContext, reconcile, hydrate, onReconciled }) {
509
- const pendingUpserts = /* @__PURE__ */ new Map();
510
- let pendingAllResolved = null;
511
- let sequence = 0;
512
- let generation = 0;
513
- let flushInFlight = null;
514
- let flushRequested = false;
515
- const settlementWaiters = [];
516
- const resolveSettlementsThrough = (version, value) => {
517
- for (let index = settlementWaiters.length - 1; index >= 0; index -= 1) {
518
- const waiter = settlementWaiters[index];
519
- if (waiter.generation !== generation || waiter.version > version) continue;
520
- settlementWaiters.splice(index, 1);
521
- waiter.resolve(value === true && waiter.requiresAllResolved ? pendingAllResolved == null : value);
522
- }
523
- };
524
- const isCurrent = (capturedGeneration, context) => generation === capturedGeneration && contextMatches(context, getContext());
525
- const clearResolved = (ids, snapshot) => {
526
- const clearedIds = /* @__PURE__ */ new Set();
527
- for (const id of ids) {
528
- if (pendingUpserts.get(id) !== snapshot.get(id)) continue;
529
- pendingUpserts.delete(id);
530
- clearedIds.add(id);
531
- }
532
- return clearedIds;
533
- };
534
- const readySnapshot = (reconcileMode) => new Map([...pendingUpserts].filter(([, entry]) => entry.ready === true && entry.reconcileMode === reconcileMode));
535
- const markRenderGatedReady = () => {
536
- let changed = false;
537
- if (pendingAllResolved?.gate === "render" && !pendingAllResolved.ready) {
538
- pendingAllResolved.ready = true;
539
- changed = true;
540
- }
541
- for (const entry of pendingUpserts.values()) {
542
- if (entry.gate !== "render" || entry.ready) continue;
543
- entry.ready = true;
544
- changed = true;
545
- }
546
- return changed;
547
- };
548
- const markEntriesReadyThrough = (maxVersion) => {
549
- let changed = false;
550
- if (pendingAllResolved && pendingAllResolved.version <= maxVersion && !pendingAllResolved.ready) {
551
- pendingAllResolved.ready = true;
552
- changed = true;
553
- }
554
- for (const entry of pendingUpserts.values()) {
555
- if (entry.version > maxVersion || entry.ready) continue;
556
- entry.ready = true;
557
- changed = true;
558
- }
559
- return changed;
560
- };
561
- const notifyReconciled = async (context, ids, result, allowEmpty = false) => {
562
- if (!allowEmpty && ids.size === 0 || typeof onReconciled !== "function") return;
563
- try {
564
- await onReconciled(context, ids, result);
565
- } catch {}
566
- };
567
- const idsFromResult = (result) => normalizeIds(result?.resolvedIds ?? (result?.items ?? []).map(itemId).filter(Boolean));
568
- const reconcileTargetedSnapshot = async (context, snapshot, capturedGeneration) => {
569
- const result = await reconcile(context, {
570
- upsertIds: new Set(snapshot.keys()),
571
- removedIds: /* @__PURE__ */ new Set(),
572
- reconcileMode: "targeted"
573
- });
574
- if (!isCurrent(capturedGeneration, context)) return;
575
- const resolvedIds = idsFromResult(result);
576
- const clearedIds = clearResolved(resolvedIds, snapshot);
577
- await notifyReconciled(context, clearedIds, result);
578
- if (new Set([...snapshot].filter(([id, version]) => pendingUpserts.get(id) === version).map(([id]) => id)).size === 0) return;
579
- const fallback = await hydrate(context, {
580
- trackedChangesListMode: "interaction-prime",
581
- refreshReason: "mutation-render-reconcile",
582
- blocking: false
583
- });
584
- if (!isCurrent(capturedGeneration, context)) return;
585
- const fallbackResolvedIds = idsFromResult(fallback);
586
- const fallbackClearedIds = clearResolved(fallbackResolvedIds, snapshot);
587
- await notifyReconciled(context, fallbackClearedIds, fallback);
588
- };
589
- const reconcileAuthoritativeSnapshot = async (context, snapshot, capturedGeneration) => {
590
- const result = await hydrate(context, {
591
- trackedChangesListMode: "all",
592
- refreshReason: "mutation-history-render-reconcile",
593
- blocking: true
594
- });
595
- if (!isCurrent(capturedGeneration, context)) return;
596
- if (![...snapshot].some(([id, entry]) => pendingUpserts.get(id) === entry)) return;
597
- const transportSucceeded = result?.ok === true;
598
- const catalogIsAuthoritative = transportSucceeded && result?.complete === true && result?.sourceCoverageComplete === true;
599
- const returnedIds = transportSucceeded ? idsFromResult(result) : /* @__PURE__ */ new Set();
600
- const clearedIds = catalogIsAuthoritative ? clearResolved(new Set(snapshot.keys()), snapshot) : /* @__PURE__ */ new Set();
601
- await notifyReconciled(context, catalogIsAuthoritative ? clearedIds : returnedIds, {
602
- ...result,
603
- reconcileMode: "authoritative"
604
- }, true);
605
- };
606
- const flush = () => {
607
- if (flushInFlight) return flushInFlight;
608
- const allResolvedSnapshot = pendingAllResolved?.ready ? pendingAllResolved : null;
609
- const targetedSnapshot = readySnapshot("targeted");
610
- const authoritativeSnapshot = readySnapshot("authoritative");
611
- if (!allResolvedSnapshot && targetedSnapshot.size === 0 && authoritativeSnapshot.size === 0) return null;
612
- const context = getContext();
613
- if (!context?.adapter) return null;
614
- flushRequested = false;
615
- const capturedGeneration = generation;
616
- flushInFlight = Promise.resolve().then(async () => {
617
- if (allResolvedSnapshot) {
618
- let result = null;
619
- try {
620
- result = await reconcile(context, {
621
- upsertIds: /* @__PURE__ */ new Set(),
622
- removedIds: /* @__PURE__ */ new Set(),
623
- allResolved: allResolvedSnapshot.fact,
624
- reconcileMode: "targeted"
625
- });
626
- } catch {}
627
- if (!isCurrent(capturedGeneration, context)) return;
628
- if (result?.ok === true) {
629
- if (pendingAllResolved === allResolvedSnapshot) pendingAllResolved = null;
630
- await notifyReconciled(context, /* @__PURE__ */ new Set(), result, true);
631
- }
632
- }
633
- if (authoritativeSnapshot.size > 0) await reconcileAuthoritativeSnapshot(context, authoritativeSnapshot, capturedGeneration).catch(() => void 0);
634
- if (targetedSnapshot.size > 0) await reconcileTargetedSnapshot(context, targetedSnapshot, capturedGeneration).catch(() => void 0);
635
- }).catch(() => {}).finally(() => {
636
- flushInFlight = null;
637
- if (flushRequested) flush();
638
- });
639
- return flushInFlight;
640
- };
641
- const enqueueWithGate = (impact, gate) => {
642
- if (!impact) return;
643
- if (impact.allResolved) {
644
- pendingUpserts.clear();
645
- pendingAllResolved = {
646
- fact: impact.allResolved,
647
- version: ++sequence,
648
- gate,
649
- ready: false
650
- };
651
- }
652
- const reconcileMode = impact.reconcileMode === "authoritative" ? "authoritative" : "targeted";
653
- const removedIds = normalizeIds(impact.removedIds);
654
- const upsertIds = normalizeIds(impact.upsertIds);
655
- for (const id of removedIds) {
656
- const existing = pendingUpserts.get(id);
657
- if (existing?.reconcileMode === "authoritative") pendingUpserts.set(id, {
658
- ...existing,
659
- version: ++sequence,
660
- gate,
661
- ready: false
662
- });
663
- else pendingUpserts.delete(id);
664
- }
665
- const context = getContext();
666
- if (removedIds.size > 0 && upsertIds.size === 0 && context?.adapter && !impact.allResolved) try {
667
- Promise.resolve(reconcile(context, {
668
- upsertIds: /* @__PURE__ */ new Set(),
669
- removedIds
670
- })).catch(() => void 0);
671
- } catch {}
672
- for (const id of upsertIds) if (!removedIds.has(id)) {
673
- const existing = pendingUpserts.get(id);
674
- pendingUpserts.set(id, {
675
- version: ++sequence,
676
- gate,
677
- ready: false,
678
- reconcileMode: reconcileMode === "authoritative" || existing?.reconcileMode === "authoritative" ? "authoritative" : "targeted"
679
- });
680
- }
681
- };
682
- const enqueue = (impact) => enqueueWithGate(impact, "render");
683
- const onRender = (supersedesInFlight = false) => {
684
- const madeReady = markRenderGatedReady();
685
- if (flushInFlight && (madeReady || supersedesInFlight)) flushRequested = true;
686
- flush();
687
- };
688
- return {
689
- enqueue,
690
- enqueueAfterPaint(impact, waitForPaint) {
691
- if (!impact || typeof waitForPaint !== "function") {
692
- enqueue(impact);
693
- return Promise.resolve(false);
694
- }
695
- enqueueWithGate(impact, "exact-paint");
696
- const context = getContext();
697
- const capturedGeneration = generation;
698
- const capturedSequence = sequence;
699
- if (!context?.adapter) {
700
- markEntriesReadyThrough(capturedSequence);
701
- return Promise.resolve(false);
702
- }
703
- const completion = new Promise((resolve) => {
704
- settlementWaiters.push({
705
- generation: capturedGeneration,
706
- version: capturedSequence,
707
- requiresAllResolved: Boolean(impact.allResolved),
708
- resolve
709
- });
710
- });
711
- const impactedIds = /* @__PURE__ */ new Set([...normalizeIds(impact.upsertIds), ...normalizeIds(impact.removedIds)]);
712
- const capturedEntries = new Map([...impactedIds].flatMap((id) => {
713
- const entry = pendingUpserts.get(id);
714
- return entry ? [[id, entry]] : [];
715
- }));
716
- Promise.resolve().then(() => waitForPaint()).then(async () => {
717
- if (!isCurrent(capturedGeneration, context)) {
718
- resolveSettlementsThrough(capturedSequence, false);
719
- return;
720
- }
721
- let madeReady = markRenderGatedReady();
722
- if (markEntriesReadyThrough(capturedSequence)) madeReady = true;
723
- for (const [id, entry] of capturedEntries) {
724
- if (pendingUpserts.get(id) !== entry) continue;
725
- if (!entry.ready) madeReady = true;
726
- entry.ready = true;
727
- }
728
- if (flushInFlight && (madeReady || capturedSequence > 0)) flushRequested = true;
729
- const preceding = flushInFlight;
730
- const pending = flush();
731
- if (pending) await pending;
732
- if (preceding) {
733
- const followUp = flushInFlight;
734
- if (followUp && followUp !== preceding) await followUp;
735
- }
736
- if (!isCurrent(capturedGeneration, context)) {
737
- resolveSettlementsThrough(capturedSequence, false);
738
- return;
739
- }
740
- resolveSettlementsThrough(capturedSequence, true);
741
- }).catch(() => {
742
- if (!isCurrent(capturedGeneration, context)) {
743
- resolveSettlementsThrough(capturedSequence, false);
744
- return;
745
- }
746
- for (const [id, entry] of capturedEntries) if (pendingUpserts.get(id) === entry) entry.gate = "render";
747
- if (pendingAllResolved?.gate === "exact-paint") pendingAllResolved.gate = "render";
748
- resolveSettlementsThrough(capturedSequence, false);
749
- });
750
- return completion;
751
- },
752
- onRender,
753
- reset() {
754
- for (const waiter of settlementWaiters.splice(0)) waiter.resolve(false);
755
- generation += 1;
756
- pendingUpserts.clear();
757
- pendingAllResolved = null;
758
- flushRequested = false;
759
- },
760
- getPendingIds() {
761
- return new Set(pendingUpserts.keys());
762
- }
763
- };
764
- }
765
- //#endregion
766
491
  //#region src/public/ui/reasons.ts
767
492
  /**
768
493
  * Stable public reason taxonomy for the v2-native `superdoc/ui` controller.
@@ -1836,6 +1561,17 @@ function collectEntityHitsFromChain(start, stopAt) {
1836
1561
  for (const hit of pendingStory) hit.story = layoutStory;
1837
1562
  pendingStory = [];
1838
1563
  }
1564
+ const citationId = node.getAttribute("data-citation-id");
1565
+ if (citationId) {
1566
+ const key = `citation:${citationId}`;
1567
+ if (!seen.has(key)) {
1568
+ seen.add(key);
1569
+ hits.push({
1570
+ type: "citation",
1571
+ id: citationId
1572
+ });
1573
+ }
1574
+ }
1839
1575
  const commentIds = node.getAttribute("data-comment-ids");
1840
1576
  if (commentIds) for (const id of parseCommaSeparatedIds(commentIds)) {
1841
1577
  const key = `comment:${id}`;
@@ -2401,22 +2137,24 @@ function isHostGeometryTarget(target) {
2401
2137
  * address lets the host prefer the exact painted review carrier after the
2402
2138
  * containing block has been materialized.
2403
2139
  */
2404
- function readTrackedChangeNavigationTarget(row) {
2140
+ function readTrackedChangeNavigationTarget(row, options) {
2405
2141
  if (!row || typeof row !== "object") return null;
2406
2142
  const target = readEntityTarget(row);
2407
- if (isHostGeometryTarget(target)) return target;
2408
2143
  const record = row;
2409
2144
  const navigationTarget = record.navigationTarget && typeof record.navigationTarget === "object" ? record.navigationTarget : null;
2410
- if (navigationTarget?.kind === "block" && typeof navigationTarget.blockId === "string" && navigationTarget.blockId.length > 0) return {
2411
- kind: "text",
2412
- blockId: navigationTarget.blockId,
2413
- range: {
2414
- start: 0,
2415
- end: 0
2416
- },
2417
- ...navigationTarget.story && typeof navigationTarget.story === "object" ? { story: navigationTarget.story } : {},
2418
- ...record.address && typeof record.address === "object" ? { address: record.address } : {}
2419
- };
2145
+ if (navigationTarget?.kind === "block" && typeof navigationTarget.blockId === "string" && navigationTarget.blockId.length > 0) {
2146
+ const stableBlockTarget = {
2147
+ kind: "text",
2148
+ blockId: navigationTarget.blockId,
2149
+ range: {
2150
+ start: 0,
2151
+ end: 0
2152
+ },
2153
+ ...navigationTarget.story && typeof navigationTarget.story === "object" ? { story: navigationTarget.story } : {},
2154
+ ...record.address && typeof record.address === "object" ? { address: record.address } : {}
2155
+ };
2156
+ if (options?.preferStableBlock || !isHostGeometryTarget(target)) return stableBlockTarget;
2157
+ }
2420
2158
  return target;
2421
2159
  }
2422
2160
  function storyLocatorSignature(story) {
@@ -3731,8 +3469,8 @@ function createSuperDocUI(options) {
3731
3469
  const getV2ReviewWindowSource = () => {
3732
3470
  const editor = getEditor();
3733
3471
  if (editor?.editorVersion !== 2) return null;
3734
- const reviewHydration = editor.reviewHydration;
3735
- return reviewHydration && typeof reviewHydration === "object" ? reviewHydration : null;
3472
+ const reviewWindow = editor.reviewWindow;
3473
+ return reviewWindow && typeof reviewWindow === "object" ? reviewWindow : null;
3736
3474
  };
3737
3475
  const getV2ReviewWindowSnapshot = () => {
3738
3476
  const source = getV2ReviewWindowSource();
@@ -4011,7 +3749,6 @@ function createSuperDocUI(options) {
4011
3749
  return `e${id}`;
4012
3750
  };
4013
3751
  let documentMutationRevision = 0;
4014
- let reviewMutationToken = {};
4015
3752
  const uiTrackedChangesCatalogHost = options.superdoc;
4016
3753
  const uiTrackedChangesCatalogState = acquireSharedUiTrackedChangesCatalog(uiTrackedChangesCatalogHost);
4017
3754
  const supersedeUiTrackedChangesCatalogRead = () => {
@@ -4043,7 +3780,6 @@ function createSuperDocUI(options) {
4043
3780
  return fallback();
4044
3781
  }
4045
3782
  if (uiTrackedChangesCatalogState.activeMutationTokens.size > 0) return Promise.reject(/* @__PURE__ */ new Error("ui-review-catalog-superseded"));
4046
- if ((getEditor()?.reviewHydration?.getDiagnostics?.())?.inFlight === true) return Promise.reject(/* @__PURE__ */ new Error("shell-review-hydration-in-flight"));
4047
3783
  const generation = uiTrackedChangesCatalogState.generation;
4048
3784
  const existing = uiTrackedChangesCatalogState.inFlight;
4049
3785
  if (existing?.generation === generation) return existing.promise;
@@ -4069,15 +3805,6 @@ function createSuperDocUI(options) {
4069
3805
  });
4070
3806
  return promise;
4071
3807
  };
4072
- let authoritativeTrackChangesPendingToken = null;
4073
- let authoritativeTrackChangesRetryTimer = null;
4074
- let authoritativeTrackChangesFailureCount = 0;
4075
- const clearAuthoritativeTrackChangesRetry = () => {
4076
- if (authoritativeTrackChangesRetryTimer) clearTimeout(authoritativeTrackChangesRetryTimer);
4077
- authoritativeTrackChangesRetryTimer = null;
4078
- authoritativeTrackChangesFailureCount = 0;
4079
- };
4080
- let scheduleAuthoritativeTrackChangesRetry = (_reconcileToken) => void 0;
4081
3808
  let postDecisionTrackChangesToken = null;
4082
3809
  let postDecisionTrackChangeIds = /* @__PURE__ */ new Set();
4083
3810
  let allTrackedChangesResolvedToken = null;
@@ -4197,65 +3924,6 @@ function createSuperDocUI(options) {
4197
3924
  publishPostDecisionTrackChanges(newlyRemoved, receipt);
4198
3925
  };
4199
3926
  const postDecisionTrackChangeIdsForToken = (token) => postDecisionTrackChangesToken === token && postDecisionTrackChangeIds.size > 0 ? postDecisionTrackChangeIds : null;
4200
- /**
4201
- * Merge a narrow tracked-change read into the two settled review caches.
4202
- * Existing enriched adapter fields are retained while the fresh Document
4203
- * API fields win. The entry keeps its original content token, so a row that
4204
- * settles after the typing invalidation is truthfully served as stale rather
4205
- * than discarded or mislabeled as a complete current-token catalog.
4206
- */
4207
- const mergeTrackedChangeItemsIntoCache = (items, orderedItems = []) => {
4208
- const projected = items.map(projectTrackChangesItem).filter((item) => item != null && readEntityId(item) != null);
4209
- if (projected.length === 0) return;
4210
- const orderedIds = orderedItems.map(projectTrackChangesItem).map((item) => item == null ? null : readEntityId(item)).filter((id) => id != null);
4211
- for (const key of ["trackChanges", "trackChanges:all"]) {
4212
- const entry = asyncReads.get(key);
4213
- const nextValue = entry?.hasSettled && Array.isArray(entry.value) ? [...entry.value] : [];
4214
- const insertedIds = /* @__PURE__ */ new Set();
4215
- let changed = false;
4216
- for (const fresh of projected) {
4217
- const id = readEntityId(fresh);
4218
- if (!id) continue;
4219
- const index = nextValue.findIndex((candidate) => readEntityId(candidate) === id);
4220
- if (index < 0) {
4221
- nextValue.push(fresh);
4222
- insertedIds.add(id);
4223
- } else {
4224
- const previous = nextValue[index];
4225
- nextValue[index] = isLooseObject(previous) ? {
4226
- ...previous,
4227
- ...fresh
4228
- } : fresh;
4229
- }
4230
- changed = true;
4231
- }
4232
- if (key === "trackChanges" && insertedIds.size > 0 && orderedIds.length > 0) {
4233
- const placeableIds = new Set(orderedIds.filter((id) => insertedIds.has(id)));
4234
- const insertedRows = new Map(nextValue.filter((item) => placeableIds.has(readEntityId(item) ?? "")).map((item) => [readEntityId(item), item]));
4235
- const placedValue = nextValue.filter((item) => !placeableIds.has(readEntityId(item) ?? ""));
4236
- for (const [orderIndex, id] of orderedIds.entries()) {
4237
- if (!placeableIds.has(id)) continue;
4238
- const item = insertedRows.get(id);
4239
- if (!item) continue;
4240
- const followingAnchor = orderedIds.slice(orderIndex + 1).map((candidateId) => placedValue.findIndex((candidate) => readEntityId(candidate) === candidateId)).find((index) => index >= 0);
4241
- if (followingAnchor != null) {
4242
- placedValue.splice(followingAnchor, 0, item);
4243
- continue;
4244
- }
4245
- const precedingAnchor = orderedIds.slice(0, orderIndex).reverse().map((candidateId) => placedValue.findIndex((candidate) => readEntityId(candidate) === candidateId)).find((index) => index >= 0);
4246
- placedValue.splice(precedingAnchor == null ? placedValue.length : precedingAnchor + 1, 0, item);
4247
- }
4248
- nextValue.splice(0, nextValue.length, ...placedValue);
4249
- }
4250
- if (!changed) continue;
4251
- asyncReads.set(key, {
4252
- token: entry?.token ?? null,
4253
- value: nextValue,
4254
- hasSettled: true,
4255
- inflightToken: null
4256
- });
4257
- }
4258
- };
4259
3927
  const replaceTrackedChangeItemsInCache = (items) => {
4260
3928
  const token = contentToken();
4261
3929
  const suppressedIds = postDecisionTrackChangeIdsForToken(token);
@@ -4271,183 +3939,6 @@ function createSuperDocUI(options) {
4271
3939
  inflightToken: null
4272
3940
  });
4273
3941
  };
4274
- const hydrateTrackedChanges = async (context, options) => {
4275
- try {
4276
- const listTrackedChanges = context.v2TrackedChanges?.listTrackedChanges;
4277
- if (typeof listTrackedChanges === "function") {
4278
- const result = await Promise.resolve(listTrackedChanges.call(context.v2TrackedChanges, {
4279
- mode: options.trackedChangesListMode,
4280
- refreshReason: options.refreshReason,
4281
- blocking: options.blocking,
4282
- ...options.targetIds?.length ? { targetIds: options.targetIds } : {}
4283
- }));
4284
- const ok = options.trackedChangesListMode === "all" && options.refreshReason === "mutation-history-render-reconcile" ? result?.ok === true : result?.ok !== false;
4285
- return {
4286
- ok,
4287
- items: ok && Array.isArray(result?.items) ? result.items : [],
4288
- ...typeof result?.complete === "boolean" ? { complete: result.complete } : {},
4289
- ...typeof result?.sourceCoverageComplete === "boolean" ? { sourceCoverageComplete: result.sourceCoverageComplete } : {}
4290
- };
4291
- }
4292
- const list = context.docTrackChanges?.list;
4293
- if (typeof list !== "function") return {
4294
- ok: false,
4295
- items: []
4296
- };
4297
- const result = await Promise.resolve(list.call(context.docTrackChanges));
4298
- return {
4299
- ok: true,
4300
- items: Array.isArray(result?.items) ? result.items : []
4301
- };
4302
- } catch (error) {
4303
- return {
4304
- ok: false,
4305
- items: [],
4306
- failures: [error]
4307
- };
4308
- }
4309
- };
4310
- /**
4311
- * Read receipt-addressed review effects after their exact canonical paint.
4312
- * The adapter's narrow get preserves its enriched row projection; the public
4313
- * Document API is the fallback for hosts without that bridge method. Empty
4314
- * successful reads remain unresolved and immediately use one bounded
4315
- * interaction-prime list instead of waiting for the typing-idle catalog.
4316
- */
4317
- const reviewMutationReconciler = createV2ReviewMutationReconciler({
4318
- getContext: () => {
4319
- const editor = getEditor();
4320
- if (!editor) return null;
4321
- const v2TrackedChanges = getV2TrackedChanges();
4322
- const docTrackChanges = getDoc()?.trackChanges ?? null;
4323
- const adapter = v2TrackedChanges ?? docTrackChanges;
4324
- if (!adapter) return null;
4325
- const rawDocumentId = editor.documentId ?? v2TrackedChanges?.documentId;
4326
- return {
4327
- editor,
4328
- adapter,
4329
- documentId: rawDocumentId == null ? null : String(rawDocumentId),
4330
- reconcileToken: reviewMutationToken,
4331
- v2TrackedChanges,
4332
- docTrackChanges
4333
- };
4334
- },
4335
- reconcile: async (context, impact) => {
4336
- if (impact.removedIds.size > 0) markPostDecisionTrackChanges(new Set(impact.removedIds));
4337
- const requestedIds = [...new Set(impact.upsertIds)];
4338
- if (requestedIds.length === 0) return {
4339
- ok: true,
4340
- items: [],
4341
- resolvedIds: [],
4342
- unresolvedIds: []
4343
- };
4344
- const settledTrackChanges = asyncReads.get("trackChanges");
4345
- const settledTrackChangeItems = settledTrackChanges?.hasSettled && Array.isArray(settledTrackChanges.value) ? settledTrackChanges.value : null;
4346
- const cachedIds = new Set(settledTrackChangeItems ? settledTrackChangeItems.map(readEntityId).filter((id) => id != null) : []);
4347
- const settledCatalogIsProvenEmpty = settledTrackChangeItems?.length === 0 && hostSourceLoadPhase() === "complete";
4348
- const idsNeedingPlacement = new Set(requestedIds.filter((id) => !cachedIds.has(id)));
4349
- const getTrackedChange = context.v2TrackedChanges?.getTrackedChange;
4350
- const getRawTrackedChange = context.docTrackChanges?.get;
4351
- if (typeof getTrackedChange !== "function" && typeof getRawTrackedChange !== "function") return {
4352
- ok: false,
4353
- items: [],
4354
- resolvedIds: [],
4355
- unresolvedIds: requestedIds
4356
- };
4357
- const reads = await Promise.all(requestedIds.map(async (id) => {
4358
- try {
4359
- if (typeof getTrackedChange === "function") {
4360
- const result = await Promise.resolve(getTrackedChange.call(context.v2TrackedChanges, id));
4361
- return {
4362
- id,
4363
- ok: result?.ok !== false,
4364
- items: result?.ok !== false && Array.isArray(result?.items) ? result.items : [],
4365
- result
4366
- };
4367
- }
4368
- const result = await Promise.resolve(getRawTrackedChange.call(context.docTrackChanges, { id }));
4369
- return {
4370
- id,
4371
- ok: true,
4372
- items: result && typeof result === "object" ? [result] : [],
4373
- result
4374
- };
4375
- } catch (error) {
4376
- return {
4377
- id,
4378
- ok: false,
4379
- items: [],
4380
- result: error
4381
- };
4382
- }
4383
- }));
4384
- const items = [];
4385
- const resolvedIds = [];
4386
- const unresolvedIds = [];
4387
- for (const read of reads) {
4388
- const matchingItems = read.items.filter((item) => {
4389
- const projected = projectTrackChangesItem(item);
4390
- return projected != null && readEntityId(projected) === read.id;
4391
- });
4392
- if (read.ok && matchingItems.length > 0) {
4393
- items.push(...matchingItems);
4394
- resolvedIds.push(read.id);
4395
- } else unresolvedIds.push(read.id);
4396
- }
4397
- let orderedItems;
4398
- const placementIds = resolvedIds.filter((id) => idsNeedingPlacement.has(id));
4399
- if (placementIds.length > 0 && !settledCatalogIsProvenEmpty) {
4400
- const orderResult = await hydrateTrackedChanges(context, {
4401
- trackedChangesListMode: "interaction-prime",
4402
- refreshReason: "mutation-render-reconcile",
4403
- blocking: false,
4404
- targetIds: placementIds
4405
- });
4406
- if (orderResult.ok) orderedItems = orderResult.items;
4407
- }
4408
- return {
4409
- ok: unresolvedIds.length === 0,
4410
- items,
4411
- orderedItems,
4412
- resolvedIds,
4413
- unresolvedIds,
4414
- failures: reads.filter((read) => !read.ok).map((read) => read.result)
4415
- };
4416
- },
4417
- hydrate: hydrateTrackedChanges,
4418
- onReconciled: (context, ids, result) => {
4419
- if (result?.reconcileMode === "authoritative") {
4420
- if (context.reconcileToken !== reviewMutationToken) return;
4421
- if (result.ok === true && result.complete === true && result.sourceCoverageComplete === true) {
4422
- clearAuthoritativeTrackChangesRetry();
4423
- replaceTrackedChangeItemsInCache(result.items ?? []);
4424
- authoritativeTrackChangesPendingToken = null;
4425
- } else if (result.ok === true) {
4426
- clearAuthoritativeTrackChangesRetry();
4427
- mergeTrackedChangeItemsIntoCache(result.items ?? []);
4428
- } else scheduleAuthoritativeTrackChangesRetry(context.reconcileToken);
4429
- recompute();
4430
- return;
4431
- }
4432
- const items = (result?.items ?? []).filter((item) => {
4433
- const projected = projectTrackChangesItem(item);
4434
- const id = projected == null ? null : readEntityId(projected);
4435
- return id != null && ids.has(id);
4436
- });
4437
- mergeTrackedChangeItemsIntoCache(items, result?.orderedItems ?? result?.items ?? []);
4438
- recompute();
4439
- }
4440
- });
4441
- scheduleAuthoritativeTrackChangesRetry = (reconcileToken) => {
4442
- if (disposed || authoritativeTrackChangesRetryTimer) return;
4443
- const delayMs = Math.min(ASYNC_READ_FAILURE_RETRY_BASE_MS * 2 ** authoritativeTrackChangesFailureCount, ASYNC_READ_FAILURE_RETRY_MAX_MS);
4444
- authoritativeTrackChangesFailureCount += 1;
4445
- authoritativeTrackChangesRetryTimer = setTimeout(() => {
4446
- authoritativeTrackChangesRetryTimer = null;
4447
- if (disposed || authoritativeTrackChangesPendingToken === null || reconcileToken !== reviewMutationToken) return;
4448
- reviewMutationReconciler.onRender(true);
4449
- }, delayMs);
4450
- };
4451
3942
  /** Token for the live selection read, including the mode that controls its public projection. */
4452
3943
  const selectionReadToken = () => `${contentToken()}|m${readDocumentMode()}|s${selectionEpoch}`;
4453
3944
  /** Stable signature of a settled selection, used to key selection-scoped reads. */
@@ -4473,6 +3964,11 @@ function createSuperDocUI(options) {
4473
3964
  return "unknown";
4474
3965
  };
4475
3966
  const demandedHeavyReads = /* @__PURE__ */ new Map();
3967
+ const incompleteTrackChangesDirectoryReadTokens = /* @__PURE__ */ new Map();
3968
+ const postSourceCompletionRefreshTokens = /* @__PURE__ */ new Map();
3969
+ let sourceCompletionObservedToken = null;
3970
+ let commentsDirectoryLeaseCount = 0;
3971
+ let trackChangesDirectoryLeaseCount = 0;
4476
3972
  const demandHeavyDocRead = (key) => {
4477
3973
  const token = contentToken();
4478
3974
  if (demandedHeavyReads.get(key) === token) return;
@@ -4485,6 +3981,41 @@ function createSuperDocUI(options) {
4485
3981
  return Array.isArray(entry.value) && entry.value.length > 0;
4486
3982
  };
4487
3983
  const heavyReadDemandActive = (key, token) => demandedHeavyReads.get(key) === token;
3984
+ const refreshIncompleteTrackChangesDirectories = () => {
3985
+ const token = contentToken();
3986
+ let invalidated = false;
3987
+ for (const [key, incompleteToken] of incompleteTrackChangesDirectoryReadTokens) {
3988
+ if (incompleteToken !== token) {
3989
+ incompleteTrackChangesDirectoryReadTokens.delete(key);
3990
+ continue;
3991
+ }
3992
+ if (postSourceCompletionRefreshTokens.get(key) === token) continue;
3993
+ const entry = asyncReads.get(key);
3994
+ if (!entry?.hasSettled || entry.token !== token || entry.inflightToken != null) continue;
3995
+ asyncReads.set(key, {
3996
+ ...entry,
3997
+ token: null,
3998
+ inflightToken: null
3999
+ });
4000
+ postSourceCompletionRefreshTokens.set(key, token);
4001
+ invalidated = true;
4002
+ }
4003
+ if (invalidated) scheduleAsyncRefresh();
4004
+ };
4005
+ const acquireDirectoryLease = (family) => {
4006
+ if (family === "comments") commentsDirectoryLeaseCount += 1;
4007
+ else trackChangesDirectoryLeaseCount += 1;
4008
+ demandHeavyDocRead(family);
4009
+ let released = false;
4010
+ return () => {
4011
+ if (released) return;
4012
+ released = true;
4013
+ if (family === "comments") commentsDirectoryLeaseCount = Math.max(0, commentsDirectoryLeaseCount - 1);
4014
+ else trackChangesDirectoryLeaseCount = Math.max(0, trackChangesDirectoryLeaseCount - 1);
4015
+ if ((family === "comments" ? commentsDirectoryLeaseCount : trackChangesDirectoryLeaseCount) === 0 && demandedHeavyReads.get(family) === contentToken()) demandedHeavyReads.delete(family);
4016
+ scheduleAsyncRefresh();
4017
+ };
4018
+ };
4488
4019
  /**
4489
4020
  * Demand-driven route for the content-controls catalog (panel / API use).
4490
4021
  * Explicit demand bypasses both active-loading and post-complete idle holds.
@@ -4555,6 +4086,8 @@ function createSuperDocUI(options) {
4555
4086
  const handleLoadingSnapshot = (snapshot) => {
4556
4087
  const stage = snapshot?.sourceStage;
4557
4088
  if (stage === "source-complete" || stage === "source-failed" || stage === "source-cancelled") {
4089
+ sourceCompletionObservedToken = contentToken();
4090
+ refreshIncompleteTrackChangesDirectories();
4558
4091
  lastEditableMutationAtMs = Math.max(lastEditableMutationAtMs, Date.now());
4559
4092
  scheduleHeavyReadCompletionRecompute();
4560
4093
  }
@@ -4879,12 +4412,8 @@ function createSuperDocUI(options) {
4879
4412
  const editor = getEditor();
4880
4413
  if (editor === lastCoordinatorEditor) return;
4881
4414
  resetPostPaintContentRefresh();
4882
- reviewMutationReconciler.reset();
4883
- reviewMutationToken = {};
4884
4415
  lastCoordinatorEditor = editor;
4885
4416
  asyncReads.clear();
4886
- authoritativeTrackChangesPendingToken = null;
4887
- clearAuthoritativeTrackChangesRetry();
4888
4417
  if (asyncReadFailureRetryTimer) clearTimeout(asyncReadFailureRetryTimer);
4889
4418
  asyncReadFailureRetryTimer = null;
4890
4419
  asyncReadFailureRetryAtMs = 0;
@@ -4896,6 +4425,9 @@ function createSuperDocUI(options) {
4896
4425
  pendingSelectionSeedValidationToken = null;
4897
4426
  coldAsyncReadDeferrals.clear();
4898
4427
  demandedHeavyReads.clear();
4428
+ incompleteTrackChangesDirectoryReadTokens.clear();
4429
+ postSourceCompletionRefreshTokens.clear();
4430
+ sourceCompletionObservedToken = null;
4899
4431
  heavyReadsHeldUntilIdle = false;
4900
4432
  if (detachSourceLoading) detachSourceLoading();
4901
4433
  detachSourceLoading = null;
@@ -4905,9 +4437,10 @@ function createSuperDocUI(options) {
4905
4437
  };
4906
4438
  /** Bump the document-mutation revision so content reads re-fetch on the next compute. */
4907
4439
  const invalidateDocumentContent = () => {
4908
- const carryAuthoritativeTrackChangesHold = authoritativeTrackChangesPendingToken !== null;
4909
4440
  documentMutationRevision += 1;
4910
- if (carryAuthoritativeTrackChangesHold) authoritativeTrackChangesPendingToken = contentToken();
4441
+ incompleteTrackChangesDirectoryReadTokens.clear();
4442
+ postSourceCompletionRefreshTokens.clear();
4443
+ sourceCompletionObservedToken = null;
4911
4444
  clearPostDecisionTrackChanges();
4912
4445
  };
4913
4446
  /**
@@ -5135,17 +4668,51 @@ function createSuperDocUI(options) {
5135
4668
  recompute();
5136
4669
  return state.selection;
5137
4670
  };
5138
- const computeComments = (selection) => {
4671
+ const readCommentsDirectory = () => {
5139
4672
  const commentsApi = getDoc()?.comments;
4673
+ return readAsync("comments", contentToken(), () => commentsApi?.list ? commentsApi.list() : void 0, (raw) => raw && Array.isArray(raw.items) ? raw.items : []);
4674
+ };
4675
+ const readTrackChangesDirectory = (key = "trackChanges") => {
4676
+ const token = contentToken();
4677
+ const tcApi = getDoc()?.trackChanges;
4678
+ const v2TrackedChanges = getV2TrackedChanges();
4679
+ const listTrackedChanges = typeof v2TrackedChanges?.listTrackedChanges === "function" ? v2TrackedChanges.listTrackedChanges : null;
4680
+ const read = readAsync(key, token, () => {
4681
+ if (listTrackedChanges) return runUiTrackedChangesCatalogRead(() => tcApi?.list?.({ in: "all" }));
4682
+ return tcApi?.list ? tcApi.list({ in: "all" }) : void 0;
4683
+ }, (raw) => {
4684
+ const result = raw && typeof raw === "object" ? raw : null;
4685
+ if (result?.complete === false || result?.sourceCoverageComplete === false) {
4686
+ incompleteTrackChangesDirectoryReadTokens.set(key, token);
4687
+ armSourceLoadCompletionRecompute();
4688
+ if (sourceCompletionObservedToken === token || hostSourceLoadPhase() === "complete") {
4689
+ const refresh = () => {
4690
+ if (!disposed) refreshIncompleteTrackChangesDirectories();
4691
+ };
4692
+ if (typeof queueMicrotask === "function") queueMicrotask(refresh);
4693
+ else Promise.resolve().then(refresh);
4694
+ }
4695
+ } else if (incompleteTrackChangesDirectoryReadTokens.get(key) === token) incompleteTrackChangesDirectoryReadTokens.delete(key);
4696
+ return result && Array.isArray(result.items) ? result.items : [];
4697
+ });
4698
+ return incompleteTrackChangesDirectoryReadTokens.get(key) === token && read.status === "ready" ? {
4699
+ ...read,
4700
+ status: "stale"
4701
+ } : read;
4702
+ };
4703
+ const computeComments = (selection) => {
5140
4704
  const reviewWindow = getV2ReviewWindowSnapshot();
5141
- const catalog = readAsync("comments", contentToken(), () => commentsApi?.list ? commentsApi.list() : void 0, (raw) => raw && Array.isArray(raw.items) ? raw.items : []);
5142
- const windowItems = hasV2ReviewWindowFeed() && Array.isArray(reviewWindow?.commentItems) ? reviewWindow.commentItems : null;
5143
- const useWindowSlice = catalog.status === "pending" && windowItems != null;
5144
- const value = useWindowSlice ? windowItems : catalog.value;
5145
- const listStatus = useWindowSlice ? reviewWindowSliceStatus(reviewWindow) : catalog.status;
4705
+ const hasWindowFeed = hasV2ReviewWindowFeed();
4706
+ const catalog = !hasWindowFeed ? readCommentsDirectory() : null;
4707
+ const windowItems = hasWindowFeed && Array.isArray(reviewWindow?.commentItems) ? reviewWindow.commentItems : null;
4708
+ const value = hasWindowFeed ? windowItems : catalog.value;
4709
+ const listStatus = hasWindowFeed ? reviewWindowSliceStatus(reviewWindow) : catalog.status;
5146
4710
  const items = value ?? [];
5147
4711
  const activeIds = selection.activeCommentIds;
5148
- if (explicitActiveCommentId && !items.some((item) => readEntityId(item) === explicitActiveCommentId)) explicitActiveCommentId = null;
4712
+ const directory = asyncReads.get("comments");
4713
+ const directoryItems = directory?.token === contentToken() && directory.hasSettled && Array.isArray(directory.value) ? directory.value : null;
4714
+ const activeValidationItems = commentsDirectoryLeaseCount > 0 ? directoryItems : items;
4715
+ if (explicitActiveCommentId && activeValidationItems && !activeValidationItems.some((item) => readEntityId(item) === explicitActiveCommentId)) explicitActiveCommentId = null;
5149
4716
  const activeId = explicitActiveCommentId ?? activeIds[0] ?? null;
5150
4717
  return {
5151
4718
  status: combineStatus(listStatus, selection.status),
@@ -5156,51 +4723,32 @@ function createSuperDocUI(options) {
5156
4723
  activeId
5157
4724
  };
5158
4725
  };
5159
- const readAllStoryTrackChanges = () => {
5160
- const tcApi = getDoc()?.trackChanges;
5161
- const token = contentToken();
5162
- const heldEntry = asyncReads.get("trackChanges:all");
5163
- const { value, status } = authoritativeTrackChangesPendingToken === token ? {
5164
- value: heldEntry?.hasSettled ? heldEntry.value : null,
5165
- status: heldEntry?.hasSettled ? "stale" : "pending"
5166
- } : readAsync("trackChanges:all", token, () => {
5167
- if (!tcApi?.list) return void 0;
5168
- const fallback = () => tcApi.list({ in: "all" });
5169
- const bridgeOwnsCatalog = typeof getV2TrackedChanges()?.listTrackedChanges === "function";
5170
- const shared = runUiTrackedChangesCatalogRead(fallback);
5171
- if (!bridgeOwnsCatalog) return shared;
5172
- return Promise.resolve(shared).then((raw) => {
5173
- const record = isLooseObject(raw) ? raw : null;
5174
- return (Array.isArray(record?.items) ? record.items : []).some((item) => readEntityStory(item) == null) ? fallback() : raw;
5175
- });
5176
- }, (raw) => {
5177
- return (raw && Array.isArray(raw.items) ? raw.items : []).map(projectTrackChangesItem).filter((item) => item != null);
5178
- });
5179
- return status === "ready" ? value : null;
4726
+ const projectedTrackChangesCache = /* @__PURE__ */ new WeakMap();
4727
+ const projectTrackChangesItems = (source) => {
4728
+ const cached = projectedTrackChangesCache.get(source);
4729
+ if (cached) return cached;
4730
+ const projected = source.map(projectTrackChangesItem).filter((item) => item != null);
4731
+ projectedTrackChangesCache.set(source, projected);
4732
+ return projected;
5180
4733
  };
5181
- const computeTrackChanges = (selection) => {
5182
- const tcApi = getDoc()?.trackChanges;
5183
- const v2TrackedChanges = getV2TrackedChanges();
5184
- const listTrackedChanges = typeof v2TrackedChanges?.listTrackedChanges === "function" ? v2TrackedChanges.listTrackedChanges : null;
5185
- const token = contentToken();
5186
- const postDecisionIds = postDecisionTrackChangeIdsForToken(token);
5187
- const heldEntry = asyncReads.get("trackChanges");
5188
- const reviewWindow = getV2ReviewWindowSnapshot();
5189
- const catalog = authoritativeTrackChangesPendingToken === token ? {
5190
- value: heldEntry?.hasSettled ? heldEntry.value : null,
5191
- status: heldEntry?.hasSettled ? "stale" : "pending"
5192
- } : readAsync("trackChanges", token, () => {
5193
- if (listTrackedChanges) return runUiTrackedChangesCatalogRead(() => tcApi?.list?.());
5194
- return tcApi?.list ? tcApi.list({ in: "all" }) : void 0;
5195
- }, (raw) => raw && Array.isArray(raw.items) ? raw.items : []);
5196
- const windowItems = hasV2ReviewWindowFeed() && Array.isArray(reviewWindow?.trackedChangeItems) ? reviewWindow.trackedChangeItems : null;
5197
- const useWindowSlice = catalog.status === "pending" && windowItems != null;
5198
- const value = useWindowSlice ? windowItems : catalog.value;
5199
- const listStatus = useWindowSlice ? reviewWindowSliceStatus(reviewWindow) : catalog.status;
5200
- const items = (value ?? []).map(projectTrackChangesItem).filter((item) => item != null).filter((item) => {
4734
+ const filteredTrackChangesCache = /* @__PURE__ */ new WeakMap();
4735
+ const filterPostDecisionTrackChanges = (source, suppressedIds) => {
4736
+ const cached = filteredTrackChangesCache.get(source);
4737
+ if (cached?.suppressedIds === suppressedIds) return cached.value;
4738
+ const filtered = suppressedIds ? source.filter((item) => {
5201
4739
  const id = readEntityId(item);
5202
- return !id || !postDecisionIds?.has(id);
4740
+ return !id || !suppressedIds.has(id);
4741
+ }) : source;
4742
+ filteredTrackChangesCache.set(source, {
4743
+ suppressedIds,
4744
+ value: filtered
5203
4745
  });
4746
+ return filtered;
4747
+ };
4748
+ const trackChangeAuthorsCache = /* @__PURE__ */ new WeakMap();
4749
+ const trackChangeAuthors = (items) => {
4750
+ const cached = trackChangeAuthorsCache.get(items);
4751
+ if (cached) return cached;
5204
4752
  const authors = /* @__PURE__ */ new Set();
5205
4753
  for (const item of items) {
5206
4754
  const change = trackChangesItemPayload(item);
@@ -5208,12 +4756,38 @@ function createSuperDocUI(options) {
5208
4756
  const author = change.author ?? change.authorEmail ?? row.author ?? row.authorEmail;
5209
4757
  if (typeof author === "string") authors.add(author);
5210
4758
  }
5211
- const allStoryItems = tcApi?.list ? readAllStoryTrackChanges() : null;
4759
+ const value = [...authors];
4760
+ trackChangeAuthorsCache.set(items, value);
4761
+ return value;
4762
+ };
4763
+ const readAllStoryTrackChanges = () => {
4764
+ const token = contentToken();
4765
+ if (hasV2ReviewWindowFeed()) {
4766
+ const directory = asyncReads.get("trackChanges");
4767
+ const source = directory?.token === token && directory.hasSettled && Array.isArray(directory.value) ? directory.value : getV2ReviewWindowSnapshot()?.trackedChangeItems;
4768
+ if (!Array.isArray(source)) return null;
4769
+ return projectTrackChangesItems(source);
4770
+ }
4771
+ const { value, status } = readTrackChangesDirectory("trackChanges:all");
4772
+ return status === "ready" ? projectTrackChangesItems(value ?? []) : null;
4773
+ };
4774
+ const computeTrackChanges = (selection) => {
4775
+ const token = contentToken();
4776
+ const postDecisionIds = postDecisionTrackChangeIdsForToken(token);
4777
+ const reviewWindow = getV2ReviewWindowSnapshot();
4778
+ const hasWindowFeed = hasV2ReviewWindowFeed();
4779
+ const catalog = !hasWindowFeed ? readTrackChangesDirectory() : null;
4780
+ const windowItems = hasWindowFeed && Array.isArray(reviewWindow?.trackedChangeItems) ? reviewWindow.trackedChangeItems : null;
4781
+ const value = hasWindowFeed ? windowItems : catalog.value;
4782
+ const listStatus = hasWindowFeed ? reviewWindowSliceStatus(reviewWindow) : catalog.status;
4783
+ const items = filterPostDecisionTrackChanges(projectTrackChangesItems(value ?? []), postDecisionIds);
4784
+ const allStoryItems = readAllStoryTrackChanges();
5212
4785
  const active = explicitActiveChange;
5213
- if (active) {
5214
- if (active.story) {
5215
- if (allStoryItems && !allStoryItems.some((row) => entityRowMatchesRequest(row, active.id, active.story))) setExplicitActiveChange(null);
5216
- } else if (!items.some((item) => readEntityId(item) === active.id)) setExplicitActiveChange(null);
4786
+ if (active && !pendingTrackChangeRevealFocuses.has(active)) {
4787
+ const directory = asyncReads.get("trackChanges");
4788
+ const directoryItems = directory?.token === token && directory.hasSettled && Array.isArray(directory.value) ? filterPostDecisionTrackChanges(projectTrackChangesItems(directory.value), postDecisionIds) : null;
4789
+ const activeValidationItems = trackChangesDirectoryLeaseCount > 0 ? directoryItems : active.story ? allStoryItems : items;
4790
+ if (activeValidationItems && !activeValidationItems.some((row) => entityRowMatchesRequest(row, active.id, active.story))) setExplicitActiveChange(null);
5217
4791
  }
5218
4792
  const publicIdItems = allStoryItems ?? items;
5219
4793
  const selectionIdContext = buildStoryScopedTrackedChangeIdContext(publicIdItems, selectionStory(selection));
@@ -5234,7 +4808,37 @@ function createSuperDocUI(options) {
5234
4808
  items,
5235
4809
  total: items.length,
5236
4810
  activeId,
5237
- authors: [...authors]
4811
+ authors: trackChangeAuthors(items)
4812
+ };
4813
+ };
4814
+ const computeCommentsDirectorySnapshot = (selection, windowSnapshot) => {
4815
+ if (!hasV2ReviewWindowFeed()) return windowSnapshot;
4816
+ demandHeavyDocRead("comments");
4817
+ const directory = readCommentsDirectory();
4818
+ const items = directory.value ?? windowSnapshot.items;
4819
+ return {
4820
+ status: combineStatus(directory.status, selection.status),
4821
+ listStatus: directory.status,
4822
+ items,
4823
+ total: items.length,
4824
+ activeIds: selection.activeCommentIds,
4825
+ activeId: explicitActiveCommentId ?? selection.activeCommentIds[0] ?? null
4826
+ };
4827
+ };
4828
+ const computeTrackChangesDirectorySnapshot = (selection, windowSnapshot) => {
4829
+ if (!hasV2ReviewWindowFeed()) return windowSnapshot;
4830
+ demandHeavyDocRead("trackChanges");
4831
+ const directory = readTrackChangesDirectory();
4832
+ const postDecisionIds = postDecisionTrackChangeIdsForToken(contentToken());
4833
+ const items = filterPostDecisionTrackChanges(projectTrackChangesItems(directory.value ?? windowSnapshot.items), postDecisionIds);
4834
+ const explicitIdContext = explicitActiveChange?.story ? buildStoryScopedTrackedChangeIdContext(items, explicitActiveChange.story) : buildTrackedChangeIdContext(items);
4835
+ const activeId = explicitActiveChange ? explicitIdContext.toPublicId(explicitActiveChange.id) ?? explicitActiveChange.id : windowSnapshot.activeId;
4836
+ return {
4837
+ status: combineStatus(directory.status, selection.status),
4838
+ items,
4839
+ total: items.length,
4840
+ activeId,
4841
+ authors: trackChangeAuthors(items)
5238
4842
  };
5239
4843
  };
5240
4844
  const computeContentControls = (selection) => {
@@ -7167,54 +6771,19 @@ function createSuperDocUI(options) {
7167
6771
  }
7168
6772
  if (type === "mutation:committed" && event.reviewMutation) settleUiReviewMutation(event.reviewMutation.token);
7169
6773
  if (type === "source:complete" || type === "source:signals-complete") {
7170
- if (authoritativeTrackChangesPendingToken !== null) {
7171
- clearAuthoritativeTrackChangesRetry();
7172
- reviewMutationReconciler.onRender(true);
7173
- }
6774
+ sourceCompletionObservedToken = contentToken();
6775
+ refreshIncompleteTrackChangesDirectories();
7174
6776
  return;
7175
6777
  }
7176
6778
  if (type === "mutation:committed" || type === "collaboration:remote-changed") lastEditableMutationAtMs = Date.now();
7177
- if (type === "mutation:committed") {
7178
- clearAuthoritativeTrackChangesRetry();
7179
- reviewMutationToken = {};
7180
- }
7181
6779
  if (type === "mutation:committed" || type === "save:completed" || type === "collaboration:remote-changed") {
7182
6780
  const impact = type === "mutation:committed" ? getV2TrackedChangeMutationImpact(event) : null;
7183
- const supersedesPendingReviewReconcile = type === "mutation:committed" && reviewMutationReconciler.getPendingIds().size > 0;
7184
6781
  if (impact?.allResolved) {
7185
- reviewMutationReconciler.reset();
7186
6782
  replaceTrackedChangeItemsInCache([]);
7187
- authoritativeTrackChangesPendingToken = null;
7188
6783
  publishAllTrackedChangesResolved(event.receipt);
7189
6784
  return;
7190
6785
  }
7191
6786
  if (impact?.removedIds.size) markPostDecisionTrackChanges(new Set(impact.removedIds), event.receipt);
7192
- const authoritativeHistoryImpact = impact?.reconcileMode === "authoritative" && impact.upsertIds.size > 0;
7193
- if (authoritativeHistoryImpact) {
7194
- invalidateDocumentContent();
7195
- clearAuthoritativeTrackChangesRetry();
7196
- authoritativeTrackChangesPendingToken = contentToken();
7197
- recompute();
7198
- }
7199
- if (impact?.upsertIds.size || supersedesPendingReviewReconcile) {
7200
- const receipt = event.origin === "history" ? null : event.receipt;
7201
- const readiness = getEditor()?.documentMutationReadiness;
7202
- const canWaitForReceiptPaint = typeof receipt?.txId === "string" && receipt.txId.length > 0 && typeof readiness?.whenPainted === "function";
7203
- const historyRenderEpoch = event.origin === "history" && typeof readiness?.getRenderEpoch === "function" ? safeCall(() => readiness.getRenderEpoch.call(readiness), null) : null;
7204
- const canWaitForHistoryPaint = event.origin === "history" && typeof readiness?.whenPainted === "function";
7205
- const upsertImpact = {
7206
- upsertIds: impact?.upsertIds ?? /* @__PURE__ */ new Set(),
7207
- removedIds: /* @__PURE__ */ new Set(),
7208
- reconcileMode: impact?.reconcileMode === "authoritative" ? "authoritative" : "targeted"
7209
- };
7210
- if (canWaitForReceiptPaint) reviewMutationReconciler.enqueueAfterPaint(upsertImpact, () => readiness.whenPainted.call(readiness, receipt));
7211
- else if (canWaitForHistoryPaint) reviewMutationReconciler.enqueueAfterPaint(upsertImpact, () => readiness.whenPainted.call(readiness, typeof historyRenderEpoch === "number" ? { afterEpoch: historyRenderEpoch } : void 0));
7212
- else {
7213
- reviewMutationReconciler.enqueue(upsertImpact);
7214
- reviewMutationReconciler.onRender(supersedesPendingReviewReconcile);
7215
- }
7216
- }
7217
- if (authoritativeHistoryImpact) return;
7218
6787
  const isTypingBurst = type === "mutation:committed" && isEditableTextMutationEvent(event) || type === "collaboration:remote-changed" && Array.isArray(event.changedStoryIds) && event.changedStoryIds.length > 0;
7219
6788
  if (isTypingBurst) lastTypingMutationAtMs = Date.now();
7220
6789
  if (type === "mutation:committed" && isTypingBurst) {
@@ -7373,6 +6942,25 @@ function createSuperDocUI(options) {
7373
6942
  subscribe: (listener) => observe((snapshot) => listener({ snapshot }))
7374
6943
  };
7375
6944
  };
6945
+ const directorySnapshotHandle = (passiveSub, directorySub, family) => {
6946
+ const passive = snapshotHandle(passiveSub);
6947
+ const directory = snapshotHandle(directorySub);
6948
+ const observe = (listener) => {
6949
+ const release = acquireDirectoryLease(family);
6950
+ recompute();
6951
+ const unsubscribe = directory.observe(listener);
6952
+ return () => {
6953
+ unsubscribe();
6954
+ release();
6955
+ };
6956
+ };
6957
+ return {
6958
+ get: passive.get,
6959
+ getSnapshot: passive.getSnapshot,
6960
+ observe,
6961
+ subscribe: (listener) => observe((snapshot) => listener({ snapshot }))
6962
+ };
6963
+ };
7376
6964
  function readTrackDecisionTargetId(target) {
7377
6965
  return target && typeof target === "object" && typeof target.id === "string" ? String(target.id) : null;
7378
6966
  }
@@ -9177,7 +8765,7 @@ function createSuperDocUI(options) {
9177
8765
  const moveSide = namespace === "trackChanges" && loadedRecord?.type === "move" && (loadedRecord.subtype === "move-to" || loadedRecord.subtype === "move-from") ? loadedRecord.subtype : null;
9178
8766
  const trackedChangeLookupId = moveSide && typeof loadedRecord?.trackedChangeCanonicalId === "string" ? loadedRecord.trackedChangeCanonicalId : id;
9179
8767
  const readResolvedTarget = (row) => {
9180
- if (!moveSide) return readTarget(row);
8768
+ if (!moveSide) return namespace === "trackChanges" ? readTrackedChangeNavigationTarget(row, { preferStableBlock: true }) : readTarget(row);
9181
8769
  const moveTarget = readEntityTarget(row);
9182
8770
  if (moveTarget?.kind !== "move") return null;
9183
8771
  const sideTarget = moveSide === "move-to" ? moveTarget.destination : moveTarget.source;
@@ -9235,7 +8823,7 @@ function createSuperDocUI(options) {
9235
8823
  return filtered.slice(offset, end);
9236
8824
  };
9237
8825
  const commentsSub = sliceHandle((s) => s.comments);
9238
- const commentsSnap = snapshotHandle(commentsSub);
8826
+ const commentsSnap = directorySnapshotHandle(commentsSub, select((s) => computeCommentsDirectorySnapshot(s.selection, s.comments)), "comments");
9239
8827
  const comments = {
9240
8828
  get: commentsSnap.get,
9241
8829
  getSnapshot: commentsSnap.getSnapshot,
@@ -9243,12 +8831,8 @@ function createSuperDocUI(options) {
9243
8831
  observe: commentsSnap.observe,
9244
8832
  list: (query) => {
9245
8833
  ensureCommentsCatalog();
9246
- const snapshot = commentsSub.get();
9247
- if (snapshot.status !== "pending") return filterCommentsSnapshot(snapshot.items, query);
9248
- const commentsApi = getDoc()?.comments;
9249
- const result = safeCall(commentsApi?.list ? () => commentsApi.list(query) : void 0, null);
9250
- if (isPromiseLike(result)) return [];
9251
- return filterCommentsSnapshot(Array.isArray(result?.items) ? result.items : [], query);
8834
+ const directory = readCommentsDirectory();
8835
+ return filterCommentsSnapshot(directory.value ?? commentsSub.get().items, query);
9252
8836
  },
9253
8837
  getById: (commentId) => {
9254
8838
  const fromSnapshot = commentsSub.get().items.find((item) => readEntityId(item) === commentId) ?? null;
@@ -9334,10 +8918,13 @@ function createSuperDocUI(options) {
9334
8918
  }
9335
8919
  if (!getEditor()) return false;
9336
8920
  const snapshot = commentsSub.get();
9337
- if (snapshot.listStatus !== "ready") return false;
9338
- const resolvedId = resolveActiveCommentId(snapshot.items, commentId);
8921
+ const directory = asyncReads.get("comments");
8922
+ const directoryItems = directory?.token === contentToken() && directory.hasSettled && Array.isArray(directory.value) ? directory.value : null;
8923
+ const items = directoryItems ?? snapshot.items;
8924
+ if (!directoryItems && snapshot.listStatus !== "ready") return false;
8925
+ const resolvedId = resolveActiveCommentId(items, commentId);
9339
8926
  if (!resolvedId) return false;
9340
- if (!snapshot.items.some((entry) => readEntityId(entry) === resolvedId)) return false;
8927
+ if (!items.some((entry) => readEntityId(entry) === resolvedId)) return false;
9341
8928
  explicitActiveCommentId = resolvedId;
9342
8929
  recompute();
9343
8930
  return true;
@@ -9359,7 +8946,8 @@ function createSuperDocUI(options) {
9359
8946
  };
9360
8947
  const trackChangesSub = sliceHandle((s) => s.trackChanges);
9361
8948
  const navigateTrackChange = (step, options) => {
9362
- const allRows = trackChangesSub.get().items.filter((item) => readEntityId(item) !== null);
8949
+ ensureTrackChangesCatalog();
8950
+ const allRows = (readTrackChangesDirectory().value ?? trackChangesSub.get().items).map(projectTrackChangesItem).filter((item) => item != null && readEntityId(item) !== null);
9363
8951
  if (allRows.length === 0) return null;
9364
8952
  const currentId = state.trackChanges.activeId;
9365
8953
  const currentStory = explicitActiveChange?.id === currentId ? explicitActiveChange.story : void 0;
@@ -9448,7 +9036,7 @@ function createSuperDocUI(options) {
9448
9036
  trackChangeNavigationTail = run.then(() => void 0, () => void 0);
9449
9037
  return run;
9450
9038
  };
9451
- const trackChangesSnap = snapshotHandle(trackChangesSub);
9039
+ const trackChangesSnap = directorySnapshotHandle(trackChangesSub, select((s) => computeTrackChangesDirectorySnapshot(s.selection, s.trackChanges)), "trackChanges");
9452
9040
  const trackChanges = {
9453
9041
  get: trackChangesSnap.get,
9454
9042
  getSnapshot: trackChangesSnap.getSnapshot,
@@ -9456,7 +9044,12 @@ function createSuperDocUI(options) {
9456
9044
  observe: trackChangesSnap.observe,
9457
9045
  list: () => {
9458
9046
  ensureTrackChangesCatalog();
9459
- return trackChangesSub.get().items;
9047
+ const source = readTrackChangesDirectory().value ?? trackChangesSub.get().items;
9048
+ const suppressed = postDecisionTrackChangeIdsForToken(contentToken());
9049
+ return source.map(projectTrackChangesItem).filter((item) => item != null).filter((item) => {
9050
+ const id = readEntityId(item);
9051
+ return !id || !suppressed?.has(id);
9052
+ });
9460
9053
  },
9461
9054
  accept: (changeId) => executeTrackDecision("accept", changeId),
9462
9055
  reject: (changeId) => executeTrackDecision("reject", changeId),
@@ -9513,7 +9106,7 @@ function createSuperDocUI(options) {
9513
9106
  if (!allStoryItems.some((row) => entityRowMatchesRequest(row, publicId, requested.story))) return false;
9514
9107
  requested.id = publicId;
9515
9108
  } else {
9516
- const items = trackChangesSub.get().items;
9109
+ const items = readAllStoryTrackChanges() ?? trackChangesSub.get().items;
9517
9110
  const publicId = buildTrackedChangeIdContext(items).toPublicId(requested.id) ?? requested.id;
9518
9111
  const item = items.find((candidate) => readEntityId(candidate) === publicId);
9519
9112
  if (!item) return false;
@@ -10723,8 +10316,6 @@ function createSuperDocUI(options) {
10723
10316
  if (disposed) return;
10724
10317
  disposed = true;
10725
10318
  releaseSharedUiTrackedChangesCatalog(uiTrackedChangesCatalogHost, uiTrackedChangesCatalogState);
10726
- reviewMutationReconciler.reset();
10727
- clearAuthoritativeTrackChangesRetry();
10728
10319
  if (foregroundAsyncRetryTimer) {
10729
10320
  clearTimeout(foregroundAsyncRetryTimer);
10730
10321
  foregroundAsyncRetryTimer = null;
@@ -10750,7 +10341,12 @@ function createSuperDocUI(options) {
10750
10341
  if (detachSourceLoading) detachSourceLoading();
10751
10342
  detachSourceLoading = null;
10752
10343
  sourceLoadingSubscriptionHost = null;
10344
+ commentsDirectoryLeaseCount = 0;
10345
+ trackChangesDirectoryLeaseCount = 0;
10753
10346
  demandedHeavyReads.clear();
10347
+ incompleteTrackChangesDirectoryReadTokens.clear();
10348
+ postSourceCompletionRefreshTokens.clear();
10349
+ sourceCompletionObservedToken = null;
10754
10350
  coldAsyncReadDeferrals.clear();
10755
10351
  for (const detach of detachers.splice(0)) detach();
10756
10352
  if (detachHostSelection) detachHostSelection();
@@ -11009,4 +10605,4 @@ function commandIsActive(descriptor, selection) {
11009
10605
  return descriptor.activeMark ? selection.activeMarks.includes(descriptor.activeMark) : false;
11010
10606
  }
11011
10607
  //#endregion
11012
- export { getV2TrackedChangeMutationImpact as a, createV2ReviewMutationReconciler as i, shallowEqual as n, DOM_CLASS_NAMES as o, BUILT_IN_COMMAND_IDS as r, createSuperDocUI as t };
10608
+ export { DOM_CLASS_NAMES as a, getV2TrackedChangeMutationImpact as i, shallowEqual as n, BUILT_IN_COMMAND_IDS as r, createSuperDocUI as t };