superdoc 2.4.0-next.2 → 2.4.0-next.3

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.
@@ -5,7 +5,7 @@ import { t as v4_default } from "./chunks/uuid-B2Sqk-3p.es.js";
5
5
  import { a as init_dist$1, i as global, n as init_dist$2, o as Buffer, r as process$1, s as init_dist, t as require_jszip_min } from "./chunks/jszip-C8srOKAO.es.js";
6
6
  import { t as __plugin_vue_export_helper_default } from "./chunks/_plugin-vue_export-helper-CInC0bKI.es.js";
7
7
  import { n as PDF_TO_CSS_UNITS } from "./chunks/constants-CY3R3_kF.es.js";
8
- import { a as createV2ReviewMutationReconciler, i as isV2EditableTextMutationEvent, o as getV2TrackedChangeMutationImpact, s as composeAuthorColorResolver, t as createSuperDocUI } from "./chunks/create-super-doc-ui-D4kk9AjM.es.js";
8
+ import { a as createV2ReviewMutationReconciler, i as isV2EditableTextMutationEvent, o as getV2TrackedChangeMutationImpact, s as composeAuthorColorResolver, t as createSuperDocUI } from "./chunks/create-super-doc-ui-Dli6Wq70.es.js";
9
9
  import * as Vue from "vue";
10
10
  import { Fragment, Teleport, Transition, computed, createApp, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineAsyncComponent, defineComponent, effectScope, getCurrentInstance, getCurrentScope, h, hasInjectionContext, inject, isReactive, isRef, markRaw, mergeProps, nextTick, normalizeClass, normalizeProps, normalizeStyle, onActivated, onBeforeUnmount, onDeactivated, onMounted, onScopeDispose, openBlock, reactive, ref, renderList, renderSlot, resolveDirective, resolveDynamicComponent, shallowRef, toDisplayString, toRaw, toRef, toRefs, unref, useAttrs, vModelText, watch, withCtx, withDirectives, withKeys, withModifiers } from "vue";
11
11
  import "y-websocket";
@@ -6720,8 +6720,17 @@ const useCommentsStore = defineStore("comments", () => {
6720
6720
  return viewingVisibility.commentsVisible || viewingVisibility.trackChangesVisible;
6721
6721
  });
6722
6722
  const v2CommentsAdapter = shallowRef(null);
6723
+ const v2CommentSyncGenerations = /* @__PURE__ */ new WeakMap();
6724
+ const commentSyncGeneration = (adapter) => v2CommentSyncGenerations.get(adapter) ?? 0;
6725
+ const supersedeCommentSyncGeneration = (adapter) => {
6726
+ if (!adapter || typeof adapter !== "object" && typeof adapter !== "function") return 0;
6727
+ const next = commentSyncGeneration(adapter) + 1;
6728
+ v2CommentSyncGenerations.set(adapter, next);
6729
+ return next;
6730
+ };
6723
6731
  const setV2CommentsAdapter = (adapter) => {
6724
6732
  v2CommentsAdapter.value = adapter ?? null;
6733
+ if (adapter && !v2CommentSyncGenerations.has(adapter)) v2CommentSyncGenerations.set(adapter, 0);
6725
6734
  };
6726
6735
  const getV2CommentsAdapter = (superdoc) => {
6727
6736
  const fromFacade = superdoc?.activeEditor?.v2Comments ?? null;
@@ -6730,14 +6739,27 @@ const useCommentsStore = defineStore("comments", () => {
6730
6739
  };
6731
6740
  const isV2EditorActive = (superdoc) => superdoc?.activeEditor?.editorVersion === 2 || v2CommentsAdapter.value !== null;
6732
6741
  const v2TrackedChangesAdapter = shallowRef(null);
6742
+ const v2TrackedChangeSyncGenerations = /* @__PURE__ */ new WeakMap();
6743
+ const trackedChangeSyncGeneration = (adapter) => v2TrackedChangeSyncGenerations.get(adapter) ?? 0;
6744
+ const supersedeTrackedChangeSyncGeneration = (adapter) => {
6745
+ if (!adapter || typeof adapter !== "object" && typeof adapter !== "function") return 0;
6746
+ const next = trackedChangeSyncGeneration(adapter) + 1;
6747
+ v2TrackedChangeSyncGenerations.set(adapter, next);
6748
+ return next;
6749
+ };
6733
6750
  const setV2TrackedChangesAdapter = (adapter) => {
6734
6751
  v2TrackedChangesAdapter.value = adapter ?? null;
6752
+ if (adapter && !v2TrackedChangeSyncGenerations.has(adapter)) v2TrackedChangeSyncGenerations.set(adapter, 0);
6735
6753
  };
6736
6754
  const getV2TrackedChangesAdapter = (superdoc) => {
6737
6755
  const fromFacade = superdoc?.activeEditor?.v2TrackedChanges ?? null;
6738
6756
  if (fromFacade) return fromFacade;
6739
6757
  return v2TrackedChangesAdapter.value;
6740
6758
  };
6759
+ const supersedeV2ReviewHydration = ({ commentsAdapter, trackedChangesAdapter } = {}) => ({
6760
+ commentsGeneration: supersedeCommentSyncGeneration(commentsAdapter ?? v2CommentsAdapter.value),
6761
+ trackedChangesGeneration: supersedeTrackedChangeSyncGeneration(trackedChangesAdapter ?? v2TrackedChangesAdapter.value)
6762
+ });
6741
6763
  const init = (config = {}) => {
6742
6764
  const updatedConfig = {
6743
6765
  ...commentsConfig,
@@ -7193,7 +7215,7 @@ const useCommentsStore = defineStore("comments", () => {
7193
7215
  }
7194
7216
  activeEditor?.commands?.setActiveComment({ commentId: activeComment.value });
7195
7217
  };
7196
- const handleTrackedChangeUpdate = ({ superdoc, params, broadcastChanges = true, documentState = void 0 }) => {
7218
+ const handleTrackedChangeUpdate = ({ superdoc, params, broadcastChanges = true, documentState = void 0, trackedChangeIdentityIndex = null }) => {
7197
7219
  const span = startInteractionSpan("store.trackedChanges.handleUpdate", "store-reconciliation", {
7198
7220
  event: params?.event ?? null,
7199
7221
  changeId: params?.changeId ?? null,
@@ -7296,6 +7318,17 @@ const useCommentsStore = defineStore("comments", () => {
7296
7318
  if (normalizedAnchorKey && commentAnchorKey) return commentAnchorKey === normalizedAnchorKey;
7297
7319
  return false;
7298
7320
  };
7321
+ if (trackedChangeIdentityIndex) {
7322
+ const candidates = trackedChangeIdentityIndex.candidates({
7323
+ changeId: normalizedChangeId,
7324
+ importedId: normalizedImportedId,
7325
+ anchorKey: normalizedAnchorKey,
7326
+ canonicalId: normalizedTrackedChangeCanonicalId,
7327
+ positionAliases: normalizedTrackedChangePositionAliases
7328
+ });
7329
+ for (const trackedComment of candidates) if (matchesId(trackedComment) && (!normalizedDocumentId || belongsToTrackedChangeSyncDocument(trackedComment, normalizedDocumentId))) return trackedComment;
7330
+ return null;
7331
+ }
7299
7332
  if (normalizedDocumentId) return commentsList.value.find((trackedComment) => matchesId(trackedComment) && belongsToTrackedChangeSyncDocument(trackedComment, normalizedDocumentId));
7300
7333
  return commentsList.value.find(matchesId);
7301
7334
  };
@@ -7364,6 +7397,7 @@ const useCommentsStore = defineStore("comments", () => {
7364
7397
  const existing = findTrackedChangeById();
7365
7398
  if (existing) {
7366
7399
  if (!updateExistingTrackedChange(existing)) return;
7400
+ trackedChangeIdentityIndex?.add(existing);
7367
7401
  emitTrackedChangeEvent({
7368
7402
  type: COMMENT_EVENTS.UPDATE,
7369
7403
  comment: getCommentEventPayload(existing)
@@ -7375,10 +7409,12 @@ const useCommentsStore = defineStore("comments", () => {
7375
7409
  comment,
7376
7410
  broadcastChanges
7377
7411
  });
7412
+ trackedChangeIdentityIndex?.add(comment);
7378
7413
  } else if (event === "update") {
7379
7414
  const existingTrackedChange = findTrackedChangeById();
7380
7415
  if (!existingTrackedChange) return;
7381
7416
  if (!updateExistingTrackedChange(existingTrackedChange)) return;
7417
+ trackedChangeIdentityIndex?.add(existingTrackedChange);
7382
7418
  emitTrackedChangeEvent({
7383
7419
  type: COMMENT_EVENTS.UPDATE,
7384
7420
  comment: getCommentEventPayload(existingTrackedChange)
@@ -8192,15 +8228,24 @@ const useCommentsStore = defineStore("comments", () => {
8192
8228
  trackedItems: preparedParams.length
8193
8229
  };
8194
8230
  });
8195
- const hydrateCommentsFromV2 = async ({ superdoc, adapter, documentId } = {}) => withInteractionSpan("store.comments.hydrateFromV2", "comments-list", { documentId: documentId ?? null }, async () => {
8231
+ const hydrateCommentsFromV2 = async ({ superdoc, adapter, documentId, signal, hydrationGeneration } = {}) => withInteractionSpan("store.comments.hydrateFromV2", "comments-list", { documentId: documentId ?? null }, async () => {
8196
8232
  const effectiveAdapter = adapter ?? getV2CommentsAdapter(superdoc);
8197
8233
  if (!effectiveAdapter || typeof effectiveAdapter.refresh !== "function") return {
8198
8234
  ok: false,
8199
8235
  reason: "adapter-missing"
8200
8236
  };
8237
+ if (signal?.aborted) return {
8238
+ ok: false,
8239
+ reason: "review-hydration-superseded",
8240
+ lateResultDropped: false
8241
+ };
8242
+ const syncGeneration = commentSyncGeneration(effectiveAdapter);
8201
8243
  let result;
8202
8244
  try {
8203
- result = await effectiveAdapter.refresh();
8245
+ const refreshOptions = {};
8246
+ if (signal) refreshOptions.signal = signal;
8247
+ if (typeof hydrationGeneration === "number") refreshOptions.hydrationGeneration = hydrationGeneration;
8248
+ result = Object.keys(refreshOptions).length > 0 ? await effectiveAdapter.refresh(refreshOptions) : await effectiveAdapter.refresh();
8204
8249
  } catch (err) {
8205
8250
  return {
8206
8251
  ok: false,
@@ -8212,21 +8257,35 @@ const useCommentsStore = defineStore("comments", () => {
8212
8257
  ok: false,
8213
8258
  reason: "adapter-stale"
8214
8259
  };
8260
+ if (signal?.aborted || commentSyncGeneration(effectiveAdapter) !== syncGeneration) return {
8261
+ ok: false,
8262
+ reason: "review-hydration-superseded",
8263
+ lateResultDropped: true
8264
+ };
8215
8265
  if (!result?.ok) return result;
8266
+ if (signal?.aborted || commentSyncGeneration(effectiveAdapter) !== syncGeneration) return {
8267
+ ok: false,
8268
+ reason: "review-hydration-superseded",
8269
+ lateResultDropped: true
8270
+ };
8216
8271
  reconcileCommentsFromV2({
8217
8272
  superdoc,
8218
8273
  adapter: effectiveAdapter,
8219
8274
  documentId,
8220
- items: result.items ?? []
8275
+ items: result.items ?? [],
8276
+ hydrationGeneration
8221
8277
  });
8222
8278
  return {
8223
8279
  ok: true,
8224
- items: result.items ?? []
8280
+ items: result.items ?? [],
8281
+ reconciled: true,
8282
+ ...typeof hydrationGeneration === "number" ? { hydrationGeneration } : {}
8225
8283
  };
8226
8284
  });
8227
- const reconcileCommentsFromV2 = ({ superdoc, adapter, documentId, items, preparedInputs = null } = {}) => withInteractionSpan("store.comments.reconcile", "store-reconciliation", {
8285
+ const reconcileCommentsFromV2 = ({ superdoc, adapter, documentId, items, preparedInputs = null, hydrationGeneration } = {}) => withInteractionSpan("store.comments.reconcile", "store-reconciliation", {
8228
8286
  documentId: documentId ?? null,
8229
- itemCount: Array.isArray(items) ? items.length : null
8287
+ itemCount: Array.isArray(items) ? items.length : null,
8288
+ hydrationGeneration: hydrationGeneration ?? null
8230
8289
  }, () => {
8231
8290
  if (!adapter || !Array.isArray(items)) return { added: null };
8232
8291
  if (!isCurrentV2CommentsAdapter(adapter)) return {
@@ -9000,7 +9059,7 @@ const useCommentsStore = defineStore("comments", () => {
9000
9059
  let removed = false;
9001
9060
  const targetIndex = targetComment ? commentsList.value.indexOf(targetComment) : -1;
9002
9061
  const targetIds = targetComment?.trackedChange ? collectTrackedChangeDecisionIds(targetComment) : /* @__PURE__ */ new Set();
9003
- if (targetIndex >= 0 && belongsToTrackedChangeSyncDocument(targetComment, normalizedDocumentId) && Array.from(normalizedDecidedIds).some((id) => targetIds.has(id))) {
9062
+ if (targetIndex >= 0 && belongsToTrackedChangeSyncDocument(targetComment, normalizedDocumentId) && Array.from(targetIds).some((id) => normalizedDecidedIds.has(id))) {
9004
9063
  targetIds.forEach((id) => removedIds.add(id));
9005
9064
  commentsList.value.splice(targetIndex, 1);
9006
9065
  removed = true;
@@ -9008,7 +9067,7 @@ const useCommentsStore = defineStore("comments", () => {
9008
9067
  if (!comment?.trackedChange) return true;
9009
9068
  if (!belongsToTrackedChangeSyncDocument(comment, normalizedDocumentId)) return true;
9010
9069
  const ids = collectTrackedChangeDecisionIds(comment);
9011
- if (!Array.from(normalizedDecidedIds).some((id) => ids.has(id))) return true;
9070
+ if (!Array.from(ids).some((id) => normalizedDecidedIds.has(id))) return true;
9012
9071
  removed = true;
9013
9072
  ids.forEach((id) => removedIds.add(id));
9014
9073
  return false;
@@ -9024,6 +9083,31 @@ const useCommentsStore = defineStore("comments", () => {
9024
9083
  removedIds
9025
9084
  };
9026
9085
  };
9086
+ const clearAllResolvedTrackedChangeRows = ({ adapter, documentId } = {}) => {
9087
+ const normalizedDocumentId = normalizeCommentId(documentId);
9088
+ if (!adapter || !normalizedDocumentId) return {
9089
+ removed: 0,
9090
+ removedIds: /* @__PURE__ */ new Set()
9091
+ };
9092
+ supersedeTrackedChangeSyncGeneration(adapter);
9093
+ const removedIds = /* @__PURE__ */ new Set();
9094
+ const activeId = normalizeCommentId(activeComment.value);
9095
+ let activeWasRemoved = false;
9096
+ const next = commentsList.value.filter((comment) => {
9097
+ if (!comment?.trackedChange || !belongsToTrackedChangeSyncDocument(comment, normalizedDocumentId)) return true;
9098
+ const aliases = collectTrackedChangeDecisionIds(comment);
9099
+ aliases.forEach((id) => removedIds.add(id));
9100
+ if (activeId && aliases.has(activeId)) activeWasRemoved = true;
9101
+ return false;
9102
+ });
9103
+ const removed = commentsList.value.length - next.length;
9104
+ if (removed > 0) commentsList.value = next;
9105
+ if (activeWasRemoved) clearActiveCommentSelection();
9106
+ return {
9107
+ removed,
9108
+ removedIds
9109
+ };
9110
+ };
9027
9111
  const clearV2ActiveTrackedChangeTarget = (adapter, decidedId) => {
9028
9112
  if (!decidedId || typeof adapter?.clearActiveTrackedChangeTargetIfMatches !== "function") return;
9029
9113
  try {
@@ -9234,7 +9318,7 @@ const useCommentsStore = defineStore("comments", () => {
9234
9318
  success: Boolean(command(id))
9235
9319
  };
9236
9320
  };
9237
- const hydrateTrackedChangesFromV2 = async ({ superdoc, adapter, documentId, trackedChangesListMode = "all", refreshReason, blocking } = {}) => withInteractionSpan("store.trackedChanges.hydrateFromV2", "tracked-change-list", {
9321
+ const hydrateTrackedChangesFromV2 = async ({ superdoc, adapter, documentId, trackedChangesListMode = "all", refreshReason, blocking, signal, hydrationGeneration } = {}) => withInteractionSpan("store.trackedChanges.hydrateFromV2", "tracked-change-list", {
9238
9322
  documentId: documentId ?? null,
9239
9323
  trackedChangesListMode,
9240
9324
  refreshReason: refreshReason ?? null,
@@ -9245,11 +9329,19 @@ const useCommentsStore = defineStore("comments", () => {
9245
9329
  ok: false,
9246
9330
  reason: "adapter-missing"
9247
9331
  };
9332
+ const syncGeneration = trackedChangeSyncGeneration(effectiveAdapter);
9333
+ if (signal?.aborted) return {
9334
+ ok: false,
9335
+ reason: "review-hydration-superseded",
9336
+ lateResultDropped: false
9337
+ };
9248
9338
  let result;
9249
9339
  try {
9250
9340
  const listOptions = { mode: trackedChangesListMode };
9251
9341
  if (refreshReason != null) listOptions.refreshReason = refreshReason;
9252
9342
  if (typeof blocking === "boolean") listOptions.blocking = blocking;
9343
+ if (signal) listOptions.signal = signal;
9344
+ if (typeof hydrationGeneration === "number") listOptions.hydrationGeneration = hydrationGeneration;
9253
9345
  result = await effectiveAdapter.listTrackedChanges(listOptions);
9254
9346
  } catch (err) {
9255
9347
  return {
@@ -9262,18 +9354,36 @@ const useCommentsStore = defineStore("comments", () => {
9262
9354
  ok: false,
9263
9355
  reason: "adapter-stale"
9264
9356
  };
9357
+ if (trackedChangeSyncGeneration(effectiveAdapter) !== syncGeneration) return {
9358
+ ok: false,
9359
+ reason: "review-hydration-superseded",
9360
+ lateResultDropped: true
9361
+ };
9362
+ if (signal?.aborted) return {
9363
+ ok: false,
9364
+ reason: "review-hydration-superseded",
9365
+ lateResultDropped: true
9366
+ };
9265
9367
  if (!result?.ok) return result;
9266
9368
  const pruneStale = !(trackedChangesListMode === "startup-page" || trackedChangesListMode === "interaction-prime" || trackedChangesListMode === "visible-window") && result.visibleWindowSource == null && result.complete === true && result.sourceCoverageComplete === true;
9369
+ if (signal?.aborted || trackedChangeSyncGeneration(effectiveAdapter) !== syncGeneration) return {
9370
+ ok: false,
9371
+ reason: "review-hydration-superseded",
9372
+ lateResultDropped: true
9373
+ };
9267
9374
  reconcileTrackedChangesFromV2({
9268
9375
  superdoc,
9269
9376
  adapter: effectiveAdapter,
9270
9377
  documentId,
9271
9378
  items: result.items ?? [],
9272
- pruneStale
9379
+ pruneStale,
9380
+ hydrationGeneration
9273
9381
  });
9274
9382
  const hydrationResult = {
9275
9383
  ok: true,
9276
- items: result.items ?? []
9384
+ items: result.items ?? [],
9385
+ reconciled: true,
9386
+ ...typeof hydrationGeneration === "number" ? { hydrationGeneration } : {}
9277
9387
  };
9278
9388
  if (typeof result.complete === "boolean") hydrationResult.complete = result.complete;
9279
9389
  if (typeof result.sourceCoverageComplete === "boolean") hydrationResult.sourceCoverageComplete = result.sourceCoverageComplete;
@@ -9284,11 +9394,14 @@ const useCommentsStore = defineStore("comments", () => {
9284
9394
  if (typeof result.refreshReason === "string") hydrationResult.refreshReason = result.refreshReason;
9285
9395
  if (typeof result.blocking === "boolean") hydrationResult.blocking = result.blocking;
9286
9396
  if (typeof result.pageCount === "number") hydrationResult.pageCount = result.pageCount;
9397
+ if (typeof result.pagesStarted === "number") hydrationResult.pagesStarted = result.pagesStarted;
9398
+ if (typeof result.pagesCompleted === "number") hydrationResult.pagesCompleted = result.pagesCompleted;
9399
+ if (typeof result.pagesSkippedAfterAbort === "number") hydrationResult.pagesSkippedAfterAbort = result.pagesSkippedAfterAbort;
9287
9400
  if (typeof result.totalItems === "number") hydrationResult.totalItems = result.totalItems;
9288
9401
  if (typeof result.coalesced === "boolean") hydrationResult.coalesced = result.coalesced;
9289
9402
  return hydrationResult;
9290
9403
  });
9291
- const reconcileTrackedChangeMutationFromV2 = async ({ superdoc, adapter, documentId, upsertIds = [], removedIds = [] } = {}) => {
9404
+ const reconcileTrackedChangeMutationFromV2 = async ({ superdoc, adapter, documentId, upsertIds = [], removedIds = [], allResolved } = {}) => {
9292
9405
  const effectiveAdapter = adapter ?? getV2TrackedChangesAdapter(superdoc);
9293
9406
  if (!effectiveAdapter) return {
9294
9407
  ok: false,
@@ -9299,6 +9412,26 @@ const useCommentsStore = defineStore("comments", () => {
9299
9412
  reason: "adapter-stale"
9300
9413
  };
9301
9414
  const effectiveDocumentId = documentId ?? effectiveAdapter.documentId ?? superdoc?.activeEditor?.documentId ?? null;
9415
+ if (allResolved && effectiveDocumentId == null) return {
9416
+ ok: false,
9417
+ reason: "document-id-missing"
9418
+ };
9419
+ if (allResolved) return {
9420
+ ok: true,
9421
+ items: [],
9422
+ resolvedIds: [],
9423
+ unresolvedIds: [],
9424
+ allResolved: true,
9425
+ removedRows: withInteractionSpan("store.trackedChanges.allResolved", "store-reconciliation", {
9426
+ documentId: String(effectiveDocumentId),
9427
+ logicalTargetCount: allResolved.logicalTargetCount ?? null,
9428
+ physicalCarrierCount: allResolved.physicalCarrierCount ?? null
9429
+ }, () => clearAllResolvedTrackedChangeRows({
9430
+ adapter: effectiveAdapter,
9431
+ documentId: effectiveDocumentId
9432
+ })).removed
9433
+ };
9434
+ const syncGeneration = trackedChangeSyncGeneration(effectiveAdapter);
9302
9435
  const normalizedRemovedIds = new Set(Array.from(removedIds ?? []).map((id) => normalizeCommentId(id)).filter(Boolean));
9303
9436
  const normalizedUpsertIds = [...new Set(Array.from(upsertIds ?? []).map((id) => normalizeCommentId(id)).filter(Boolean))];
9304
9437
  if (normalizedRemovedIds.size > 0 && normalizedUpsertIds.length === 0 && effectiveDocumentId != null) pruneDecidedTrackedChangeRow({
@@ -9337,6 +9470,10 @@ const useCommentsStore = defineStore("comments", () => {
9337
9470
  ok: false,
9338
9471
  reason: "adapter-stale"
9339
9472
  };
9473
+ if (trackedChangeSyncGeneration(effectiveAdapter) !== syncGeneration) return {
9474
+ ok: false,
9475
+ reason: "generation-stale"
9476
+ };
9340
9477
  const resolvedIds = [];
9341
9478
  const unresolvedIds = [];
9342
9479
  const items = [];
@@ -9363,35 +9500,105 @@ const useCommentsStore = defineStore("comments", () => {
9363
9500
  failures: reads.map(({ result }) => result).filter((result) => !result?.ok)
9364
9501
  };
9365
9502
  };
9366
- const reconcileTrackedChangesFromV2 = ({ superdoc, adapter, documentId, items, pruneStale = true, preparedParams = null } = {}) => withInteractionSpan("store.trackedChanges.reconcile", "store-reconciliation", {
9503
+ const createTrackedChangeBatchIdentityIndex = (documentId) => {
9504
+ const byIdentityAlias = /* @__PURE__ */ new Map();
9505
+ const indexedRows = /* @__PURE__ */ new Set();
9506
+ let aliasLookups = 0;
9507
+ let candidateVisits = 0;
9508
+ const addAlias = (index, value, comment) => {
9509
+ const alias = normalizeCommentId(value);
9510
+ if (!alias) return;
9511
+ const bucket = index.get(alias) ?? /* @__PURE__ */ new Set();
9512
+ bucket.add(comment);
9513
+ index.set(alias, bucket);
9514
+ };
9515
+ const add = (comment) => {
9516
+ if (!comment?.trackedChange) return;
9517
+ if (documentId && !belongsToTrackedChangeSyncDocument(comment, documentId)) return;
9518
+ indexedRows.add(comment);
9519
+ addAlias(byIdentityAlias, comment.commentId, comment);
9520
+ addAlias(byIdentityAlias, comment.importedId, comment);
9521
+ addAlias(byIdentityAlias, comment.trackedChangeCanonicalId, comment);
9522
+ addAlias(byIdentityAlias, comment.trackedChangeAnchorKey, comment);
9523
+ getCommentAliasIds(comment).forEach((alias) => addAlias(byIdentityAlias, alias, comment));
9524
+ };
9525
+ commentsList.value.forEach(add);
9526
+ return {
9527
+ add,
9528
+ work: () => ({
9529
+ trackedRowsIndexed: indexedRows.size,
9530
+ invalidatedIdMembershipChecks: 0,
9531
+ incomingAliasLookups: aliasLookups,
9532
+ candidateVisits
9533
+ }),
9534
+ candidates({ changeId, importedId, anchorKey, canonicalId, positionAliases = [] } = {}) {
9535
+ const out = /* @__PURE__ */ new Set();
9536
+ const include = (value) => {
9537
+ const alias = normalizeCommentId(value);
9538
+ if (!alias) return;
9539
+ aliasLookups += 1;
9540
+ const bucket = byIdentityAlias.get(alias);
9541
+ if (!bucket) return;
9542
+ candidateVisits += bucket.size;
9543
+ bucket.forEach((comment) => out.add(comment));
9544
+ };
9545
+ new Set([
9546
+ changeId,
9547
+ importedId,
9548
+ canonicalId,
9549
+ anchorKey,
9550
+ ...positionAliases
9551
+ ]).forEach(include);
9552
+ return out;
9553
+ }
9554
+ };
9555
+ };
9556
+ const reconcileTrackedChangesFromV2 = ({ superdoc, adapter, documentId, items, pruneStale = true, preparedParams = null, hydrationGeneration } = {}) => withInteractionSpan("store.trackedChanges.reconcile", "store-reconciliation", {
9367
9557
  documentId: documentId ?? null,
9368
9558
  itemCount: Array.isArray(items) ? items.length : null,
9369
- pruneStale
9559
+ pruneStale,
9560
+ hydrationGeneration: hydrationGeneration ?? null
9370
9561
  }, () => {
9371
9562
  if (!adapter || !Array.isArray(items)) return;
9372
9563
  if (!isCurrentV2TrackedChangesAdapter(adapter)) return;
9373
9564
  const effectiveDocumentId = documentId ?? adapter.documentId ?? superdoc?.activeEditor?.documentId ?? null;
9374
9565
  const liveAnchorKeys = /* @__PURE__ */ new Set();
9375
9566
  const liveIds = /* @__PURE__ */ new Set();
9567
+ const trackedChangeIdentityIndex = createTrackedChangeBatchIdentityIndex(effectiveDocumentId == null ? null : String(effectiveDocumentId));
9568
+ const identitySpan = startInteractionSpan("store.trackedChanges.batchIdentity", "store-reconciliation", { itemCount: items.length });
9376
9569
  const paramsList = Array.isArray(preparedParams) ? preparedParams : items.map((item) => adapter.mapV2TrackedChangeToCommentParams(item));
9377
- for (const params of paramsList) {
9378
- if (!params) continue;
9379
- if (params.event === "omit") {
9570
+ try {
9571
+ for (const params of paramsList) {
9572
+ if (!params) continue;
9573
+ if (params.event === "omit") {
9574
+ if (params.changeId != null) liveIds.add(String(params.changeId));
9575
+ continue;
9576
+ }
9577
+ if (effectiveDocumentId && params.documentId == null) params.documentId = effectiveDocumentId;
9380
9578
  if (params.changeId != null) liveIds.add(String(params.changeId));
9381
- continue;
9579
+ if (params.trackedChangeAnchorKey != null) liveAnchorKeys.add(String(params.trackedChangeAnchorKey));
9580
+ handleTrackedChangeUpdate({
9581
+ superdoc,
9582
+ params,
9583
+ broadcastChanges: false,
9584
+ trackedChangeIdentityIndex
9585
+ });
9382
9586
  }
9383
- if (effectiveDocumentId && params.documentId == null) params.documentId = effectiveDocumentId;
9384
- if (params.changeId != null) liveIds.add(String(params.changeId));
9385
- if (params.trackedChangeAnchorKey != null) liveAnchorKeys.add(String(params.trackedChangeAnchorKey));
9386
- handleTrackedChangeUpdate({
9387
- superdoc,
9388
- params,
9389
- broadcastChanges: false
9390
- });
9587
+ } finally {
9588
+ endInteractionSpan(identitySpan, trackedChangeIdentityIndex.work());
9391
9589
  }
9392
9590
  if (!effectiveDocumentId || !pruneStale) return;
9393
9591
  pruneStaleTrackedChangeComments(liveIds, liveAnchorKeys, String(effectiveDocumentId), superdoc, { broadcastChanges: false });
9394
9592
  });
9593
+ const getV2TrackedChangeRowCount = (documentId = null) => {
9594
+ const normalizedDocumentId = documentId == null ? null : String(documentId);
9595
+ return commentsList.value.filter((comment) => {
9596
+ if (comment?.trackedChange !== true) return false;
9597
+ if (normalizedDocumentId == null) return true;
9598
+ const rowDocumentId = comment?.documentId ?? comment?.fileId ?? null;
9599
+ return rowDocumentId != null && String(rowDocumentId) === normalizedDocumentId;
9600
+ }).length;
9601
+ };
9395
9602
  const syncTrackedChangeComments = ({ superdoc, editor, broadcastChanges = true }) => {
9396
9603
  if (!superdoc || !editor) return;
9397
9604
  const activeDocumentId = editor?.options?.documentId != null ? String(editor.options.documentId) : null;
@@ -9958,6 +10165,7 @@ const useCommentsStore = defineStore("comments", () => {
9958
10165
  getV2CommentsAdapter,
9959
10166
  hydrateCommentsFromV2,
9960
10167
  applyReviewSnapshotFromV2,
10168
+ supersedeV2ReviewHydration,
9961
10169
  reconcileCommentsFromV2,
9962
10170
  isV2EditorActive,
9963
10171
  replyCommentV2,
@@ -9967,6 +10175,7 @@ const useCommentsStore = defineStore("comments", () => {
9967
10175
  v2TrackedChangesAdapter,
9968
10176
  setV2TrackedChangesAdapter,
9969
10177
  getV2TrackedChangesAdapter,
10178
+ getV2TrackedChangeRowCount,
9970
10179
  hydrateTrackedChangesFromV2,
9971
10180
  reconcileTrackedChangeMutationFromV2,
9972
10181
  reconcileTrackedChangesFromV2,
@@ -18897,6 +19106,23 @@ var SuperDoc_default = /* @__PURE__ */ __plugin_vue_export_helper_default({
18897
19106
  const onV2HostEvent = (document$1, event) => {
18898
19107
  if (!event) return;
18899
19108
  const documentId = document$1?.id ?? null;
19109
+ if (event.type === "review-mutation:started") {
19110
+ const activeEditor = proxy.$superdoc?.activeEditor ?? null;
19111
+ commentsStore.supersedeV2ReviewHydration?.({
19112
+ commentsAdapter: activeEditor?.v2Comments ?? null,
19113
+ trackedChangesAdapter: activeEditor?.v2TrackedChanges ?? null
19114
+ });
19115
+ v2ReviewHydrationController.beginMutation(event.reviewMutation);
19116
+ return;
19117
+ }
19118
+ if (event.type === "review-mutation:aborted") {
19119
+ v2ReviewHydrationController.settleMutation(event.reviewMutation?.token, {
19120
+ outcome: "aborted",
19121
+ resumeDomains: ["comments", "trackedChanges"],
19122
+ trackedRowCount: commentsStore.getV2TrackedChangeRowCount?.(documentId) ?? null
19123
+ });
19124
+ return;
19125
+ }
18900
19126
  if (event.type === "reviewTarget:changed") {
18901
19127
  syncSidebarActiveCommentFromV2ReviewTarget(event.next);
18902
19128
  return;
@@ -18915,6 +19141,11 @@ var SuperDoc_default = /* @__PURE__ */ __plugin_vue_export_helper_default({
18915
19141
  return;
18916
19142
  }
18917
19143
  if (event.type === "mutation:rejected") {
19144
+ if (event.reviewMutation?.token) v2ReviewHydrationController.settleMutation(event.reviewMutation.token, {
19145
+ outcome: "rejected",
19146
+ resumeDomains: ["comments", "trackedChanges"],
19147
+ trackedRowCount: commentsStore.getV2TrackedChangeRowCount?.(documentId) ?? null
19148
+ });
18918
19149
  maybeNotifyV2AuthorRequired(documentId, event);
18919
19150
  return;
18920
19151
  }
@@ -18930,7 +19161,22 @@ var SuperDoc_default = /* @__PURE__ */ __plugin_vue_export_helper_default({
18930
19161
  const readiness = (proxy.$superdoc?.activeEditor ?? null)?.documentMutationReadiness ?? null;
18931
19162
  const receipt = event.origin === "history" ? null : event.receipt;
18932
19163
  const canWaitForExactPaint = reviewImpact && typeof receipt?.txId === "string" && receipt.txId.length > 0 && typeof readiness?.whenPainted === "function";
18933
- v2ReviewMutationReconciler.enqueueAfterPaint(reviewImpact, canWaitForExactPaint ? () => readiness.whenPainted.call(readiness, receipt) : null);
19164
+ const reconciliation = v2ReviewMutationReconciler.enqueueAfterPaint(reviewImpact, canWaitForExactPaint ? () => readiness.whenPainted.call(readiness, receipt) : null);
19165
+ if (event.reviewMutation?.token) {
19166
+ const reviewMutation = event.reviewMutation;
19167
+ const allResolved = Boolean(reviewImpact?.allResolved ?? event.trackedChangeAllResolved);
19168
+ const settleReviewMutation = (reconciled) => {
19169
+ const reconciledAllResolved = allResolved && reconciled === true;
19170
+ v2ReviewHydrationController.settleMutation(reviewMutation.token, {
19171
+ outcome: "committed",
19172
+ allResolved: reconciledAllResolved,
19173
+ resumeDomains: reconciledAllResolved ? ["comments"] : ["comments", "trackedChanges"],
19174
+ trackedRowCount: commentsStore.getV2TrackedChangeRowCount?.(documentId) ?? null
19175
+ });
19176
+ };
19177
+ Promise.resolve(reconciliation).then(settleReviewMutation, () => settleReviewMutation(false));
19178
+ return;
19179
+ }
18934
19180
  if (reviewImpact?.reconcileMode === "authoritative" && reviewImpact.upsertIds.size > 0) {
18935
19181
  hydrateV2CommentRowsFromHost();
18936
19182
  return;
@@ -20065,7 +20311,7 @@ var SuperDoc_default = /* @__PURE__ */ __plugin_vue_export_helper_default({
20065
20311
  ], 38);
20066
20312
  };
20067
20313
  }
20068
- }, [["__scopeId", "data-v-d75089d2"]]);
20314
+ }, [["__scopeId", "data-v-e489229f"]]);
20069
20315
  var PINIA_DEVTOOLS_SETUP_EVENT = "devtools-plugin:setup";
20070
20316
  var PINIA_DEVTOOLS_PLUGIN_ID = "dev.esm.pinia";
20071
20317
  var piniaDevtoolsSuppressionState = {
@@ -38779,7 +39025,7 @@ var SuperDoc = class extends import_eventemitter3.default {
38779
39025
  this.config.colors = shuffleArray(this.config.colors);
38780
39026
  this.userColorMap = /* @__PURE__ */ new Map();
38781
39027
  this.colorIndex = 0;
38782
- this.version = "2.4.0-next.2";
39028
+ this.version = "2.4.0-next.3";
38783
39029
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
38784
39030
  this.superdocId = config.superdocId || v4_default();
38785
39031
  this.colors = this.config.colors ?? [];
@@ -39661,8 +39907,8 @@ var SuperDoc = class extends import_eventemitter3.default {
39661
39907
  },
39662
39908
  {
39663
39909
  feature: "shell.tracked-change-sidebar.bulk",
39664
- status: "not-shipped",
39665
- reason: "bulk-tracked-change-decisions-omitted: acceptAll/rejectAll are matrix-disabled by default in the v2 host"
39910
+ status: "supported",
39911
+ reason: "SD-4039/SD-4040: the shipped v2 command posture exposes all-story Accept All and Reject All through the canonical doc.trackChanges.decide({ target: { kind: 'all' } }) mutation"
39666
39912
  },
39667
39913
  {
39668
39914
  feature: "shell.tracked-change-sidebar.non-body",