superdoc 2.4.0-next.21 → 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 (43) hide show
  1. package/dist/chunks/{create-super-doc-ui-CFsY0uxw.es.js → create-super-doc-ui-CmOSA_lN.es.js} +252 -657
  2. package/dist/chunks/{create-super-doc-ui-jSJQis_0.cjs → create-super-doc-ui-CqGmoMMA.cjs} +251 -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/index.d.ts +73 -1
  8. package/dist/layout-engine/contracts/src/inline-box.d.ts +19 -0
  9. package/dist/layout-engine/contracts/src/paragraph-visible-text.d.ts +19 -0
  10. package/dist/layout-engine/layout-bridge/src/inline-box-key.d.ts +3 -0
  11. package/dist/layout-engine/layout-bridge/src/run-visual-marks.d.ts +2 -0
  12. package/dist/layout-engine/layout-bridge/src/text-measurement.d.ts +0 -12
  13. package/dist/layout-engine/painters/dom/src/runs/inline-box.d.ts +8 -0
  14. package/dist/public/ui-react.cjs +1 -1
  15. package/dist/public/ui-react.es.js +1 -1
  16. package/dist/public/ui.cjs +1 -1
  17. package/dist/public/ui.es.js +1 -1
  18. package/dist/style.css +93 -92
  19. package/dist/style.layered.css +93 -92
  20. package/dist/superdoc/src/components/CommentsLayer/CommentDialog.vue.d.ts +2 -0
  21. package/dist/superdoc/src/components/CommentsLayer/commentsList/ReviewDirectoryListItem.vue.d.ts +16 -0
  22. package/dist/superdoc/src/components/CommentsLayer/commentsList/commentsList.vue.d.ts +3 -1
  23. package/dist/superdoc/src/components/CommentsLayer/commentsList/super-comments-list.d.ts +12 -0
  24. package/dist/superdoc/src/core/SuperDoc.d.ts +6 -7
  25. package/dist/superdoc/src/core/extensions/index.d.ts +1 -1
  26. package/dist/superdoc/src/core/extensions/types.d.ts +63 -5
  27. package/dist/superdoc/src/core/types/index.d.ts +19 -0
  28. package/dist/superdoc/src/core/v2-integration/v2-integration.d.ts +3 -3
  29. package/dist/superdoc/src/helpers/v2-review-mutation-impact.d.ts +3 -5
  30. package/dist/superdoc/src/public/index.d.cts +6 -0
  31. package/dist/superdoc/src/public/index.d.ts +3 -0
  32. package/dist/superdoc/src/public/ui/types.d.ts +5 -5
  33. package/dist/superdoc/src/stores/comments-store.d.ts +81 -78
  34. package/dist/superdoc/src/stores/superdoc-store.d.ts +243 -234
  35. package/dist/superdoc.cjs +660 -857
  36. package/dist/superdoc.es.js +648 -846
  37. package/dist-cdn/style.layered.css +1 -1
  38. package/dist-cdn/superdoc.min.css +1 -1
  39. package/dist-cdn/superdoc.min.js +36 -36
  40. package/package.json +2 -2
  41. package/dist/superdoc/src/helpers/v2-remote-review-hydration.d.ts +0 -12
  42. package/dist/superdoc/src/helpers/v2-review-mutation-reconciler.d.ts +0 -19
  43. package/dist/superdoc/src/helpers/v2-typing-review-hydration.d.ts +0 -16
@@ -3,7 +3,7 @@ import { t as blank_default } from "./chunks/blank-docx-DzQccOlW.es.js";
3
3
  import { t as import_eventemitter3 } from "./chunks/eventemitter3-DEIiXiH2.es.js";
4
4
  import { t as v4 } from "./chunks/uuid-H0Xcmmhy.es.js";
5
5
  import { a as init_dist$1, i as global, n as init_dist$2, o as Buffer, r as process$1, s as init_dist, t as require_jszip_min } from "./chunks/jszip-BzJ3CyxR.es.js";
6
- import { a as getV2TrackedChangeMutationImpact, i as createV2ReviewMutationReconciler, o as DOM_CLASS_NAMES, t as createSuperDocUI } from "./chunks/create-super-doc-ui-CFsY0uxw.es.js";
6
+ import { a as DOM_CLASS_NAMES, i as getV2TrackedChangeMutationImpact, t as createSuperDocUI } from "./chunks/create-super-doc-ui-CmOSA_lN.es.js";
7
7
  import { t as _plugin_vue_export_helper_default } from "./chunks/_plugin-vue_export-helper-BOaGB7Aw.es.js";
8
8
  import { t as PDF_TO_CSS_UNITS } from "./chunks/constants-B6VBlmKp.es.js";
9
9
  import * as Vue from "vue";
@@ -6741,14 +6741,21 @@ function createStubGeometryPublisher() {
6741
6741
  }
6742
6742
  };
6743
6743
  }
6744
- function createStubReviewHydrationController() {
6744
+ function createStubReviewWindowController() {
6745
6745
  return {
6746
6746
  setContext() {},
6747
- onRenderReadiness() {},
6748
- hydrateNow() {},
6749
- reconcileInBackground() {},
6747
+ onCommittedPagePaint() {},
6748
+ refreshCommittedWindow() {},
6750
6749
  invalidate() {},
6750
+ beginMutation() {},
6751
+ settleMutation() {},
6751
6752
  reset() {},
6753
+ getSnapshot() {
6754
+ return null;
6755
+ },
6756
+ subscribe() {
6757
+ return () => void 0;
6758
+ },
6752
6759
  getDiagnostics() {
6753
6760
  return null;
6754
6761
  }
@@ -6777,7 +6784,7 @@ function createStubV2Integration() {
6777
6784
  EditorComponent: StubV2EditorComponent,
6778
6785
  RulerComponent: null,
6779
6786
  createGeometryPublisher: createStubGeometryPublisher,
6780
- createReviewHydrationController: createStubReviewHydrationController,
6787
+ createReviewWindowController: createStubReviewWindowController,
6781
6788
  isSyntheticTrackedChangeCommentLaneItem,
6782
6789
  isV2SyntheticTrackedChangeRow
6783
6790
  };
@@ -6802,7 +6809,7 @@ function createDefaultV2Integration() {
6802
6809
  EditorComponent: integration.EditorComponent,
6803
6810
  RulerComponent: integration.RulerComponent ?? stub.RulerComponent,
6804
6811
  createGeometryPublisher: integration.createGeometryPublisher ?? stub.createGeometryPublisher,
6805
- createReviewHydrationController: integration.createReviewHydrationController ?? stub.createReviewHydrationController,
6812
+ createReviewWindowController: integration.createReviewWindowController ?? stub.createReviewWindowController,
6806
6813
  isSyntheticTrackedChangeCommentLaneItem: integration.isSyntheticTrackedChangeCommentLaneItem ?? stub.isSyntheticTrackedChangeCommentLaneItem,
6807
6814
  isV2SyntheticTrackedChangeRow: integration.isV2SyntheticTrackedChangeRow ?? stub.isV2SyntheticTrackedChangeRow
6808
6815
  };
@@ -7051,8 +7058,6 @@ var COMMENT_DRAFT_BLOCK_OPEN_TAG = /<(?:p|div|li|blockquote|pre|h[1-6])(?:\s[^>]
7051
7058
  var COMMENT_DRAFT_BLOCK_CLOSE_TAG = /<\/(?:p|div|li|blockquote|pre|h[1-6])\s*>/gi;
7052
7059
  var COMMENT_DRAFT_ANY_TAG = /<\/?[A-Za-z][A-Za-z0-9:-]*(?:\s[^>]*)?\s*\/?>/g;
7053
7060
  var COMMENT_DRAFT_ENTITY = /&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z][a-zA-Z0-9]+);/g;
7054
- var TRACKED_CHANGE_BACKGROUND_RECONCILE_BATCH_SIZE = 50;
7055
- var TRACKED_CHANGE_BACKGROUND_RECONCILE_YIELD_TIMEOUT_MS = 50;
7056
7061
  var COMMENT_DRAFT_NAMED_ENTITIES = {
7057
7062
  amp: "&",
7058
7063
  apos: "'",
@@ -7070,20 +7075,6 @@ var shallowEqual = (a, b) => {
7070
7075
  return aKeys.every((key) => Object.prototype.hasOwnProperty.call(b, key) && Object.is(a[key], b[key]));
7071
7076
  };
7072
7077
  /**
7073
- * Yield between bounded background reconciliation batches. A full tracked-
7074
- * change catalog can contain thousands of rows; applying it in one Pinia/Vue
7075
- * turn blocks painted-window review work. `requestIdleCallback` keeps the work
7076
- * off the critical path when available, while the zero-delay fallback still
7077
- * creates a task boundary in browsers without it.
7078
- */
7079
- var yieldForTrackedChangeBackgroundReconcile = () => new Promise((resolve) => {
7080
- if (typeof globalThis.requestIdleCallback === "function") {
7081
- globalThis.requestIdleCallback(resolve, { timeout: TRACKED_CHANGE_BACKGROUND_RECONCILE_YIELD_TIMEOUT_MS });
7082
- return;
7083
- }
7084
- setTimeout(resolve, 0);
7085
- });
7086
- /**
7087
7078
  * Normalize the optional signed tracked-change detail lines
7088
7079
  * (`[{ excerpt, label }]`, TCS-LIST-005) to a stable shape; absent, empty, or
7089
7080
  * malformed input reads as `null` so legacy rows stay untouched.
@@ -7254,6 +7245,7 @@ var useCommentsStore = defineStore("comments", () => {
7254
7245
  const suppressInternalExternal = ref(true);
7255
7246
  const currentCommentText = ref("");
7256
7247
  const commentsList = ref([]);
7248
+ const reviewDirectoryList = shallowRef([]);
7257
7249
  let priorTrackedChangeThreadIndex = /* @__PURE__ */ new Map();
7258
7250
  const trackedChangeThreadIndex = computed(() => {
7259
7251
  const next = buildTrackedChangeThreadIndex(commentsList.value, priorTrackedChangeThreadIndex);
@@ -7271,6 +7263,8 @@ var useCommentsStore = defineStore("comments", () => {
7271
7263
  return next;
7272
7264
  });
7273
7265
  const isCommentsListVisible = ref(false);
7266
+ const isReviewDirectoryActive = ref(false);
7267
+ const isReviewDirectoryLoading = ref(false);
7274
7268
  const editorCommentIds = ref([]);
7275
7269
  const editorCommentPositions = ref({});
7276
7270
  const isCommentHighlighted = ref(false);
@@ -7291,17 +7285,8 @@ var useCommentsStore = defineStore("comments", () => {
7291
7285
  return viewingVisibility.commentsVisible || viewingVisibility.trackChangesVisible;
7292
7286
  });
7293
7287
  const v2CommentsAdapter = shallowRef(null);
7294
- const v2CommentSyncGenerations = /* @__PURE__ */ new WeakMap();
7295
- const commentSyncGeneration = (adapter) => v2CommentSyncGenerations.get(adapter) ?? 0;
7296
- const supersedeCommentSyncGeneration = (adapter) => {
7297
- if (!adapter || typeof adapter !== "object" && typeof adapter !== "function") return 0;
7298
- const next = commentSyncGeneration(adapter) + 1;
7299
- v2CommentSyncGenerations.set(adapter, next);
7300
- return next;
7301
- };
7302
7288
  const setV2CommentsAdapter = (adapter) => {
7303
7289
  v2CommentsAdapter.value = adapter ?? null;
7304
- if (adapter && !v2CommentSyncGenerations.has(adapter)) v2CommentSyncGenerations.set(adapter, 0);
7305
7290
  };
7306
7291
  const getV2CommentsAdapter = (superdoc) => {
7307
7292
  const fromFacade = superdoc?.activeEditor?.v2Comments ?? null;
@@ -7310,27 +7295,14 @@ var useCommentsStore = defineStore("comments", () => {
7310
7295
  };
7311
7296
  const isV2EditorActive = (superdoc) => superdoc?.activeEditor?.editorVersion === 2 || v2CommentsAdapter.value !== null;
7312
7297
  const v2TrackedChangesAdapter = shallowRef(null);
7313
- const v2TrackedChangeSyncGenerations = /* @__PURE__ */ new WeakMap();
7314
- const trackedChangeSyncGeneration = (adapter) => v2TrackedChangeSyncGenerations.get(adapter) ?? 0;
7315
- const supersedeTrackedChangeSyncGeneration = (adapter) => {
7316
- if (!adapter || typeof adapter !== "object" && typeof adapter !== "function") return 0;
7317
- const next = trackedChangeSyncGeneration(adapter) + 1;
7318
- v2TrackedChangeSyncGenerations.set(adapter, next);
7319
- return next;
7320
- };
7321
7298
  const setV2TrackedChangesAdapter = (adapter) => {
7322
7299
  v2TrackedChangesAdapter.value = adapter ?? null;
7323
- if (adapter && !v2TrackedChangeSyncGenerations.has(adapter)) v2TrackedChangeSyncGenerations.set(adapter, 0);
7324
7300
  };
7325
7301
  const getV2TrackedChangesAdapter = (superdoc) => {
7326
7302
  const fromFacade = superdoc?.activeEditor?.v2TrackedChanges ?? null;
7327
7303
  if (fromFacade) return fromFacade;
7328
7304
  return v2TrackedChangesAdapter.value;
7329
7305
  };
7330
- const supersedeV2ReviewHydration = ({ commentsAdapter, trackedChangesAdapter } = {}) => ({
7331
- commentsGeneration: supersedeCommentSyncGeneration(commentsAdapter ?? v2CommentsAdapter.value),
7332
- trackedChangesGeneration: supersedeTrackedChangeSyncGeneration(trackedChangesAdapter ?? v2TrackedChangesAdapter.value)
7333
- });
7334
7306
  /**
7335
7307
  * Initialize the store
7336
7308
  *
@@ -7351,37 +7323,6 @@ var useCommentsStore = defineStore("comments", () => {
7351
7323
  if (!Array.isArray(aliases)) return [];
7352
7324
  return [...new Set(aliases.map((id) => normalizeCommentId(id)).filter(Boolean))];
7353
7325
  };
7354
- const trackedChangeItemId = (item) => normalizeCommentId(item?.id ?? item?.changeId ?? item?.address?.entityId);
7355
- const trackedChangeItemAliases = (item) => normalizeTrackedChangePositionAliases([
7356
- trackedChangeItemId(item),
7357
- item?.changeId,
7358
- item?.address?.entityId,
7359
- item?.trackedChangeAnchorKey,
7360
- item?.anchorKey,
7361
- item?.address?.anchorKey,
7362
- ...Array.isArray(item?.trackedChangePositionAliases) ? item.trackedChangePositionAliases : [],
7363
- ...Array.isArray(item?.sourceIds?.rsids) ? item.sourceIds.rsids : [],
7364
- item?.sourceIds?.wordIdDelete,
7365
- item?.sourceIds?.wordIdInsert,
7366
- ...Array.isArray(item?.sourceIds?.wordIdOther) ? item.sourceIds.wordIdOther : []
7367
- ]);
7368
- const trackedChangeItemIdentityIds = (item) => new Set(trackedChangeItemAliases(item).flatMap((id) => {
7369
- const parsedAnchor = parseTrackedChangeAnchorKey(id);
7370
- return parsedAnchor?.rawId ? [id, parsedAnchor.rawId] : [id];
7371
- }));
7372
- const dedupeTrackedChangeItems = (values) => {
7373
- const seen = /* @__PURE__ */ new Set();
7374
- const deduped = [];
7375
- for (const item of values) {
7376
- const id = trackedChangeItemId(item);
7377
- if (id) {
7378
- if (seen.has(id)) continue;
7379
- seen.add(id);
7380
- }
7381
- deduped.push(item);
7382
- }
7383
- return deduped;
7384
- };
7385
7326
  const getTrackedChangeStoryKey = (comment) => {
7386
7327
  if (!comment?.trackedChange) return null;
7387
7328
  const anchorParts = parseTrackedChangeAnchorKey(comment.trackedChangeAnchorKey);
@@ -7492,7 +7433,9 @@ var useCommentsStore = defineStore("comments", () => {
7492
7433
  */
7493
7434
  const getComment = (id) => {
7494
7435
  if (id === void 0 || id === null) return null;
7495
- return commentsList.value.find((c) => c.commentId == id || c.importedId == id || c.trackedChangeAnchorKey == id || c?.trackedChange && buildTrackedChangeImportedPositionId(c.importedId) == id) || getTrackedChangeCommentByPositionAlias(id);
7436
+ const directMatch = commentsList.value.find((c) => c.commentId == id || c.importedId == id || c.trackedChangeAnchorKey == id || c?.trackedChange && buildTrackedChangeImportedPositionId(c.importedId) == id);
7437
+ if (directMatch) return directMatch;
7438
+ return reviewDirectoryList.value.find((c) => c.commentId == id || c.importedId == id || c.trackedChangeAnchorKey == id || c?.trackedChange && buildTrackedChangeImportedPositionId(c.importedId) == id) || getTrackedChangeCommentByPositionAlias(id);
7496
7439
  };
7497
7440
  const getThreadParent = (comment) => {
7498
7441
  if (!comment?.parentCommentId) return comment;
@@ -7969,8 +7912,8 @@ var useCommentsStore = defineStore("comments", () => {
7969
7912
  ...commentAliasIds
7970
7913
  ].filter(Boolean).some((id) => incomingAliasIds.has(id))) return true;
7971
7914
  const hasSyntheticSideIdentity = normalizedTrackedChangeCanonicalId && normalizedTrackedChangeCanonicalId !== normalizedChangeId || commentCanonicalId && commentCanonicalId !== commentId;
7972
- if (commentId === normalizedChangeId) return true;
7973
- if (!hasSyntheticSideIdentity && (importedId === normalizedChangeId || normalizedImportedId !== void 0 && normalizedImportedId != null && importedId === normalizedImportedId)) return true;
7915
+ if (hasCompatibleStory && commentId === normalizedChangeId) return true;
7916
+ if (hasCompatibleStory && !hasSyntheticSideIdentity && (importedId === normalizedChangeId || normalizedImportedId !== void 0 && normalizedImportedId != null && importedId === normalizedImportedId)) return true;
7974
7917
  if (hasSyntheticSideIdentity) return false;
7975
7918
  if (normalizedAnchorKey && commentAnchorKey) return commentAnchorKey === normalizedAnchorKey;
7976
7919
  return false;
@@ -8282,11 +8225,11 @@ var useCommentsStore = defineStore("comments", () => {
8282
8225
  * @param {(a: Object, b: Object) => number} sorter - Comparator function for sorting comments
8283
8226
  * @returns {{parentComments: Array, resolvedComments: Array}} Grouped and sorted comments
8284
8227
  */
8285
- const buildGroupedComments = (sorter) => {
8228
+ const buildGroupedCommentsFrom = (source, sorter) => {
8286
8229
  const parentComments = [];
8287
8230
  const resolvedComments = [];
8288
8231
  const childCommentMap = /* @__PURE__ */ new Map();
8289
- commentsList.value.forEach((comment) => {
8232
+ source.forEach((comment) => {
8290
8233
  if (!isThreadVisible(comment)) return;
8291
8234
  const trackedChangeThreadParentId = shouldThreadWithTrackedChange(comment) ? trackedChangeThreadParentIdForComment$1(comment) : null;
8292
8235
  const parentId = comment.trackedChange ? null : comment.parentCommentId || trackedChangeThreadParentId;
@@ -8302,8 +8245,10 @@ var useCommentsStore = defineStore("comments", () => {
8302
8245
  resolvedComments: resolvedComments.sort(sorter)
8303
8246
  };
8304
8247
  };
8248
+ const buildGroupedComments = (sorter) => buildGroupedCommentsFrom(commentsList.value, sorter);
8305
8249
  /** @type {import('vue').ComputedRef<{parentComments: Array, resolvedComments: Array}>} Comments grouped and sorted by creation time */
8306
8250
  const getGroupedComments = computed(() => buildGroupedComments(compareByCreatedTime));
8251
+ const getGroupedReviewDirectory = computed(() => buildGroupedCommentsFrom(reviewDirectoryList.value, compareByCreatedTime));
8307
8252
  const hasOpenTrackedChanges = computed(() => getGroupedComments.value?.parentComments.some((comment) => comment.trackedChange && !comment.resolvedTime) ?? false);
8308
8253
  /** @type {import('vue').ComputedRef<{parentComments: Array, resolvedComments: Array}>} Comments grouped and sorted by document position */
8309
8254
  const getCommentsByPosition = computed(() => buildGroupedComments(compareByPosition));
@@ -8523,11 +8468,32 @@ var useCommentsStore = defineStore("comments", () => {
8523
8468
  });
8524
8469
  return outcome;
8525
8470
  }
8526
- reconcileCommentsFromV2({
8471
+ const removedIds = new Set((Array.isArray(outcome.removedCommentIds) ? outcome.removedCommentIds : []).map(normalizeCommentId).filter(Boolean));
8472
+ if (removedIds.has(normalizeCommentId(commentId))) {
8473
+ if (importedId != null) removedIds.add(normalizeCommentId(importedId));
8474
+ let addedChild = true;
8475
+ while (addedChild) {
8476
+ addedChild = false;
8477
+ for (const row of commentsList.value) {
8478
+ const parentId = normalizeCommentId(row?.parentCommentId);
8479
+ if (!parentId || !removedIds.has(parentId)) continue;
8480
+ for (const alias of [row?.commentId, row?.importedId].map(normalizeCommentId).filter(Boolean)) {
8481
+ if (removedIds.has(alias)) continue;
8482
+ removedIds.add(alias);
8483
+ addedChild = true;
8484
+ }
8485
+ }
8486
+ }
8487
+ commentsList.value = commentsList.value.filter((row) => {
8488
+ return ![row?.commentId, row?.importedId].map(normalizeCommentId).filter(Boolean).some((alias) => removedIds.has(alias));
8489
+ });
8490
+ if (removedIds.has(normalizeCommentId(activeComment.value))) clearActiveCommentSelection();
8491
+ } else reconcileCommentsFromV2({
8527
8492
  superdoc,
8528
8493
  adapter: v2Adapter,
8529
8494
  documentId: v2Adapter.documentId ?? fileId,
8530
- items: outcome.items
8495
+ items: outcome.items ?? [],
8496
+ pruneStale: outcome.complete !== false
8531
8497
  });
8532
8498
  superdoc.emit("comments-update", {
8533
8499
  type: COMMENT_EVENTS.DELETED,
@@ -8698,6 +8664,78 @@ var useCommentsStore = defineStore("comments", () => {
8698
8664
  fileType: null
8699
8665
  })).filter(Boolean);
8700
8666
  };
8667
+ const isBlankDirectoryCommentText = (value) => typeof value !== "string" || value.replace(/<[^>]*>/g, "").replace(/&nbsp;/gi, " ").trim().length === 0;
8668
+ const trackedChangeDirectoryComment = (params) => {
8669
+ if (!params || params.event === "omit" || params.changeId == null) return null;
8670
+ return getPendingComment({
8671
+ documentId: params.documentId,
8672
+ commentId: params.changeId,
8673
+ trackedChange: true,
8674
+ trackedChangeText: params.trackedChangeText,
8675
+ trackedChangeType: params.trackedChangeType,
8676
+ trackedChangeDisplayType: params.trackedChangeDisplayType,
8677
+ semanticColorKey: params.semanticColorKey,
8678
+ semanticColor: params.semanticColor,
8679
+ deletedText: params.deletedText,
8680
+ trackedChangeLabel: typeof params.trackedChangeLabel === "string" && params.trackedChangeLabel.length > 0 ? params.trackedChangeLabel : null,
8681
+ trackedChangeDetailLines: normalizeTrackedChangeDetailLines(params.trackedChangeDetailLines),
8682
+ trackedChangeImagePreview: normalizeTrackedChangeImagePreview(params.trackedChangeImagePreview),
8683
+ createdTime: params.date,
8684
+ creatorId: params.authorId ?? null,
8685
+ creatorName: params.author,
8686
+ creatorEmail: params.authorEmail,
8687
+ creatorImage: params.authorImage,
8688
+ isInternal: false,
8689
+ importedAuthor: params.importedAuthor,
8690
+ ...Object.prototype.hasOwnProperty.call(params, "importedId") ? { importedId: params.importedId != null ? String(params.importedId) : null } : {},
8691
+ trackedChangeStory: params.trackedChangeStory ?? null,
8692
+ trackedChangeStoryKind: params.trackedChangeStoryKind ?? null,
8693
+ trackedChangeStoryLabel: params.trackedChangeStoryLabel ?? "",
8694
+ trackedChangeAnchorKey: params.trackedChangeAnchorKey ?? null,
8695
+ trackedChangeCanonicalId: params.trackedChangeCanonicalId ?? null,
8696
+ trackedChangePositionAliases: normalizeTrackedChangePositionAliases(params.trackedChangePositionAliases),
8697
+ selection: {
8698
+ source: DOCUMENT_EDITOR_SELECTION_SOURCE,
8699
+ selectionBounds: params.coords ?? null
8700
+ }
8701
+ });
8702
+ };
8703
+ const setReviewDirectoryFromV2 = ({ superdoc, commentItems = [], trackedChangeItems = [] } = {}) => {
8704
+ const commentsAdapter = getV2CommentsAdapter(superdoc);
8705
+ const trackedChangesAdapter = getV2TrackedChangesAdapter(superdoc);
8706
+ if (!commentsAdapter || !trackedChangesAdapter || typeof commentsAdapter.mapV2CommentToUseCommentInput !== "function" || typeof trackedChangesAdapter.mapV2TrackedChangeToCommentParams !== "function") {
8707
+ reviewDirectoryList.value = [];
8708
+ return {
8709
+ ok: false,
8710
+ reason: "adapter-unavailable"
8711
+ };
8712
+ }
8713
+ const documentId = superdoc?.activeEditor?.documentId ?? superdoc?.activeEditor?.options?.documentId ?? commentsAdapter.documentId ?? trackedChangesAdapter.documentId ?? null;
8714
+ const document = documentId != null ? superdocStore.getDocument(String(documentId)) : null;
8715
+ const mappedCommentInputs = commentItems.filter((item) => !isSyntheticTrackedChangeCommentLaneItem(item)).map((item) => commentsAdapter.mapV2CommentToUseCommentInput(item, {
8716
+ fileId: documentId,
8717
+ fileType: document?.type ?? null
8718
+ })).filter(Boolean);
8719
+ const trackedParentBySidecarId = /* @__PURE__ */ new Map();
8720
+ for (const input of mappedCommentInputs) if (input.trackedChange !== true && input.trackedChangeThreadParentId && !input.parentCommentId && isBlankDirectoryCommentText(input.commentText)) trackedParentBySidecarId.set(String(input.commentId), String(input.trackedChangeThreadParentId));
8721
+ const commentRows = mappedCommentInputs.filter((input) => !trackedParentBySidecarId.has(String(input.commentId))).map((input) => {
8722
+ const trackedParent = input.parentCommentId ? trackedParentBySidecarId.get(String(input.parentCommentId)) : null;
8723
+ if (trackedParent) {
8724
+ input.threadingParentCommentId = trackedParent;
8725
+ input.trackedChangeThreadParentId ??= trackedParent;
8726
+ }
8727
+ return useComment(input);
8728
+ });
8729
+ const trackedRows = trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).map((params) => trackedChangeDirectoryComment(params)).filter(Boolean);
8730
+ reviewDirectoryList.value = [...commentRows, ...trackedRows];
8731
+ return {
8732
+ ok: true,
8733
+ count: reviewDirectoryList.value.length
8734
+ };
8735
+ };
8736
+ const clearReviewDirectory = () => {
8737
+ reviewDirectoryList.value = [];
8738
+ };
8701
8739
  const commentInputId = (input) => {
8702
8740
  const id = input?.commentId ?? input?.importedId;
8703
8741
  return id != null ? String(id) : null;
@@ -8999,12 +9037,54 @@ var useCommentsStore = defineStore("comments", () => {
8999
9037
  if (!adapter) return false;
9000
9038
  return v2TrackedChangesAdapter.value === adapter;
9001
9039
  };
9040
+ const reviewPresentationIdentityIds = (row) => new Set([
9041
+ ...getCommentAliasIds(row),
9042
+ row?.commentId,
9043
+ row?.importedId,
9044
+ row?.trackedChangeCanonicalId,
9045
+ row?.trackedChangeAnchorKey,
9046
+ row?.parentCommentId,
9047
+ row?.threadingParentCommentId,
9048
+ row?.trackedChangeParentId,
9049
+ row?.trackedChangeThreadParentId
9050
+ ].map((value) => normalizeCommentId(value)).filter(Boolean));
9051
+ /** Keep one mounted review window plus rows whose interaction is still live. */
9052
+ const boundReviewPresentationRows = ({ documentId, visibleIds }) => {
9053
+ const retainedIds = new Set([
9054
+ ...visibleIds,
9055
+ activeComment.value,
9056
+ activeFloatingCommentInstanceId.value,
9057
+ editingCommentId.value,
9058
+ pendingComment.value?.commentId,
9059
+ instantSidebarAlignmentThreadId.value,
9060
+ instantSidebarAlignmentInstanceId.value
9061
+ ].map((value) => normalizeCommentId(value)).filter(Boolean));
9062
+ let changed = true;
9063
+ while (changed) {
9064
+ changed = false;
9065
+ for (const row of commentsList.value) {
9066
+ if (!belongsToTrackedChangeSyncDocument(row, documentId)) continue;
9067
+ const ids = reviewPresentationIdentityIds(row);
9068
+ if (![...ids].some((id) => retainedIds.has(id))) continue;
9069
+ for (const id of ids) {
9070
+ if (retainedIds.has(id)) continue;
9071
+ retainedIds.add(id);
9072
+ changed = true;
9073
+ }
9074
+ }
9075
+ }
9076
+ commentsList.value = commentsList.value.filter((row) => {
9077
+ if (!belongsToTrackedChangeSyncDocument(row, documentId)) return true;
9078
+ if (row === pendingComment.value) return true;
9079
+ return [...reviewPresentationIdentityIds(row)].some((id) => retainedIds.has(id));
9080
+ });
9081
+ };
9002
9082
  /**
9003
- * Synchronously reconcile one host-validated review snapshot. The caller
9083
+ * Synchronously reconcile one host-validated committed review window. The caller
9004
9084
  * wraps this action in one Pinia `$patch`; all fallible mapping completes
9005
9085
  * before either row family mutates.
9006
9086
  */
9007
- const applyReviewSnapshotFromV2 = ({ superdoc, commentsAdapter, trackedChangesAdapter, documentId, commentItems, trackedChangeItems, trackedList, sourceCoverageRevision, evaluatedRevision, patch } = {}) => withInteractionSpan("store.reviewSnapshot.apply", "store-reconciliation", {
9087
+ const applyReviewWindowFromV2 = ({ superdoc, commentsAdapter, trackedChangesAdapter, documentId, commentItems, trackedChangeItems, requestedCommentIds, requestedTrackedChangeIds, unresolvedCommentIds, trackedList, sourceCoverageRevision, evaluatedRevision, patch } = {}) => withInteractionSpan("store.reviewWindow.apply", "store-reconciliation", {
9008
9088
  commentItemCount: Array.isArray(commentItems) ? commentItems.length : null,
9009
9089
  trackedItemCount: Array.isArray(trackedChangeItems) ? trackedChangeItems.length : null
9010
9090
  }, () => {
@@ -9034,7 +9114,7 @@ var useCommentsStore = defineStore("comments", () => {
9034
9114
  ok: false,
9035
9115
  reason: "items-invalid"
9036
9116
  };
9037
- if (trackedList?.complete !== false || !["review-snapshot", "visible-window"].includes(trackedList?.visibleWindowSource)) return {
9117
+ if (trackedList?.complete !== false || trackedList?.visibleWindowSource !== "visible-window") return {
9038
9118
  ok: false,
9039
9119
  reason: "tracked-list-not-partial-visible-window"
9040
9120
  };
@@ -9043,7 +9123,6 @@ var useCommentsStore = defineStore("comments", () => {
9043
9123
  reason: "adapter-mapper-missing"
9044
9124
  };
9045
9125
  let effectiveCommentItems = commentItems;
9046
- let commentItemsArePartial = false;
9047
9126
  if (typeof commentsAdapter.seedReviewCatalog === "function") {
9048
9127
  const seeded = commentsAdapter.seedReviewCatalog(commentItems, {
9049
9128
  sourceCoverageRevision,
@@ -9054,10 +9133,7 @@ var useCommentsStore = defineStore("comments", () => {
9054
9133
  reason: seeded?.reason ?? "comment-catalog-seed-failed"
9055
9134
  };
9056
9135
  const selected = commentsAdapter.selectVisibleReviewComments?.();
9057
- if (selected?.ok === true && Array.isArray(selected.items) && (!Array.isArray(selected.unresolvedIds) || selected.unresolvedIds.length === 0)) {
9058
- effectiveCommentItems = selected.items;
9059
- commentItemsArePartial = true;
9060
- }
9136
+ if (selected?.ok === true && Array.isArray(selected.items) && (!Array.isArray(selected.unresolvedIds) || selected.unresolvedIds.length === 0)) effectiveCommentItems = selected.items;
9061
9137
  }
9062
9138
  const fileType = (normalizedDocumentId ? superdocStore.getDocument(normalizedDocumentId) : null)?.type ?? null;
9063
9139
  let preparedInputs;
@@ -9079,6 +9155,28 @@ var useCommentsStore = defineStore("comments", () => {
9079
9155
  ok: false,
9080
9156
  reason: "patch-missing"
9081
9157
  };
9158
+ const visibleIds = new Set([
9159
+ ...Array.isArray(requestedCommentIds) ? requestedCommentIds : [],
9160
+ ...Array.isArray(requestedTrackedChangeIds) ? requestedTrackedChangeIds : [],
9161
+ ...Array.isArray(unresolvedCommentIds) ? unresolvedCommentIds : [],
9162
+ ...effectiveCommentItems.flatMap((item) => [
9163
+ item?.id,
9164
+ item?.commentId,
9165
+ item?.importedId
9166
+ ]),
9167
+ ...trackedChangeItems.flatMap((item) => [item?.id, item?.changeId]),
9168
+ ...preparedInputs.flatMap((input) => [
9169
+ input?.commentId,
9170
+ input?.importedId,
9171
+ input?.parentCommentId,
9172
+ input?.trackedChangeThreadParentId
9173
+ ]),
9174
+ ...preparedParams.flatMap((params) => [
9175
+ params?.changeId,
9176
+ params?.trackedChangeCanonicalId,
9177
+ params?.trackedChangeAnchorKey
9178
+ ])
9179
+ ].map((value) => normalizeCommentId(value)).filter(Boolean));
9082
9180
  patch(() => {
9083
9181
  reconcileTrackedChangesFromV2({
9084
9182
  superdoc,
@@ -9094,7 +9192,11 @@ var useCommentsStore = defineStore("comments", () => {
9094
9192
  documentId: normalizedDocumentId,
9095
9193
  items: effectiveCommentItems,
9096
9194
  preparedInputs,
9097
- pruneStale: !commentItemsArePartial
9195
+ pruneStale: false
9196
+ });
9197
+ boundReviewPresentationRows({
9198
+ documentId: normalizedDocumentId,
9199
+ visibleIds
9098
9200
  });
9099
9201
  });
9100
9202
  return {
@@ -9103,70 +9205,6 @@ var useCommentsStore = defineStore("comments", () => {
9103
9205
  trackedItems: preparedParams.length
9104
9206
  };
9105
9207
  });
9106
- const hydrateCommentsFromV2 = async ({ superdoc, adapter, documentId, trackedChangesListMode = "all", commentIds, isCurrent, signal, hydrationGeneration } = {}) => withInteractionSpan("store.comments.hydrateFromV2", "comments-list", { documentId: documentId ?? null }, async () => {
9107
- const effectiveAdapter = adapter ?? getV2CommentsAdapter(superdoc);
9108
- const visibleWindow = trackedChangesListMode === "visible-window";
9109
- const read = visibleWindow ? effectiveAdapter?.selectVisibleReviewComments : effectiveAdapter?.refresh;
9110
- if (!effectiveAdapter || typeof read !== "function") return {
9111
- ok: false,
9112
- reason: "adapter-missing"
9113
- };
9114
- const hydrationIsCurrent = () => isCurrent?.() !== false && isCurrentV2CommentsAdapter(effectiveAdapter);
9115
- if (!hydrationIsCurrent()) return {
9116
- ok: false,
9117
- reason: "adapter-stale"
9118
- };
9119
- if (signal?.aborted) return {
9120
- ok: false,
9121
- reason: "review-hydration-superseded",
9122
- lateResultDropped: false
9123
- };
9124
- const syncGeneration = commentSyncGeneration(effectiveAdapter);
9125
- let result;
9126
- try {
9127
- const refreshOptions = {};
9128
- if (visibleWindow && Array.isArray(commentIds)) refreshOptions.targetIds = commentIds;
9129
- if (signal) refreshOptions.signal = signal;
9130
- if (typeof hydrationGeneration === "number") refreshOptions.hydrationGeneration = hydrationGeneration;
9131
- result = Object.keys(refreshOptions).length > 0 ? await read.call(effectiveAdapter, refreshOptions) : await read.call(effectiveAdapter);
9132
- } catch (err) {
9133
- return {
9134
- ok: false,
9135
- reason: "adapter-threw",
9136
- detail: err?.message ?? String(err)
9137
- };
9138
- }
9139
- if (!hydrationIsCurrent()) return {
9140
- ok: false,
9141
- reason: "adapter-stale"
9142
- };
9143
- if (signal?.aborted || commentSyncGeneration(effectiveAdapter) !== syncGeneration) return {
9144
- ok: false,
9145
- reason: "review-hydration-superseded",
9146
- lateResultDropped: true
9147
- };
9148
- if (!result?.ok) return result;
9149
- if (signal?.aborted || commentSyncGeneration(effectiveAdapter) !== syncGeneration) return {
9150
- ok: false,
9151
- reason: "review-hydration-superseded",
9152
- lateResultDropped: true
9153
- };
9154
- reconcileCommentsFromV2({
9155
- superdoc,
9156
- adapter: effectiveAdapter,
9157
- documentId,
9158
- items: result.items ?? [],
9159
- pruneStale: !visibleWindow || Array.isArray(commentIds) && (!Array.isArray(result.unresolvedIds) || result.unresolvedIds.length === 0),
9160
- hydrationGeneration
9161
- });
9162
- return {
9163
- ok: true,
9164
- items: result.items ?? [],
9165
- reconciled: true,
9166
- ...typeof hydrationGeneration === "number" ? { hydrationGeneration } : {},
9167
- ...result.visibleWindowSource != null ? { visibleWindowSource: result.visibleWindowSource } : {}
9168
- };
9169
- });
9170
9208
  const reconcileCommentsFromV2 = ({ superdoc, adapter, documentId, items, preparedInputs = null, pruneStale = true, hydrationGeneration } = {}) => withInteractionSpan("store.comments.reconcile", "store-reconciliation", {
9171
9209
  documentId: documentId ?? null,
9172
9210
  itemCount: Array.isArray(items) ? items.length : null,
@@ -9389,27 +9427,6 @@ var useCommentsStore = defineStore("comments", () => {
9389
9427
  storySnapshots
9390
9428
  };
9391
9429
  };
9392
- const reconcileAuthoritativeV2TrackedChangeSidebar = ({ superdoc, editor, documentId, refreshReason = "tracked-change-sidebar-authoritative" } = {}) => {
9393
- const adapter = getV2TrackedChangesAdapter(superdoc) ?? editor?.v2TrackedChanges ?? null;
9394
- if (!adapter || typeof adapter.listTrackedChanges !== "function" || typeof adapter.mapV2TrackedChangeToCommentParams !== "function") return false;
9395
- const effectiveDocumentId = documentId ?? adapter.documentId ?? editor?.documentId ?? editor?.options?.documentId ?? superdoc?.activeEditor?.documentId ?? superdoc?.activeEditor?.options?.documentId ?? null;
9396
- if (effectiveDocumentId == null) return false;
9397
- const normalizedDocumentId = String(effectiveDocumentId);
9398
- hydrateTrackedChangesFromV2({
9399
- superdoc,
9400
- adapter,
9401
- documentId: normalizedDocumentId,
9402
- trackedChangesListMode: "background-reconcile",
9403
- refreshReason,
9404
- blocking: false,
9405
- isCurrent: () => {
9406
- const activeEditor = superdoc?.activeEditor ?? null;
9407
- const activeDocumentId = activeEditor?.documentId ?? activeEditor?.options?.documentId ?? null;
9408
- return activeDocumentId == null || String(activeDocumentId) === normalizedDocumentId;
9409
- }
9410
- });
9411
- return true;
9412
- };
9413
9430
  /**
9414
9431
  * Bootstrap tracked-change comment threads after a DOCX import finishes.
9415
9432
  *
@@ -9447,12 +9464,6 @@ var useCommentsStore = defineStore("comments", () => {
9447
9464
  editor,
9448
9465
  structuralChanges: captured.structuralChanges
9449
9466
  });
9450
- reconcileAuthoritativeV2TrackedChangeSidebar({
9451
- superdoc,
9452
- editor,
9453
- documentId: editor?.options?.documentId,
9454
- refreshReason: "import-bootstrap-authoritative"
9455
- });
9456
9467
  return true;
9457
9468
  };
9458
9469
  const isCurrentImportedTrackedChangeBootstrap = (task) => {
@@ -10121,7 +10132,6 @@ var useCommentsStore = defineStore("comments", () => {
10121
10132
  removed: 0,
10122
10133
  removedIds: /* @__PURE__ */ new Set()
10123
10134
  };
10124
- supersedeTrackedChangeSyncGeneration(adapter);
10125
10135
  const removedIds = /* @__PURE__ */ new Set();
10126
10136
  const activeId = normalizeCommentId(activeComment.value);
10127
10137
  let activeWasRemoved = false;
@@ -10214,94 +10224,15 @@ var useCommentsStore = defineStore("comments", () => {
10214
10224
  reason: outcome?.reason,
10215
10225
  detail: outcome?.detail
10216
10226
  };
10217
- const reliableDecidedIds = collectReliableDecidedTrackedChangeIds(outcome);
10227
+ const receiptDecidedIds = collectReliableDecidedTrackedChangeIds(outcome);
10228
+ const localDecidedIds = receiptDecidedIds.size ? receiptDecidedIds : collectTrackedChangeDecisionIds(comment);
10218
10229
  const effectiveDocumentId = outcome?.documentId ?? v2Adapter.documentId ?? superdoc?.activeEditor?.documentId ?? superdoc?.activeEditor?.options?.documentId ?? null;
10219
- const runBlockingPostDecisionReconcile = async () => {
10220
- let listResult;
10221
- try {
10222
- listResult = await v2Adapter.listTrackedChanges({
10223
- mode: "all",
10224
- refreshReason: "post-decision-blocking",
10225
- blocking: true
10226
- });
10227
- } catch (err) {
10228
- return {
10229
- ok: false,
10230
- committed: true,
10231
- reason: "relist-after-commit-failed",
10232
- detail: err?.message ?? String(err)
10233
- };
10234
- }
10235
- if (!isCurrentV2TrackedChangesAdapter(v2Adapter)) return {
10236
- ok: false,
10237
- committed: true,
10238
- reason: "adapter-stale"
10239
- };
10240
- if (!listResult?.ok) return {
10241
- ok: false,
10242
- committed: true,
10243
- reason: "relist-after-commit-failed",
10244
- detail: listResult?.reason ?? listResult?.detail ?? null
10245
- };
10246
- resolveLinkedCommentsForTrackedChangeDecision({
10247
- superdoc,
10248
- comment,
10249
- decision
10250
- });
10251
- detachLinkedCommentsForTrackedChangeDecision({
10252
- comment,
10253
- decision
10254
- });
10255
- reconcileTrackedChangesFromV2({
10256
- superdoc,
10257
- adapter: v2Adapter,
10258
- documentId: effectiveDocumentId,
10259
- items: listResult.items ?? [],
10260
- pruneStale: listResult.complete === true && listResult.sourceCoverageComplete === true
10261
- });
10262
- const liveIds = new Set((listResult.items ?? []).map((item) => item && item.id != null ? String(item.id) : null).filter(Boolean));
10263
- if (!(decidedId != null && liveIds.has(decidedId))) {
10264
- if (wasActiveBeforeDecide) clearActiveCommentSelection();
10265
- clearV2ActiveTrackedChangeTarget(v2Adapter, decidedId);
10266
- }
10267
- const commentsAdapter = getV2CommentsAdapter(superdoc);
10268
- if (commentsAdapter && typeof commentsAdapter.refresh === "function" && isCurrentV2CommentsAdapter(commentsAdapter)) {
10269
- let commentsResult;
10270
- try {
10271
- commentsResult = await commentsAdapter.refresh();
10272
- } catch (err) {
10273
- return {
10274
- ok: false,
10275
- committed: true,
10276
- reason: "comments-relist-after-tracked-decision-failed",
10277
- detail: err?.message ?? String(err)
10278
- };
10279
- }
10280
- if (!isCurrentV2CommentsAdapter(commentsAdapter)) return {
10281
- ok: false,
10282
- committed: true,
10283
- reason: "comments-adapter-stale"
10284
- };
10285
- if (!commentsResult?.ok) return {
10286
- ok: false,
10287
- committed: true,
10288
- reason: "comments-relist-after-tracked-decision-failed",
10289
- detail: commentsResult?.reason ?? commentsResult?.detail ?? null
10290
- };
10291
- reconcileCommentsFromV2({
10292
- superdoc,
10293
- adapter: commentsAdapter,
10294
- documentId: commentsAdapter.documentId ?? effectiveDocumentId,
10295
- items: commentsResult.items ?? []
10296
- });
10297
- }
10298
- return {
10299
- ok: true,
10300
- success: true
10301
- };
10230
+ if (!localDecidedIds.size || !effectiveDocumentId) return {
10231
+ ok: false,
10232
+ committed: true,
10233
+ reason: "decision-scope-missing"
10302
10234
  };
10303
- if (!reliableDecidedIds.size || !effectiveDocumentId) return runBlockingPostDecisionReconcile();
10304
- reliableDecidedIds.forEach((id) => clearV2ActiveTrackedChangeTarget(v2Adapter, id));
10235
+ localDecidedIds.forEach((id) => clearV2ActiveTrackedChangeTarget(v2Adapter, id));
10305
10236
  await waitForTrackedDecisionCanonicalPaint({
10306
10237
  superdoc,
10307
10238
  receipt: outcome.receipt
@@ -10318,7 +10249,7 @@ var useCommentsStore = defineStore("comments", () => {
10318
10249
  pruneDecidedTrackedChangeRow({
10319
10250
  superdoc,
10320
10251
  comment,
10321
- decidedIds: reliableDecidedIds,
10252
+ decidedIds: localDecidedIds,
10322
10253
  documentId: effectiveDocumentId
10323
10254
  });
10324
10255
  if (wasActiveBeforeDecide) clearActiveCommentSelection();
@@ -10361,166 +10292,11 @@ var useCommentsStore = defineStore("comments", () => {
10361
10292
  success: Boolean(command(id))
10362
10293
  };
10363
10294
  };
10364
- const hydrateTrackedChangesFromV2 = async ({ superdoc, adapter, documentId, trackedChangesListMode = "all", refreshReason, blocking, isCurrent, targetIds, reconciliationBatchSize = TRACKED_CHANGE_BACKGROUND_RECONCILE_BATCH_SIZE, signal, hydrationGeneration } = {}) => withInteractionSpan("store.trackedChanges.hydrateFromV2", "tracked-change-list", {
10365
- documentId: documentId ?? null,
10366
- trackedChangesListMode,
10367
- refreshReason: refreshReason ?? null,
10368
- blocking: blocking ?? null
10369
- }, async () => {
10370
- const effectiveAdapter = adapter ?? getV2TrackedChangesAdapter(superdoc);
10371
- if (!effectiveAdapter || typeof effectiveAdapter.listTrackedChanges !== "function") return {
10372
- ok: false,
10373
- reason: "adapter-missing"
10374
- };
10375
- const hydrationIsCurrent = () => isCurrent?.() !== false && isCurrentV2TrackedChangesAdapter(effectiveAdapter);
10376
- if (!hydrationIsCurrent()) return {
10377
- ok: false,
10378
- reason: "adapter-stale"
10379
- };
10380
- const syncGeneration = trackedChangeSyncGeneration(effectiveAdapter);
10381
- const hydrationCanContinue = () => hydrationIsCurrent() && !signal?.aborted && trackedChangeSyncGeneration(effectiveAdapter) === syncGeneration;
10382
- if (signal?.aborted) return {
10383
- ok: false,
10384
- reason: "review-hydration-superseded",
10385
- lateResultDropped: false
10386
- };
10387
- const incrementalLiveIds = /* @__PURE__ */ new Set();
10388
- const incrementalLiveAnchorKeys = /* @__PURE__ */ new Set();
10389
- let incrementalPagesApplied = 0;
10390
- let incrementalItemsApplied = 0;
10391
- let result;
10392
- try {
10393
- const listOptions = { mode: trackedChangesListMode };
10394
- if (Array.isArray(targetIds)) listOptions.targetIds = targetIds;
10395
- if (refreshReason != null) listOptions.refreshReason = refreshReason;
10396
- if (typeof blocking === "boolean") listOptions.blocking = blocking;
10397
- if (signal) listOptions.signal = signal;
10398
- if (typeof hydrationGeneration === "number") listOptions.hydrationGeneration = hydrationGeneration;
10399
- if (trackedChangesListMode === "background-reconcile") {
10400
- listOptions.shouldContinue = hydrationCanContinue;
10401
- listOptions.onPage = async (page) => {
10402
- if (!hydrationCanContinue()) return;
10403
- const pageItems = Array.isArray(page?.items) ? page.items : [];
10404
- if (!reconcileTrackedChangesFromV2({
10405
- superdoc,
10406
- adapter: effectiveAdapter,
10407
- documentId,
10408
- items: pageItems,
10409
- pruneStale: false,
10410
- liveIds: incrementalLiveIds,
10411
- liveAnchorKeys: incrementalLiveAnchorKeys,
10412
- hydrationGeneration
10413
- }) || !hydrationCanContinue()) return;
10414
- incrementalPagesApplied += 1;
10415
- incrementalItemsApplied += pageItems.length;
10416
- };
10417
- }
10418
- result = await effectiveAdapter.listTrackedChanges(listOptions);
10419
- } catch (err) {
10420
- return {
10421
- ok: false,
10422
- reason: "adapter-threw",
10423
- detail: err?.message ?? String(err)
10424
- };
10425
- }
10426
- if (!hydrationIsCurrent()) return {
10427
- ok: false,
10428
- reason: "adapter-stale"
10429
- };
10430
- if (trackedChangeSyncGeneration(effectiveAdapter) !== syncGeneration) return {
10431
- ok: false,
10432
- reason: "review-hydration-superseded",
10433
- lateResultDropped: true
10434
- };
10435
- if (signal?.aborted) return {
10436
- ok: false,
10437
- reason: "review-hydration-superseded",
10438
- lateResultDropped: true
10439
- };
10440
- if (!result?.ok) return result;
10441
- const partialListMode = trackedChangesListMode === "startup-page" || trackedChangesListMode === "interaction-prime" || trackedChangesListMode === "visible-window";
10442
- const pruneStale = trackedChangesListMode === "visible-window" && Array.isArray(targetIds) && (!Array.isArray(result.visibleTrackedChangeFailedIds) || result.visibleTrackedChangeFailedIds.length === 0) || !partialListMode && result.visibleWindowSource == null && result.complete === true && result.sourceCoverageComplete === true;
10443
- if (signal?.aborted || trackedChangeSyncGeneration(effectiveAdapter) !== syncGeneration) return {
10444
- ok: false,
10445
- reason: "review-hydration-superseded",
10446
- lateResultDropped: true
10447
- };
10448
- if (trackedChangesListMode === "background-reconcile") if (incrementalPagesApplied > 0) {
10449
- if (!hydrationCanContinue()) return {
10450
- ok: false,
10451
- reason: "reconciliation-stale"
10452
- };
10453
- const effectiveDocumentId = documentId ?? effectiveAdapter.documentId ?? superdoc?.activeEditor?.documentId ?? null;
10454
- if (pruneStale && effectiveDocumentId) withInteractionSpan("store.trackedChanges.reconcile.finalPrune", "store-reconciliation", {
10455
- documentId: effectiveDocumentId,
10456
- liveIds: incrementalLiveIds.size,
10457
- liveAnchorKeys: incrementalLiveAnchorKeys.size
10458
- }, () => {
10459
- if (!hydrationCanContinue()) return;
10460
- pruneStaleTrackedChangeComments(incrementalLiveIds, incrementalLiveAnchorKeys, String(effectiveDocumentId), superdoc, { broadcastChanges: false });
10461
- });
10462
- } else {
10463
- const reconcileResult = await reconcileTrackedChangesFromV2Incrementally({
10464
- superdoc,
10465
- adapter: effectiveAdapter,
10466
- documentId,
10467
- items: result.items ?? [],
10468
- pruneStale,
10469
- isCurrent: hydrationIsCurrent,
10470
- batchSize: reconciliationBatchSize,
10471
- hydrationGeneration
10472
- });
10473
- if (!reconcileResult.ok) return reconcileResult;
10474
- incrementalItemsApplied = reconcileResult.appliedItems;
10475
- }
10476
- else reconcileTrackedChangesFromV2({
10477
- superdoc,
10478
- adapter: effectiveAdapter,
10479
- documentId,
10480
- items: result.items ?? [],
10481
- pruneStale,
10482
- hydrationGeneration
10483
- });
10484
- const hydrationResult = {
10485
- ok: true,
10486
- items: result.items ?? [],
10487
- reconciled: true,
10488
- ...typeof hydrationGeneration === "number" ? { hydrationGeneration } : {}
10489
- };
10490
- if (typeof result.complete === "boolean") hydrationResult.complete = result.complete;
10491
- if (typeof result.sourceCoverageComplete === "boolean") hydrationResult.sourceCoverageComplete = result.sourceCoverageComplete;
10492
- if (result.visibleWindowSource != null) hydrationResult.visibleWindowSource = result.visibleWindowSource;
10493
- if (result.visibleWindowFallbackReason != null) hydrationResult.visibleWindowFallbackReason = result.visibleWindowFallbackReason;
10494
- if (typeof result.visibleTrackedChangeExpectedRows === "number") hydrationResult.visibleTrackedChangeExpectedRows = result.visibleTrackedChangeExpectedRows;
10495
- if (typeof result.visibleTrackedChangeObservedRows === "number") hydrationResult.visibleTrackedChangeObservedRows = result.visibleTrackedChangeObservedRows;
10496
- for (const key of [
10497
- "visibleTrackedChangeRequestedIds",
10498
- "visibleTrackedChangeSucceededIds",
10499
- "visibleTrackedChangeFailedIds"
10500
- ]) if (Array.isArray(result[key])) hydrationResult[key] = [...result[key]];
10501
- if (Array.isArray(result.visibleWindowGetFailures)) hydrationResult.visibleWindowGetFailures = result.visibleWindowGetFailures.map((failure) => ({ ...failure }));
10502
- if (typeof result.refreshReason === "string") hydrationResult.refreshReason = result.refreshReason;
10503
- if (typeof result.blocking === "boolean") hydrationResult.blocking = result.blocking;
10504
- if (typeof result.pageCount === "number") hydrationResult.pageCount = result.pageCount;
10505
- if (typeof result.pagesStarted === "number") hydrationResult.pagesStarted = result.pagesStarted;
10506
- if (typeof result.pagesCompleted === "number") hydrationResult.pagesCompleted = result.pagesCompleted;
10507
- if (typeof result.pagesSkippedAfterAbort === "number") hydrationResult.pagesSkippedAfterAbort = result.pagesSkippedAfterAbort;
10508
- if (typeof result.totalItems === "number") hydrationResult.totalItems = result.totalItems;
10509
- if (typeof result.coalesced === "boolean") hydrationResult.coalesced = result.coalesced;
10510
- if (typeof result.evaluatedRevision === "string") hydrationResult.evaluatedRevision = result.evaluatedRevision;
10511
- if (trackedChangesListMode === "background-reconcile") {
10512
- hydrationResult.incrementalPagesApplied = incrementalPagesApplied;
10513
- hydrationResult.incrementalItemsApplied = incrementalItemsApplied;
10514
- }
10515
- return hydrationResult;
10516
- });
10517
10295
  /**
10518
- * Reconcile receipt-addressed tracked-change identities without enumerating
10519
- * the full catalog. Removals converge synchronously; surviving/new identities
10520
- * use the adapter's narrow get and then flow through the ordinary row mapper.
10521
- * A later idle hydration remains the bounded completeness check.
10296
+ * Apply only receipt-owned removals. Remaps are updated in place by the
10297
+ * caller; surviving/new rows arrive with the next committed review window.
10522
10298
  */
10523
- const reconcileTrackedChangeMutationFromV2 = async ({ superdoc, adapter, documentId, upsertIds = [], removedIds = [], allResolved } = {}) => {
10299
+ const reconcileTrackedChangeMutationFromV2 = async ({ superdoc, adapter, documentId, upsertIds = [], removedIds = [], remappedPairs = [], allResolved } = {}) => {
10524
10300
  const effectiveAdapter = adapter ?? getV2TrackedChangesAdapter(superdoc);
10525
10301
  if (!effectiveAdapter) return {
10526
10302
  ok: false,
@@ -10550,73 +10326,21 @@ var useCommentsStore = defineStore("comments", () => {
10550
10326
  documentId: effectiveDocumentId
10551
10327
  })).removed
10552
10328
  };
10553
- const syncGeneration = trackedChangeSyncGeneration(effectiveAdapter);
10554
10329
  const normalizedRemovedIds = new Set(Array.from(removedIds ?? []).map((id) => normalizeCommentId(id)).filter(Boolean));
10555
10330
  const normalizedUpsertIds = [...new Set(Array.from(upsertIds ?? []).map((id) => normalizeCommentId(id)).filter(Boolean))];
10556
- if (normalizedRemovedIds.size > 0 && normalizedUpsertIds.length === 0 && effectiveDocumentId != null) pruneDecidedTrackedChangeRow({
10331
+ const remappedSourceIds = new Set((Array.isArray(remappedPairs) ? remappedPairs : []).map((pair) => normalizeCommentId(pair?.from)).filter(Boolean));
10332
+ const locallyRemovedIds = new Set([...normalizedRemovedIds].filter((id) => !remappedSourceIds.has(id)));
10333
+ if (locallyRemovedIds.size > 0 && effectiveDocumentId != null) pruneDecidedTrackedChangeRow({
10557
10334
  superdoc,
10558
- decidedIds: normalizedRemovedIds,
10335
+ decidedIds: locallyRemovedIds,
10559
10336
  documentId: effectiveDocumentId
10560
10337
  });
10561
- if (normalizedUpsertIds.length === 0) return {
10338
+ return {
10562
10339
  ok: true,
10563
10340
  items: [],
10564
10341
  resolvedIds: [],
10565
- unresolvedIds: []
10566
- };
10567
- if (typeof effectiveAdapter.getTrackedChange !== "function") return {
10568
- ok: false,
10569
- reason: "adapter-get-missing"
10570
- };
10571
- const reads = await Promise.all(normalizedUpsertIds.map(async (id) => {
10572
- try {
10573
- return {
10574
- id,
10575
- result: await effectiveAdapter.getTrackedChange(id)
10576
- };
10577
- } catch (err) {
10578
- return {
10579
- id,
10580
- result: {
10581
- ok: false,
10582
- reason: "adapter-threw",
10583
- detail: err?.message ?? String(err)
10584
- }
10585
- };
10586
- }
10587
- }));
10588
- if (!isCurrentV2TrackedChangesAdapter(effectiveAdapter)) return {
10589
- ok: false,
10590
- reason: "adapter-stale"
10591
- };
10592
- if (trackedChangeSyncGeneration(effectiveAdapter) !== syncGeneration) return {
10593
- ok: false,
10594
- reason: "generation-stale"
10595
- };
10596
- const resolvedIds = [];
10597
- const unresolvedIds = [];
10598
- const items = [];
10599
- for (const { id, result } of reads) {
10600
- const matchingItems = (result?.ok && Array.isArray(result.items) ? result.items : []).filter((item) => trackedChangeItemIdentityIds(item).has(id));
10601
- if (matchingItems.length > 0) {
10602
- resolvedIds.push(id);
10603
- items.push(...matchingItems);
10604
- } else unresolvedIds.push(id);
10605
- }
10606
- const dedupedItems = dedupeTrackedChangeItems(items);
10607
- if (items.length > 0) reconcileTrackedChangesFromV2({
10608
- superdoc,
10609
- adapter: effectiveAdapter,
10610
- documentId: effectiveDocumentId,
10611
- items: dedupedItems,
10612
- pruneStale: false
10613
- });
10614
- return {
10615
- ok: unresolvedIds.length === 0,
10616
- items: dedupedItems,
10617
- resolvedIds,
10618
- unresolvedIds,
10619
- failures: reads.map(({ result }) => result).filter((result) => !result?.ok)
10342
+ unresolvedIds: normalizedUpsertIds,
10343
+ removedIds: [...locallyRemovedIds]
10620
10344
  };
10621
10345
  };
10622
10346
  const createTrackedChangeBatchIdentityIndex = (documentId) => {
@@ -10720,84 +10444,6 @@ var useCommentsStore = defineStore("comments", () => {
10720
10444
  appliedCount
10721
10445
  };
10722
10446
  });
10723
- /**
10724
- * Cooperatively apply an authoritative background catalog. Each batch is a
10725
- * non-pruning merge, so interruption can only leave extra/stale rows in the
10726
- * sidebar; destructive pruning happens once, after every batch has applied
10727
- * and the caller's generation/revision guard is still current.
10728
- */
10729
- const reconcileTrackedChangesFromV2Incrementally = async ({ superdoc, adapter, documentId, items, pruneStale, isCurrent, batchSize = TRACKED_CHANGE_BACKGROUND_RECONCILE_BATCH_SIZE, hydrationGeneration } = {}) => {
10730
- if (!adapter || !Array.isArray(items)) return {
10731
- ok: false,
10732
- reason: "reconcile-input-invalid"
10733
- };
10734
- const current = () => isCurrent?.() !== false && isCurrentV2TrackedChangesAdapter(adapter);
10735
- if (!current()) return {
10736
- ok: false,
10737
- reason: "reconciliation-stale"
10738
- };
10739
- const effectiveBatchSize = Math.max(1, Math.floor(Number(batchSize) || TRACKED_CHANGE_BACKGROUND_RECONCILE_BATCH_SIZE));
10740
- const effectiveDocumentId = documentId ?? adapter.documentId ?? superdoc?.activeEditor?.documentId ?? null;
10741
- const liveIds = /* @__PURE__ */ new Set();
10742
- const liveAnchorKeys = /* @__PURE__ */ new Set();
10743
- let appliedItems = 0;
10744
- let batchCount = 0;
10745
- for (let offset = 0; offset < items.length; offset += effectiveBatchSize) {
10746
- if (!current()) return {
10747
- ok: false,
10748
- reason: "reconciliation-stale",
10749
- appliedItems,
10750
- batchCount
10751
- };
10752
- const batch = items.slice(offset, offset + effectiveBatchSize);
10753
- if (!reconcileTrackedChangesFromV2({
10754
- superdoc,
10755
- adapter,
10756
- documentId: effectiveDocumentId,
10757
- items: batch,
10758
- pruneStale: false,
10759
- liveIds,
10760
- liveAnchorKeys,
10761
- hydrationGeneration
10762
- }) || !current()) return {
10763
- ok: false,
10764
- reason: "reconciliation-stale",
10765
- appliedItems,
10766
- batchCount
10767
- };
10768
- appliedItems += batch.length;
10769
- batchCount += 1;
10770
- if (offset + effectiveBatchSize < items.length) await yieldForTrackedChangeBackgroundReconcile();
10771
- }
10772
- if (!current()) return {
10773
- ok: false,
10774
- reason: "reconciliation-stale",
10775
- appliedItems,
10776
- batchCount
10777
- };
10778
- if (pruneStale && effectiveDocumentId) {
10779
- withInteractionSpan("store.trackedChanges.reconcile.finalPrune", "store-reconciliation", {
10780
- documentId: effectiveDocumentId,
10781
- liveIds: liveIds.size,
10782
- liveAnchorKeys: liveAnchorKeys.size
10783
- }, () => {
10784
- if (!current()) return;
10785
- pruneStaleTrackedChangeComments(liveIds, liveAnchorKeys, String(effectiveDocumentId), superdoc, { broadcastChanges: false });
10786
- });
10787
- if (!current()) return {
10788
- ok: false,
10789
- reason: "reconciliation-stale",
10790
- appliedItems,
10791
- batchCount
10792
- };
10793
- }
10794
- return {
10795
- ok: true,
10796
- appliedItems,
10797
- batchCount,
10798
- pruned: Boolean(pruneStale && effectiveDocumentId)
10799
- };
10800
- };
10801
10447
  const getV2TrackedChangeRowCount = (documentId = null) => {
10802
10448
  const normalizedDocumentId = documentId == null ? null : String(documentId);
10803
10449
  return commentsList.value.filter((comment) => {
@@ -10843,12 +10489,6 @@ var useCommentsStore = defineStore("comments", () => {
10843
10489
  broadcastChanges,
10844
10490
  structuralChanges: captured.structuralChanges
10845
10491
  });
10846
- reconcileAuthoritativeV2TrackedChangeSidebar({
10847
- superdoc,
10848
- editor,
10849
- documentId: activeDocumentId,
10850
- refreshReason: "sync-authoritative"
10851
- });
10852
10492
  };
10853
10493
  /**
10854
10494
  * Surface decidable whole-table structural tracked changes (table insert /
@@ -11418,7 +11058,10 @@ var useCommentsStore = defineStore("comments", () => {
11418
11058
  pendingComment,
11419
11059
  currentCommentText,
11420
11060
  commentsList,
11061
+ reviewDirectoryList,
11421
11062
  isCommentsListVisible,
11063
+ isReviewDirectoryActive,
11064
+ isReviewDirectoryLoading,
11422
11065
  generalCommentIds,
11423
11066
  editorCommentIds,
11424
11067
  commentsParentElement,
@@ -11436,6 +11079,7 @@ var useCommentsStore = defineStore("comments", () => {
11436
11079
  getConfig,
11437
11080
  documentsWithConverations,
11438
11081
  getGroupedComments,
11082
+ getGroupedReviewDirectory,
11439
11083
  hasOpenTrackedChanges,
11440
11084
  getCommentsByPosition,
11441
11085
  getFloatingComments,
@@ -11467,6 +11111,8 @@ var useCommentsStore = defineStore("comments", () => {
11467
11111
  removePendingComment,
11468
11112
  cancelImportedTrackedChangeBootstrap,
11469
11113
  removeCommentsForDocument,
11114
+ setReviewDirectoryFromV2,
11115
+ clearReviewDirectory,
11470
11116
  processLoadedDocxComments,
11471
11117
  translateCommentsForExport,
11472
11118
  handleEditorLocationsUpdate,
@@ -11484,9 +11130,7 @@ var useCommentsStore = defineStore("comments", () => {
11484
11130
  v2CommentsAdapter,
11485
11131
  setV2CommentsAdapter,
11486
11132
  getV2CommentsAdapter,
11487
- hydrateCommentsFromV2,
11488
- applyReviewSnapshotFromV2,
11489
- supersedeV2ReviewHydration,
11133
+ applyReviewWindowFromV2,
11490
11134
  reconcileCommentsFromV2,
11491
11135
  isV2EditorActive,
11492
11136
  replyCommentV2,
@@ -11497,7 +11141,6 @@ var useCommentsStore = defineStore("comments", () => {
11497
11141
  setV2TrackedChangesAdapter,
11498
11142
  getV2TrackedChangesAdapter,
11499
11143
  getV2TrackedChangeRowCount,
11500
- hydrateTrackedChangesFromV2,
11501
11144
  reconcileTrackedChangeMutationFromV2,
11502
11145
  reconcileTrackedChangesFromV2,
11503
11146
  remapTrackedChangeIdentities
@@ -11636,13 +11279,13 @@ function useUiFontFamily() {
11636
11279
  }
11637
11280
  //#endregion
11638
11281
  //#region src/components/CommentsLayer/CommentsDropdown.vue
11639
- var _hoisted_1$30 = { class: "comments-dropdown" };
11282
+ var _hoisted_1$31 = { class: "comments-dropdown" };
11640
11283
  var _hoisted_2$24 = ["onClick"];
11641
11284
  var _hoisted_3$20 = {
11642
11285
  key: 0,
11643
11286
  class: "comments-dropdown__option-icon"
11644
11287
  };
11645
- var _hoisted_4$13 = ["innerHTML"];
11288
+ var _hoisted_4$12 = ["innerHTML"];
11646
11289
  var _hoisted_5$8 = { class: "comments-dropdown__option-label" };
11647
11290
  var CommentsDropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11648
11291
  __name: "CommentsDropdown",
@@ -11767,7 +11410,7 @@ var CommentsDropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11767
11410
  window.removeEventListener("scroll", updateMenuPosition, true);
11768
11411
  });
11769
11412
  return (_ctx, _cache) => {
11770
- return openBlock(), createElementBlock("div", _hoisted_1$30, [createElementVNode("div", {
11413
+ return openBlock(), createElementBlock("div", _hoisted_1$31, [createElementVNode("div", {
11771
11414
  ref_key: "triggerRef",
11772
11415
  ref: triggerRef,
11773
11416
  class: "comments-dropdown__trigger",
@@ -11786,7 +11429,7 @@ var CommentsDropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11786
11429
  }, [hasIcon(option) ? (openBlock(), createElementBlock("span", _hoisted_3$20, [option.iconString ? (openBlock(), createElementBlock("span", {
11787
11430
  key: 0,
11788
11431
  innerHTML: option.iconString
11789
- }, null, 8, _hoisted_4$13)) : (openBlock(), createBlock(unref(OptionIcon), {
11432
+ }, null, 8, _hoisted_4$12)) : (openBlock(), createBlock(unref(OptionIcon), {
11790
11433
  key: 1,
11791
11434
  option
11792
11435
  }, null, 8, ["option"]))])) : createCommentVNode("", true), createElementVNode("span", _hoisted_5$8, toDisplayString(option.label), 1)], 10, _hoisted_2$24);
@@ -11796,10 +11439,10 @@ var CommentsDropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11796
11439
  }, [["__scopeId", "data-v-7eced80a"]]);
11797
11440
  //#endregion
11798
11441
  //#region src/components/CommentsLayer/InternalDropdown.vue
11799
- var _hoisted_1$29 = { class: "sd-comment-option" };
11442
+ var _hoisted_1$30 = { class: "sd-comment-option" };
11800
11443
  var _hoisted_2$23 = ["innerHTML"];
11801
11444
  var _hoisted_3$19 = { class: "sd-option-state" };
11802
- var _hoisted_4$12 = ["innerHTML"];
11445
+ var _hoisted_4$11 = ["innerHTML"];
11803
11446
  var InternalDropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11804
11447
  __name: "InternalDropdown",
11805
11448
  props: {
@@ -11867,7 +11510,7 @@ var InternalDropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11867
11510
  disabled: __props.isDisabled,
11868
11511
  "content-style": { fontFamily: unref(uiFontFamily) }
11869
11512
  }, {
11870
- default: withCtx(() => [createElementVNode("div", _hoisted_1$29, [
11513
+ default: withCtx(() => [createElementVNode("div", _hoisted_1$30, [
11871
11514
  createElementVNode("div", {
11872
11515
  class: "sd-active-icon",
11873
11516
  innerHTML: activeIcon.value
@@ -11876,7 +11519,7 @@ var InternalDropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11876
11519
  createElementVNode("div", {
11877
11520
  class: "sd-dropdown-caret",
11878
11521
  innerHTML: unref(superdocIcons).caretDown
11879
- }, null, 8, _hoisted_4$12)
11522
+ }, null, 8, _hoisted_4$11)
11880
11523
  ])]),
11881
11524
  _: 1
11882
11525
  }, 8, ["disabled", "content-style"])], 4);
@@ -12025,7 +11668,7 @@ var isAllowed = (permission, role, isInternal, context = {}) => {
12025
11668
  };
12026
11669
  //#endregion
12027
11670
  //#region src/components/general/Avatar.vue
12028
- var _hoisted_1$28 = { class: "user-container" };
11671
+ var _hoisted_1$29 = { class: "user-container" };
12029
11672
  var _hoisted_2$22 = ["src"];
12030
11673
  var _hoisted_3$18 = {
12031
11674
  key: 1,
@@ -12048,7 +11691,7 @@ var Avatar_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12048
11691
  return email?.substring(0, 1)?.toUpperCase() || null;
12049
11692
  };
12050
11693
  return (_ctx, _cache) => {
12051
- return openBlock(), createElementBlock("div", _hoisted_1$28, [__props.user.image ? (openBlock(), createElementBlock("img", {
11694
+ return openBlock(), createElementBlock("div", _hoisted_1$29, [__props.user.image ? (openBlock(), createElementBlock("img", {
12052
11695
  key: 0,
12053
11696
  class: "user-bg",
12054
11697
  src: __props.user.image.startsWith("http") ? __props.user.image : `data:image/png;base64,${__props.user.image}`
@@ -12058,10 +11701,10 @@ var Avatar_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12058
11701
  }, [["__scopeId", "data-v-c95b2073"]]);
12059
11702
  //#endregion
12060
11703
  //#region src/components/CommentsLayer/CommentHeader.vue
12061
- var _hoisted_1$27 = { class: "card-section comment-header" };
11704
+ var _hoisted_1$28 = { class: "card-section comment-header" };
12062
11705
  var _hoisted_2$21 = { class: "comment-header-left" };
12063
11706
  var _hoisted_3$17 = { class: "user-info" };
12064
- var _hoisted_4$11 = { class: "user-name" };
11707
+ var _hoisted_4$10 = { class: "user-name" };
12065
11708
  var _hoisted_5$7 = {
12066
11709
  key: 0,
12067
11710
  class: "imported-tag"
@@ -12291,10 +11934,10 @@ var CommentHeader_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12291
11934
  return user;
12292
11935
  });
12293
11936
  return (_ctx, _cache) => {
12294
- return openBlock(), createElementBlock("div", _hoisted_1$27, [createElementVNode("div", _hoisted_2$21, [createVNode(Avatar_default, {
11937
+ return openBlock(), createElementBlock("div", _hoisted_1$28, [createElementVNode("div", _hoisted_2$21, [createVNode(Avatar_default, {
12295
11938
  user: getCurrentUser.value,
12296
11939
  class: "avatar"
12297
- }, null, 8, ["user"]), createElementVNode("div", _hoisted_3$17, [createElementVNode("div", _hoisted_4$11, [createTextVNode(toDisplayString(getCurrentUser.value.name), 1), isImported.value ? (openBlock(), createElementBlock("span", _hoisted_5$7, "IMPORTED")) : createCommentVNode("", true)]), props.comment.createdTime ? (openBlock(), createElementBlock("div", _hoisted_6$6, toDisplayString(unref(formatDate)(props.comment.createdTime)), 1)) : createCommentVNode("", true)])]), createElementVNode("div", { class: normalizeClass(["overflow-menu", { "is-visible": props.isActive }]) }, [
11940
+ }, null, 8, ["user"]), createElementVNode("div", _hoisted_3$17, [createElementVNode("div", _hoisted_4$10, [createTextVNode(toDisplayString(getCurrentUser.value.name), 1), isImported.value ? (openBlock(), createElementBlock("span", _hoisted_5$7, "IMPORTED")) : createCommentVNode("", true)]), props.comment.createdTime ? (openBlock(), createElementBlock("div", _hoisted_6$6, toDisplayString(unref(formatDate)(props.comment.createdTime)), 1)) : createCommentVNode("", true)])]), createElementVNode("div", { class: normalizeClass(["overflow-menu", { "is-visible": props.isActive }]) }, [
12298
11941
  allowResolve.value ? (openBlock(), createElementBlock("div", {
12299
11942
  key: 0,
12300
11943
  class: normalizeClass(["overflow-menu__icon", { "sd-is-disabled": Boolean(__props.resolveDisabledReason) }]),
@@ -12342,7 +11985,7 @@ var CommentHeader_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12342
11985
  }, [["__scopeId", "data-v-ca6050aa"]]);
12343
11986
  //#endregion
12344
11987
  //#region src/components/CommentsLayer/CommentInput.vue
12345
- var _hoisted_1$26 = { class: "input-section" };
11988
+ var _hoisted_1$27 = { class: "input-section" };
12346
11989
  var TEXTAREA_MIN_HEIGHT = 28;
12347
11990
  var TEXTAREA_MAX_HEIGHT = 132;
12348
11991
  var CommentInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
@@ -12415,7 +12058,7 @@ var CommentInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12415
12058
  watch(currentCommentText, scheduleInputHeightSync);
12416
12059
  __expose({ focus });
12417
12060
  return (_ctx, _cache) => {
12418
- return openBlock(), createElementBlock("div", _hoisted_1$26, [__props.includeHeader ? (openBlock(), createBlock(CommentHeader_default, {
12061
+ return openBlock(), createElementBlock("div", _hoisted_1$27, [__props.includeHeader ? (openBlock(), createBlock(CommentHeader_default, {
12419
12062
  key: 0,
12420
12063
  config: __props.config,
12421
12064
  comment: __props.comment,
@@ -12436,7 +12079,8 @@ var CommentInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12436
12079
  }, [["__scopeId", "data-v-cc54b489"]]);
12437
12080
  //#endregion
12438
12081
  //#region src/components/CommentsLayer/CommentDialog.vue
12439
- var _hoisted_1$25 = [
12082
+ var _hoisted_1$26 = [
12083
+ "aria-label",
12440
12084
  "data-comment-instance-id",
12441
12085
  "data-comment-thread-id",
12442
12086
  "data-comment-position-key",
@@ -12447,7 +12091,7 @@ var _hoisted_2$20 = {
12447
12091
  class: "existing-internal-input"
12448
12092
  };
12449
12093
  var _hoisted_3$16 = { class: "new-comment-input-wrapper" };
12450
- var _hoisted_4$10 = { class: "reply-actions" };
12094
+ var _hoisted_4$9 = { class: "reply-actions" };
12451
12095
  var _hoisted_5$6 = ["disabled"];
12452
12096
  var _hoisted_6$5 = {
12453
12097
  key: 0,
@@ -12568,6 +12212,10 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12568
12212
  isFloatingInstanceActive: {
12569
12213
  type: Boolean,
12570
12214
  default: void 0
12215
+ },
12216
+ threadComments: {
12217
+ type: Array,
12218
+ default: null
12571
12219
  }
12572
12220
  },
12573
12221
  emits: [
@@ -12770,6 +12418,11 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12770
12418
  };
12771
12419
  const comments = computed(() => {
12772
12420
  const parentComment = props.comment;
12421
+ if (Array.isArray(props.threadComments)) return [...props.threadComments].sort((a, b) => {
12422
+ if (a.commentId === parentComment.commentId) return -1;
12423
+ if (b.commentId === parentComment.commentId) return 1;
12424
+ return a.createdTime - b.createdTime;
12425
+ });
12773
12426
  if (parentComment.trackedChange) return commentsStore.getTrackedChangeThread(parentComment);
12774
12427
  return commentsStore.commentsList.filter((comment) => {
12775
12428
  const isThreadedComment = comment.parentCommentId === parentComment.commentId;
@@ -12781,6 +12434,11 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12781
12434
  return a.createdTime - b.createdTime;
12782
12435
  });
12783
12436
  });
12437
+ const dialogAccessibleLabel = computed(() => {
12438
+ if (props.comment.trackedChangeLabel) return props.comment.trackedChangeLabel;
12439
+ if (props.comment.trackedChange) return "Tracked change discussion";
12440
+ return props.comment.creatorName ? `Comment by ${props.comment.creatorName}` : "Comment thread";
12441
+ });
12784
12442
  const textExpanded = ref(false);
12785
12443
  const parentBodyRef = ref(null);
12786
12444
  const isTextOverflowing = ref(false);
@@ -13379,6 +13037,7 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13379
13037
  ref_key: "commentDialogElement",
13380
13038
  ref: commentDialogElement,
13381
13039
  role: "dialog",
13040
+ "aria-label": dialogAccessibleLabel.value,
13382
13041
  "data-sd-part": "comment-thread",
13383
13042
  "data-editor-ui-surface": "",
13384
13043
  "data-comment-instance-id": props.floatingInstanceId ?? "",
@@ -13410,7 +13069,7 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13410
13069
  "config",
13411
13070
  "comment"
13412
13071
  ])]),
13413
- createElementVNode("div", _hoisted_4$10, [createElementVNode("button", {
13072
+ createElementVNode("div", _hoisted_4$9, [createElementVNode("button", {
13414
13073
  class: "sd-button reply-btn-cancel",
13415
13074
  onClick: withModifiers(handleCancel, ["stop", "prevent"])
13416
13075
  }, "Cancel"), createElementVNode("button", {
@@ -13587,21 +13246,159 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13587
13246
  disabled: !hasTextContent.value || isV2WriteDisabled.value || isSubmittingReply.value,
13588
13247
  "data-disabled-reason": v2ReplyDisabledReason.value
13589
13248
  }, " Reply ", 10, _hoisted_51)])]))], 64)) : createCommentVNode("", true)
13590
- ], 64))], 14, _hoisted_1$25)), [[_directive_click_outside, handleClickOutside]]);
13249
+ ], 64))], 14, _hoisted_1$26)), [[_directive_click_outside, handleClickOutside]]);
13591
13250
  };
13592
13251
  }
13593
- }, [["__scopeId", "data-v-f26decd0"]]);
13252
+ }, [["__scopeId", "data-v-0277e53b"]]);
13594
13253
  //#endregion
13595
- //#region src/components/CommentsLayer/commentsList/commentsList.vue
13596
- var _hoisted_1$24 = { class: "comments-list" };
13597
- var _hoisted_2$19 = { key: 0 };
13598
- var _hoisted_3$15 = [
13254
+ //#region src/components/CommentsLayer/commentsList/ReviewDirectoryListItem.vue
13255
+ var _hoisted_1$25 = [
13256
+ "tabindex",
13257
+ "aria-posinset",
13258
+ "aria-setsize",
13259
+ "aria-label",
13599
13260
  "data-comment-instance-id",
13600
13261
  "data-comment-thread-id",
13601
13262
  "data-comment-position-key",
13602
- "data-comment-page-index"
13263
+ "data-comment-page-index",
13264
+ "data-review-directory-index"
13603
13265
  ];
13604
- var _hoisted_4$9 = { key: 1 };
13266
+ var ReviewDirectoryListItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13267
+ __name: "ReviewDirectoryListItem",
13268
+ props: {
13269
+ item: {
13270
+ type: Object,
13271
+ required: true
13272
+ },
13273
+ index: {
13274
+ type: Number,
13275
+ required: true
13276
+ },
13277
+ total: {
13278
+ type: Number,
13279
+ required: true
13280
+ },
13281
+ tabIndex: {
13282
+ type: Number,
13283
+ default: -1
13284
+ }
13285
+ },
13286
+ emits: ["focus-item", "navigate"],
13287
+ setup(__props, { emit: __emit }) {
13288
+ const props = __props;
13289
+ const emit = __emit;
13290
+ const { activeComment, editingCommentId, pendingComment } = storeToRefs(useCommentsStore());
13291
+ const shell = ref(null);
13292
+ const measuredHeight = ref(112);
13293
+ const intersects = ref(typeof IntersectionObserver === "undefined");
13294
+ const containsFocus = ref(false);
13295
+ let intersectionObserver = null;
13296
+ let resizeObserver = null;
13297
+ const threadIds = computed(() => new Set((props.item.threadComments ?? []).flatMap((comment) => [
13298
+ comment?.commentId,
13299
+ comment?.importedId,
13300
+ comment?.trackedChangeCanonicalId
13301
+ ]).filter((id) => id != null).map(String)));
13302
+ const isPinned = computed(() => {
13303
+ const pendingId = pendingComment.value?.commentId;
13304
+ return containsFocus.value || activeComment.value != null && threadIds.value.has(String(activeComment.value)) || editingCommentId.value != null && threadIds.value.has(String(editingCommentId.value)) || pendingId != null && threadIds.value.has(String(pendingId));
13305
+ });
13306
+ const materialized = computed(() => intersects.value || isPinned.value);
13307
+ const placeholderStyle = computed(() => materialized.value ? void 0 : { minHeight: `${Math.max(1, measuredHeight.value)}px` });
13308
+ const accessibleSummary = computed(() => {
13309
+ const comment = props.item.comment;
13310
+ const text = comment?.trackedChangeLabel ?? comment?.commentText ?? comment?.trackedChangeText ?? "Review item";
13311
+ return String(text).replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().slice(0, 160) || "Review item";
13312
+ });
13313
+ const measure = () => {
13314
+ const height = shell.value?.getBoundingClientRect?.().height;
13315
+ if (Number.isFinite(height) && height > 0) measuredHeight.value = height;
13316
+ };
13317
+ const onFocusIn = () => {
13318
+ containsFocus.value = true;
13319
+ emit("focus-item", props.index);
13320
+ };
13321
+ const onKeydown = (event) => {
13322
+ if (![
13323
+ "ArrowDown",
13324
+ "ArrowUp",
13325
+ "Home",
13326
+ "End"
13327
+ ].includes(event.key)) return;
13328
+ if (event.target?.closest?.("input, textarea, select, button, [contenteditable=\"true\"]")) return;
13329
+ event.preventDefault();
13330
+ emit("navigate", {
13331
+ index: props.index,
13332
+ key: event.key
13333
+ });
13334
+ };
13335
+ onMounted(() => {
13336
+ if (typeof IntersectionObserver !== "undefined") {
13337
+ intersectionObserver = new IntersectionObserver((entries) => {
13338
+ const entry = entries[0];
13339
+ if (!entry) return;
13340
+ intersects.value = entry.isIntersecting;
13341
+ if (entry.isIntersecting) nextTick(measure);
13342
+ }, {
13343
+ root: null,
13344
+ rootMargin: "600px 0px"
13345
+ });
13346
+ intersectionObserver.observe(shell.value);
13347
+ }
13348
+ if (typeof ResizeObserver !== "undefined") {
13349
+ resizeObserver = new ResizeObserver(measure);
13350
+ resizeObserver.observe(shell.value);
13351
+ }
13352
+ nextTick(measure);
13353
+ });
13354
+ onBeforeUnmount(() => {
13355
+ intersectionObserver?.disconnect();
13356
+ resizeObserver?.disconnect();
13357
+ });
13358
+ return (_ctx, _cache) => {
13359
+ return openBlock(), createElementBlock("div", {
13360
+ ref_key: "shell",
13361
+ ref: shell,
13362
+ class: "comment-item",
13363
+ role: "listitem",
13364
+ tabindex: __props.tabIndex,
13365
+ "aria-posinset": __props.index + 1,
13366
+ "aria-setsize": __props.total,
13367
+ "aria-label": accessibleSummary.value,
13368
+ style: normalizeStyle(placeholderStyle.value),
13369
+ "data-comment-instance-id": __props.item.floatingInstanceId ?? "",
13370
+ "data-comment-thread-id": __props.item.comment.commentId ?? "",
13371
+ "data-comment-position-key": __props.item.comment.trackedChangeAnchorKey ?? "",
13372
+ "data-comment-page-index": Number.isFinite(__props.item.floatingPageIndex) ? __props.item.floatingPageIndex : "",
13373
+ "data-review-directory-index": __props.index,
13374
+ onFocusin: onFocusIn,
13375
+ onFocusout: _cache[0] || (_cache[0] = ($event) => containsFocus.value = shell.value?.contains?.($event.relatedTarget) === true),
13376
+ onKeydown
13377
+ }, [materialized.value ? (openBlock(), createBlock(CommentDialog_default, {
13378
+ key: 0,
13379
+ comment: __props.item.comment,
13380
+ "thread-comments": __props.item.threadComments,
13381
+ "floating-instance-id": __props.item.floatingInstanceId,
13382
+ "floating-page-index": __props.item.floatingPageIndex,
13383
+ "floating-position-entry": __props.item.floatingPositionEntry,
13384
+ "is-floating-instance-active": __props.item.isFloatingInstanceActive,
13385
+ onResize: measure
13386
+ }, null, 8, [
13387
+ "comment",
13388
+ "thread-comments",
13389
+ "floating-instance-id",
13390
+ "floating-page-index",
13391
+ "floating-position-entry",
13392
+ "is-floating-instance-active"
13393
+ ])) : createCommentVNode("", true)], 44, _hoisted_1$25);
13394
+ };
13395
+ }
13396
+ }, [["__scopeId", "data-v-29650a06"]]);
13397
+ //#endregion
13398
+ //#region src/components/CommentsLayer/commentsList/commentsList.vue
13399
+ var _hoisted_1$24 = ["aria-busy"];
13400
+ var _hoisted_2$19 = { key: 0 };
13401
+ var _hoisted_3$15 = { key: 1 };
13605
13402
  var REPEATED_HEADER_FOOTER_STORY_TYPE = "headerFooterPart";
13606
13403
  var commentsList_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13607
13404
  __name: "commentsList",
@@ -13618,10 +13415,41 @@ var commentsList_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13618
13415
  setup(__props) {
13619
13416
  const props = __props;
13620
13417
  const commentsStore = useCommentsStore();
13418
+ const listRoot = ref(null);
13419
+ const rovingIndex = ref(0);
13621
13420
  const isCommentsListVisible = toRef(commentsStore, "isCommentsListVisible");
13622
- const groupedComments = computed(() => commentsStore.getGroupedComments);
13623
13421
  const floatingCommentInstances = computed(() => commentsStore.getFloatingCommentInstances);
13624
- const { activeComment, activeFloatingCommentInstanceId } = storeToRefs(commentsStore);
13422
+ const { activeComment, activeFloatingCommentInstanceId, commentsList, reviewDirectoryList, isReviewDirectoryActive, isReviewDirectoryLoading } = storeToRefs(commentsStore);
13423
+ const groupedComments = computed(() => isReviewDirectoryActive.value ? commentsStore.getGroupedReviewDirectory : commentsStore.getGroupedComments);
13424
+ const reviewRows = computed(() => isReviewDirectoryActive.value ? reviewDirectoryList.value : commentsList.value);
13425
+ const commentAliases = (comment) => [
13426
+ comment?.commentId,
13427
+ comment?.importedId,
13428
+ comment?.trackedChangeCanonicalId
13429
+ ].filter((id) => id != null).map(String);
13430
+ const directoryThreads = computed(() => {
13431
+ const threadsByAlias = /* @__PURE__ */ new Map();
13432
+ for (const comment of reviewRows.value) {
13433
+ const thread = [comment];
13434
+ for (const alias of commentAliases(comment)) threadsByAlias.set(alias, thread);
13435
+ }
13436
+ for (const comment of reviewRows.value) {
13437
+ const parentThread = [
13438
+ comment?.parentCommentId,
13439
+ comment?.threadingParentCommentId,
13440
+ comment?.trackedChangeThreadParentId
13441
+ ].filter((id) => id != null).map(String).map((alias) => threadsByAlias.get(alias)).find(Boolean);
13442
+ if (parentThread && !parentThread.includes(comment)) parentThread.push(comment);
13443
+ }
13444
+ return threadsByAlias;
13445
+ });
13446
+ const threadCommentsFor = (parentComment) => {
13447
+ for (const alias of commentAliases(parentComment)) {
13448
+ const thread = directoryThreads.value.get(alias);
13449
+ if (thread) return thread;
13450
+ }
13451
+ return [parentComment];
13452
+ };
13625
13453
  const getRepeatedHeaderFooterInstances = (comment) => {
13626
13454
  const matchingInstances = floatingCommentInstances.value.filter((instance) => instance?.comment?.commentId === comment?.commentId);
13627
13455
  if (matchingInstances.length < 2) return [];
@@ -13643,7 +13471,8 @@ var commentsList_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13643
13471
  floatingInstanceId: null,
13644
13472
  floatingPageIndex: null,
13645
13473
  floatingPositionEntry: null,
13646
- isFloatingInstanceActive: void 0
13474
+ isFloatingInstanceActive: void 0,
13475
+ threadComments: threadCommentsFor(comment)
13647
13476
  }];
13648
13477
  return repeatedInstances.map((instance) => ({
13649
13478
  id: instance.id,
@@ -13651,7 +13480,8 @@ var commentsList_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13651
13480
  floatingInstanceId: String(instance.id),
13652
13481
  floatingPageIndex: instance.pageIndex,
13653
13482
  floatingPositionEntry: instance.positionEntry ?? null,
13654
- isFloatingInstanceActive: isRepeatedInstanceActive(instance)
13483
+ isFloatingInstanceActive: isRepeatedInstanceActive(instance),
13484
+ threadComments: threadCommentsFor(comment)
13655
13485
  }));
13656
13486
  };
13657
13487
  const parentCommentDisplayItems = computed(() => {
@@ -13660,6 +13490,30 @@ var commentsList_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13660
13490
  const shouldShowResolvedComments = computed(() => {
13661
13491
  return props.showResolvedComments && groupedComments.value?.resolvedComments?.length > 0;
13662
13492
  });
13493
+ const resolvedCommentDisplayItems = computed(() => groupedComments.value.resolvedComments.map((comment) => ({
13494
+ id: comment.commentId,
13495
+ comment,
13496
+ floatingInstanceId: null,
13497
+ floatingPageIndex: null,
13498
+ floatingPositionEntry: null,
13499
+ isFloatingInstanceActive: void 0,
13500
+ threadComments: threadCommentsFor(comment)
13501
+ })));
13502
+ const displayedMainCount = computed(() => props.showMainComments ? parentCommentDisplayItems.value.length : 0);
13503
+ const totalDisplayItems = computed(() => displayedMainCount.value + (shouldShowResolvedComments.value ? resolvedCommentDisplayItems.value.length : 0));
13504
+ watch(totalDisplayItems, (total) => {
13505
+ rovingIndex.value = Math.max(0, Math.min(rovingIndex.value, total - 1));
13506
+ });
13507
+ const focusDirectoryItem = async ({ index, key }) => {
13508
+ const lastIndex = totalDisplayItems.value - 1;
13509
+ if (lastIndex < 0) return;
13510
+ const nextIndex = key === "Home" ? 0 : key === "End" ? lastIndex : key === "ArrowUp" ? Math.max(0, index - 1) : Math.min(lastIndex, index + 1);
13511
+ rovingIndex.value = nextIndex;
13512
+ await nextTick();
13513
+ const target = listRoot.value?.querySelector?.(`[data-review-directory-index="${nextIndex}"]`);
13514
+ target?.scrollIntoView?.({ block: "nearest" });
13515
+ target?.focus?.({ preventScroll: true });
13516
+ };
13663
13517
  onMounted(() => {
13664
13518
  isCommentsListVisible.value = true;
13665
13519
  });
@@ -13667,36 +13521,46 @@ var commentsList_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13667
13521
  isCommentsListVisible.value = false;
13668
13522
  });
13669
13523
  return (_ctx, _cache) => {
13670
- return openBlock(), createElementBlock("div", _hoisted_1$24, [__props.showMainComments ? (openBlock(), createElementBlock("div", _hoisted_2$19, [(openBlock(true), createElementBlock(Fragment, null, renderList(parentCommentDisplayItems.value, (item) => {
13671
- return openBlock(), createElementBlock("div", {
13524
+ return openBlock(), createElementBlock("div", {
13525
+ ref_key: "listRoot",
13526
+ ref: listRoot,
13527
+ class: "comments-list",
13528
+ role: "list",
13529
+ "aria-busy": unref(isReviewDirectoryActive) && unref(isReviewDirectoryLoading) ? "true" : "false"
13530
+ }, [__props.showMainComments ? (openBlock(), createElementBlock("div", _hoisted_2$19, [(openBlock(true), createElementBlock(Fragment, null, renderList(parentCommentDisplayItems.value, (item, index) => {
13531
+ return openBlock(), createBlock(ReviewDirectoryListItem_default, {
13672
13532
  key: item.id,
13673
- class: "comment-item",
13674
- "data-comment-instance-id": item.floatingInstanceId ?? "",
13675
- "data-comment-thread-id": item.comment.commentId ?? "",
13676
- "data-comment-position-key": item.comment.trackedChangeAnchorKey ?? "",
13677
- "data-comment-page-index": Number.isFinite(item.floatingPageIndex) ? item.floatingPageIndex : ""
13678
- }, [createVNode(CommentDialog_default, {
13679
- comment: item.comment,
13680
- "floating-instance-id": item.floatingInstanceId,
13681
- "floating-page-index": item.floatingPageIndex,
13682
- "floating-position-entry": item.floatingPositionEntry,
13683
- "is-floating-instance-active": item.isFloatingInstanceActive
13533
+ item,
13534
+ index,
13535
+ total: totalDisplayItems.value,
13536
+ "tab-index": index === rovingIndex.value ? 0 : -1,
13537
+ onFocusItem: _cache[0] || (_cache[0] = ($event) => rovingIndex.value = $event),
13538
+ onNavigate: focusDirectoryItem
13684
13539
  }, null, 8, [
13685
- "comment",
13686
- "floating-instance-id",
13687
- "floating-page-index",
13688
- "floating-position-entry",
13689
- "is-floating-instance-active"
13690
- ])], 8, _hoisted_3$15);
13691
- }), 128))])) : createCommentVNode("", true), shouldShowResolvedComments.value ? (openBlock(), createElementBlock("div", _hoisted_4$9, [_cache[0] || (_cache[0] = createElementVNode("div", { class: "comment-title" }, "Resolved", -1)), (openBlock(true), createElementBlock(Fragment, null, renderList(groupedComments.value.resolvedComments, (comment) => {
13692
- return openBlock(), createElementBlock("div", {
13693
- key: comment.commentId,
13694
- class: "comment-item"
13695
- }, [createVNode(CommentDialog_default, { comment }, null, 8, ["comment"])]);
13696
- }), 128))])) : createCommentVNode("", true)]);
13540
+ "item",
13541
+ "index",
13542
+ "total",
13543
+ "tab-index"
13544
+ ]);
13545
+ }), 128))])) : createCommentVNode("", true), shouldShowResolvedComments.value ? (openBlock(), createElementBlock("div", _hoisted_3$15, [_cache[2] || (_cache[2] = createElementVNode("div", { class: "comment-title" }, "Resolved", -1)), (openBlock(true), createElementBlock(Fragment, null, renderList(resolvedCommentDisplayItems.value, (item, index) => {
13546
+ return openBlock(), createBlock(ReviewDirectoryListItem_default, {
13547
+ key: item.id,
13548
+ item,
13549
+ index: displayedMainCount.value + index,
13550
+ total: totalDisplayItems.value,
13551
+ "tab-index": displayedMainCount.value + index === rovingIndex.value ? 0 : -1,
13552
+ onFocusItem: _cache[1] || (_cache[1] = ($event) => rovingIndex.value = $event),
13553
+ onNavigate: focusDirectoryItem
13554
+ }, null, 8, [
13555
+ "item",
13556
+ "index",
13557
+ "total",
13558
+ "tab-index"
13559
+ ]);
13560
+ }), 128))])) : createCommentVNode("", true)], 8, _hoisted_1$24);
13697
13561
  };
13698
13562
  }
13699
- }, [["__scopeId", "data-v-e89dbc33"]]);
13563
+ }, [["__scopeId", "data-v-89244745"]]);
13700
13564
  //#endregion
13701
13565
  //#region src/components/CommentsLayer/commentsList/super-comments-list.js
13702
13566
  /**
@@ -13720,8 +13584,65 @@ var SuperComments = class extends import_eventemitter3.default {
13720
13584
  this.element = this.config.element;
13721
13585
  this.app = null;
13722
13586
  this.superdoc = superdoc;
13587
+ this.stopDirectoryObservers = [];
13588
+ this.directorySnapshots = {
13589
+ comments: null,
13590
+ trackChanges: null
13591
+ };
13592
+ this.directoryItemRefs = {
13593
+ comments: null,
13594
+ trackChanges: null
13595
+ };
13596
+ this.directoryGeneration = 0;
13723
13597
  this.open();
13724
13598
  }
13599
+ syncDirectory() {
13600
+ const store = this.superdoc?.commentsStore;
13601
+ if (!store) return;
13602
+ const comments = this.directorySnapshots.comments;
13603
+ const trackChanges = this.directorySnapshots.trackChanges;
13604
+ store.isReviewDirectoryLoading = comments == null || trackChanges == null;
13605
+ if (comments == null && trackChanges == null) return;
13606
+ store.setReviewDirectoryFromV2({
13607
+ superdoc: this.superdoc,
13608
+ commentItems: comments ?? [],
13609
+ trackedChangeItems: trackChanges ?? []
13610
+ });
13611
+ }
13612
+ startDirectoryObservers() {
13613
+ const generation = ++this.directoryGeneration;
13614
+ const subscribe = (handle, family) => {
13615
+ const acceptDirectorySnapshot = (snapshot) => {
13616
+ if (generation !== this.directoryGeneration) return;
13617
+ if ((snapshot?.listStatus ?? snapshot?.status) !== "ready") {
13618
+ if (this.superdoc?.commentsStore) this.superdoc.commentsStore.isReviewDirectoryLoading = true;
13619
+ return;
13620
+ }
13621
+ const items = Array.isArray(snapshot.items) ? snapshot.items : [];
13622
+ if (this.directoryItemRefs[family] === items) {
13623
+ if (this.superdoc?.commentsStore) this.superdoc.commentsStore.isReviewDirectoryLoading = this.directorySnapshots.comments == null || this.directorySnapshots.trackChanges == null;
13624
+ return;
13625
+ }
13626
+ this.directoryItemRefs[family] = items;
13627
+ this.directorySnapshots[family] = items;
13628
+ this.syncDirectory();
13629
+ };
13630
+ const stop = handle.observe((snapshot) => {
13631
+ acceptDirectorySnapshot(snapshot);
13632
+ });
13633
+ const readAfterLeaseAcquisition = () => acceptDirectorySnapshot(handle.getSnapshot());
13634
+ if (typeof globalThis.queueMicrotask === "function") globalThis.queueMicrotask(readAfterLeaseAcquisition);
13635
+ else Promise.resolve().then(readAfterLeaseAcquisition);
13636
+ return stop;
13637
+ };
13638
+ const ui = this.superdoc?.ui;
13639
+ if (!ui) return;
13640
+ if (this.superdoc?.commentsStore) {
13641
+ this.superdoc.commentsStore.isReviewDirectoryActive = true;
13642
+ this.superdoc.commentsStore.isReviewDirectoryLoading = true;
13643
+ }
13644
+ this.stopDirectoryObservers = [subscribe(ui.comments, "comments"), subscribe(ui.trackChanges, "trackChanges")];
13645
+ }
13725
13646
  createVueApp() {
13726
13647
  this.app = createApp(commentsList_default);
13727
13648
  const parentProvides = this.superdoc?.app?._context?.provides;
@@ -13735,8 +13656,24 @@ var SuperComments = class extends import_eventemitter3.default {
13735
13656
  this.app.config.globalProperties.$superdoc = this.superdoc;
13736
13657
  if (!this.element && this.config.selector) this.element = document.getElementById(this.config.selector);
13737
13658
  this.container = this.app.mount(this.element);
13659
+ this.startDirectoryObservers();
13738
13660
  }
13739
13661
  close() {
13662
+ this.directoryGeneration += 1;
13663
+ for (const stop of this.stopDirectoryObservers.splice(0)) stop?.();
13664
+ this.directorySnapshots = {
13665
+ comments: null,
13666
+ trackChanges: null
13667
+ };
13668
+ this.directoryItemRefs = {
13669
+ comments: null,
13670
+ trackChanges: null
13671
+ };
13672
+ if (this.superdoc?.commentsStore) {
13673
+ this.superdoc.commentsStore.clearReviewDirectory();
13674
+ this.superdoc.commentsStore.isReviewDirectoryActive = false;
13675
+ this.superdoc.commentsStore.isReviewDirectoryLoading = false;
13676
+ }
13740
13677
  if (this.app) {
13741
13678
  this.app.unmount();
13742
13679
  this.app = null;
@@ -20593,27 +20530,6 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
20593
20530
  editor: proxy.$superdoc?.activeEditor
20594
20531
  });
20595
20532
  };
20596
- const reconcileAuthoritativeV2TrackedChangeSidebar = ({ adapter, documentId, refreshReason = "startup-authoritative" } = {}) => {
20597
- if (!adapter || typeof adapter.listTrackedChanges !== "function") return false;
20598
- const normalizedDocumentId = documentId == null ? null : String(documentId);
20599
- if (!normalizedDocumentId) return false;
20600
- commentsStore.hydrateTrackedChangesFromV2?.({
20601
- superdoc: proxy.$superdoc,
20602
- adapter,
20603
- documentId: normalizedDocumentId,
20604
- trackedChangesListMode: "background-reconcile",
20605
- refreshReason,
20606
- blocking: false,
20607
- isCurrent: () => {
20608
- const activeEditor = proxy.$superdoc?.activeEditor ?? null;
20609
- const activeDocumentId = activeEditor?.documentId ?? activeEditor?.options?.documentId ?? null;
20610
- if (activeDocumentId != null && String(activeDocumentId) !== normalizedDocumentId) return false;
20611
- const activeAdapter = activeEditor?.v2TrackedChanges ?? commentsStore.getV2TrackedChangesAdapter?.(proxy.$superdoc);
20612
- return !activeAdapter || activeAdapter === adapter;
20613
- }
20614
- });
20615
- return true;
20616
- };
20617
20533
  const scheduleReplayTrackedChangeSync = () => {
20618
20534
  pendingReplayTrackedChangeSync.value = true;
20619
20535
  nextTick(() => {
@@ -20771,31 +20687,25 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
20771
20687
  proxy.$superdoc.setActiveRuntime(runtimeId, "v2-editor-ready");
20772
20688
  return true;
20773
20689
  };
20774
- const v2ReviewHydrationController = resolvedEditorIntegration.createReviewHydrationController({
20775
- hydrateComments: (ctx) => commentsStore.hydrateCommentsFromV2?.(ctx),
20776
- hydrateTrackedChanges: (ctx) => commentsStore.hydrateTrackedChangesFromV2?.(ctx),
20777
- primeTrackedChanges: (ctx) => ctx.trackedChangesAdapter?.primeVisibleTrackedChanges?.(ctx.targetIds, ctx.plan),
20778
- readReviewSnapshot: (ctx) => ctx.trackedChangesAdapter?.readReviewSnapshot?.(ctx.snapshot),
20779
- validateReviewSnapshot: (_ctx, result) => result?.validate?.() ?? {
20780
- ok: false,
20781
- reason: "validator-missing"
20690
+ const v2ReviewWindowController = resolvedEditorIntegration.createReviewWindowController({ applyReviewWindow: (ctx, result) => commentsStore.applyReviewWindowFromV2?.({
20691
+ superdoc: ctx?.superdoc,
20692
+ commentsAdapter: ctx?.commentsAdapter,
20693
+ trackedChangesAdapter: ctx?.trackedChangesAdapter,
20694
+ documentId: ctx?.documentId,
20695
+ commentItems: result?.comments?.items,
20696
+ trackedChangeItems: result?.trackedChanges?.items,
20697
+ requestedCommentIds: result?.comments?.requestedIds,
20698
+ requestedTrackedChangeIds: result?.trackedChanges?.requestedIds,
20699
+ unresolvedCommentIds: result?.comments?.unresolvedCommentIds,
20700
+ trackedList: {
20701
+ complete: false,
20702
+ visibleWindowSource: "visible-window"
20782
20703
  },
20783
- applyReviewSnapshot: (ctx, result) => commentsStore.applyReviewSnapshotFromV2?.({
20784
- superdoc: ctx.superdoc,
20785
- commentsAdapter: ctx.commentsAdapter,
20786
- trackedChangesAdapter: ctx.trackedChangesAdapter,
20787
- documentId: ctx.documentId,
20788
- commentItems: result.comments?.items,
20789
- trackedChangeItems: result.trackedChanges?.items,
20790
- trackedList: result.trackedChanges,
20791
- sourceCoverageRevision: result.sourceCoverageRevision,
20792
- evaluatedRevision: result.evaluatedRevision,
20793
- patch: (callback) => commentsStore.$patch(callback)
20794
- }) ?? {
20795
- ok: false,
20796
- reason: "store-action-missing"
20797
- }
20798
- });
20704
+ patch: (callback) => commentsStore.$patch(callback)
20705
+ }) ?? {
20706
+ ok: false,
20707
+ reason: "store-action-missing"
20708
+ } });
20799
20709
  const v2EditorFailures = ref({});
20800
20710
  const v2AuthorRequiredGate = createV2AuthorRequiredNotificationGate();
20801
20711
  const v2AuthorRequiredMessages = ref({});
@@ -20889,7 +20799,7 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
20889
20799
  if (!payload) return;
20890
20800
  clearV2EditorFailure(payload.documentId ?? null);
20891
20801
  clearV2AuthorRequired(payload.documentId ?? null);
20892
- const { host, mount, documentId, capabilities, editCommands, bindEditShortcuts, bindSessionShortcuts, commentsAdapter, trackedChangesAdapter, documentApi, documentMutationReadiness, documentApiUnavailableReason, pageMetrics, pageLayout, pageFurniture, presence, lock, fonts, replaceFile } = payload;
20802
+ const { host, mount, documentId, capabilities, editCommands, bindEditShortcuts, bindSessionShortcuts, commentsAdapter, trackedChangesAdapter, documentApi, documentMutationReadiness, documentApiUnavailableReason, pageMetrics, pageLayout, pageFurniture, presence, lock, fonts, replaceFile, upgradeToCollaboration } = payload;
20893
20803
  const saveV2Bytes = async (saveOptions = {}) => {
20894
20804
  if (!host || typeof host.save !== "function") throw new Error("v2-editor: save unavailable");
20895
20805
  return host.save(saveOptions);
@@ -20906,6 +20816,10 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
20906
20816
  if (typeof replaceFile !== "function") throw new Error("v2-editor: replaceFile unavailable");
20907
20817
  return replaceFile(source);
20908
20818
  };
20819
+ const upgradeV2ToCollaboration = async (source, collaboration) => {
20820
+ if (typeof upgradeToCollaboration !== "function") throw new Error("v2-editor: upgradeToCollaboration unavailable");
20821
+ return upgradeToCollaboration(source, collaboration);
20822
+ };
20909
20823
  const focusV2Editable = (options = {}) => {
20910
20824
  if (mount?.focus && typeof mount.focus.focus === "function") return mount.focus.focus(options);
20911
20825
  return false;
@@ -21144,6 +21058,7 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
21144
21058
  save: saveV2Bytes,
21145
21059
  exportDocx: exportV2Docx,
21146
21060
  replaceFile: replaceV2File,
21061
+ upgradeToCollaboration: upgradeV2ToCollaboration,
21147
21062
  doc: documentApi ?? null,
21148
21063
  documentMutationReadiness: documentMutationReadiness ?? null,
21149
21064
  documentApiUnavailableReason: documentApiUnavailableReason ?? null,
@@ -21157,10 +21072,10 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
21157
21072
  pageFurniture: pageFurniture ?? null,
21158
21073
  presence: presence ?? null,
21159
21074
  lock: lock ?? null,
21160
- reviewHydration: {
21161
- getDiagnostics: () => v2ReviewHydrationController.getDiagnostics(),
21162
- getSnapshot: () => v2ReviewHydrationController.getSnapshot?.() ?? null,
21163
- subscribe: (listener) => v2ReviewHydrationController.subscribe?.(listener) ?? (() => void 0)
21075
+ reviewWindow: {
21076
+ getDiagnostics: () => v2ReviewWindowController.getDiagnostics(),
21077
+ getSnapshot: () => v2ReviewWindowController.getSnapshot?.() ?? null,
21078
+ subscribe: (listener) => v2ReviewWindowController.subscribe?.(listener) ?? (() => void 0)
21164
21079
  },
21165
21080
  extensions: createV2ExtensionsFacet(host),
21166
21081
  fonts: fonts ?? host?.getFontRuntime?.() ?? null,
@@ -21203,24 +21118,12 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
21203
21118
  const commentsModuleEnabled = proxy.$superdoc.config.modules?.comments !== false;
21204
21119
  if (commentsAdapter && commentsModuleEnabled) commentsStore.setV2CommentsAdapter?.(commentsAdapter);
21205
21120
  if (trackedChangesAdapter && commentsModuleEnabled) commentsStore.setV2TrackedChangesAdapter?.(trackedChangesAdapter);
21206
- if (commentsModuleEnabled && (commentsAdapter || trackedChangesAdapter)) {
21207
- v2ReviewHydrationController.setContext({
21208
- superdoc: proxy.$superdoc,
21209
- documentId,
21210
- commentsAdapter: commentsAdapter ?? null,
21211
- trackedChangesAdapter: trackedChangesAdapter ?? null
21212
- });
21213
- try {
21214
- const snapshot = host?.getRenderReadinessSnapshot?.();
21215
- if (snapshot) v2ReviewHydrationController.onRenderReadiness(snapshot);
21216
- } catch (err) {
21217
- console.warn("[SuperDoc][v2] initial render-readiness snapshot failed", err);
21218
- }
21219
- }
21220
- if (commentsModuleEnabled && trackedChangesAdapter) reconcileAuthoritativeV2TrackedChangeSidebar({
21221
- adapter: trackedChangesAdapter,
21121
+ if (commentsAdapter || trackedChangesAdapter) v2ReviewWindowController.setContext({
21122
+ superdoc: proxy.$superdoc,
21222
21123
  documentId,
21223
- refreshReason: "startup-authoritative"
21124
+ commentsAdapter: commentsAdapter ?? null,
21125
+ trackedChangesAdapter: trackedChangesAdapter ?? null,
21126
+ resolveReviewWindow: (input) => host.resolveReviewWindow(input)
21224
21127
  });
21225
21128
  if (areDocumentsReady.value && !proxy.$superdoc.config.collaboration) isReady.value = true;
21226
21129
  isFloatingCommentsReady.value = true;
@@ -21488,12 +21391,6 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
21488
21391
  v2ReviewSidebarUnlocked.value = true;
21489
21392
  v2GeometryEpoch.value = v2GeometryPublisher.getLastEpoch();
21490
21393
  }
21491
- },
21492
- onCommittedPagePaint: (commit) => {
21493
- v2ReviewHydrationController.onCommittedPagePaint?.({
21494
- ...commit,
21495
- documentId: commit?.payload?.documentId ?? null
21496
- });
21497
21394
  }
21498
21395
  });
21499
21396
  const armV2TrackedChangeRestampGeometryRetention = (reason = "tracked-change-restamp") => {
@@ -21531,17 +21428,11 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
21531
21428
  scheduleV2GeometryPublish(payload, () => takeV2TrackedChangeRestampGeometryRetention());
21532
21429
  if (v2HasRangeSelection.value) scheduleV2SelectionToolbarStateSync();
21533
21430
  };
21534
- const onV2RenderReadiness = (payload) => {
21535
- const snapshot = payload?.snapshot ?? payload ?? null;
21536
- if (!snapshot) return;
21537
- v2ReviewHydrationController.onRenderReadiness(snapshot);
21538
- };
21539
21431
  const v2PageMetricsSnapshot = shallowRef(null);
21540
21432
  const v2MountStagesByDocumentId = /* @__PURE__ */ new Map();
21541
21433
  let latestV2MountStage = null;
21542
21434
  const onV2PageMetrics = (payload) => {
21543
21435
  if (!payload?.snapshot) return;
21544
- v2ReviewMutationReconciler.onRender();
21545
21436
  const snapshot = payload.snapshot;
21546
21437
  const metricStage = payload.stage;
21547
21438
  if (typeof HTMLElement !== "undefined" && metricStage instanceof HTMLElement) {
@@ -21581,8 +21472,7 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
21581
21472
  commentsStore.cancelImportedTrackedChangeBootstrap?.(clearedDocumentId ?? void 0);
21582
21473
  if (clearedDocumentId) v2MountStagesByDocumentId.delete(clearedDocumentId);
21583
21474
  if (latestV2MountStage && (clearedDocumentId == null || latestV2MountStage.dataset?.superdocV2DocumentId === clearedDocumentId)) latestV2MountStage = null;
21584
- v2ReviewHydrationController.reset("render-cleared");
21585
- v2ReviewMutationReconciler.reset();
21475
+ v2ReviewWindowController.reset("render-cleared");
21586
21476
  commentsStore.setV2CommentsAdapter?.(null);
21587
21477
  commentsStore.setV2TrackedChangesAdapter?.(null);
21588
21478
  commentsStore.clearEditorCommentPositions?.();
@@ -21591,49 +21481,15 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
21591
21481
  v2RulerHostStyle.value = {};
21592
21482
  v2RulerReady.value = false;
21593
21483
  };
21594
- const v2ReviewMutationReconciler = createV2ReviewMutationReconciler({
21595
- getContext: () => {
21596
- const activeEditor = proxy.$superdoc?.activeEditor ?? null;
21597
- const adapter = activeEditor?.v2TrackedChanges ?? null;
21598
- if (!adapter) return null;
21599
- return {
21600
- superdoc: proxy.$superdoc,
21601
- adapter,
21602
- documentId: activeEditor.documentId ?? null
21603
- };
21604
- },
21605
- reconcile: (context, impact) => commentsStore.reconcileTrackedChangeMutationFromV2?.({
21606
- superdoc: context.superdoc,
21607
- adapter: context.adapter,
21608
- documentId: context.documentId,
21609
- ...impact
21610
- }),
21611
- hydrate: (context, options) => commentsStore.hydrateTrackedChangesFromV2?.({
21612
- superdoc: context.superdoc,
21613
- adapter: context.adapter,
21614
- documentId: context.documentId,
21615
- ...options
21616
- }),
21617
- onReconciled: (_context, ids) => v2GeometryPublisher.recollect({
21618
- retainMissingTrackedChangeGeometry: true,
21619
- retainedTrackedChangeIds: ids,
21620
- reason: "review-reconcile"
21621
- })
21622
- });
21623
21484
  const onV2HostEvent = (document, event) => {
21624
21485
  if (!event) return;
21625
21486
  const documentId = document?.id ?? null;
21626
21487
  if (event.type === "review-mutation:started") {
21627
- const activeEditor = proxy.$superdoc?.activeEditor ?? null;
21628
- commentsStore.supersedeV2ReviewHydration?.({
21629
- commentsAdapter: activeEditor?.v2Comments ?? null,
21630
- trackedChangesAdapter: activeEditor?.v2TrackedChanges ?? null
21631
- });
21632
- v2ReviewHydrationController.beginMutation(event.reviewMutation);
21488
+ v2ReviewWindowController.beginMutation(event.reviewMutation);
21633
21489
  return;
21634
21490
  }
21635
21491
  if (event.type === "review-mutation:aborted") {
21636
- v2ReviewHydrationController.settleMutation(event.reviewMutation?.token, {
21492
+ v2ReviewWindowController.settleMutation(event.reviewMutation?.token, {
21637
21493
  outcome: "aborted",
21638
21494
  resumeDomains: ["comments", "trackedChanges"],
21639
21495
  trackedRowCount: commentsStore.getV2TrackedChangeRowCount?.(documentId) ?? null
@@ -21645,7 +21501,10 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
21645
21501
  return;
21646
21502
  }
21647
21503
  if (event.type === "collaboration:remote-changed") {
21648
- if (event.reviewChanged !== false) v2ReviewHydrationController.invalidate("collaboration:remote-review-changed");
21504
+ if (event.reviewChanged !== false) {
21505
+ v2ReviewWindowController.invalidate("collaboration:remote-review-changed");
21506
+ v2ReviewWindowController.refreshCommittedWindow("collaboration:remote-review-changed");
21507
+ }
21649
21508
  return;
21650
21509
  }
21651
21510
  if (event.type === "source:complete") {
@@ -21657,7 +21516,7 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
21657
21516
  return;
21658
21517
  }
21659
21518
  if (event.type === "mutation:rejected") {
21660
- if (event.reviewMutation?.token) v2ReviewHydrationController.settleMutation(event.reviewMutation.token, {
21519
+ if (event.reviewMutation?.token) v2ReviewWindowController.settleMutation(event.reviewMutation.token, {
21661
21520
  outcome: "rejected",
21662
21521
  resumeDomains: ["comments", "trackedChanges"],
21663
21522
  trackedRowCount: commentsStore.getV2TrackedChangeRowCount?.(documentId) ?? null
@@ -21670,37 +21529,34 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
21670
21529
  emitV2EditorUpdate();
21671
21530
  }
21672
21531
  if (event.type !== "mutation:committed") return;
21673
- if (event.reviewSidecarOnly === true) v2ReviewHydrationController.hydrateNow("review-sidecar-committed");
21532
+ if (event.origin === "history") v2ReviewWindowController.refreshCommittedWindow(`history-${event.direction}`);
21533
+ else if (event.reviewSidecarOnly === true) v2ReviewWindowController.refreshCommittedWindow("review-sidecar-committed");
21674
21534
  const reviewImpact = getV2TrackedChangeMutationImpact(event);
21675
21535
  if (Array.isArray(reviewImpact?.remappedPairs) && reviewImpact.remappedPairs.length > 0) commentsStore.remapTrackedChangeIdentities?.(reviewImpact.remappedPairs, { documentId });
21676
21536
  if (reviewImpact) armV2TrackedChangeRestampGeometryRetention("tracked-change-mutation");
21677
21537
  const activeEditor = proxy.$superdoc?.activeEditor ?? null;
21678
- const readiness = activeEditor?.documentMutationReadiness ?? null;
21679
- const receipt = event.origin === "history" ? null : event.receipt;
21680
- const canWaitForExactPaint = reviewImpact && typeof receipt?.txId === "string" && receipt.txId.length > 0 && typeof readiness?.whenPainted === "function";
21681
- const reconciliation = v2ReviewMutationReconciler.enqueueAfterPaint(reviewImpact, canWaitForExactPaint ? () => readiness.whenPainted.call(readiness, receipt) : null);
21538
+ const reconciliation = reviewImpact ? commentsStore.reconcileTrackedChangeMutationFromV2?.({
21539
+ superdoc: proxy.$superdoc,
21540
+ adapter: activeEditor?.v2TrackedChanges ?? null,
21541
+ documentId,
21542
+ ...reviewImpact
21543
+ }) : Promise.resolve({ ok: true });
21682
21544
  if (event.reviewMutation?.token) {
21683
21545
  const reviewMutation = event.reviewMutation;
21684
21546
  const allResolved = Boolean(reviewImpact?.allResolved ?? event.trackedChangeAllResolved);
21685
- const settleReviewMutation = (reconciled) => {
21686
- const reconciledAllResolved = allResolved && reconciled === true;
21687
- v2ReviewHydrationController.settleMutation(reviewMutation.token, {
21547
+ const settleReviewMutation = (result) => {
21548
+ const reconciledAllResolved = allResolved && result?.ok === true && result?.allResolved === true;
21549
+ v2ReviewWindowController.settleMutation(reviewMutation.token, {
21688
21550
  outcome: "committed",
21689
21551
  allResolved: reconciledAllResolved,
21690
21552
  resumeDomains: reconciledAllResolved ? ["comments"] : ["comments", "trackedChanges"],
21691
21553
  trackedRowCount: commentsStore.getV2TrackedChangeRowCount?.(documentId) ?? null
21692
21554
  });
21693
- if (!reconciledAllResolved) reconcileAuthoritativeV2TrackedChangeSidebar({
21694
- adapter: activeEditor?.v2TrackedChanges ?? null,
21695
- documentId,
21696
- refreshReason: "mutation-authoritative"
21697
- });
21698
21555
  };
21699
- Promise.resolve(reconciliation).then(settleReviewMutation, () => settleReviewMutation(false));
21556
+ Promise.resolve(reconciliation).then(settleReviewMutation, () => settleReviewMutation(null));
21700
21557
  return;
21701
21558
  }
21702
- if (reviewImpact?.reconcileMode === "authoritative" && reviewImpact.upsertIds.size > 0) return;
21703
- if (reviewImpact?.removedIds.size > 0 && reviewImpact.upsertIds.size === 0) return;
21559
+ Promise.resolve(reconciliation).catch(() => void 0);
21704
21560
  };
21705
21561
  const onV2LinkClick = (payload) => {
21706
21562
  linkPopover.handleLinkClick(payload);
@@ -21721,9 +21577,9 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
21721
21577
  const handleV2DocumentModeChange = () => {
21722
21578
  if (!isV2Mode.value) return;
21723
21579
  try {
21724
- v2ReviewHydrationController.hydrateNow("document-mode-change");
21580
+ v2ReviewWindowController.refreshCommittedWindow("document-mode-change");
21725
21581
  } catch (err) {
21726
- console.warn("[SuperDoc][v2] document-mode-change rehydrate failed", err);
21582
+ console.warn("[SuperDoc][v2] document-mode review-window refresh failed", err);
21727
21583
  }
21728
21584
  const republishGeometry = () => {
21729
21585
  if (!isV2Mode.value) return;
@@ -21865,19 +21721,14 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
21865
21721
  onCommentsUpdate: onEditorCommentsUpdate,
21866
21722
  onFontsResolved: onFontsResolvedFn,
21867
21723
  onPageCountKnown: proxy.$superdoc.config.onPageCountKnown ?? null,
21868
- onReviewWindowPlanned: (payload) => {
21869
- v2ReviewHydrationController.onReviewWindowPlanned?.({
21870
- ...payload,
21871
- documentId: doc.id
21872
- });
21873
- },
21874
21724
  onReviewWindowCommitted: (payload) => {
21875
- v2ReviewHydrationController.onCommittedPagePaint?.({
21725
+ v2ReviewWindowController.onCommittedPagePaint?.({
21876
21726
  ...payload,
21877
21727
  documentId: doc.id
21878
21728
  });
21879
21729
  },
21880
21730
  fontAssets: proxy.$superdoc.config.fonts,
21731
+ workerUrls: proxy.$superdoc.config.workerUrls,
21881
21732
  proofing: resolvedProofingConfig.value,
21882
21733
  isNewFile,
21883
21734
  password: getDocumentLoadPassword(doc),
@@ -22806,7 +22657,6 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
22806
22657
  onV2EditorFailed,
22807
22658
  onV2Render,
22808
22659
  onV2RenderCleared,
22809
- onV2RenderReadiness,
22810
22660
  onV2SelectionChanged,
22811
22661
  onV2HostEvent: (event) => onV2HostEvent(doc, event),
22812
22662
  onV2LinkClick,
@@ -22874,7 +22724,7 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
22874
22724
  ], 38);
22875
22725
  };
22876
22726
  }
22877
- }, [["__scopeId", "data-v-6833a014"]]);
22727
+ }, [["__scopeId", "data-v-7770edae"]]);
22878
22728
  //#endregion
22879
22729
  //#region src/core/create-app.js
22880
22730
  var PINIA_DEVTOOLS_SETUP_EVENT = "devtools-plugin:setup";
@@ -43351,7 +43201,7 @@ var SuperDoc = class extends import_eventemitter3.default {
43351
43201
  this.config.colors = shuffleArray(this.config.colors);
43352
43202
  this.userColorMap = /* @__PURE__ */ new Map();
43353
43203
  this.colorIndex = 0;
43354
- this.version = "2.4.0-next.21";
43204
+ this.version = "2.4.0-next.22";
43355
43205
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
43356
43206
  this.superdocId = config.superdocId || v4();
43357
43207
  this.colors = this.config.colors ?? [];
@@ -43768,44 +43618,34 @@ var SuperDoc = class extends import_eventemitter3.default {
43768
43618
  user.color = palette[Math.abs(hash) % palette.length];
43769
43619
  }
43770
43620
  /**
43771
- * Upgrade a local SuperDoc instance into collaboration by overwriting
43772
- * the supplied room with the current local document and comment state,
43773
- * then attaching collaboration to the live editor instance in place.
43621
+ * Upgrade a local SuperDoc instance into collaboration by creating the
43622
+ * supplied room from the current local document and comment state, then
43623
+ * attaching collaboration to the live editor instance in place.
43774
43624
  *
43775
- * This is a **destructive promotion**: the target room is authoritatively
43776
- * overwritten with the caller's current local state. It is NOT the API
43777
- * for joining an existing room without changing its content.
43625
+ * The target room must not already exist. This is not the API for joining
43626
+ * an existing room or merging its content.
43778
43627
  *
43779
43628
  * Currently limited to:
43780
43629
  * - A single DOCX document
43781
43630
  * - A supported v2 single-doc `v2Collaboration` target
43782
- * - Overwrite-and-upgrade only (no merge semantics)
43631
+ * - Create-and-upgrade only (no merge semantics)
43783
43632
  *
43784
43633
  * @returns Resolves once the collaborative runtime is ready
43785
43634
  */
43786
43635
  async upgradeToCollaboration(options) {
43787
- const { target, mountedDocumentId } = this.#validateUpgradePrerequisites(options);
43636
+ const { target } = this.#validateUpgradePrerequisites(options);
43788
43637
  this.#isUpgrading = true;
43789
43638
  try {
43790
43639
  const localSource = await this.#captureCurrentDocxSourceForUpgrade();
43791
43640
  const rollback = this.#snapshotV2UpgradeState();
43792
43641
  const lockSeed = this.#snapshotV2LockSeed();
43793
43642
  this.#assertNotDestroyed();
43794
- let ready = null;
43795
43643
  try {
43796
43644
  this.#pendingV2LockSeed = lockSeed;
43797
- ready = this.#waitForV2CollaborationReady(mountedDocumentId);
43798
- const promotion = this.#promoteSingleDocumentToV2Collaboration(target, localSource);
43799
- await ready;
43645
+ const promotion = await this.#promoteSingleDocumentToV2Collaboration(target, localSource);
43800
43646
  this.#finalizeSingleDocumentV2Collaboration(promotion, target);
43801
43647
  this.#pendingV2LockSeed = null;
43802
43648
  } catch (err) {
43803
- if (ready && this.#abortUpgrade) {
43804
- const abortUpgrade = this.#abortUpgrade;
43805
- this.#abortUpgrade = null;
43806
- abortUpgrade();
43807
- await ready.catch(() => void 0);
43808
- }
43809
43649
  this.#rollbackV2UpgradeState(rollback);
43810
43650
  throw err;
43811
43651
  }
@@ -43842,7 +43682,6 @@ var SuperDoc = class extends import_eventemitter3.default {
43842
43682
  if (rollback.storeDoc) {
43843
43683
  rollback.storeDoc.data = rollback.storeData;
43844
43684
  this.#writeStoreDocV2Collaboration(rollback.storeDoc, rollback.storeV2Collaboration);
43845
- this.#bumpStoreDocumentMountNonce(rollback.storeDoc);
43846
43685
  }
43847
43686
  }
43848
43687
  /**
@@ -43924,15 +43763,6 @@ var SuperDoc = class extends import_eventemitter3.default {
43924
43763
  }
43925
43764
  storeDoc.v2Collaboration = value;
43926
43765
  }
43927
- #bumpStoreDocumentMountNonce(storeDoc) {
43928
- const nonce = toRaw(storeDoc).editorMountNonce;
43929
- if (nonce && typeof nonce === "object" && "value" in nonce && typeof nonce.value === "number") {
43930
- nonce.value += 1;
43931
- return;
43932
- }
43933
- const writable = storeDoc;
43934
- writable.editorMountNonce = (typeof writable.editorMountNonce === "number" ? writable.editorMountNonce : 0) + 1;
43935
- }
43936
43766
  #toV2CollaborationConfig(target) {
43937
43767
  if (target.providerFamily === "liveblocks") return {
43938
43768
  providerType: "liveblocks",
@@ -43957,21 +43787,22 @@ var SuperDoc = class extends import_eventemitter3.default {
43957
43787
  ...target.params ? { params: target.params } : {}
43958
43788
  };
43959
43789
  }
43960
- #promoteSingleDocumentToV2Collaboration(target, localSource) {
43790
+ async #promoteSingleDocumentToV2Collaboration(target, localSource) {
43961
43791
  const configDoc = this.config.documents.find((d) => d.type === DOCX);
43962
43792
  const storeDoc = this.#requireSuperdocStore("upgradeToCollaboration").documents.find((d) => d.id === configDoc.id);
43963
43793
  if (!storeDoc) throw new Error("SuperDoc: source document store entry is not available for upgrade");
43794
+ const editor = storeDoc.getEditor?.();
43795
+ if (!isV2ActiveEditorFacade(editor) || typeof editor.upgradeToCollaboration !== "function") throw new Error("SuperDoc: live v2 editor cannot attach collaboration in place");
43964
43796
  const v2Collaboration = this.#toV2CollaborationConfig({
43965
43797
  ...target,
43966
43798
  roomMode: "create"
43967
43799
  });
43968
- configDoc.data = localSource;
43969
43800
  configDoc.v2Collaboration = v2Collaboration;
43970
- storeDoc.data = localSource;
43971
43801
  this.#writeStoreDocV2Collaboration(storeDoc, v2Collaboration);
43972
43802
  this.isCollaborative = true;
43973
- this.readyEditors = 0;
43974
- this.#bumpStoreDocumentMountNonce(storeDoc);
43803
+ await editor.upgradeToCollaboration(localSource, v2Collaboration);
43804
+ configDoc.data = localSource;
43805
+ storeDoc.data = localSource;
43975
43806
  return {
43976
43807
  configDoc,
43977
43808
  storeDoc
@@ -44087,35 +43918,6 @@ var SuperDoc = class extends import_eventemitter3.default {
44087
43918
  }
44088
43919
  });
44089
43920
  }
44090
- #waitForV2CollaborationReady(mountedDocumentId) {
44091
- const TIMEOUT_MS = 2e4;
44092
- return new Promise((resolve, reject) => {
44093
- let settled = false;
44094
- const cleanup = () => {
44095
- if (settled) return;
44096
- settled = true;
44097
- clearTimeout(timer);
44098
- this.off("collaboration-ready", onReady);
44099
- };
44100
- const onReady = (payload) => {
44101
- const editor = payload?.editor;
44102
- if (!isV2ActiveEditorFacade(editor)) return;
44103
- if (getActiveEditorDocumentId(editor) !== mountedDocumentId) return;
44104
- if (!editor.presence && !editor.lock) return;
44105
- cleanup();
44106
- resolve();
44107
- };
44108
- const timer = setTimeout(() => {
44109
- cleanup();
44110
- reject(/* @__PURE__ */ new Error(`SuperDoc: v2 collaboration runtime did not become ready within ${TIMEOUT_MS} ms for mounted document "${mountedDocumentId}".`));
44111
- }, TIMEOUT_MS);
44112
- this.on("collaboration-ready", onReady);
44113
- this.#abortUpgrade = () => {
44114
- cleanup();
44115
- reject(/* @__PURE__ */ new Error("SuperDoc: instance was destroyed during upgrade"));
44116
- };
44117
- });
44118
- }
44119
43921
  /**
44120
43922
  * Wait for the provider to report synced, with a timeout.
44121
43923
  *
@@ -44183,10 +43985,7 @@ var SuperDoc = class extends import_eventemitter3.default {
44183
43985
  ...typeof window !== "undefined" ? { authEndpointBaseUrl: window.location.href } : {}
44184
43986
  });
44185
43987
  if (!resolution.ok) throw new Error(`SuperDoc: upgradeToCollaboration() ${resolution.reason}: ${resolution.message}`);
44186
- return {
44187
- target: resolution.target,
44188
- mountedDocumentId
44189
- };
43988
+ return { target: resolution.target };
44190
43989
  }
44191
43990
  #unwrapMaybeRef(value) {
44192
43991
  if (value && typeof value === "object" && "value" in value) return value.value;
@@ -44770,6 +44569,7 @@ var SuperDoc = class extends import_eventemitter3.default {
44770
44569
  */
44771
44570
  addCommentsList(element) {
44772
44571
  if (!this.config?.modules?.comments || this.config.role === "viewer") return;
44572
+ this.commentsList?.close();
44773
44573
  if (element) this.config.modules.comments.element = element;
44774
44574
  this.commentsList = new SuperComments(this.config.modules?.comments, this);
44775
44575
  if (this.config.onCommentsListChange) this.config.onCommentsListChange({ isRendered: true });
@@ -45489,6 +45289,8 @@ var SuperDoc = class extends import_eventemitter3.default {
45489
45289
  }
45490
45290
  if (this.#surfaceManager) this.#surfaceManager.destroy();
45491
45291
  this.toolbar?.destroy?.();
45292
+ this.commentsList?.close();
45293
+ this.commentsList = null;
45492
45294
  if (this.app) {
45493
45295
  this.#log("[superdoc] Unmounting app");
45494
45296
  this.superdocStore?.reset();