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
package/dist/superdoc.cjs CHANGED
@@ -4,7 +4,7 @@ const require_blank_docx = require("./chunks/blank-docx-BuFAbRjs.cjs");
4
4
  const require_eventemitter3 = require("./chunks/eventemitter3-C_TAnXOl.cjs");
5
5
  const require_uuid = require("./chunks/uuid-BhG0ngwk.cjs");
6
6
  const require_jszip = require("./chunks/jszip-D8mAFF-r.cjs");
7
- const require_create_super_doc_ui = require("./chunks/create-super-doc-ui-jSJQis_0.cjs");
7
+ const require_create_super_doc_ui = require("./chunks/create-super-doc-ui-CqGmoMMA.cjs");
8
8
  const require__plugin_vue_export_helper = require("./chunks/_plugin-vue_export-helper-SDR04tiH.cjs");
9
9
  const require_constants = require("./chunks/constants-DpXuDx_g.cjs");
10
10
  let vue = require("vue");
@@ -6742,14 +6742,21 @@ function createStubGeometryPublisher() {
6742
6742
  }
6743
6743
  };
6744
6744
  }
6745
- function createStubReviewHydrationController() {
6745
+ function createStubReviewWindowController() {
6746
6746
  return {
6747
6747
  setContext() {},
6748
- onRenderReadiness() {},
6749
- hydrateNow() {},
6750
- reconcileInBackground() {},
6748
+ onCommittedPagePaint() {},
6749
+ refreshCommittedWindow() {},
6751
6750
  invalidate() {},
6751
+ beginMutation() {},
6752
+ settleMutation() {},
6752
6753
  reset() {},
6754
+ getSnapshot() {
6755
+ return null;
6756
+ },
6757
+ subscribe() {
6758
+ return () => void 0;
6759
+ },
6753
6760
  getDiagnostics() {
6754
6761
  return null;
6755
6762
  }
@@ -6778,7 +6785,7 @@ function createStubV2Integration() {
6778
6785
  EditorComponent: StubV2EditorComponent,
6779
6786
  RulerComponent: null,
6780
6787
  createGeometryPublisher: createStubGeometryPublisher,
6781
- createReviewHydrationController: createStubReviewHydrationController,
6788
+ createReviewWindowController: createStubReviewWindowController,
6782
6789
  isSyntheticTrackedChangeCommentLaneItem,
6783
6790
  isV2SyntheticTrackedChangeRow
6784
6791
  };
@@ -6803,7 +6810,7 @@ function createDefaultV2Integration() {
6803
6810
  EditorComponent: integration.EditorComponent,
6804
6811
  RulerComponent: integration.RulerComponent ?? stub.RulerComponent,
6805
6812
  createGeometryPublisher: integration.createGeometryPublisher ?? stub.createGeometryPublisher,
6806
- createReviewHydrationController: integration.createReviewHydrationController ?? stub.createReviewHydrationController,
6813
+ createReviewWindowController: integration.createReviewWindowController ?? stub.createReviewWindowController,
6807
6814
  isSyntheticTrackedChangeCommentLaneItem: integration.isSyntheticTrackedChangeCommentLaneItem ?? stub.isSyntheticTrackedChangeCommentLaneItem,
6808
6815
  isV2SyntheticTrackedChangeRow: integration.isV2SyntheticTrackedChangeRow ?? stub.isV2SyntheticTrackedChangeRow
6809
6816
  };
@@ -7052,8 +7059,6 @@ var COMMENT_DRAFT_BLOCK_OPEN_TAG = /<(?:p|div|li|blockquote|pre|h[1-6])(?:\s[^>]
7052
7059
  var COMMENT_DRAFT_BLOCK_CLOSE_TAG = /<\/(?:p|div|li|blockquote|pre|h[1-6])\s*>/gi;
7053
7060
  var COMMENT_DRAFT_ANY_TAG = /<\/?[A-Za-z][A-Za-z0-9:-]*(?:\s[^>]*)?\s*\/?>/g;
7054
7061
  var COMMENT_DRAFT_ENTITY = /&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z][a-zA-Z0-9]+);/g;
7055
- var TRACKED_CHANGE_BACKGROUND_RECONCILE_BATCH_SIZE = 50;
7056
- var TRACKED_CHANGE_BACKGROUND_RECONCILE_YIELD_TIMEOUT_MS = 50;
7057
7062
  var COMMENT_DRAFT_NAMED_ENTITIES = {
7058
7063
  amp: "&",
7059
7064
  apos: "'",
@@ -7071,20 +7076,6 @@ var shallowEqual = (a, b) => {
7071
7076
  return aKeys.every((key) => Object.prototype.hasOwnProperty.call(b, key) && Object.is(a[key], b[key]));
7072
7077
  };
7073
7078
  /**
7074
- * Yield between bounded background reconciliation batches. A full tracked-
7075
- * change catalog can contain thousands of rows; applying it in one Pinia/Vue
7076
- * turn blocks painted-window review work. `requestIdleCallback` keeps the work
7077
- * off the critical path when available, while the zero-delay fallback still
7078
- * creates a task boundary in browsers without it.
7079
- */
7080
- var yieldForTrackedChangeBackgroundReconcile = () => new Promise((resolve) => {
7081
- if (typeof globalThis.requestIdleCallback === "function") {
7082
- globalThis.requestIdleCallback(resolve, { timeout: TRACKED_CHANGE_BACKGROUND_RECONCILE_YIELD_TIMEOUT_MS });
7083
- return;
7084
- }
7085
- setTimeout(resolve, 0);
7086
- });
7087
- /**
7088
7079
  * Normalize the optional signed tracked-change detail lines
7089
7080
  * (`[{ excerpt, label }]`, TCS-LIST-005) to a stable shape; absent, empty, or
7090
7081
  * malformed input reads as `null` so legacy rows stay untouched.
@@ -7255,6 +7246,7 @@ var useCommentsStore = defineStore("comments", () => {
7255
7246
  const suppressInternalExternal = (0, vue.ref)(true);
7256
7247
  const currentCommentText = (0, vue.ref)("");
7257
7248
  const commentsList = (0, vue.ref)([]);
7249
+ const reviewDirectoryList = (0, vue.shallowRef)([]);
7258
7250
  let priorTrackedChangeThreadIndex = /* @__PURE__ */ new Map();
7259
7251
  const trackedChangeThreadIndex = (0, vue.computed)(() => {
7260
7252
  const next = buildTrackedChangeThreadIndex(commentsList.value, priorTrackedChangeThreadIndex);
@@ -7272,6 +7264,8 @@ var useCommentsStore = defineStore("comments", () => {
7272
7264
  return next;
7273
7265
  });
7274
7266
  const isCommentsListVisible = (0, vue.ref)(false);
7267
+ const isReviewDirectoryActive = (0, vue.ref)(false);
7268
+ const isReviewDirectoryLoading = (0, vue.ref)(false);
7275
7269
  const editorCommentIds = (0, vue.ref)([]);
7276
7270
  const editorCommentPositions = (0, vue.ref)({});
7277
7271
  const isCommentHighlighted = (0, vue.ref)(false);
@@ -7292,17 +7286,8 @@ var useCommentsStore = defineStore("comments", () => {
7292
7286
  return viewingVisibility.commentsVisible || viewingVisibility.trackChangesVisible;
7293
7287
  });
7294
7288
  const v2CommentsAdapter = (0, vue.shallowRef)(null);
7295
- const v2CommentSyncGenerations = /* @__PURE__ */ new WeakMap();
7296
- const commentSyncGeneration = (adapter) => v2CommentSyncGenerations.get(adapter) ?? 0;
7297
- const supersedeCommentSyncGeneration = (adapter) => {
7298
- if (!adapter || typeof adapter !== "object" && typeof adapter !== "function") return 0;
7299
- const next = commentSyncGeneration(adapter) + 1;
7300
- v2CommentSyncGenerations.set(adapter, next);
7301
- return next;
7302
- };
7303
7289
  const setV2CommentsAdapter = (adapter) => {
7304
7290
  v2CommentsAdapter.value = adapter ?? null;
7305
- if (adapter && !v2CommentSyncGenerations.has(adapter)) v2CommentSyncGenerations.set(adapter, 0);
7306
7291
  };
7307
7292
  const getV2CommentsAdapter = (superdoc) => {
7308
7293
  const fromFacade = superdoc?.activeEditor?.v2Comments ?? null;
@@ -7311,27 +7296,14 @@ var useCommentsStore = defineStore("comments", () => {
7311
7296
  };
7312
7297
  const isV2EditorActive = (superdoc) => superdoc?.activeEditor?.editorVersion === 2 || v2CommentsAdapter.value !== null;
7313
7298
  const v2TrackedChangesAdapter = (0, vue.shallowRef)(null);
7314
- const v2TrackedChangeSyncGenerations = /* @__PURE__ */ new WeakMap();
7315
- const trackedChangeSyncGeneration = (adapter) => v2TrackedChangeSyncGenerations.get(adapter) ?? 0;
7316
- const supersedeTrackedChangeSyncGeneration = (adapter) => {
7317
- if (!adapter || typeof adapter !== "object" && typeof adapter !== "function") return 0;
7318
- const next = trackedChangeSyncGeneration(adapter) + 1;
7319
- v2TrackedChangeSyncGenerations.set(adapter, next);
7320
- return next;
7321
- };
7322
7299
  const setV2TrackedChangesAdapter = (adapter) => {
7323
7300
  v2TrackedChangesAdapter.value = adapter ?? null;
7324
- if (adapter && !v2TrackedChangeSyncGenerations.has(adapter)) v2TrackedChangeSyncGenerations.set(adapter, 0);
7325
7301
  };
7326
7302
  const getV2TrackedChangesAdapter = (superdoc) => {
7327
7303
  const fromFacade = superdoc?.activeEditor?.v2TrackedChanges ?? null;
7328
7304
  if (fromFacade) return fromFacade;
7329
7305
  return v2TrackedChangesAdapter.value;
7330
7306
  };
7331
- const supersedeV2ReviewHydration = ({ commentsAdapter, trackedChangesAdapter } = {}) => ({
7332
- commentsGeneration: supersedeCommentSyncGeneration(commentsAdapter ?? v2CommentsAdapter.value),
7333
- trackedChangesGeneration: supersedeTrackedChangeSyncGeneration(trackedChangesAdapter ?? v2TrackedChangesAdapter.value)
7334
- });
7335
7307
  /**
7336
7308
  * Initialize the store
7337
7309
  *
@@ -7352,37 +7324,6 @@ var useCommentsStore = defineStore("comments", () => {
7352
7324
  if (!Array.isArray(aliases)) return [];
7353
7325
  return [...new Set(aliases.map((id) => normalizeCommentId(id)).filter(Boolean))];
7354
7326
  };
7355
- const trackedChangeItemId = (item) => normalizeCommentId(item?.id ?? item?.changeId ?? item?.address?.entityId);
7356
- const trackedChangeItemAliases = (item) => normalizeTrackedChangePositionAliases([
7357
- trackedChangeItemId(item),
7358
- item?.changeId,
7359
- item?.address?.entityId,
7360
- item?.trackedChangeAnchorKey,
7361
- item?.anchorKey,
7362
- item?.address?.anchorKey,
7363
- ...Array.isArray(item?.trackedChangePositionAliases) ? item.trackedChangePositionAliases : [],
7364
- ...Array.isArray(item?.sourceIds?.rsids) ? item.sourceIds.rsids : [],
7365
- item?.sourceIds?.wordIdDelete,
7366
- item?.sourceIds?.wordIdInsert,
7367
- ...Array.isArray(item?.sourceIds?.wordIdOther) ? item.sourceIds.wordIdOther : []
7368
- ]);
7369
- const trackedChangeItemIdentityIds = (item) => new Set(trackedChangeItemAliases(item).flatMap((id) => {
7370
- const parsedAnchor = parseTrackedChangeAnchorKey(id);
7371
- return parsedAnchor?.rawId ? [id, parsedAnchor.rawId] : [id];
7372
- }));
7373
- const dedupeTrackedChangeItems = (values) => {
7374
- const seen = /* @__PURE__ */ new Set();
7375
- const deduped = [];
7376
- for (const item of values) {
7377
- const id = trackedChangeItemId(item);
7378
- if (id) {
7379
- if (seen.has(id)) continue;
7380
- seen.add(id);
7381
- }
7382
- deduped.push(item);
7383
- }
7384
- return deduped;
7385
- };
7386
7327
  const getTrackedChangeStoryKey = (comment) => {
7387
7328
  if (!comment?.trackedChange) return null;
7388
7329
  const anchorParts = parseTrackedChangeAnchorKey(comment.trackedChangeAnchorKey);
@@ -7493,7 +7434,9 @@ var useCommentsStore = defineStore("comments", () => {
7493
7434
  */
7494
7435
  const getComment = (id) => {
7495
7436
  if (id === void 0 || id === null) return null;
7496
- return commentsList.value.find((c) => c.commentId == id || c.importedId == id || c.trackedChangeAnchorKey == id || c?.trackedChange && buildTrackedChangeImportedPositionId(c.importedId) == id) || getTrackedChangeCommentByPositionAlias(id);
7437
+ const directMatch = commentsList.value.find((c) => c.commentId == id || c.importedId == id || c.trackedChangeAnchorKey == id || c?.trackedChange && buildTrackedChangeImportedPositionId(c.importedId) == id);
7438
+ if (directMatch) return directMatch;
7439
+ return reviewDirectoryList.value.find((c) => c.commentId == id || c.importedId == id || c.trackedChangeAnchorKey == id || c?.trackedChange && buildTrackedChangeImportedPositionId(c.importedId) == id) || getTrackedChangeCommentByPositionAlias(id);
7497
7440
  };
7498
7441
  const getThreadParent = (comment) => {
7499
7442
  if (!comment?.parentCommentId) return comment;
@@ -7970,8 +7913,8 @@ var useCommentsStore = defineStore("comments", () => {
7970
7913
  ...commentAliasIds
7971
7914
  ].filter(Boolean).some((id) => incomingAliasIds.has(id))) return true;
7972
7915
  const hasSyntheticSideIdentity = normalizedTrackedChangeCanonicalId && normalizedTrackedChangeCanonicalId !== normalizedChangeId || commentCanonicalId && commentCanonicalId !== commentId;
7973
- if (commentId === normalizedChangeId) return true;
7974
- if (!hasSyntheticSideIdentity && (importedId === normalizedChangeId || normalizedImportedId !== void 0 && normalizedImportedId != null && importedId === normalizedImportedId)) return true;
7916
+ if (hasCompatibleStory && commentId === normalizedChangeId) return true;
7917
+ if (hasCompatibleStory && !hasSyntheticSideIdentity && (importedId === normalizedChangeId || normalizedImportedId !== void 0 && normalizedImportedId != null && importedId === normalizedImportedId)) return true;
7975
7918
  if (hasSyntheticSideIdentity) return false;
7976
7919
  if (normalizedAnchorKey && commentAnchorKey) return commentAnchorKey === normalizedAnchorKey;
7977
7920
  return false;
@@ -8283,11 +8226,11 @@ var useCommentsStore = defineStore("comments", () => {
8283
8226
  * @param {(a: Object, b: Object) => number} sorter - Comparator function for sorting comments
8284
8227
  * @returns {{parentComments: Array, resolvedComments: Array}} Grouped and sorted comments
8285
8228
  */
8286
- const buildGroupedComments = (sorter) => {
8229
+ const buildGroupedCommentsFrom = (source, sorter) => {
8287
8230
  const parentComments = [];
8288
8231
  const resolvedComments = [];
8289
8232
  const childCommentMap = /* @__PURE__ */ new Map();
8290
- commentsList.value.forEach((comment) => {
8233
+ source.forEach((comment) => {
8291
8234
  if (!isThreadVisible(comment)) return;
8292
8235
  const trackedChangeThreadParentId = shouldThreadWithTrackedChange(comment) ? trackedChangeThreadParentIdForComment$1(comment) : null;
8293
8236
  const parentId = comment.trackedChange ? null : comment.parentCommentId || trackedChangeThreadParentId;
@@ -8303,8 +8246,10 @@ var useCommentsStore = defineStore("comments", () => {
8303
8246
  resolvedComments: resolvedComments.sort(sorter)
8304
8247
  };
8305
8248
  };
8249
+ const buildGroupedComments = (sorter) => buildGroupedCommentsFrom(commentsList.value, sorter);
8306
8250
  /** @type {import('vue').ComputedRef<{parentComments: Array, resolvedComments: Array}>} Comments grouped and sorted by creation time */
8307
8251
  const getGroupedComments = (0, vue.computed)(() => buildGroupedComments(compareByCreatedTime));
8252
+ const getGroupedReviewDirectory = (0, vue.computed)(() => buildGroupedCommentsFrom(reviewDirectoryList.value, compareByCreatedTime));
8308
8253
  const hasOpenTrackedChanges = (0, vue.computed)(() => getGroupedComments.value?.parentComments.some((comment) => comment.trackedChange && !comment.resolvedTime) ?? false);
8309
8254
  /** @type {import('vue').ComputedRef<{parentComments: Array, resolvedComments: Array}>} Comments grouped and sorted by document position */
8310
8255
  const getCommentsByPosition = (0, vue.computed)(() => buildGroupedComments(compareByPosition));
@@ -8524,11 +8469,32 @@ var useCommentsStore = defineStore("comments", () => {
8524
8469
  });
8525
8470
  return outcome;
8526
8471
  }
8527
- reconcileCommentsFromV2({
8472
+ const removedIds = new Set((Array.isArray(outcome.removedCommentIds) ? outcome.removedCommentIds : []).map(normalizeCommentId).filter(Boolean));
8473
+ if (removedIds.has(normalizeCommentId(commentId))) {
8474
+ if (importedId != null) removedIds.add(normalizeCommentId(importedId));
8475
+ let addedChild = true;
8476
+ while (addedChild) {
8477
+ addedChild = false;
8478
+ for (const row of commentsList.value) {
8479
+ const parentId = normalizeCommentId(row?.parentCommentId);
8480
+ if (!parentId || !removedIds.has(parentId)) continue;
8481
+ for (const alias of [row?.commentId, row?.importedId].map(normalizeCommentId).filter(Boolean)) {
8482
+ if (removedIds.has(alias)) continue;
8483
+ removedIds.add(alias);
8484
+ addedChild = true;
8485
+ }
8486
+ }
8487
+ }
8488
+ commentsList.value = commentsList.value.filter((row) => {
8489
+ return ![row?.commentId, row?.importedId].map(normalizeCommentId).filter(Boolean).some((alias) => removedIds.has(alias));
8490
+ });
8491
+ if (removedIds.has(normalizeCommentId(activeComment.value))) clearActiveCommentSelection();
8492
+ } else reconcileCommentsFromV2({
8528
8493
  superdoc,
8529
8494
  adapter: v2Adapter,
8530
8495
  documentId: v2Adapter.documentId ?? fileId,
8531
- items: outcome.items
8496
+ items: outcome.items ?? [],
8497
+ pruneStale: outcome.complete !== false
8532
8498
  });
8533
8499
  superdoc.emit("comments-update", {
8534
8500
  type: COMMENT_EVENTS.DELETED,
@@ -8699,6 +8665,78 @@ var useCommentsStore = defineStore("comments", () => {
8699
8665
  fileType: null
8700
8666
  })).filter(Boolean);
8701
8667
  };
8668
+ const isBlankDirectoryCommentText = (value) => typeof value !== "string" || value.replace(/<[^>]*>/g, "").replace(/&nbsp;/gi, " ").trim().length === 0;
8669
+ const trackedChangeDirectoryComment = (params) => {
8670
+ if (!params || params.event === "omit" || params.changeId == null) return null;
8671
+ return getPendingComment({
8672
+ documentId: params.documentId,
8673
+ commentId: params.changeId,
8674
+ trackedChange: true,
8675
+ trackedChangeText: params.trackedChangeText,
8676
+ trackedChangeType: params.trackedChangeType,
8677
+ trackedChangeDisplayType: params.trackedChangeDisplayType,
8678
+ semanticColorKey: params.semanticColorKey,
8679
+ semanticColor: params.semanticColor,
8680
+ deletedText: params.deletedText,
8681
+ trackedChangeLabel: typeof params.trackedChangeLabel === "string" && params.trackedChangeLabel.length > 0 ? params.trackedChangeLabel : null,
8682
+ trackedChangeDetailLines: normalizeTrackedChangeDetailLines(params.trackedChangeDetailLines),
8683
+ trackedChangeImagePreview: normalizeTrackedChangeImagePreview(params.trackedChangeImagePreview),
8684
+ createdTime: params.date,
8685
+ creatorId: params.authorId ?? null,
8686
+ creatorName: params.author,
8687
+ creatorEmail: params.authorEmail,
8688
+ creatorImage: params.authorImage,
8689
+ isInternal: false,
8690
+ importedAuthor: params.importedAuthor,
8691
+ ...Object.prototype.hasOwnProperty.call(params, "importedId") ? { importedId: params.importedId != null ? String(params.importedId) : null } : {},
8692
+ trackedChangeStory: params.trackedChangeStory ?? null,
8693
+ trackedChangeStoryKind: params.trackedChangeStoryKind ?? null,
8694
+ trackedChangeStoryLabel: params.trackedChangeStoryLabel ?? "",
8695
+ trackedChangeAnchorKey: params.trackedChangeAnchorKey ?? null,
8696
+ trackedChangeCanonicalId: params.trackedChangeCanonicalId ?? null,
8697
+ trackedChangePositionAliases: normalizeTrackedChangePositionAliases(params.trackedChangePositionAliases),
8698
+ selection: {
8699
+ source: DOCUMENT_EDITOR_SELECTION_SOURCE,
8700
+ selectionBounds: params.coords ?? null
8701
+ }
8702
+ });
8703
+ };
8704
+ const setReviewDirectoryFromV2 = ({ superdoc, commentItems = [], trackedChangeItems = [] } = {}) => {
8705
+ const commentsAdapter = getV2CommentsAdapter(superdoc);
8706
+ const trackedChangesAdapter = getV2TrackedChangesAdapter(superdoc);
8707
+ if (!commentsAdapter || !trackedChangesAdapter || typeof commentsAdapter.mapV2CommentToUseCommentInput !== "function" || typeof trackedChangesAdapter.mapV2TrackedChangeToCommentParams !== "function") {
8708
+ reviewDirectoryList.value = [];
8709
+ return {
8710
+ ok: false,
8711
+ reason: "adapter-unavailable"
8712
+ };
8713
+ }
8714
+ const documentId = superdoc?.activeEditor?.documentId ?? superdoc?.activeEditor?.options?.documentId ?? commentsAdapter.documentId ?? trackedChangesAdapter.documentId ?? null;
8715
+ const document = documentId != null ? superdocStore.getDocument(String(documentId)) : null;
8716
+ const mappedCommentInputs = commentItems.filter((item) => !isSyntheticTrackedChangeCommentLaneItem(item)).map((item) => commentsAdapter.mapV2CommentToUseCommentInput(item, {
8717
+ fileId: documentId,
8718
+ fileType: document?.type ?? null
8719
+ })).filter(Boolean);
8720
+ const trackedParentBySidecarId = /* @__PURE__ */ new Map();
8721
+ for (const input of mappedCommentInputs) if (input.trackedChange !== true && input.trackedChangeThreadParentId && !input.parentCommentId && isBlankDirectoryCommentText(input.commentText)) trackedParentBySidecarId.set(String(input.commentId), String(input.trackedChangeThreadParentId));
8722
+ const commentRows = mappedCommentInputs.filter((input) => !trackedParentBySidecarId.has(String(input.commentId))).map((input) => {
8723
+ const trackedParent = input.parentCommentId ? trackedParentBySidecarId.get(String(input.parentCommentId)) : null;
8724
+ if (trackedParent) {
8725
+ input.threadingParentCommentId = trackedParent;
8726
+ input.trackedChangeThreadParentId ??= trackedParent;
8727
+ }
8728
+ return useComment(input);
8729
+ });
8730
+ const trackedRows = trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).map((params) => trackedChangeDirectoryComment(params)).filter(Boolean);
8731
+ reviewDirectoryList.value = [...commentRows, ...trackedRows];
8732
+ return {
8733
+ ok: true,
8734
+ count: reviewDirectoryList.value.length
8735
+ };
8736
+ };
8737
+ const clearReviewDirectory = () => {
8738
+ reviewDirectoryList.value = [];
8739
+ };
8702
8740
  const commentInputId = (input) => {
8703
8741
  const id = input?.commentId ?? input?.importedId;
8704
8742
  return id != null ? String(id) : null;
@@ -9000,12 +9038,54 @@ var useCommentsStore = defineStore("comments", () => {
9000
9038
  if (!adapter) return false;
9001
9039
  return v2TrackedChangesAdapter.value === adapter;
9002
9040
  };
9041
+ const reviewPresentationIdentityIds = (row) => new Set([
9042
+ ...getCommentAliasIds(row),
9043
+ row?.commentId,
9044
+ row?.importedId,
9045
+ row?.trackedChangeCanonicalId,
9046
+ row?.trackedChangeAnchorKey,
9047
+ row?.parentCommentId,
9048
+ row?.threadingParentCommentId,
9049
+ row?.trackedChangeParentId,
9050
+ row?.trackedChangeThreadParentId
9051
+ ].map((value) => normalizeCommentId(value)).filter(Boolean));
9052
+ /** Keep one mounted review window plus rows whose interaction is still live. */
9053
+ const boundReviewPresentationRows = ({ documentId, visibleIds }) => {
9054
+ const retainedIds = new Set([
9055
+ ...visibleIds,
9056
+ activeComment.value,
9057
+ activeFloatingCommentInstanceId.value,
9058
+ editingCommentId.value,
9059
+ pendingComment.value?.commentId,
9060
+ instantSidebarAlignmentThreadId.value,
9061
+ instantSidebarAlignmentInstanceId.value
9062
+ ].map((value) => normalizeCommentId(value)).filter(Boolean));
9063
+ let changed = true;
9064
+ while (changed) {
9065
+ changed = false;
9066
+ for (const row of commentsList.value) {
9067
+ if (!belongsToTrackedChangeSyncDocument(row, documentId)) continue;
9068
+ const ids = reviewPresentationIdentityIds(row);
9069
+ if (![...ids].some((id) => retainedIds.has(id))) continue;
9070
+ for (const id of ids) {
9071
+ if (retainedIds.has(id)) continue;
9072
+ retainedIds.add(id);
9073
+ changed = true;
9074
+ }
9075
+ }
9076
+ }
9077
+ commentsList.value = commentsList.value.filter((row) => {
9078
+ if (!belongsToTrackedChangeSyncDocument(row, documentId)) return true;
9079
+ if (row === pendingComment.value) return true;
9080
+ return [...reviewPresentationIdentityIds(row)].some((id) => retainedIds.has(id));
9081
+ });
9082
+ };
9003
9083
  /**
9004
- * Synchronously reconcile one host-validated review snapshot. The caller
9084
+ * Synchronously reconcile one host-validated committed review window. The caller
9005
9085
  * wraps this action in one Pinia `$patch`; all fallible mapping completes
9006
9086
  * before either row family mutates.
9007
9087
  */
9008
- const applyReviewSnapshotFromV2 = ({ superdoc, commentsAdapter, trackedChangesAdapter, documentId, commentItems, trackedChangeItems, trackedList, sourceCoverageRevision, evaluatedRevision, patch } = {}) => withInteractionSpan("store.reviewSnapshot.apply", "store-reconciliation", {
9088
+ const applyReviewWindowFromV2 = ({ superdoc, commentsAdapter, trackedChangesAdapter, documentId, commentItems, trackedChangeItems, requestedCommentIds, requestedTrackedChangeIds, unresolvedCommentIds, trackedList, sourceCoverageRevision, evaluatedRevision, patch } = {}) => withInteractionSpan("store.reviewWindow.apply", "store-reconciliation", {
9009
9089
  commentItemCount: Array.isArray(commentItems) ? commentItems.length : null,
9010
9090
  trackedItemCount: Array.isArray(trackedChangeItems) ? trackedChangeItems.length : null
9011
9091
  }, () => {
@@ -9035,7 +9115,7 @@ var useCommentsStore = defineStore("comments", () => {
9035
9115
  ok: false,
9036
9116
  reason: "items-invalid"
9037
9117
  };
9038
- if (trackedList?.complete !== false || !["review-snapshot", "visible-window"].includes(trackedList?.visibleWindowSource)) return {
9118
+ if (trackedList?.complete !== false || trackedList?.visibleWindowSource !== "visible-window") return {
9039
9119
  ok: false,
9040
9120
  reason: "tracked-list-not-partial-visible-window"
9041
9121
  };
@@ -9044,7 +9124,6 @@ var useCommentsStore = defineStore("comments", () => {
9044
9124
  reason: "adapter-mapper-missing"
9045
9125
  };
9046
9126
  let effectiveCommentItems = commentItems;
9047
- let commentItemsArePartial = false;
9048
9127
  if (typeof commentsAdapter.seedReviewCatalog === "function") {
9049
9128
  const seeded = commentsAdapter.seedReviewCatalog(commentItems, {
9050
9129
  sourceCoverageRevision,
@@ -9055,10 +9134,7 @@ var useCommentsStore = defineStore("comments", () => {
9055
9134
  reason: seeded?.reason ?? "comment-catalog-seed-failed"
9056
9135
  };
9057
9136
  const selected = commentsAdapter.selectVisibleReviewComments?.();
9058
- if (selected?.ok === true && Array.isArray(selected.items) && (!Array.isArray(selected.unresolvedIds) || selected.unresolvedIds.length === 0)) {
9059
- effectiveCommentItems = selected.items;
9060
- commentItemsArePartial = true;
9061
- }
9137
+ if (selected?.ok === true && Array.isArray(selected.items) && (!Array.isArray(selected.unresolvedIds) || selected.unresolvedIds.length === 0)) effectiveCommentItems = selected.items;
9062
9138
  }
9063
9139
  const fileType = (normalizedDocumentId ? superdocStore.getDocument(normalizedDocumentId) : null)?.type ?? null;
9064
9140
  let preparedInputs;
@@ -9080,6 +9156,28 @@ var useCommentsStore = defineStore("comments", () => {
9080
9156
  ok: false,
9081
9157
  reason: "patch-missing"
9082
9158
  };
9159
+ const visibleIds = new Set([
9160
+ ...Array.isArray(requestedCommentIds) ? requestedCommentIds : [],
9161
+ ...Array.isArray(requestedTrackedChangeIds) ? requestedTrackedChangeIds : [],
9162
+ ...Array.isArray(unresolvedCommentIds) ? unresolvedCommentIds : [],
9163
+ ...effectiveCommentItems.flatMap((item) => [
9164
+ item?.id,
9165
+ item?.commentId,
9166
+ item?.importedId
9167
+ ]),
9168
+ ...trackedChangeItems.flatMap((item) => [item?.id, item?.changeId]),
9169
+ ...preparedInputs.flatMap((input) => [
9170
+ input?.commentId,
9171
+ input?.importedId,
9172
+ input?.parentCommentId,
9173
+ input?.trackedChangeThreadParentId
9174
+ ]),
9175
+ ...preparedParams.flatMap((params) => [
9176
+ params?.changeId,
9177
+ params?.trackedChangeCanonicalId,
9178
+ params?.trackedChangeAnchorKey
9179
+ ])
9180
+ ].map((value) => normalizeCommentId(value)).filter(Boolean));
9083
9181
  patch(() => {
9084
9182
  reconcileTrackedChangesFromV2({
9085
9183
  superdoc,
@@ -9095,7 +9193,11 @@ var useCommentsStore = defineStore("comments", () => {
9095
9193
  documentId: normalizedDocumentId,
9096
9194
  items: effectiveCommentItems,
9097
9195
  preparedInputs,
9098
- pruneStale: !commentItemsArePartial
9196
+ pruneStale: false
9197
+ });
9198
+ boundReviewPresentationRows({
9199
+ documentId: normalizedDocumentId,
9200
+ visibleIds
9099
9201
  });
9100
9202
  });
9101
9203
  return {
@@ -9104,70 +9206,6 @@ var useCommentsStore = defineStore("comments", () => {
9104
9206
  trackedItems: preparedParams.length
9105
9207
  };
9106
9208
  });
9107
- const hydrateCommentsFromV2 = async ({ superdoc, adapter, documentId, trackedChangesListMode = "all", commentIds, isCurrent, signal, hydrationGeneration } = {}) => withInteractionSpan("store.comments.hydrateFromV2", "comments-list", { documentId: documentId ?? null }, async () => {
9108
- const effectiveAdapter = adapter ?? getV2CommentsAdapter(superdoc);
9109
- const visibleWindow = trackedChangesListMode === "visible-window";
9110
- const read = visibleWindow ? effectiveAdapter?.selectVisibleReviewComments : effectiveAdapter?.refresh;
9111
- if (!effectiveAdapter || typeof read !== "function") return {
9112
- ok: false,
9113
- reason: "adapter-missing"
9114
- };
9115
- const hydrationIsCurrent = () => isCurrent?.() !== false && isCurrentV2CommentsAdapter(effectiveAdapter);
9116
- if (!hydrationIsCurrent()) return {
9117
- ok: false,
9118
- reason: "adapter-stale"
9119
- };
9120
- if (signal?.aborted) return {
9121
- ok: false,
9122
- reason: "review-hydration-superseded",
9123
- lateResultDropped: false
9124
- };
9125
- const syncGeneration = commentSyncGeneration(effectiveAdapter);
9126
- let result;
9127
- try {
9128
- const refreshOptions = {};
9129
- if (visibleWindow && Array.isArray(commentIds)) refreshOptions.targetIds = commentIds;
9130
- if (signal) refreshOptions.signal = signal;
9131
- if (typeof hydrationGeneration === "number") refreshOptions.hydrationGeneration = hydrationGeneration;
9132
- result = Object.keys(refreshOptions).length > 0 ? await read.call(effectiveAdapter, refreshOptions) : await read.call(effectiveAdapter);
9133
- } catch (err) {
9134
- return {
9135
- ok: false,
9136
- reason: "adapter-threw",
9137
- detail: err?.message ?? String(err)
9138
- };
9139
- }
9140
- if (!hydrationIsCurrent()) return {
9141
- ok: false,
9142
- reason: "adapter-stale"
9143
- };
9144
- if (signal?.aborted || commentSyncGeneration(effectiveAdapter) !== syncGeneration) return {
9145
- ok: false,
9146
- reason: "review-hydration-superseded",
9147
- lateResultDropped: true
9148
- };
9149
- if (!result?.ok) return result;
9150
- if (signal?.aborted || commentSyncGeneration(effectiveAdapter) !== syncGeneration) return {
9151
- ok: false,
9152
- reason: "review-hydration-superseded",
9153
- lateResultDropped: true
9154
- };
9155
- reconcileCommentsFromV2({
9156
- superdoc,
9157
- adapter: effectiveAdapter,
9158
- documentId,
9159
- items: result.items ?? [],
9160
- pruneStale: !visibleWindow || Array.isArray(commentIds) && (!Array.isArray(result.unresolvedIds) || result.unresolvedIds.length === 0),
9161
- hydrationGeneration
9162
- });
9163
- return {
9164
- ok: true,
9165
- items: result.items ?? [],
9166
- reconciled: true,
9167
- ...typeof hydrationGeneration === "number" ? { hydrationGeneration } : {},
9168
- ...result.visibleWindowSource != null ? { visibleWindowSource: result.visibleWindowSource } : {}
9169
- };
9170
- });
9171
9209
  const reconcileCommentsFromV2 = ({ superdoc, adapter, documentId, items, preparedInputs = null, pruneStale = true, hydrationGeneration } = {}) => withInteractionSpan("store.comments.reconcile", "store-reconciliation", {
9172
9210
  documentId: documentId ?? null,
9173
9211
  itemCount: Array.isArray(items) ? items.length : null,
@@ -9390,27 +9428,6 @@ var useCommentsStore = defineStore("comments", () => {
9390
9428
  storySnapshots
9391
9429
  };
9392
9430
  };
9393
- const reconcileAuthoritativeV2TrackedChangeSidebar = ({ superdoc, editor, documentId, refreshReason = "tracked-change-sidebar-authoritative" } = {}) => {
9394
- const adapter = getV2TrackedChangesAdapter(superdoc) ?? editor?.v2TrackedChanges ?? null;
9395
- if (!adapter || typeof adapter.listTrackedChanges !== "function" || typeof adapter.mapV2TrackedChangeToCommentParams !== "function") return false;
9396
- const effectiveDocumentId = documentId ?? adapter.documentId ?? editor?.documentId ?? editor?.options?.documentId ?? superdoc?.activeEditor?.documentId ?? superdoc?.activeEditor?.options?.documentId ?? null;
9397
- if (effectiveDocumentId == null) return false;
9398
- const normalizedDocumentId = String(effectiveDocumentId);
9399
- hydrateTrackedChangesFromV2({
9400
- superdoc,
9401
- adapter,
9402
- documentId: normalizedDocumentId,
9403
- trackedChangesListMode: "background-reconcile",
9404
- refreshReason,
9405
- blocking: false,
9406
- isCurrent: () => {
9407
- const activeEditor = superdoc?.activeEditor ?? null;
9408
- const activeDocumentId = activeEditor?.documentId ?? activeEditor?.options?.documentId ?? null;
9409
- return activeDocumentId == null || String(activeDocumentId) === normalizedDocumentId;
9410
- }
9411
- });
9412
- return true;
9413
- };
9414
9431
  /**
9415
9432
  * Bootstrap tracked-change comment threads after a DOCX import finishes.
9416
9433
  *
@@ -9448,12 +9465,6 @@ var useCommentsStore = defineStore("comments", () => {
9448
9465
  editor,
9449
9466
  structuralChanges: captured.structuralChanges
9450
9467
  });
9451
- reconcileAuthoritativeV2TrackedChangeSidebar({
9452
- superdoc,
9453
- editor,
9454
- documentId: editor?.options?.documentId,
9455
- refreshReason: "import-bootstrap-authoritative"
9456
- });
9457
9468
  return true;
9458
9469
  };
9459
9470
  const isCurrentImportedTrackedChangeBootstrap = (task) => {
@@ -10122,7 +10133,6 @@ var useCommentsStore = defineStore("comments", () => {
10122
10133
  removed: 0,
10123
10134
  removedIds: /* @__PURE__ */ new Set()
10124
10135
  };
10125
- supersedeTrackedChangeSyncGeneration(adapter);
10126
10136
  const removedIds = /* @__PURE__ */ new Set();
10127
10137
  const activeId = normalizeCommentId(activeComment.value);
10128
10138
  let activeWasRemoved = false;
@@ -10215,94 +10225,15 @@ var useCommentsStore = defineStore("comments", () => {
10215
10225
  reason: outcome?.reason,
10216
10226
  detail: outcome?.detail
10217
10227
  };
10218
- const reliableDecidedIds = collectReliableDecidedTrackedChangeIds(outcome);
10228
+ const receiptDecidedIds = collectReliableDecidedTrackedChangeIds(outcome);
10229
+ const localDecidedIds = receiptDecidedIds.size ? receiptDecidedIds : collectTrackedChangeDecisionIds(comment);
10219
10230
  const effectiveDocumentId = outcome?.documentId ?? v2Adapter.documentId ?? superdoc?.activeEditor?.documentId ?? superdoc?.activeEditor?.options?.documentId ?? null;
10220
- const runBlockingPostDecisionReconcile = async () => {
10221
- let listResult;
10222
- try {
10223
- listResult = await v2Adapter.listTrackedChanges({
10224
- mode: "all",
10225
- refreshReason: "post-decision-blocking",
10226
- blocking: true
10227
- });
10228
- } catch (err) {
10229
- return {
10230
- ok: false,
10231
- committed: true,
10232
- reason: "relist-after-commit-failed",
10233
- detail: err?.message ?? String(err)
10234
- };
10235
- }
10236
- if (!isCurrentV2TrackedChangesAdapter(v2Adapter)) return {
10237
- ok: false,
10238
- committed: true,
10239
- reason: "adapter-stale"
10240
- };
10241
- if (!listResult?.ok) return {
10242
- ok: false,
10243
- committed: true,
10244
- reason: "relist-after-commit-failed",
10245
- detail: listResult?.reason ?? listResult?.detail ?? null
10246
- };
10247
- resolveLinkedCommentsForTrackedChangeDecision({
10248
- superdoc,
10249
- comment,
10250
- decision
10251
- });
10252
- detachLinkedCommentsForTrackedChangeDecision({
10253
- comment,
10254
- decision
10255
- });
10256
- reconcileTrackedChangesFromV2({
10257
- superdoc,
10258
- adapter: v2Adapter,
10259
- documentId: effectiveDocumentId,
10260
- items: listResult.items ?? [],
10261
- pruneStale: listResult.complete === true && listResult.sourceCoverageComplete === true
10262
- });
10263
- const liveIds = new Set((listResult.items ?? []).map((item) => item && item.id != null ? String(item.id) : null).filter(Boolean));
10264
- if (!(decidedId != null && liveIds.has(decidedId))) {
10265
- if (wasActiveBeforeDecide) clearActiveCommentSelection();
10266
- clearV2ActiveTrackedChangeTarget(v2Adapter, decidedId);
10267
- }
10268
- const commentsAdapter = getV2CommentsAdapter(superdoc);
10269
- if (commentsAdapter && typeof commentsAdapter.refresh === "function" && isCurrentV2CommentsAdapter(commentsAdapter)) {
10270
- let commentsResult;
10271
- try {
10272
- commentsResult = await commentsAdapter.refresh();
10273
- } catch (err) {
10274
- return {
10275
- ok: false,
10276
- committed: true,
10277
- reason: "comments-relist-after-tracked-decision-failed",
10278
- detail: err?.message ?? String(err)
10279
- };
10280
- }
10281
- if (!isCurrentV2CommentsAdapter(commentsAdapter)) return {
10282
- ok: false,
10283
- committed: true,
10284
- reason: "comments-adapter-stale"
10285
- };
10286
- if (!commentsResult?.ok) return {
10287
- ok: false,
10288
- committed: true,
10289
- reason: "comments-relist-after-tracked-decision-failed",
10290
- detail: commentsResult?.reason ?? commentsResult?.detail ?? null
10291
- };
10292
- reconcileCommentsFromV2({
10293
- superdoc,
10294
- adapter: commentsAdapter,
10295
- documentId: commentsAdapter.documentId ?? effectiveDocumentId,
10296
- items: commentsResult.items ?? []
10297
- });
10298
- }
10299
- return {
10300
- ok: true,
10301
- success: true
10302
- };
10231
+ if (!localDecidedIds.size || !effectiveDocumentId) return {
10232
+ ok: false,
10233
+ committed: true,
10234
+ reason: "decision-scope-missing"
10303
10235
  };
10304
- if (!reliableDecidedIds.size || !effectiveDocumentId) return runBlockingPostDecisionReconcile();
10305
- reliableDecidedIds.forEach((id) => clearV2ActiveTrackedChangeTarget(v2Adapter, id));
10236
+ localDecidedIds.forEach((id) => clearV2ActiveTrackedChangeTarget(v2Adapter, id));
10306
10237
  await waitForTrackedDecisionCanonicalPaint({
10307
10238
  superdoc,
10308
10239
  receipt: outcome.receipt
@@ -10319,7 +10250,7 @@ var useCommentsStore = defineStore("comments", () => {
10319
10250
  pruneDecidedTrackedChangeRow({
10320
10251
  superdoc,
10321
10252
  comment,
10322
- decidedIds: reliableDecidedIds,
10253
+ decidedIds: localDecidedIds,
10323
10254
  documentId: effectiveDocumentId
10324
10255
  });
10325
10256
  if (wasActiveBeforeDecide) clearActiveCommentSelection();
@@ -10362,166 +10293,11 @@ var useCommentsStore = defineStore("comments", () => {
10362
10293
  success: Boolean(command(id))
10363
10294
  };
10364
10295
  };
10365
- 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", {
10366
- documentId: documentId ?? null,
10367
- trackedChangesListMode,
10368
- refreshReason: refreshReason ?? null,
10369
- blocking: blocking ?? null
10370
- }, async () => {
10371
- const effectiveAdapter = adapter ?? getV2TrackedChangesAdapter(superdoc);
10372
- if (!effectiveAdapter || typeof effectiveAdapter.listTrackedChanges !== "function") return {
10373
- ok: false,
10374
- reason: "adapter-missing"
10375
- };
10376
- const hydrationIsCurrent = () => isCurrent?.() !== false && isCurrentV2TrackedChangesAdapter(effectiveAdapter);
10377
- if (!hydrationIsCurrent()) return {
10378
- ok: false,
10379
- reason: "adapter-stale"
10380
- };
10381
- const syncGeneration = trackedChangeSyncGeneration(effectiveAdapter);
10382
- const hydrationCanContinue = () => hydrationIsCurrent() && !signal?.aborted && trackedChangeSyncGeneration(effectiveAdapter) === syncGeneration;
10383
- if (signal?.aborted) return {
10384
- ok: false,
10385
- reason: "review-hydration-superseded",
10386
- lateResultDropped: false
10387
- };
10388
- const incrementalLiveIds = /* @__PURE__ */ new Set();
10389
- const incrementalLiveAnchorKeys = /* @__PURE__ */ new Set();
10390
- let incrementalPagesApplied = 0;
10391
- let incrementalItemsApplied = 0;
10392
- let result;
10393
- try {
10394
- const listOptions = { mode: trackedChangesListMode };
10395
- if (Array.isArray(targetIds)) listOptions.targetIds = targetIds;
10396
- if (refreshReason != null) listOptions.refreshReason = refreshReason;
10397
- if (typeof blocking === "boolean") listOptions.blocking = blocking;
10398
- if (signal) listOptions.signal = signal;
10399
- if (typeof hydrationGeneration === "number") listOptions.hydrationGeneration = hydrationGeneration;
10400
- if (trackedChangesListMode === "background-reconcile") {
10401
- listOptions.shouldContinue = hydrationCanContinue;
10402
- listOptions.onPage = async (page) => {
10403
- if (!hydrationCanContinue()) return;
10404
- const pageItems = Array.isArray(page?.items) ? page.items : [];
10405
- if (!reconcileTrackedChangesFromV2({
10406
- superdoc,
10407
- adapter: effectiveAdapter,
10408
- documentId,
10409
- items: pageItems,
10410
- pruneStale: false,
10411
- liveIds: incrementalLiveIds,
10412
- liveAnchorKeys: incrementalLiveAnchorKeys,
10413
- hydrationGeneration
10414
- }) || !hydrationCanContinue()) return;
10415
- incrementalPagesApplied += 1;
10416
- incrementalItemsApplied += pageItems.length;
10417
- };
10418
- }
10419
- result = await effectiveAdapter.listTrackedChanges(listOptions);
10420
- } catch (err) {
10421
- return {
10422
- ok: false,
10423
- reason: "adapter-threw",
10424
- detail: err?.message ?? String(err)
10425
- };
10426
- }
10427
- if (!hydrationIsCurrent()) return {
10428
- ok: false,
10429
- reason: "adapter-stale"
10430
- };
10431
- if (trackedChangeSyncGeneration(effectiveAdapter) !== syncGeneration) return {
10432
- ok: false,
10433
- reason: "review-hydration-superseded",
10434
- lateResultDropped: true
10435
- };
10436
- if (signal?.aborted) return {
10437
- ok: false,
10438
- reason: "review-hydration-superseded",
10439
- lateResultDropped: true
10440
- };
10441
- if (!result?.ok) return result;
10442
- const partialListMode = trackedChangesListMode === "startup-page" || trackedChangesListMode === "interaction-prime" || trackedChangesListMode === "visible-window";
10443
- 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;
10444
- if (signal?.aborted || trackedChangeSyncGeneration(effectiveAdapter) !== syncGeneration) return {
10445
- ok: false,
10446
- reason: "review-hydration-superseded",
10447
- lateResultDropped: true
10448
- };
10449
- if (trackedChangesListMode === "background-reconcile") if (incrementalPagesApplied > 0) {
10450
- if (!hydrationCanContinue()) return {
10451
- ok: false,
10452
- reason: "reconciliation-stale"
10453
- };
10454
- const effectiveDocumentId = documentId ?? effectiveAdapter.documentId ?? superdoc?.activeEditor?.documentId ?? null;
10455
- if (pruneStale && effectiveDocumentId) withInteractionSpan("store.trackedChanges.reconcile.finalPrune", "store-reconciliation", {
10456
- documentId: effectiveDocumentId,
10457
- liveIds: incrementalLiveIds.size,
10458
- liveAnchorKeys: incrementalLiveAnchorKeys.size
10459
- }, () => {
10460
- if (!hydrationCanContinue()) return;
10461
- pruneStaleTrackedChangeComments(incrementalLiveIds, incrementalLiveAnchorKeys, String(effectiveDocumentId), superdoc, { broadcastChanges: false });
10462
- });
10463
- } else {
10464
- const reconcileResult = await reconcileTrackedChangesFromV2Incrementally({
10465
- superdoc,
10466
- adapter: effectiveAdapter,
10467
- documentId,
10468
- items: result.items ?? [],
10469
- pruneStale,
10470
- isCurrent: hydrationIsCurrent,
10471
- batchSize: reconciliationBatchSize,
10472
- hydrationGeneration
10473
- });
10474
- if (!reconcileResult.ok) return reconcileResult;
10475
- incrementalItemsApplied = reconcileResult.appliedItems;
10476
- }
10477
- else reconcileTrackedChangesFromV2({
10478
- superdoc,
10479
- adapter: effectiveAdapter,
10480
- documentId,
10481
- items: result.items ?? [],
10482
- pruneStale,
10483
- hydrationGeneration
10484
- });
10485
- const hydrationResult = {
10486
- ok: true,
10487
- items: result.items ?? [],
10488
- reconciled: true,
10489
- ...typeof hydrationGeneration === "number" ? { hydrationGeneration } : {}
10490
- };
10491
- if (typeof result.complete === "boolean") hydrationResult.complete = result.complete;
10492
- if (typeof result.sourceCoverageComplete === "boolean") hydrationResult.sourceCoverageComplete = result.sourceCoverageComplete;
10493
- if (result.visibleWindowSource != null) hydrationResult.visibleWindowSource = result.visibleWindowSource;
10494
- if (result.visibleWindowFallbackReason != null) hydrationResult.visibleWindowFallbackReason = result.visibleWindowFallbackReason;
10495
- if (typeof result.visibleTrackedChangeExpectedRows === "number") hydrationResult.visibleTrackedChangeExpectedRows = result.visibleTrackedChangeExpectedRows;
10496
- if (typeof result.visibleTrackedChangeObservedRows === "number") hydrationResult.visibleTrackedChangeObservedRows = result.visibleTrackedChangeObservedRows;
10497
- for (const key of [
10498
- "visibleTrackedChangeRequestedIds",
10499
- "visibleTrackedChangeSucceededIds",
10500
- "visibleTrackedChangeFailedIds"
10501
- ]) if (Array.isArray(result[key])) hydrationResult[key] = [...result[key]];
10502
- if (Array.isArray(result.visibleWindowGetFailures)) hydrationResult.visibleWindowGetFailures = result.visibleWindowGetFailures.map((failure) => ({ ...failure }));
10503
- if (typeof result.refreshReason === "string") hydrationResult.refreshReason = result.refreshReason;
10504
- if (typeof result.blocking === "boolean") hydrationResult.blocking = result.blocking;
10505
- if (typeof result.pageCount === "number") hydrationResult.pageCount = result.pageCount;
10506
- if (typeof result.pagesStarted === "number") hydrationResult.pagesStarted = result.pagesStarted;
10507
- if (typeof result.pagesCompleted === "number") hydrationResult.pagesCompleted = result.pagesCompleted;
10508
- if (typeof result.pagesSkippedAfterAbort === "number") hydrationResult.pagesSkippedAfterAbort = result.pagesSkippedAfterAbort;
10509
- if (typeof result.totalItems === "number") hydrationResult.totalItems = result.totalItems;
10510
- if (typeof result.coalesced === "boolean") hydrationResult.coalesced = result.coalesced;
10511
- if (typeof result.evaluatedRevision === "string") hydrationResult.evaluatedRevision = result.evaluatedRevision;
10512
- if (trackedChangesListMode === "background-reconcile") {
10513
- hydrationResult.incrementalPagesApplied = incrementalPagesApplied;
10514
- hydrationResult.incrementalItemsApplied = incrementalItemsApplied;
10515
- }
10516
- return hydrationResult;
10517
- });
10518
10296
  /**
10519
- * Reconcile receipt-addressed tracked-change identities without enumerating
10520
- * the full catalog. Removals converge synchronously; surviving/new identities
10521
- * use the adapter's narrow get and then flow through the ordinary row mapper.
10522
- * A later idle hydration remains the bounded completeness check.
10297
+ * Apply only receipt-owned removals. Remaps are updated in place by the
10298
+ * caller; surviving/new rows arrive with the next committed review window.
10523
10299
  */
10524
- const reconcileTrackedChangeMutationFromV2 = async ({ superdoc, adapter, documentId, upsertIds = [], removedIds = [], allResolved } = {}) => {
10300
+ const reconcileTrackedChangeMutationFromV2 = async ({ superdoc, adapter, documentId, upsertIds = [], removedIds = [], remappedPairs = [], allResolved } = {}) => {
10525
10301
  const effectiveAdapter = adapter ?? getV2TrackedChangesAdapter(superdoc);
10526
10302
  if (!effectiveAdapter) return {
10527
10303
  ok: false,
@@ -10551,73 +10327,21 @@ var useCommentsStore = defineStore("comments", () => {
10551
10327
  documentId: effectiveDocumentId
10552
10328
  })).removed
10553
10329
  };
10554
- const syncGeneration = trackedChangeSyncGeneration(effectiveAdapter);
10555
10330
  const normalizedRemovedIds = new Set(Array.from(removedIds ?? []).map((id) => normalizeCommentId(id)).filter(Boolean));
10556
10331
  const normalizedUpsertIds = [...new Set(Array.from(upsertIds ?? []).map((id) => normalizeCommentId(id)).filter(Boolean))];
10557
- if (normalizedRemovedIds.size > 0 && normalizedUpsertIds.length === 0 && effectiveDocumentId != null) pruneDecidedTrackedChangeRow({
10332
+ const remappedSourceIds = new Set((Array.isArray(remappedPairs) ? remappedPairs : []).map((pair) => normalizeCommentId(pair?.from)).filter(Boolean));
10333
+ const locallyRemovedIds = new Set([...normalizedRemovedIds].filter((id) => !remappedSourceIds.has(id)));
10334
+ if (locallyRemovedIds.size > 0 && effectiveDocumentId != null) pruneDecidedTrackedChangeRow({
10558
10335
  superdoc,
10559
- decidedIds: normalizedRemovedIds,
10336
+ decidedIds: locallyRemovedIds,
10560
10337
  documentId: effectiveDocumentId
10561
10338
  });
10562
- if (normalizedUpsertIds.length === 0) return {
10339
+ return {
10563
10340
  ok: true,
10564
10341
  items: [],
10565
10342
  resolvedIds: [],
10566
- unresolvedIds: []
10567
- };
10568
- if (typeof effectiveAdapter.getTrackedChange !== "function") return {
10569
- ok: false,
10570
- reason: "adapter-get-missing"
10571
- };
10572
- const reads = await Promise.all(normalizedUpsertIds.map(async (id) => {
10573
- try {
10574
- return {
10575
- id,
10576
- result: await effectiveAdapter.getTrackedChange(id)
10577
- };
10578
- } catch (err) {
10579
- return {
10580
- id,
10581
- result: {
10582
- ok: false,
10583
- reason: "adapter-threw",
10584
- detail: err?.message ?? String(err)
10585
- }
10586
- };
10587
- }
10588
- }));
10589
- if (!isCurrentV2TrackedChangesAdapter(effectiveAdapter)) return {
10590
- ok: false,
10591
- reason: "adapter-stale"
10592
- };
10593
- if (trackedChangeSyncGeneration(effectiveAdapter) !== syncGeneration) return {
10594
- ok: false,
10595
- reason: "generation-stale"
10596
- };
10597
- const resolvedIds = [];
10598
- const unresolvedIds = [];
10599
- const items = [];
10600
- for (const { id, result } of reads) {
10601
- const matchingItems = (result?.ok && Array.isArray(result.items) ? result.items : []).filter((item) => trackedChangeItemIdentityIds(item).has(id));
10602
- if (matchingItems.length > 0) {
10603
- resolvedIds.push(id);
10604
- items.push(...matchingItems);
10605
- } else unresolvedIds.push(id);
10606
- }
10607
- const dedupedItems = dedupeTrackedChangeItems(items);
10608
- if (items.length > 0) reconcileTrackedChangesFromV2({
10609
- superdoc,
10610
- adapter: effectiveAdapter,
10611
- documentId: effectiveDocumentId,
10612
- items: dedupedItems,
10613
- pruneStale: false
10614
- });
10615
- return {
10616
- ok: unresolvedIds.length === 0,
10617
- items: dedupedItems,
10618
- resolvedIds,
10619
- unresolvedIds,
10620
- failures: reads.map(({ result }) => result).filter((result) => !result?.ok)
10343
+ unresolvedIds: normalizedUpsertIds,
10344
+ removedIds: [...locallyRemovedIds]
10621
10345
  };
10622
10346
  };
10623
10347
  const createTrackedChangeBatchIdentityIndex = (documentId) => {
@@ -10721,84 +10445,6 @@ var useCommentsStore = defineStore("comments", () => {
10721
10445
  appliedCount
10722
10446
  };
10723
10447
  });
10724
- /**
10725
- * Cooperatively apply an authoritative background catalog. Each batch is a
10726
- * non-pruning merge, so interruption can only leave extra/stale rows in the
10727
- * sidebar; destructive pruning happens once, after every batch has applied
10728
- * and the caller's generation/revision guard is still current.
10729
- */
10730
- const reconcileTrackedChangesFromV2Incrementally = async ({ superdoc, adapter, documentId, items, pruneStale, isCurrent, batchSize = TRACKED_CHANGE_BACKGROUND_RECONCILE_BATCH_SIZE, hydrationGeneration } = {}) => {
10731
- if (!adapter || !Array.isArray(items)) return {
10732
- ok: false,
10733
- reason: "reconcile-input-invalid"
10734
- };
10735
- const current = () => isCurrent?.() !== false && isCurrentV2TrackedChangesAdapter(adapter);
10736
- if (!current()) return {
10737
- ok: false,
10738
- reason: "reconciliation-stale"
10739
- };
10740
- const effectiveBatchSize = Math.max(1, Math.floor(Number(batchSize) || TRACKED_CHANGE_BACKGROUND_RECONCILE_BATCH_SIZE));
10741
- const effectiveDocumentId = documentId ?? adapter.documentId ?? superdoc?.activeEditor?.documentId ?? null;
10742
- const liveIds = /* @__PURE__ */ new Set();
10743
- const liveAnchorKeys = /* @__PURE__ */ new Set();
10744
- let appliedItems = 0;
10745
- let batchCount = 0;
10746
- for (let offset = 0; offset < items.length; offset += effectiveBatchSize) {
10747
- if (!current()) return {
10748
- ok: false,
10749
- reason: "reconciliation-stale",
10750
- appliedItems,
10751
- batchCount
10752
- };
10753
- const batch = items.slice(offset, offset + effectiveBatchSize);
10754
- if (!reconcileTrackedChangesFromV2({
10755
- superdoc,
10756
- adapter,
10757
- documentId: effectiveDocumentId,
10758
- items: batch,
10759
- pruneStale: false,
10760
- liveIds,
10761
- liveAnchorKeys,
10762
- hydrationGeneration
10763
- }) || !current()) return {
10764
- ok: false,
10765
- reason: "reconciliation-stale",
10766
- appliedItems,
10767
- batchCount
10768
- };
10769
- appliedItems += batch.length;
10770
- batchCount += 1;
10771
- if (offset + effectiveBatchSize < items.length) await yieldForTrackedChangeBackgroundReconcile();
10772
- }
10773
- if (!current()) return {
10774
- ok: false,
10775
- reason: "reconciliation-stale",
10776
- appliedItems,
10777
- batchCount
10778
- };
10779
- if (pruneStale && effectiveDocumentId) {
10780
- withInteractionSpan("store.trackedChanges.reconcile.finalPrune", "store-reconciliation", {
10781
- documentId: effectiveDocumentId,
10782
- liveIds: liveIds.size,
10783
- liveAnchorKeys: liveAnchorKeys.size
10784
- }, () => {
10785
- if (!current()) return;
10786
- pruneStaleTrackedChangeComments(liveIds, liveAnchorKeys, String(effectiveDocumentId), superdoc, { broadcastChanges: false });
10787
- });
10788
- if (!current()) return {
10789
- ok: false,
10790
- reason: "reconciliation-stale",
10791
- appliedItems,
10792
- batchCount
10793
- };
10794
- }
10795
- return {
10796
- ok: true,
10797
- appliedItems,
10798
- batchCount,
10799
- pruned: Boolean(pruneStale && effectiveDocumentId)
10800
- };
10801
- };
10802
10448
  const getV2TrackedChangeRowCount = (documentId = null) => {
10803
10449
  const normalizedDocumentId = documentId == null ? null : String(documentId);
10804
10450
  return commentsList.value.filter((comment) => {
@@ -10844,12 +10490,6 @@ var useCommentsStore = defineStore("comments", () => {
10844
10490
  broadcastChanges,
10845
10491
  structuralChanges: captured.structuralChanges
10846
10492
  });
10847
- reconcileAuthoritativeV2TrackedChangeSidebar({
10848
- superdoc,
10849
- editor,
10850
- documentId: activeDocumentId,
10851
- refreshReason: "sync-authoritative"
10852
- });
10853
10493
  };
10854
10494
  /**
10855
10495
  * Surface decidable whole-table structural tracked changes (table insert /
@@ -11419,7 +11059,10 @@ var useCommentsStore = defineStore("comments", () => {
11419
11059
  pendingComment,
11420
11060
  currentCommentText,
11421
11061
  commentsList,
11062
+ reviewDirectoryList,
11422
11063
  isCommentsListVisible,
11064
+ isReviewDirectoryActive,
11065
+ isReviewDirectoryLoading,
11423
11066
  generalCommentIds,
11424
11067
  editorCommentIds,
11425
11068
  commentsParentElement,
@@ -11437,6 +11080,7 @@ var useCommentsStore = defineStore("comments", () => {
11437
11080
  getConfig,
11438
11081
  documentsWithConverations,
11439
11082
  getGroupedComments,
11083
+ getGroupedReviewDirectory,
11440
11084
  hasOpenTrackedChanges,
11441
11085
  getCommentsByPosition,
11442
11086
  getFloatingComments,
@@ -11468,6 +11112,8 @@ var useCommentsStore = defineStore("comments", () => {
11468
11112
  removePendingComment,
11469
11113
  cancelImportedTrackedChangeBootstrap,
11470
11114
  removeCommentsForDocument,
11115
+ setReviewDirectoryFromV2,
11116
+ clearReviewDirectory,
11471
11117
  processLoadedDocxComments,
11472
11118
  translateCommentsForExport,
11473
11119
  handleEditorLocationsUpdate,
@@ -11485,9 +11131,7 @@ var useCommentsStore = defineStore("comments", () => {
11485
11131
  v2CommentsAdapter,
11486
11132
  setV2CommentsAdapter,
11487
11133
  getV2CommentsAdapter,
11488
- hydrateCommentsFromV2,
11489
- applyReviewSnapshotFromV2,
11490
- supersedeV2ReviewHydration,
11134
+ applyReviewWindowFromV2,
11491
11135
  reconcileCommentsFromV2,
11492
11136
  isV2EditorActive,
11493
11137
  replyCommentV2,
@@ -11498,7 +11142,6 @@ var useCommentsStore = defineStore("comments", () => {
11498
11142
  setV2TrackedChangesAdapter,
11499
11143
  getV2TrackedChangesAdapter,
11500
11144
  getV2TrackedChangeRowCount,
11501
- hydrateTrackedChangesFromV2,
11502
11145
  reconcileTrackedChangeMutationFromV2,
11503
11146
  reconcileTrackedChangesFromV2,
11504
11147
  remapTrackedChangeIdentities
@@ -11637,15 +11280,15 @@ function useUiFontFamily() {
11637
11280
  }
11638
11281
  //#endregion
11639
11282
  //#region src/components/CommentsLayer/CommentsDropdown.vue
11640
- var _hoisted_1$30 = { class: "comments-dropdown" };
11283
+ var _hoisted_1$31 = { class: "comments-dropdown" };
11641
11284
  var _hoisted_2$24 = ["onClick"];
11642
11285
  var _hoisted_3$20 = {
11643
11286
  key: 0,
11644
11287
  class: "comments-dropdown__option-icon"
11645
11288
  };
11646
- var _hoisted_4$13 = ["innerHTML"];
11289
+ var _hoisted_4$12 = ["innerHTML"];
11647
11290
  var _hoisted_5$8 = { class: "comments-dropdown__option-label" };
11648
- var _sfc_main$37 = {
11291
+ var _sfc_main$38 = {
11649
11292
  __name: "CommentsDropdown",
11650
11293
  props: {
11651
11294
  options: {
@@ -11768,7 +11411,7 @@ var _sfc_main$37 = {
11768
11411
  window.removeEventListener("scroll", updateMenuPosition, true);
11769
11412
  });
11770
11413
  return (_ctx, _cache) => {
11771
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$30, [(0, vue.createElementVNode)("div", {
11414
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$31, [(0, vue.createElementVNode)("div", {
11772
11415
  ref_key: "triggerRef",
11773
11416
  ref: triggerRef,
11774
11417
  class: "comments-dropdown__trigger",
@@ -11787,7 +11430,7 @@ var _sfc_main$37 = {
11787
11430
  }, [hasIcon(option) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_3$20, [option.iconString ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
11788
11431
  key: 0,
11789
11432
  innerHTML: option.iconString
11790
- }, null, 8, _hoisted_4$13)) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(OptionIcon), {
11433
+ }, null, 8, _hoisted_4$12)) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(OptionIcon), {
11791
11434
  key: 1,
11792
11435
  option
11793
11436
  }, null, 8, ["option"]))])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("span", _hoisted_5$8, (0, vue.toDisplayString)(option.label), 1)], 10, _hoisted_2$24);
@@ -11795,14 +11438,14 @@ var _sfc_main$37 = {
11795
11438
  };
11796
11439
  }
11797
11440
  };
11798
- var CommentsDropdown_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$37, [["__scopeId", "data-v-7eced80a"]]);
11441
+ var CommentsDropdown_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$38, [["__scopeId", "data-v-7eced80a"]]);
11799
11442
  //#endregion
11800
11443
  //#region src/components/CommentsLayer/InternalDropdown.vue
11801
- var _hoisted_1$29 = { class: "sd-comment-option" };
11444
+ var _hoisted_1$30 = { class: "sd-comment-option" };
11802
11445
  var _hoisted_2$23 = ["innerHTML"];
11803
11446
  var _hoisted_3$19 = { class: "sd-option-state" };
11804
- var _hoisted_4$12 = ["innerHTML"];
11805
- var _sfc_main$36 = {
11447
+ var _hoisted_4$11 = ["innerHTML"];
11448
+ var _sfc_main$37 = {
11806
11449
  __name: "InternalDropdown",
11807
11450
  props: {
11808
11451
  state: {
@@ -11869,7 +11512,7 @@ var _sfc_main$36 = {
11869
11512
  disabled: __props.isDisabled,
11870
11513
  "content-style": { fontFamily: (0, vue.unref)(uiFontFamily) }
11871
11514
  }, {
11872
- default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", _hoisted_1$29, [
11515
+ default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", _hoisted_1$30, [
11873
11516
  (0, vue.createElementVNode)("div", {
11874
11517
  class: "sd-active-icon",
11875
11518
  innerHTML: activeIcon.value
@@ -11878,14 +11521,14 @@ var _sfc_main$36 = {
11878
11521
  (0, vue.createElementVNode)("div", {
11879
11522
  class: "sd-dropdown-caret",
11880
11523
  innerHTML: (0, vue.unref)(superdocIcons).caretDown
11881
- }, null, 8, _hoisted_4$12)
11524
+ }, null, 8, _hoisted_4$11)
11882
11525
  ])]),
11883
11526
  _: 1
11884
11527
  }, 8, ["disabled", "content-style"])], 4);
11885
11528
  };
11886
11529
  }
11887
11530
  };
11888
- var InternalDropdown_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$36, [["__scopeId", "data-v-138d641e"]]);
11531
+ var InternalDropdown_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$37, [["__scopeId", "data-v-138d641e"]]);
11889
11532
  //#endregion
11890
11533
  //#region src/components/CommentsLayer/helpers.js
11891
11534
  /**
@@ -12028,13 +11671,13 @@ var isAllowed = (permission, role, isInternal, context = {}) => {
12028
11671
  };
12029
11672
  //#endregion
12030
11673
  //#region src/components/general/Avatar.vue
12031
- var _hoisted_1$28 = { class: "user-container" };
11674
+ var _hoisted_1$29 = { class: "user-container" };
12032
11675
  var _hoisted_2$22 = ["src"];
12033
11676
  var _hoisted_3$18 = {
12034
11677
  key: 1,
12035
11678
  class: "user-bg"
12036
11679
  };
12037
- var _sfc_main$35 = {
11680
+ var _sfc_main$36 = {
12038
11681
  __name: "Avatar",
12039
11682
  props: { user: {
12040
11683
  type: Object,
@@ -12051,7 +11694,7 @@ var _sfc_main$35 = {
12051
11694
  return email?.substring(0, 1)?.toUpperCase() || null;
12052
11695
  };
12053
11696
  return (_ctx, _cache) => {
12054
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$28, [__props.user.image ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("img", {
11697
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$29, [__props.user.image ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("img", {
12055
11698
  key: 0,
12056
11699
  class: "user-bg",
12057
11700
  src: __props.user.image.startsWith("http") ? __props.user.image : `data:image/png;base64,${__props.user.image}`
@@ -12059,13 +11702,13 @@ var _sfc_main$35 = {
12059
11702
  };
12060
11703
  }
12061
11704
  };
12062
- var Avatar_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$35, [["__scopeId", "data-v-c95b2073"]]);
11705
+ var Avatar_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$36, [["__scopeId", "data-v-c95b2073"]]);
12063
11706
  //#endregion
12064
11707
  //#region src/components/CommentsLayer/CommentHeader.vue
12065
- var _hoisted_1$27 = { class: "card-section comment-header" };
11708
+ var _hoisted_1$28 = { class: "card-section comment-header" };
12066
11709
  var _hoisted_2$21 = { class: "comment-header-left" };
12067
11710
  var _hoisted_3$17 = { class: "user-info" };
12068
- var _hoisted_4$11 = { class: "user-name" };
11711
+ var _hoisted_4$10 = { class: "user-name" };
12069
11712
  var _hoisted_5$7 = {
12070
11713
  key: 0,
12071
11714
  class: "imported-tag"
@@ -12091,7 +11734,7 @@ var _hoisted_9$3 = [
12091
11734
  ];
12092
11735
  var _hoisted_10$3 = { class: "overflow-menu__icon" };
12093
11736
  var _hoisted_11$3 = ["innerHTML"];
12094
- var _sfc_main$34 = {
11737
+ var _sfc_main$35 = {
12095
11738
  __name: "CommentHeader",
12096
11739
  props: {
12097
11740
  timestamp: {
@@ -12295,10 +11938,10 @@ var _sfc_main$34 = {
12295
11938
  return user;
12296
11939
  });
12297
11940
  return (_ctx, _cache) => {
12298
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$27, [(0, vue.createElementVNode)("div", _hoisted_2$21, [(0, vue.createVNode)(Avatar_default, {
11941
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$28, [(0, vue.createElementVNode)("div", _hoisted_2$21, [(0, vue.createVNode)(Avatar_default, {
12299
11942
  user: getCurrentUser.value,
12300
11943
  class: "avatar"
12301
- }, null, 8, ["user"]), (0, vue.createElementVNode)("div", _hoisted_3$17, [(0, vue.createElementVNode)("div", _hoisted_4$11, [(0, vue.createTextVNode)((0, vue.toDisplayString)(getCurrentUser.value.name), 1), isImported.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_5$7, "IMPORTED")) : (0, vue.createCommentVNode)("", true)]), props.comment.createdTime ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_6$6, (0, vue.toDisplayString)((0, vue.unref)(formatDate)(props.comment.createdTime)), 1)) : (0, vue.createCommentVNode)("", true)])]), (0, vue.createElementVNode)("div", { class: (0, vue.normalizeClass)(["overflow-menu", { "is-visible": props.isActive }]) }, [
11944
+ }, null, 8, ["user"]), (0, vue.createElementVNode)("div", _hoisted_3$17, [(0, vue.createElementVNode)("div", _hoisted_4$10, [(0, vue.createTextVNode)((0, vue.toDisplayString)(getCurrentUser.value.name), 1), isImported.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_5$7, "IMPORTED")) : (0, vue.createCommentVNode)("", true)]), props.comment.createdTime ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_6$6, (0, vue.toDisplayString)((0, vue.unref)(formatDate)(props.comment.createdTime)), 1)) : (0, vue.createCommentVNode)("", true)])]), (0, vue.createElementVNode)("div", { class: (0, vue.normalizeClass)(["overflow-menu", { "is-visible": props.isActive }]) }, [
12302
11945
  allowResolve.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
12303
11946
  key: 0,
12304
11947
  class: (0, vue.normalizeClass)(["overflow-menu__icon", { "sd-is-disabled": Boolean(__props.resolveDisabledReason) }]),
@@ -12344,13 +11987,13 @@ var _sfc_main$34 = {
12344
11987
  };
12345
11988
  }
12346
11989
  };
12347
- var CommentHeader_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$34, [["__scopeId", "data-v-ca6050aa"]]);
11990
+ var CommentHeader_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$35, [["__scopeId", "data-v-ca6050aa"]]);
12348
11991
  //#endregion
12349
11992
  //#region src/components/CommentsLayer/CommentInput.vue
12350
- var _hoisted_1$26 = { class: "input-section" };
11993
+ var _hoisted_1$27 = { class: "input-section" };
12351
11994
  var TEXTAREA_MIN_HEIGHT = 28;
12352
11995
  var TEXTAREA_MAX_HEIGHT = 132;
12353
- var _sfc_main$33 = {
11996
+ var _sfc_main$34 = {
12354
11997
  __name: "CommentInput",
12355
11998
  props: {
12356
11999
  users: {
@@ -12420,7 +12063,7 @@ var _sfc_main$33 = {
12420
12063
  (0, vue.watch)(currentCommentText, scheduleInputHeightSync);
12421
12064
  __expose({ focus });
12422
12065
  return (_ctx, _cache) => {
12423
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$26, [__props.includeHeader ? ((0, vue.openBlock)(), (0, vue.createBlock)(CommentHeader_default, {
12066
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$27, [__props.includeHeader ? ((0, vue.openBlock)(), (0, vue.createBlock)(CommentHeader_default, {
12424
12067
  key: 0,
12425
12068
  config: __props.config,
12426
12069
  comment: __props.comment,
@@ -12439,10 +12082,11 @@ var _sfc_main$33 = {
12439
12082
  };
12440
12083
  }
12441
12084
  };
12442
- var CommentInput_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$33, [["__scopeId", "data-v-cc54b489"]]);
12085
+ var CommentInput_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$34, [["__scopeId", "data-v-cc54b489"]]);
12443
12086
  //#endregion
12444
12087
  //#region src/components/CommentsLayer/CommentDialog.vue
12445
- var _hoisted_1$25 = [
12088
+ var _hoisted_1$26 = [
12089
+ "aria-label",
12446
12090
  "data-comment-instance-id",
12447
12091
  "data-comment-thread-id",
12448
12092
  "data-comment-position-key",
@@ -12453,7 +12097,7 @@ var _hoisted_2$20 = {
12453
12097
  class: "existing-internal-input"
12454
12098
  };
12455
12099
  var _hoisted_3$16 = { class: "new-comment-input-wrapper" };
12456
- var _hoisted_4$10 = { class: "reply-actions" };
12100
+ var _hoisted_4$9 = { class: "reply-actions" };
12457
12101
  var _hoisted_5$6 = ["disabled"];
12458
12102
  var _hoisted_6$5 = {
12459
12103
  key: 0,
@@ -12544,7 +12188,7 @@ var _hoisted_50 = { class: "reply-actions" };
12544
12188
  var _hoisted_51 = ["disabled", "data-disabled-reason"];
12545
12189
  var CLICK_OUTSIDE_HIT_TOLERANCE_PX = 3;
12546
12190
  var TRACKED_CHANGE_PREVIEW_MAX_PX = 90;
12547
- var _sfc_main$32 = {
12191
+ var _sfc_main$33 = {
12548
12192
  __name: "CommentDialog",
12549
12193
  props: {
12550
12194
  comment: {
@@ -12574,6 +12218,10 @@ var _sfc_main$32 = {
12574
12218
  isFloatingInstanceActive: {
12575
12219
  type: Boolean,
12576
12220
  default: void 0
12221
+ },
12222
+ threadComments: {
12223
+ type: Array,
12224
+ default: null
12577
12225
  }
12578
12226
  },
12579
12227
  emits: [
@@ -12776,6 +12424,11 @@ var _sfc_main$32 = {
12776
12424
  };
12777
12425
  const comments = (0, vue.computed)(() => {
12778
12426
  const parentComment = props.comment;
12427
+ if (Array.isArray(props.threadComments)) return [...props.threadComments].sort((a, b) => {
12428
+ if (a.commentId === parentComment.commentId) return -1;
12429
+ if (b.commentId === parentComment.commentId) return 1;
12430
+ return a.createdTime - b.createdTime;
12431
+ });
12779
12432
  if (parentComment.trackedChange) return commentsStore.getTrackedChangeThread(parentComment);
12780
12433
  return commentsStore.commentsList.filter((comment) => {
12781
12434
  const isThreadedComment = comment.parentCommentId === parentComment.commentId;
@@ -12787,6 +12440,11 @@ var _sfc_main$32 = {
12787
12440
  return a.createdTime - b.createdTime;
12788
12441
  });
12789
12442
  });
12443
+ const dialogAccessibleLabel = (0, vue.computed)(() => {
12444
+ if (props.comment.trackedChangeLabel) return props.comment.trackedChangeLabel;
12445
+ if (props.comment.trackedChange) return "Tracked change discussion";
12446
+ return props.comment.creatorName ? `Comment by ${props.comment.creatorName}` : "Comment thread";
12447
+ });
12790
12448
  const textExpanded = (0, vue.ref)(false);
12791
12449
  const parentBodyRef = (0, vue.ref)(null);
12792
12450
  const isTextOverflowing = (0, vue.ref)(false);
@@ -13385,6 +13043,7 @@ var _sfc_main$32 = {
13385
13043
  ref_key: "commentDialogElement",
13386
13044
  ref: commentDialogElement,
13387
13045
  role: "dialog",
13046
+ "aria-label": dialogAccessibleLabel.value,
13388
13047
  "data-sd-part": "comment-thread",
13389
13048
  "data-editor-ui-surface": "",
13390
13049
  "data-comment-instance-id": props.floatingInstanceId ?? "",
@@ -13416,7 +13075,7 @@ var _sfc_main$32 = {
13416
13075
  "config",
13417
13076
  "comment"
13418
13077
  ])]),
13419
- (0, vue.createElementVNode)("div", _hoisted_4$10, [(0, vue.createElementVNode)("button", {
13078
+ (0, vue.createElementVNode)("div", _hoisted_4$9, [(0, vue.createElementVNode)("button", {
13420
13079
  class: "sd-button reply-btn-cancel",
13421
13080
  onClick: (0, vue.withModifiers)(handleCancel, ["stop", "prevent"])
13422
13081
  }, "Cancel"), (0, vue.createElementVNode)("button", {
@@ -13593,22 +13252,161 @@ var _sfc_main$32 = {
13593
13252
  disabled: !hasTextContent.value || isV2WriteDisabled.value || isSubmittingReply.value,
13594
13253
  "data-disabled-reason": v2ReplyDisabledReason.value
13595
13254
  }, " Reply ", 10, _hoisted_51)])]))], 64)) : (0, vue.createCommentVNode)("", true)
13596
- ], 64))], 14, _hoisted_1$25)), [[_directive_click_outside, handleClickOutside]]);
13255
+ ], 64))], 14, _hoisted_1$26)), [[_directive_click_outside, handleClickOutside]]);
13597
13256
  };
13598
13257
  }
13599
13258
  };
13600
- var CommentDialog_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$32, [["__scopeId", "data-v-f26decd0"]]);
13259
+ var CommentDialog_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$33, [["__scopeId", "data-v-0277e53b"]]);
13601
13260
  //#endregion
13602
- //#region src/components/CommentsLayer/commentsList/commentsList.vue
13603
- var _hoisted_1$24 = { class: "comments-list" };
13604
- var _hoisted_2$19 = { key: 0 };
13605
- var _hoisted_3$15 = [
13261
+ //#region src/components/CommentsLayer/commentsList/ReviewDirectoryListItem.vue
13262
+ var _hoisted_1$25 = [
13263
+ "tabindex",
13264
+ "aria-posinset",
13265
+ "aria-setsize",
13266
+ "aria-label",
13606
13267
  "data-comment-instance-id",
13607
13268
  "data-comment-thread-id",
13608
13269
  "data-comment-position-key",
13609
- "data-comment-page-index"
13270
+ "data-comment-page-index",
13271
+ "data-review-directory-index"
13610
13272
  ];
13611
- var _hoisted_4$9 = { key: 1 };
13273
+ var _sfc_main$32 = {
13274
+ __name: "ReviewDirectoryListItem",
13275
+ props: {
13276
+ item: {
13277
+ type: Object,
13278
+ required: true
13279
+ },
13280
+ index: {
13281
+ type: Number,
13282
+ required: true
13283
+ },
13284
+ total: {
13285
+ type: Number,
13286
+ required: true
13287
+ },
13288
+ tabIndex: {
13289
+ type: Number,
13290
+ default: -1
13291
+ }
13292
+ },
13293
+ emits: ["focus-item", "navigate"],
13294
+ setup(__props, { emit: __emit }) {
13295
+ const props = __props;
13296
+ const emit = __emit;
13297
+ const { activeComment, editingCommentId, pendingComment } = storeToRefs(useCommentsStore());
13298
+ const shell = (0, vue.ref)(null);
13299
+ const measuredHeight = (0, vue.ref)(112);
13300
+ const intersects = (0, vue.ref)(typeof IntersectionObserver === "undefined");
13301
+ const containsFocus = (0, vue.ref)(false);
13302
+ let intersectionObserver = null;
13303
+ let resizeObserver = null;
13304
+ const threadIds = (0, vue.computed)(() => new Set((props.item.threadComments ?? []).flatMap((comment) => [
13305
+ comment?.commentId,
13306
+ comment?.importedId,
13307
+ comment?.trackedChangeCanonicalId
13308
+ ]).filter((id) => id != null).map(String)));
13309
+ const isPinned = (0, vue.computed)(() => {
13310
+ const pendingId = pendingComment.value?.commentId;
13311
+ 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));
13312
+ });
13313
+ const materialized = (0, vue.computed)(() => intersects.value || isPinned.value);
13314
+ const placeholderStyle = (0, vue.computed)(() => materialized.value ? void 0 : { minHeight: `${Math.max(1, measuredHeight.value)}px` });
13315
+ const accessibleSummary = (0, vue.computed)(() => {
13316
+ const comment = props.item.comment;
13317
+ const text = comment?.trackedChangeLabel ?? comment?.commentText ?? comment?.trackedChangeText ?? "Review item";
13318
+ return String(text).replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().slice(0, 160) || "Review item";
13319
+ });
13320
+ const measure = () => {
13321
+ const height = shell.value?.getBoundingClientRect?.().height;
13322
+ if (Number.isFinite(height) && height > 0) measuredHeight.value = height;
13323
+ };
13324
+ const onFocusIn = () => {
13325
+ containsFocus.value = true;
13326
+ emit("focus-item", props.index);
13327
+ };
13328
+ const onKeydown = (event) => {
13329
+ if (![
13330
+ "ArrowDown",
13331
+ "ArrowUp",
13332
+ "Home",
13333
+ "End"
13334
+ ].includes(event.key)) return;
13335
+ if (event.target?.closest?.("input, textarea, select, button, [contenteditable=\"true\"]")) return;
13336
+ event.preventDefault();
13337
+ emit("navigate", {
13338
+ index: props.index,
13339
+ key: event.key
13340
+ });
13341
+ };
13342
+ (0, vue.onMounted)(() => {
13343
+ if (typeof IntersectionObserver !== "undefined") {
13344
+ intersectionObserver = new IntersectionObserver((entries) => {
13345
+ const entry = entries[0];
13346
+ if (!entry) return;
13347
+ intersects.value = entry.isIntersecting;
13348
+ if (entry.isIntersecting) (0, vue.nextTick)(measure);
13349
+ }, {
13350
+ root: null,
13351
+ rootMargin: "600px 0px"
13352
+ });
13353
+ intersectionObserver.observe(shell.value);
13354
+ }
13355
+ if (typeof ResizeObserver !== "undefined") {
13356
+ resizeObserver = new ResizeObserver(measure);
13357
+ resizeObserver.observe(shell.value);
13358
+ }
13359
+ (0, vue.nextTick)(measure);
13360
+ });
13361
+ (0, vue.onBeforeUnmount)(() => {
13362
+ intersectionObserver?.disconnect();
13363
+ resizeObserver?.disconnect();
13364
+ });
13365
+ return (_ctx, _cache) => {
13366
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
13367
+ ref_key: "shell",
13368
+ ref: shell,
13369
+ class: "comment-item",
13370
+ role: "listitem",
13371
+ tabindex: __props.tabIndex,
13372
+ "aria-posinset": __props.index + 1,
13373
+ "aria-setsize": __props.total,
13374
+ "aria-label": accessibleSummary.value,
13375
+ style: (0, vue.normalizeStyle)(placeholderStyle.value),
13376
+ "data-comment-instance-id": __props.item.floatingInstanceId ?? "",
13377
+ "data-comment-thread-id": __props.item.comment.commentId ?? "",
13378
+ "data-comment-position-key": __props.item.comment.trackedChangeAnchorKey ?? "",
13379
+ "data-comment-page-index": Number.isFinite(__props.item.floatingPageIndex) ? __props.item.floatingPageIndex : "",
13380
+ "data-review-directory-index": __props.index,
13381
+ onFocusin: onFocusIn,
13382
+ onFocusout: _cache[0] || (_cache[0] = ($event) => containsFocus.value = shell.value?.contains?.($event.relatedTarget) === true),
13383
+ onKeydown
13384
+ }, [materialized.value ? ((0, vue.openBlock)(), (0, vue.createBlock)(CommentDialog_default, {
13385
+ key: 0,
13386
+ comment: __props.item.comment,
13387
+ "thread-comments": __props.item.threadComments,
13388
+ "floating-instance-id": __props.item.floatingInstanceId,
13389
+ "floating-page-index": __props.item.floatingPageIndex,
13390
+ "floating-position-entry": __props.item.floatingPositionEntry,
13391
+ "is-floating-instance-active": __props.item.isFloatingInstanceActive,
13392
+ onResize: measure
13393
+ }, null, 8, [
13394
+ "comment",
13395
+ "thread-comments",
13396
+ "floating-instance-id",
13397
+ "floating-page-index",
13398
+ "floating-position-entry",
13399
+ "is-floating-instance-active"
13400
+ ])) : (0, vue.createCommentVNode)("", true)], 44, _hoisted_1$25);
13401
+ };
13402
+ }
13403
+ };
13404
+ var ReviewDirectoryListItem_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$32, [["__scopeId", "data-v-29650a06"]]);
13405
+ //#endregion
13406
+ //#region src/components/CommentsLayer/commentsList/commentsList.vue
13407
+ var _hoisted_1$24 = ["aria-busy"];
13408
+ var _hoisted_2$19 = { key: 0 };
13409
+ var _hoisted_3$15 = { key: 1 };
13612
13410
  var REPEATED_HEADER_FOOTER_STORY_TYPE = "headerFooterPart";
13613
13411
  var _sfc_main$31 = {
13614
13412
  __name: "commentsList",
@@ -13625,10 +13423,41 @@ var _sfc_main$31 = {
13625
13423
  setup(__props) {
13626
13424
  const props = __props;
13627
13425
  const commentsStore = useCommentsStore();
13426
+ const listRoot = (0, vue.ref)(null);
13427
+ const rovingIndex = (0, vue.ref)(0);
13628
13428
  const isCommentsListVisible = (0, vue.toRef)(commentsStore, "isCommentsListVisible");
13629
- const groupedComments = (0, vue.computed)(() => commentsStore.getGroupedComments);
13630
13429
  const floatingCommentInstances = (0, vue.computed)(() => commentsStore.getFloatingCommentInstances);
13631
- const { activeComment, activeFloatingCommentInstanceId } = storeToRefs(commentsStore);
13430
+ const { activeComment, activeFloatingCommentInstanceId, commentsList, reviewDirectoryList, isReviewDirectoryActive, isReviewDirectoryLoading } = storeToRefs(commentsStore);
13431
+ const groupedComments = (0, vue.computed)(() => isReviewDirectoryActive.value ? commentsStore.getGroupedReviewDirectory : commentsStore.getGroupedComments);
13432
+ const reviewRows = (0, vue.computed)(() => isReviewDirectoryActive.value ? reviewDirectoryList.value : commentsList.value);
13433
+ const commentAliases = (comment) => [
13434
+ comment?.commentId,
13435
+ comment?.importedId,
13436
+ comment?.trackedChangeCanonicalId
13437
+ ].filter((id) => id != null).map(String);
13438
+ const directoryThreads = (0, vue.computed)(() => {
13439
+ const threadsByAlias = /* @__PURE__ */ new Map();
13440
+ for (const comment of reviewRows.value) {
13441
+ const thread = [comment];
13442
+ for (const alias of commentAliases(comment)) threadsByAlias.set(alias, thread);
13443
+ }
13444
+ for (const comment of reviewRows.value) {
13445
+ const parentThread = [
13446
+ comment?.parentCommentId,
13447
+ comment?.threadingParentCommentId,
13448
+ comment?.trackedChangeThreadParentId
13449
+ ].filter((id) => id != null).map(String).map((alias) => threadsByAlias.get(alias)).find(Boolean);
13450
+ if (parentThread && !parentThread.includes(comment)) parentThread.push(comment);
13451
+ }
13452
+ return threadsByAlias;
13453
+ });
13454
+ const threadCommentsFor = (parentComment) => {
13455
+ for (const alias of commentAliases(parentComment)) {
13456
+ const thread = directoryThreads.value.get(alias);
13457
+ if (thread) return thread;
13458
+ }
13459
+ return [parentComment];
13460
+ };
13632
13461
  const getRepeatedHeaderFooterInstances = (comment) => {
13633
13462
  const matchingInstances = floatingCommentInstances.value.filter((instance) => instance?.comment?.commentId === comment?.commentId);
13634
13463
  if (matchingInstances.length < 2) return [];
@@ -13650,7 +13479,8 @@ var _sfc_main$31 = {
13650
13479
  floatingInstanceId: null,
13651
13480
  floatingPageIndex: null,
13652
13481
  floatingPositionEntry: null,
13653
- isFloatingInstanceActive: void 0
13482
+ isFloatingInstanceActive: void 0,
13483
+ threadComments: threadCommentsFor(comment)
13654
13484
  }];
13655
13485
  return repeatedInstances.map((instance) => ({
13656
13486
  id: instance.id,
@@ -13658,7 +13488,8 @@ var _sfc_main$31 = {
13658
13488
  floatingInstanceId: String(instance.id),
13659
13489
  floatingPageIndex: instance.pageIndex,
13660
13490
  floatingPositionEntry: instance.positionEntry ?? null,
13661
- isFloatingInstanceActive: isRepeatedInstanceActive(instance)
13491
+ isFloatingInstanceActive: isRepeatedInstanceActive(instance),
13492
+ threadComments: threadCommentsFor(comment)
13662
13493
  }));
13663
13494
  };
13664
13495
  const parentCommentDisplayItems = (0, vue.computed)(() => {
@@ -13667,6 +13498,30 @@ var _sfc_main$31 = {
13667
13498
  const shouldShowResolvedComments = (0, vue.computed)(() => {
13668
13499
  return props.showResolvedComments && groupedComments.value?.resolvedComments?.length > 0;
13669
13500
  });
13501
+ const resolvedCommentDisplayItems = (0, vue.computed)(() => groupedComments.value.resolvedComments.map((comment) => ({
13502
+ id: comment.commentId,
13503
+ comment,
13504
+ floatingInstanceId: null,
13505
+ floatingPageIndex: null,
13506
+ floatingPositionEntry: null,
13507
+ isFloatingInstanceActive: void 0,
13508
+ threadComments: threadCommentsFor(comment)
13509
+ })));
13510
+ const displayedMainCount = (0, vue.computed)(() => props.showMainComments ? parentCommentDisplayItems.value.length : 0);
13511
+ const totalDisplayItems = (0, vue.computed)(() => displayedMainCount.value + (shouldShowResolvedComments.value ? resolvedCommentDisplayItems.value.length : 0));
13512
+ (0, vue.watch)(totalDisplayItems, (total) => {
13513
+ rovingIndex.value = Math.max(0, Math.min(rovingIndex.value, total - 1));
13514
+ });
13515
+ const focusDirectoryItem = async ({ index, key }) => {
13516
+ const lastIndex = totalDisplayItems.value - 1;
13517
+ if (lastIndex < 0) return;
13518
+ const nextIndex = key === "Home" ? 0 : key === "End" ? lastIndex : key === "ArrowUp" ? Math.max(0, index - 1) : Math.min(lastIndex, index + 1);
13519
+ rovingIndex.value = nextIndex;
13520
+ await (0, vue.nextTick)();
13521
+ const target = listRoot.value?.querySelector?.(`[data-review-directory-index="${nextIndex}"]`);
13522
+ target?.scrollIntoView?.({ block: "nearest" });
13523
+ target?.focus?.({ preventScroll: true });
13524
+ };
13670
13525
  (0, vue.onMounted)(() => {
13671
13526
  isCommentsListVisible.value = true;
13672
13527
  });
@@ -13674,37 +13529,47 @@ var _sfc_main$31 = {
13674
13529
  isCommentsListVisible.value = false;
13675
13530
  });
13676
13531
  return (_ctx, _cache) => {
13677
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$24, [__props.showMainComments ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$19, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(parentCommentDisplayItems.value, (item) => {
13678
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
13532
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
13533
+ ref_key: "listRoot",
13534
+ ref: listRoot,
13535
+ class: "comments-list",
13536
+ role: "list",
13537
+ "aria-busy": (0, vue.unref)(isReviewDirectoryActive) && (0, vue.unref)(isReviewDirectoryLoading) ? "true" : "false"
13538
+ }, [__props.showMainComments ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$19, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(parentCommentDisplayItems.value, (item, index) => {
13539
+ return (0, vue.openBlock)(), (0, vue.createBlock)(ReviewDirectoryListItem_default, {
13679
13540
  key: item.id,
13680
- class: "comment-item",
13681
- "data-comment-instance-id": item.floatingInstanceId ?? "",
13682
- "data-comment-thread-id": item.comment.commentId ?? "",
13683
- "data-comment-position-key": item.comment.trackedChangeAnchorKey ?? "",
13684
- "data-comment-page-index": Number.isFinite(item.floatingPageIndex) ? item.floatingPageIndex : ""
13685
- }, [(0, vue.createVNode)(CommentDialog_default, {
13686
- comment: item.comment,
13687
- "floating-instance-id": item.floatingInstanceId,
13688
- "floating-page-index": item.floatingPageIndex,
13689
- "floating-position-entry": item.floatingPositionEntry,
13690
- "is-floating-instance-active": item.isFloatingInstanceActive
13541
+ item,
13542
+ index,
13543
+ total: totalDisplayItems.value,
13544
+ "tab-index": index === rovingIndex.value ? 0 : -1,
13545
+ onFocusItem: _cache[0] || (_cache[0] = ($event) => rovingIndex.value = $event),
13546
+ onNavigate: focusDirectoryItem
13691
13547
  }, null, 8, [
13692
- "comment",
13693
- "floating-instance-id",
13694
- "floating-page-index",
13695
- "floating-position-entry",
13696
- "is-floating-instance-active"
13697
- ])], 8, _hoisted_3$15);
13698
- }), 128))])) : (0, vue.createCommentVNode)("", true), shouldShowResolvedComments.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_4$9, [_cache[0] || (_cache[0] = (0, vue.createElementVNode)("div", { class: "comment-title" }, "Resolved", -1)), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(groupedComments.value.resolvedComments, (comment) => {
13699
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
13700
- key: comment.commentId,
13701
- class: "comment-item"
13702
- }, [(0, vue.createVNode)(CommentDialog_default, { comment }, null, 8, ["comment"])]);
13703
- }), 128))])) : (0, vue.createCommentVNode)("", true)]);
13548
+ "item",
13549
+ "index",
13550
+ "total",
13551
+ "tab-index"
13552
+ ]);
13553
+ }), 128))])) : (0, vue.createCommentVNode)("", true), shouldShowResolvedComments.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_3$15, [_cache[2] || (_cache[2] = (0, vue.createElementVNode)("div", { class: "comment-title" }, "Resolved", -1)), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(resolvedCommentDisplayItems.value, (item, index) => {
13554
+ return (0, vue.openBlock)(), (0, vue.createBlock)(ReviewDirectoryListItem_default, {
13555
+ key: item.id,
13556
+ item,
13557
+ index: displayedMainCount.value + index,
13558
+ total: totalDisplayItems.value,
13559
+ "tab-index": displayedMainCount.value + index === rovingIndex.value ? 0 : -1,
13560
+ onFocusItem: _cache[1] || (_cache[1] = ($event) => rovingIndex.value = $event),
13561
+ onNavigate: focusDirectoryItem
13562
+ }, null, 8, [
13563
+ "item",
13564
+ "index",
13565
+ "total",
13566
+ "tab-index"
13567
+ ]);
13568
+ }), 128))])) : (0, vue.createCommentVNode)("", true)], 8, _hoisted_1$24);
13704
13569
  };
13705
13570
  }
13706
13571
  };
13707
- var commentsList_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$31, [["__scopeId", "data-v-e89dbc33"]]);
13572
+ var commentsList_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$31, [["__scopeId", "data-v-89244745"]]);
13708
13573
  //#endregion
13709
13574
  //#region src/components/CommentsLayer/commentsList/super-comments-list.js
13710
13575
  /**
@@ -13728,8 +13593,65 @@ var SuperComments = class extends require_eventemitter3.import_eventemitter3.def
13728
13593
  this.element = this.config.element;
13729
13594
  this.app = null;
13730
13595
  this.superdoc = superdoc;
13596
+ this.stopDirectoryObservers = [];
13597
+ this.directorySnapshots = {
13598
+ comments: null,
13599
+ trackChanges: null
13600
+ };
13601
+ this.directoryItemRefs = {
13602
+ comments: null,
13603
+ trackChanges: null
13604
+ };
13605
+ this.directoryGeneration = 0;
13731
13606
  this.open();
13732
13607
  }
13608
+ syncDirectory() {
13609
+ const store = this.superdoc?.commentsStore;
13610
+ if (!store) return;
13611
+ const comments = this.directorySnapshots.comments;
13612
+ const trackChanges = this.directorySnapshots.trackChanges;
13613
+ store.isReviewDirectoryLoading = comments == null || trackChanges == null;
13614
+ if (comments == null && trackChanges == null) return;
13615
+ store.setReviewDirectoryFromV2({
13616
+ superdoc: this.superdoc,
13617
+ commentItems: comments ?? [],
13618
+ trackedChangeItems: trackChanges ?? []
13619
+ });
13620
+ }
13621
+ startDirectoryObservers() {
13622
+ const generation = ++this.directoryGeneration;
13623
+ const subscribe = (handle, family) => {
13624
+ const acceptDirectorySnapshot = (snapshot) => {
13625
+ if (generation !== this.directoryGeneration) return;
13626
+ if ((snapshot?.listStatus ?? snapshot?.status) !== "ready") {
13627
+ if (this.superdoc?.commentsStore) this.superdoc.commentsStore.isReviewDirectoryLoading = true;
13628
+ return;
13629
+ }
13630
+ const items = Array.isArray(snapshot.items) ? snapshot.items : [];
13631
+ if (this.directoryItemRefs[family] === items) {
13632
+ if (this.superdoc?.commentsStore) this.superdoc.commentsStore.isReviewDirectoryLoading = this.directorySnapshots.comments == null || this.directorySnapshots.trackChanges == null;
13633
+ return;
13634
+ }
13635
+ this.directoryItemRefs[family] = items;
13636
+ this.directorySnapshots[family] = items;
13637
+ this.syncDirectory();
13638
+ };
13639
+ const stop = handle.observe((snapshot) => {
13640
+ acceptDirectorySnapshot(snapshot);
13641
+ });
13642
+ const readAfterLeaseAcquisition = () => acceptDirectorySnapshot(handle.getSnapshot());
13643
+ if (typeof globalThis.queueMicrotask === "function") globalThis.queueMicrotask(readAfterLeaseAcquisition);
13644
+ else Promise.resolve().then(readAfterLeaseAcquisition);
13645
+ return stop;
13646
+ };
13647
+ const ui = this.superdoc?.ui;
13648
+ if (!ui) return;
13649
+ if (this.superdoc?.commentsStore) {
13650
+ this.superdoc.commentsStore.isReviewDirectoryActive = true;
13651
+ this.superdoc.commentsStore.isReviewDirectoryLoading = true;
13652
+ }
13653
+ this.stopDirectoryObservers = [subscribe(ui.comments, "comments"), subscribe(ui.trackChanges, "trackChanges")];
13654
+ }
13733
13655
  createVueApp() {
13734
13656
  this.app = (0, vue.createApp)(commentsList_default);
13735
13657
  const parentProvides = this.superdoc?.app?._context?.provides;
@@ -13743,8 +13665,24 @@ var SuperComments = class extends require_eventemitter3.import_eventemitter3.def
13743
13665
  this.app.config.globalProperties.$superdoc = this.superdoc;
13744
13666
  if (!this.element && this.config.selector) this.element = document.getElementById(this.config.selector);
13745
13667
  this.container = this.app.mount(this.element);
13668
+ this.startDirectoryObservers();
13746
13669
  }
13747
13670
  close() {
13671
+ this.directoryGeneration += 1;
13672
+ for (const stop of this.stopDirectoryObservers.splice(0)) stop?.();
13673
+ this.directorySnapshots = {
13674
+ comments: null,
13675
+ trackChanges: null
13676
+ };
13677
+ this.directoryItemRefs = {
13678
+ comments: null,
13679
+ trackChanges: null
13680
+ };
13681
+ if (this.superdoc?.commentsStore) {
13682
+ this.superdoc.commentsStore.clearReviewDirectory();
13683
+ this.superdoc.commentsStore.isReviewDirectoryActive = false;
13684
+ this.superdoc.commentsStore.isReviewDirectoryLoading = false;
13685
+ }
13748
13686
  if (this.app) {
13749
13687
  this.app.unmount();
13750
13688
  this.app = null;
@@ -20632,27 +20570,6 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
20632
20570
  editor: proxy.$superdoc?.activeEditor
20633
20571
  });
20634
20572
  };
20635
- const reconcileAuthoritativeV2TrackedChangeSidebar = ({ adapter, documentId, refreshReason = "startup-authoritative" } = {}) => {
20636
- if (!adapter || typeof adapter.listTrackedChanges !== "function") return false;
20637
- const normalizedDocumentId = documentId == null ? null : String(documentId);
20638
- if (!normalizedDocumentId) return false;
20639
- commentsStore.hydrateTrackedChangesFromV2?.({
20640
- superdoc: proxy.$superdoc,
20641
- adapter,
20642
- documentId: normalizedDocumentId,
20643
- trackedChangesListMode: "background-reconcile",
20644
- refreshReason,
20645
- blocking: false,
20646
- isCurrent: () => {
20647
- const activeEditor = proxy.$superdoc?.activeEditor ?? null;
20648
- const activeDocumentId = activeEditor?.documentId ?? activeEditor?.options?.documentId ?? null;
20649
- if (activeDocumentId != null && String(activeDocumentId) !== normalizedDocumentId) return false;
20650
- const activeAdapter = activeEditor?.v2TrackedChanges ?? commentsStore.getV2TrackedChangesAdapter?.(proxy.$superdoc);
20651
- return !activeAdapter || activeAdapter === adapter;
20652
- }
20653
- });
20654
- return true;
20655
- };
20656
20573
  const scheduleReplayTrackedChangeSync = () => {
20657
20574
  pendingReplayTrackedChangeSync.value = true;
20658
20575
  (0, vue.nextTick)(() => {
@@ -20810,31 +20727,25 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
20810
20727
  proxy.$superdoc.setActiveRuntime(runtimeId, "v2-editor-ready");
20811
20728
  return true;
20812
20729
  };
20813
- const v2ReviewHydrationController = resolvedEditorIntegration.createReviewHydrationController({
20814
- hydrateComments: (ctx) => commentsStore.hydrateCommentsFromV2?.(ctx),
20815
- hydrateTrackedChanges: (ctx) => commentsStore.hydrateTrackedChangesFromV2?.(ctx),
20816
- primeTrackedChanges: (ctx) => ctx.trackedChangesAdapter?.primeVisibleTrackedChanges?.(ctx.targetIds, ctx.plan),
20817
- readReviewSnapshot: (ctx) => ctx.trackedChangesAdapter?.readReviewSnapshot?.(ctx.snapshot),
20818
- validateReviewSnapshot: (_ctx, result) => result?.validate?.() ?? {
20819
- ok: false,
20820
- reason: "validator-missing"
20730
+ const v2ReviewWindowController = resolvedEditorIntegration.createReviewWindowController({ applyReviewWindow: (ctx, result) => commentsStore.applyReviewWindowFromV2?.({
20731
+ superdoc: ctx?.superdoc,
20732
+ commentsAdapter: ctx?.commentsAdapter,
20733
+ trackedChangesAdapter: ctx?.trackedChangesAdapter,
20734
+ documentId: ctx?.documentId,
20735
+ commentItems: result?.comments?.items,
20736
+ trackedChangeItems: result?.trackedChanges?.items,
20737
+ requestedCommentIds: result?.comments?.requestedIds,
20738
+ requestedTrackedChangeIds: result?.trackedChanges?.requestedIds,
20739
+ unresolvedCommentIds: result?.comments?.unresolvedCommentIds,
20740
+ trackedList: {
20741
+ complete: false,
20742
+ visibleWindowSource: "visible-window"
20821
20743
  },
20822
- applyReviewSnapshot: (ctx, result) => commentsStore.applyReviewSnapshotFromV2?.({
20823
- superdoc: ctx.superdoc,
20824
- commentsAdapter: ctx.commentsAdapter,
20825
- trackedChangesAdapter: ctx.trackedChangesAdapter,
20826
- documentId: ctx.documentId,
20827
- commentItems: result.comments?.items,
20828
- trackedChangeItems: result.trackedChanges?.items,
20829
- trackedList: result.trackedChanges,
20830
- sourceCoverageRevision: result.sourceCoverageRevision,
20831
- evaluatedRevision: result.evaluatedRevision,
20832
- patch: (callback) => commentsStore.$patch(callback)
20833
- }) ?? {
20834
- ok: false,
20835
- reason: "store-action-missing"
20836
- }
20837
- });
20744
+ patch: (callback) => commentsStore.$patch(callback)
20745
+ }) ?? {
20746
+ ok: false,
20747
+ reason: "store-action-missing"
20748
+ } });
20838
20749
  const v2EditorFailures = (0, vue.ref)({});
20839
20750
  const v2AuthorRequiredGate = createV2AuthorRequiredNotificationGate();
20840
20751
  const v2AuthorRequiredMessages = (0, vue.ref)({});
@@ -20928,7 +20839,7 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
20928
20839
  if (!payload) return;
20929
20840
  clearV2EditorFailure(payload.documentId ?? null);
20930
20841
  clearV2AuthorRequired(payload.documentId ?? null);
20931
- const { host, mount, documentId, capabilities, editCommands, bindEditShortcuts, bindSessionShortcuts, commentsAdapter, trackedChangesAdapter, documentApi, documentMutationReadiness, documentApiUnavailableReason, pageMetrics, pageLayout, pageFurniture, presence, lock, fonts, replaceFile } = payload;
20842
+ const { host, mount, documentId, capabilities, editCommands, bindEditShortcuts, bindSessionShortcuts, commentsAdapter, trackedChangesAdapter, documentApi, documentMutationReadiness, documentApiUnavailableReason, pageMetrics, pageLayout, pageFurniture, presence, lock, fonts, replaceFile, upgradeToCollaboration } = payload;
20932
20843
  const saveV2Bytes = async (saveOptions = {}) => {
20933
20844
  if (!host || typeof host.save !== "function") throw new Error("v2-editor: save unavailable");
20934
20845
  return host.save(saveOptions);
@@ -20945,6 +20856,10 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
20945
20856
  if (typeof replaceFile !== "function") throw new Error("v2-editor: replaceFile unavailable");
20946
20857
  return replaceFile(source);
20947
20858
  };
20859
+ const upgradeV2ToCollaboration = async (source, collaboration) => {
20860
+ if (typeof upgradeToCollaboration !== "function") throw new Error("v2-editor: upgradeToCollaboration unavailable");
20861
+ return upgradeToCollaboration(source, collaboration);
20862
+ };
20948
20863
  const focusV2Editable = (options = {}) => {
20949
20864
  if (mount?.focus && typeof mount.focus.focus === "function") return mount.focus.focus(options);
20950
20865
  return false;
@@ -21183,6 +21098,7 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
21183
21098
  save: saveV2Bytes,
21184
21099
  exportDocx: exportV2Docx,
21185
21100
  replaceFile: replaceV2File,
21101
+ upgradeToCollaboration: upgradeV2ToCollaboration,
21186
21102
  doc: documentApi ?? null,
21187
21103
  documentMutationReadiness: documentMutationReadiness ?? null,
21188
21104
  documentApiUnavailableReason: documentApiUnavailableReason ?? null,
@@ -21196,10 +21112,10 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
21196
21112
  pageFurniture: pageFurniture ?? null,
21197
21113
  presence: presence ?? null,
21198
21114
  lock: lock ?? null,
21199
- reviewHydration: {
21200
- getDiagnostics: () => v2ReviewHydrationController.getDiagnostics(),
21201
- getSnapshot: () => v2ReviewHydrationController.getSnapshot?.() ?? null,
21202
- subscribe: (listener) => v2ReviewHydrationController.subscribe?.(listener) ?? (() => void 0)
21115
+ reviewWindow: {
21116
+ getDiagnostics: () => v2ReviewWindowController.getDiagnostics(),
21117
+ getSnapshot: () => v2ReviewWindowController.getSnapshot?.() ?? null,
21118
+ subscribe: (listener) => v2ReviewWindowController.subscribe?.(listener) ?? (() => void 0)
21203
21119
  },
21204
21120
  extensions: createV2ExtensionsFacet(host),
21205
21121
  fonts: fonts ?? host?.getFontRuntime?.() ?? null,
@@ -21242,24 +21158,12 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
21242
21158
  const commentsModuleEnabled = proxy.$superdoc.config.modules?.comments !== false;
21243
21159
  if (commentsAdapter && commentsModuleEnabled) commentsStore.setV2CommentsAdapter?.(commentsAdapter);
21244
21160
  if (trackedChangesAdapter && commentsModuleEnabled) commentsStore.setV2TrackedChangesAdapter?.(trackedChangesAdapter);
21245
- if (commentsModuleEnabled && (commentsAdapter || trackedChangesAdapter)) {
21246
- v2ReviewHydrationController.setContext({
21247
- superdoc: proxy.$superdoc,
21248
- documentId,
21249
- commentsAdapter: commentsAdapter ?? null,
21250
- trackedChangesAdapter: trackedChangesAdapter ?? null
21251
- });
21252
- try {
21253
- const snapshot = host?.getRenderReadinessSnapshot?.();
21254
- if (snapshot) v2ReviewHydrationController.onRenderReadiness(snapshot);
21255
- } catch (err) {
21256
- console.warn("[SuperDoc][v2] initial render-readiness snapshot failed", err);
21257
- }
21258
- }
21259
- if (commentsModuleEnabled && trackedChangesAdapter) reconcileAuthoritativeV2TrackedChangeSidebar({
21260
- adapter: trackedChangesAdapter,
21161
+ if (commentsAdapter || trackedChangesAdapter) v2ReviewWindowController.setContext({
21162
+ superdoc: proxy.$superdoc,
21261
21163
  documentId,
21262
- refreshReason: "startup-authoritative"
21164
+ commentsAdapter: commentsAdapter ?? null,
21165
+ trackedChangesAdapter: trackedChangesAdapter ?? null,
21166
+ resolveReviewWindow: (input) => host.resolveReviewWindow(input)
21263
21167
  });
21264
21168
  if (areDocumentsReady.value && !proxy.$superdoc.config.collaboration) isReady.value = true;
21265
21169
  isFloatingCommentsReady.value = true;
@@ -21527,12 +21431,6 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
21527
21431
  v2ReviewSidebarUnlocked.value = true;
21528
21432
  v2GeometryEpoch.value = v2GeometryPublisher.getLastEpoch();
21529
21433
  }
21530
- },
21531
- onCommittedPagePaint: (commit) => {
21532
- v2ReviewHydrationController.onCommittedPagePaint?.({
21533
- ...commit,
21534
- documentId: commit?.payload?.documentId ?? null
21535
- });
21536
21434
  }
21537
21435
  });
21538
21436
  const armV2TrackedChangeRestampGeometryRetention = (reason = "tracked-change-restamp") => {
@@ -21570,17 +21468,11 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
21570
21468
  scheduleV2GeometryPublish(payload, () => takeV2TrackedChangeRestampGeometryRetention());
21571
21469
  if (v2HasRangeSelection.value) scheduleV2SelectionToolbarStateSync();
21572
21470
  };
21573
- const onV2RenderReadiness = (payload) => {
21574
- const snapshot = payload?.snapshot ?? payload ?? null;
21575
- if (!snapshot) return;
21576
- v2ReviewHydrationController.onRenderReadiness(snapshot);
21577
- };
21578
21471
  const v2PageMetricsSnapshot = (0, vue.shallowRef)(null);
21579
21472
  const v2MountStagesByDocumentId = /* @__PURE__ */ new Map();
21580
21473
  let latestV2MountStage = null;
21581
21474
  const onV2PageMetrics = (payload) => {
21582
21475
  if (!payload?.snapshot) return;
21583
- v2ReviewMutationReconciler.onRender();
21584
21476
  const snapshot = payload.snapshot;
21585
21477
  const metricStage = payload.stage;
21586
21478
  if (typeof HTMLElement !== "undefined" && metricStage instanceof HTMLElement) {
@@ -21620,8 +21512,7 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
21620
21512
  commentsStore.cancelImportedTrackedChangeBootstrap?.(clearedDocumentId ?? void 0);
21621
21513
  if (clearedDocumentId) v2MountStagesByDocumentId.delete(clearedDocumentId);
21622
21514
  if (latestV2MountStage && (clearedDocumentId == null || latestV2MountStage.dataset?.superdocV2DocumentId === clearedDocumentId)) latestV2MountStage = null;
21623
- v2ReviewHydrationController.reset("render-cleared");
21624
- v2ReviewMutationReconciler.reset();
21515
+ v2ReviewWindowController.reset("render-cleared");
21625
21516
  commentsStore.setV2CommentsAdapter?.(null);
21626
21517
  commentsStore.setV2TrackedChangesAdapter?.(null);
21627
21518
  commentsStore.clearEditorCommentPositions?.();
@@ -21630,49 +21521,15 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
21630
21521
  v2RulerHostStyle.value = {};
21631
21522
  v2RulerReady.value = false;
21632
21523
  };
21633
- const v2ReviewMutationReconciler = require_create_super_doc_ui.createV2ReviewMutationReconciler({
21634
- getContext: () => {
21635
- const activeEditor = proxy.$superdoc?.activeEditor ?? null;
21636
- const adapter = activeEditor?.v2TrackedChanges ?? null;
21637
- if (!adapter) return null;
21638
- return {
21639
- superdoc: proxy.$superdoc,
21640
- adapter,
21641
- documentId: activeEditor.documentId ?? null
21642
- };
21643
- },
21644
- reconcile: (context, impact) => commentsStore.reconcileTrackedChangeMutationFromV2?.({
21645
- superdoc: context.superdoc,
21646
- adapter: context.adapter,
21647
- documentId: context.documentId,
21648
- ...impact
21649
- }),
21650
- hydrate: (context, options) => commentsStore.hydrateTrackedChangesFromV2?.({
21651
- superdoc: context.superdoc,
21652
- adapter: context.adapter,
21653
- documentId: context.documentId,
21654
- ...options
21655
- }),
21656
- onReconciled: (_context, ids) => v2GeometryPublisher.recollect({
21657
- retainMissingTrackedChangeGeometry: true,
21658
- retainedTrackedChangeIds: ids,
21659
- reason: "review-reconcile"
21660
- })
21661
- });
21662
21524
  const onV2HostEvent = (document, event) => {
21663
21525
  if (!event) return;
21664
21526
  const documentId = document?.id ?? null;
21665
21527
  if (event.type === "review-mutation:started") {
21666
- const activeEditor = proxy.$superdoc?.activeEditor ?? null;
21667
- commentsStore.supersedeV2ReviewHydration?.({
21668
- commentsAdapter: activeEditor?.v2Comments ?? null,
21669
- trackedChangesAdapter: activeEditor?.v2TrackedChanges ?? null
21670
- });
21671
- v2ReviewHydrationController.beginMutation(event.reviewMutation);
21528
+ v2ReviewWindowController.beginMutation(event.reviewMutation);
21672
21529
  return;
21673
21530
  }
21674
21531
  if (event.type === "review-mutation:aborted") {
21675
- v2ReviewHydrationController.settleMutation(event.reviewMutation?.token, {
21532
+ v2ReviewWindowController.settleMutation(event.reviewMutation?.token, {
21676
21533
  outcome: "aborted",
21677
21534
  resumeDomains: ["comments", "trackedChanges"],
21678
21535
  trackedRowCount: commentsStore.getV2TrackedChangeRowCount?.(documentId) ?? null
@@ -21684,7 +21541,10 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
21684
21541
  return;
21685
21542
  }
21686
21543
  if (event.type === "collaboration:remote-changed") {
21687
- if (event.reviewChanged !== false) v2ReviewHydrationController.invalidate("collaboration:remote-review-changed");
21544
+ if (event.reviewChanged !== false) {
21545
+ v2ReviewWindowController.invalidate("collaboration:remote-review-changed");
21546
+ v2ReviewWindowController.refreshCommittedWindow("collaboration:remote-review-changed");
21547
+ }
21688
21548
  return;
21689
21549
  }
21690
21550
  if (event.type === "source:complete") {
@@ -21696,7 +21556,7 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
21696
21556
  return;
21697
21557
  }
21698
21558
  if (event.type === "mutation:rejected") {
21699
- if (event.reviewMutation?.token) v2ReviewHydrationController.settleMutation(event.reviewMutation.token, {
21559
+ if (event.reviewMutation?.token) v2ReviewWindowController.settleMutation(event.reviewMutation.token, {
21700
21560
  outcome: "rejected",
21701
21561
  resumeDomains: ["comments", "trackedChanges"],
21702
21562
  trackedRowCount: commentsStore.getV2TrackedChangeRowCount?.(documentId) ?? null
@@ -21709,37 +21569,34 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
21709
21569
  emitV2EditorUpdate();
21710
21570
  }
21711
21571
  if (event.type !== "mutation:committed") return;
21712
- if (event.reviewSidecarOnly === true) v2ReviewHydrationController.hydrateNow("review-sidecar-committed");
21572
+ if (event.origin === "history") v2ReviewWindowController.refreshCommittedWindow(`history-${event.direction}`);
21573
+ else if (event.reviewSidecarOnly === true) v2ReviewWindowController.refreshCommittedWindow("review-sidecar-committed");
21713
21574
  const reviewImpact = require_create_super_doc_ui.getV2TrackedChangeMutationImpact(event);
21714
21575
  if (Array.isArray(reviewImpact?.remappedPairs) && reviewImpact.remappedPairs.length > 0) commentsStore.remapTrackedChangeIdentities?.(reviewImpact.remappedPairs, { documentId });
21715
21576
  if (reviewImpact) armV2TrackedChangeRestampGeometryRetention("tracked-change-mutation");
21716
21577
  const activeEditor = proxy.$superdoc?.activeEditor ?? null;
21717
- const readiness = activeEditor?.documentMutationReadiness ?? null;
21718
- const receipt = event.origin === "history" ? null : event.receipt;
21719
- const canWaitForExactPaint = reviewImpact && typeof receipt?.txId === "string" && receipt.txId.length > 0 && typeof readiness?.whenPainted === "function";
21720
- const reconciliation = v2ReviewMutationReconciler.enqueueAfterPaint(reviewImpact, canWaitForExactPaint ? () => readiness.whenPainted.call(readiness, receipt) : null);
21578
+ const reconciliation = reviewImpact ? commentsStore.reconcileTrackedChangeMutationFromV2?.({
21579
+ superdoc: proxy.$superdoc,
21580
+ adapter: activeEditor?.v2TrackedChanges ?? null,
21581
+ documentId,
21582
+ ...reviewImpact
21583
+ }) : Promise.resolve({ ok: true });
21721
21584
  if (event.reviewMutation?.token) {
21722
21585
  const reviewMutation = event.reviewMutation;
21723
21586
  const allResolved = Boolean(reviewImpact?.allResolved ?? event.trackedChangeAllResolved);
21724
- const settleReviewMutation = (reconciled) => {
21725
- const reconciledAllResolved = allResolved && reconciled === true;
21726
- v2ReviewHydrationController.settleMutation(reviewMutation.token, {
21587
+ const settleReviewMutation = (result) => {
21588
+ const reconciledAllResolved = allResolved && result?.ok === true && result?.allResolved === true;
21589
+ v2ReviewWindowController.settleMutation(reviewMutation.token, {
21727
21590
  outcome: "committed",
21728
21591
  allResolved: reconciledAllResolved,
21729
21592
  resumeDomains: reconciledAllResolved ? ["comments"] : ["comments", "trackedChanges"],
21730
21593
  trackedRowCount: commentsStore.getV2TrackedChangeRowCount?.(documentId) ?? null
21731
21594
  });
21732
- if (!reconciledAllResolved) reconcileAuthoritativeV2TrackedChangeSidebar({
21733
- adapter: activeEditor?.v2TrackedChanges ?? null,
21734
- documentId,
21735
- refreshReason: "mutation-authoritative"
21736
- });
21737
21595
  };
21738
- Promise.resolve(reconciliation).then(settleReviewMutation, () => settleReviewMutation(false));
21596
+ Promise.resolve(reconciliation).then(settleReviewMutation, () => settleReviewMutation(null));
21739
21597
  return;
21740
21598
  }
21741
- if (reviewImpact?.reconcileMode === "authoritative" && reviewImpact.upsertIds.size > 0) return;
21742
- if (reviewImpact?.removedIds.size > 0 && reviewImpact.upsertIds.size === 0) return;
21599
+ Promise.resolve(reconciliation).catch(() => void 0);
21743
21600
  };
21744
21601
  const onV2LinkClick = (payload) => {
21745
21602
  linkPopover.handleLinkClick(payload);
@@ -21760,9 +21617,9 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
21760
21617
  const handleV2DocumentModeChange = () => {
21761
21618
  if (!isV2Mode.value) return;
21762
21619
  try {
21763
- v2ReviewHydrationController.hydrateNow("document-mode-change");
21620
+ v2ReviewWindowController.refreshCommittedWindow("document-mode-change");
21764
21621
  } catch (err) {
21765
- console.warn("[SuperDoc][v2] document-mode-change rehydrate failed", err);
21622
+ console.warn("[SuperDoc][v2] document-mode review-window refresh failed", err);
21766
21623
  }
21767
21624
  const republishGeometry = () => {
21768
21625
  if (!isV2Mode.value) return;
@@ -21904,19 +21761,14 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
21904
21761
  onCommentsUpdate: onEditorCommentsUpdate,
21905
21762
  onFontsResolved: onFontsResolvedFn,
21906
21763
  onPageCountKnown: proxy.$superdoc.config.onPageCountKnown ?? null,
21907
- onReviewWindowPlanned: (payload) => {
21908
- v2ReviewHydrationController.onReviewWindowPlanned?.({
21909
- ...payload,
21910
- documentId: doc.id
21911
- });
21912
- },
21913
21764
  onReviewWindowCommitted: (payload) => {
21914
- v2ReviewHydrationController.onCommittedPagePaint?.({
21765
+ v2ReviewWindowController.onCommittedPagePaint?.({
21915
21766
  ...payload,
21916
21767
  documentId: doc.id
21917
21768
  });
21918
21769
  },
21919
21770
  fontAssets: proxy.$superdoc.config.fonts,
21771
+ workerUrls: proxy.$superdoc.config.workerUrls,
21920
21772
  proofing: resolvedProofingConfig.value,
21921
21773
  isNewFile,
21922
21774
  password: getDocumentLoadPassword(doc),
@@ -22845,7 +22697,6 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
22845
22697
  onV2EditorFailed,
22846
22698
  onV2Render,
22847
22699
  onV2RenderCleared,
22848
- onV2RenderReadiness,
22849
22700
  onV2SelectionChanged,
22850
22701
  onV2HostEvent: (event) => onV2HostEvent(doc, event),
22851
22702
  onV2LinkClick,
@@ -22913,7 +22764,7 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
22913
22764
  ], 38);
22914
22765
  };
22915
22766
  }
22916
- }, [["__scopeId", "data-v-6833a014"]]);
22767
+ }, [["__scopeId", "data-v-7770edae"]]);
22917
22768
  //#endregion
22918
22769
  //#region src/core/create-app.js
22919
22770
  var PINIA_DEVTOOLS_SETUP_EVENT = "devtools-plugin:setup";
@@ -43417,7 +43268,7 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
43417
43268
  this.config.colors = shuffleArray(this.config.colors);
43418
43269
  this.userColorMap = /* @__PURE__ */ new Map();
43419
43270
  this.colorIndex = 0;
43420
- this.version = "2.4.0-next.21";
43271
+ this.version = "2.4.0-next.22";
43421
43272
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
43422
43273
  this.superdocId = config.superdocId || require_uuid.v4();
43423
43274
  this.colors = this.config.colors ?? [];
@@ -43834,44 +43685,34 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
43834
43685
  user.color = palette[Math.abs(hash) % palette.length];
43835
43686
  }
43836
43687
  /**
43837
- * Upgrade a local SuperDoc instance into collaboration by overwriting
43838
- * the supplied room with the current local document and comment state,
43839
- * then attaching collaboration to the live editor instance in place.
43688
+ * Upgrade a local SuperDoc instance into collaboration by creating the
43689
+ * supplied room from the current local document and comment state, then
43690
+ * attaching collaboration to the live editor instance in place.
43840
43691
  *
43841
- * This is a **destructive promotion**: the target room is authoritatively
43842
- * overwritten with the caller's current local state. It is NOT the API
43843
- * for joining an existing room without changing its content.
43692
+ * The target room must not already exist. This is not the API for joining
43693
+ * an existing room or merging its content.
43844
43694
  *
43845
43695
  * Currently limited to:
43846
43696
  * - A single DOCX document
43847
43697
  * - A supported v2 single-doc `v2Collaboration` target
43848
- * - Overwrite-and-upgrade only (no merge semantics)
43698
+ * - Create-and-upgrade only (no merge semantics)
43849
43699
  *
43850
43700
  * @returns Resolves once the collaborative runtime is ready
43851
43701
  */
43852
43702
  async upgradeToCollaboration(options) {
43853
- const { target, mountedDocumentId } = this.#validateUpgradePrerequisites(options);
43703
+ const { target } = this.#validateUpgradePrerequisites(options);
43854
43704
  this.#isUpgrading = true;
43855
43705
  try {
43856
43706
  const localSource = await this.#captureCurrentDocxSourceForUpgrade();
43857
43707
  const rollback = this.#snapshotV2UpgradeState();
43858
43708
  const lockSeed = this.#snapshotV2LockSeed();
43859
43709
  this.#assertNotDestroyed();
43860
- let ready = null;
43861
43710
  try {
43862
43711
  this.#pendingV2LockSeed = lockSeed;
43863
- ready = this.#waitForV2CollaborationReady(mountedDocumentId);
43864
- const promotion = this.#promoteSingleDocumentToV2Collaboration(target, localSource);
43865
- await ready;
43712
+ const promotion = await this.#promoteSingleDocumentToV2Collaboration(target, localSource);
43866
43713
  this.#finalizeSingleDocumentV2Collaboration(promotion, target);
43867
43714
  this.#pendingV2LockSeed = null;
43868
43715
  } catch (err) {
43869
- if (ready && this.#abortUpgrade) {
43870
- const abortUpgrade = this.#abortUpgrade;
43871
- this.#abortUpgrade = null;
43872
- abortUpgrade();
43873
- await ready.catch(() => void 0);
43874
- }
43875
43716
  this.#rollbackV2UpgradeState(rollback);
43876
43717
  throw err;
43877
43718
  }
@@ -43908,7 +43749,6 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
43908
43749
  if (rollback.storeDoc) {
43909
43750
  rollback.storeDoc.data = rollback.storeData;
43910
43751
  this.#writeStoreDocV2Collaboration(rollback.storeDoc, rollback.storeV2Collaboration);
43911
- this.#bumpStoreDocumentMountNonce(rollback.storeDoc);
43912
43752
  }
43913
43753
  }
43914
43754
  /**
@@ -43990,15 +43830,6 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
43990
43830
  }
43991
43831
  storeDoc.v2Collaboration = value;
43992
43832
  }
43993
- #bumpStoreDocumentMountNonce(storeDoc) {
43994
- const nonce = (0, vue.toRaw)(storeDoc).editorMountNonce;
43995
- if (nonce && typeof nonce === "object" && "value" in nonce && typeof nonce.value === "number") {
43996
- nonce.value += 1;
43997
- return;
43998
- }
43999
- const writable = storeDoc;
44000
- writable.editorMountNonce = (typeof writable.editorMountNonce === "number" ? writable.editorMountNonce : 0) + 1;
44001
- }
44002
43833
  #toV2CollaborationConfig(target) {
44003
43834
  if (target.providerFamily === "liveblocks") return {
44004
43835
  providerType: "liveblocks",
@@ -44023,21 +43854,22 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
44023
43854
  ...target.params ? { params: target.params } : {}
44024
43855
  };
44025
43856
  }
44026
- #promoteSingleDocumentToV2Collaboration(target, localSource) {
43857
+ async #promoteSingleDocumentToV2Collaboration(target, localSource) {
44027
43858
  const configDoc = this.config.documents.find((d) => d.type === DOCX);
44028
43859
  const storeDoc = this.#requireSuperdocStore("upgradeToCollaboration").documents.find((d) => d.id === configDoc.id);
44029
43860
  if (!storeDoc) throw new Error("SuperDoc: source document store entry is not available for upgrade");
43861
+ const editor = storeDoc.getEditor?.();
43862
+ if (!isV2ActiveEditorFacade(editor) || typeof editor.upgradeToCollaboration !== "function") throw new Error("SuperDoc: live v2 editor cannot attach collaboration in place");
44030
43863
  const v2Collaboration = this.#toV2CollaborationConfig({
44031
43864
  ...target,
44032
43865
  roomMode: "create"
44033
43866
  });
44034
- configDoc.data = localSource;
44035
43867
  configDoc.v2Collaboration = v2Collaboration;
44036
- storeDoc.data = localSource;
44037
43868
  this.#writeStoreDocV2Collaboration(storeDoc, v2Collaboration);
44038
43869
  this.isCollaborative = true;
44039
- this.readyEditors = 0;
44040
- this.#bumpStoreDocumentMountNonce(storeDoc);
43870
+ await editor.upgradeToCollaboration(localSource, v2Collaboration);
43871
+ configDoc.data = localSource;
43872
+ storeDoc.data = localSource;
44041
43873
  return {
44042
43874
  configDoc,
44043
43875
  storeDoc
@@ -44153,35 +43985,6 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
44153
43985
  }
44154
43986
  });
44155
43987
  }
44156
- #waitForV2CollaborationReady(mountedDocumentId) {
44157
- const TIMEOUT_MS = 2e4;
44158
- return new Promise((resolve, reject) => {
44159
- let settled = false;
44160
- const cleanup = () => {
44161
- if (settled) return;
44162
- settled = true;
44163
- clearTimeout(timer);
44164
- this.off("collaboration-ready", onReady);
44165
- };
44166
- const onReady = (payload) => {
44167
- const editor = payload?.editor;
44168
- if (!isV2ActiveEditorFacade(editor)) return;
44169
- if (getActiveEditorDocumentId(editor) !== mountedDocumentId) return;
44170
- if (!editor.presence && !editor.lock) return;
44171
- cleanup();
44172
- resolve();
44173
- };
44174
- const timer = setTimeout(() => {
44175
- cleanup();
44176
- reject(/* @__PURE__ */ new Error(`SuperDoc: v2 collaboration runtime did not become ready within ${TIMEOUT_MS} ms for mounted document "${mountedDocumentId}".`));
44177
- }, TIMEOUT_MS);
44178
- this.on("collaboration-ready", onReady);
44179
- this.#abortUpgrade = () => {
44180
- cleanup();
44181
- reject(/* @__PURE__ */ new Error("SuperDoc: instance was destroyed during upgrade"));
44182
- };
44183
- });
44184
- }
44185
43988
  /**
44186
43989
  * Wait for the provider to report synced, with a timeout.
44187
43990
  *
@@ -44249,10 +44052,7 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
44249
44052
  ...typeof window !== "undefined" ? { authEndpointBaseUrl: window.location.href } : {}
44250
44053
  });
44251
44054
  if (!resolution.ok) throw new Error(`SuperDoc: upgradeToCollaboration() ${resolution.reason}: ${resolution.message}`);
44252
- return {
44253
- target: resolution.target,
44254
- mountedDocumentId
44255
- };
44055
+ return { target: resolution.target };
44256
44056
  }
44257
44057
  #unwrapMaybeRef(value) {
44258
44058
  if (value && typeof value === "object" && "value" in value) return value.value;
@@ -44836,6 +44636,7 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
44836
44636
  */
44837
44637
  addCommentsList(element) {
44838
44638
  if (!this.config?.modules?.comments || this.config.role === "viewer") return;
44639
+ this.commentsList?.close();
44839
44640
  if (element) this.config.modules.comments.element = element;
44840
44641
  this.commentsList = new SuperComments(this.config.modules?.comments, this);
44841
44642
  if (this.config.onCommentsListChange) this.config.onCommentsListChange({ isRendered: true });
@@ -45555,6 +45356,8 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
45555
45356
  }
45556
45357
  if (this.#surfaceManager) this.#surfaceManager.destroy();
45557
45358
  this.toolbar?.destroy?.();
45359
+ this.commentsList?.close();
45360
+ this.commentsList = null;
45558
45361
  if (this.app) {
45559
45362
  this.#log("[superdoc] Unmounting app");
45560
45363
  this.superdocStore?.reset();