superdoc 2.29.0 → 2.30.0-next.19

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 (138) hide show
  1. package/README.md +127 -25
  2. package/dist/chunks/{FindReplaceSurface-B_5ggdWg.es.js → FindReplaceSurface-BEnRLpD9.es.js} +1 -1
  3. package/dist/chunks/{FindReplaceSurface-fVHxPXtn.cjs → FindReplaceSurface-DWXgWgg7.cjs} +1 -1
  4. package/dist/chunks/{PasswordPromptSurface-CZqhDQQ1.es.js → PasswordPromptSurface-DjV1JgjD.es.js} +1 -1
  5. package/dist/chunks/{PasswordPromptSurface-x8GNmMJT.cjs → PasswordPromptSurface-VFPnQOML.cjs} +1 -1
  6. package/dist/chunks/{PdfViewer-CW09MNrB.es.js → PdfViewer-C2r-K8zC.es.js} +1 -1
  7. package/dist/chunks/{PdfViewer-CkrAHpC-.cjs → PdfViewer-DYTbPVUr.cjs} +1 -1
  8. package/dist/chunks/{_plugin-vue_export-helper-BkbCxNeT.cjs → _plugin-vue_export-helper-BzM2_tVU.cjs} +9 -0
  9. package/dist/chunks/{_plugin-vue_export-helper-DRPRMkeA.es.js → _plugin-vue_export-helper-CmkaJDnr.es.js} +4 -1
  10. package/dist/chunks/{create-super-doc-ui-L60GhVAX.cjs → create-super-doc-ui-D7uBUZfs.cjs} +14347 -13625
  11. package/dist/chunks/{create-super-doc-ui-Dc8dQAMc.es.js → create-super-doc-ui-_t6kMzLs.es.js} +14076 -13360
  12. package/dist/collaboration-upgrade-engine.cjs +1 -1
  13. package/dist/collaboration-upgrade-engine.es.js +1 -1
  14. package/dist/document-api/src/changes/changes.d.ts +26 -0
  15. package/dist/document-api/src/changes/changes.types.d.ts +156 -0
  16. package/dist/document-api/src/comments/comment-create-attribution.d.ts +4 -0
  17. package/dist/document-api/src/comments/comments.types.d.ts +4 -0
  18. package/dist/document-api/src/content-controls/content-controls.types.d.ts +1 -0
  19. package/dist/document-api/src/contract/command-catalog.d.ts +1 -1
  20. package/dist/document-api/src/contract/operation-definitions.d.ts +76 -8
  21. package/dist/document-api/src/contract/operation-registry.d.ts +2 -2
  22. package/dist/document-api/src/contract/schemas.d.ts +7 -1
  23. package/dist/document-api/src/create/create.d.ts +0 -4
  24. package/dist/document-api/src/diff/diff.types.d.ts +6 -6
  25. package/dist/document-api/src/header-footers/header-footers.d.ts +1 -3
  26. package/dist/document-api/src/index.d.ts +13 -1
  27. package/dist/document-api/src/inline-semantics/token-sets.d.ts +0 -2
  28. package/dist/document-api/src/insert/insert.d.ts +0 -1
  29. package/dist/document-api/src/paragraphs/paragraphs.d.ts +8 -3
  30. package/dist/document-api/src/paragraphs/paragraphs.types.d.ts +49 -5
  31. package/dist/document-api/src/tables/data/table-preset-appearance.generated.d.ts +2 -0
  32. package/dist/document-api/src/tables/table-style-presets.d.ts +73 -0
  33. package/dist/document-api/src/tables/tables.d.ts +4 -1
  34. package/dist/document-api/src/types/sd-props.d.ts +7 -0
  35. package/dist/document-api/src/types/table-operations.types.d.ts +12 -15
  36. package/dist/layout-engine/contracts/src/index.d.ts +4 -1
  37. package/dist/layout-engine/contracts/src/table-cell-vector-overlay.d.ts +2 -0
  38. package/dist/layout-engine/dom-contract/src/data-attrs.d.ts +4 -0
  39. package/dist/layout-engine/layout-bridge/src/coupled-footnote-pagination.d.ts +1 -0
  40. package/dist/layout-engine/layout-bridge/src/footnote-page-certificate.d.ts +1 -0
  41. package/dist/layout-engine/layout-bridge/src/footnote-page-planner.d.ts +5 -0
  42. package/dist/layout-engine/layout-bridge/src/incrementalLayout.d.ts +1 -0
  43. package/dist/layout-engine/layout-engine/src/footnote-page-flow.d.ts +3 -0
  44. package/dist/layout-engine/layout-engine/src/index.d.ts +7 -0
  45. package/dist/layout-engine/layout-engine/src/layout-paragraph.d.ts +5 -0
  46. package/dist/layout-engine/layout-engine/src/layout-table.d.ts +3 -1
  47. package/dist/layout-engine/layout-engine/src/layout-utils.d.ts +0 -4
  48. package/dist/layout-engine/layout-engine/src/paginator.d.ts +4 -0
  49. package/dist/layout-engine/painters/dom/src/index.d.ts +2 -0
  50. package/dist/layout-engine/painters/dom/src/paragraph/list-marker.d.ts +1 -1
  51. package/dist/layout-engine/painters/dom/src/paragraph/renderParagraphContent.d.ts +1 -1
  52. package/dist/layout-engine/painters/dom/src/styles.d.ts +0 -10
  53. package/dist/layout-engine/painters/dom/src/svg-utils.d.ts +1 -13
  54. package/dist/public/ui-react.cjs +1 -1
  55. package/dist/public/ui-react.es.js +1 -1
  56. package/dist/public/ui-vue.cjs +1 -1
  57. package/dist/public/ui-vue.es.js +1 -1
  58. package/dist/public/ui.cjs +1 -1
  59. package/dist/public/ui.es.js +1 -1
  60. package/dist/shared/common/Telemetry.d.ts +50 -0
  61. package/dist/shared/common/event-types.d.ts +13 -0
  62. package/dist/shared/font-system/src/bundled-face-names.d.ts +1 -0
  63. package/dist/shared/font-system/src/resolver.d.ts +1 -0
  64. package/dist/style.css +1 -1
  65. package/dist/style.layered.css +1 -1
  66. package/dist/superdoc/src/components/CommentsLayer/CommentDialog.vue.d.ts +17 -33
  67. package/dist/superdoc/src/components/CommentsLayer/collect-tracked-change-thread.d.ts +6 -4
  68. package/dist/superdoc/src/components/CommentsLayer/comment-params.d.ts +84 -0
  69. package/dist/superdoc/src/components/CommentsLayer/use-comment.d.ts +15 -95
  70. package/dist/superdoc/src/components/Whiteboard/use-whiteboard.d.ts +30 -9
  71. package/dist/superdoc/src/composables/use-comment-sidebar.d.ts +6 -0
  72. package/dist/superdoc/src/composables/use-document.d.ts +51 -99
  73. package/dist/superdoc/src/core/SuperDoc.d.ts +12 -3
  74. package/dist/superdoc/src/core/collaboration/cdn-collaboration-comment-map.d.ts +1 -0
  75. package/dist/superdoc/src/core/collaboration/collaboration-comment-map.d.ts +2 -0
  76. package/dist/superdoc/src/core/collaboration/helpers.d.ts +1 -1
  77. package/dist/superdoc/src/core/config/normalize-interaction-config.d.ts +10 -1
  78. package/dist/superdoc/src/core/document-open-telemetry.d.ts +1 -1
  79. package/dist/superdoc/src/core/editor-runtime/types.d.ts +4 -0
  80. package/dist/superdoc/src/core/editor-runtime/v2/v2-editor-runtime-adapter.d.ts +1 -0
  81. package/dist/superdoc/src/core/types/index.d.ts +110 -42
  82. package/dist/superdoc/src/core/v2-integration/v2-integration.d.ts +6 -0
  83. package/dist/superdoc/src/helpers/deprecation.d.ts +12 -3
  84. package/dist/superdoc/src/helpers/object-guards.d.ts +1 -0
  85. package/dist/superdoc/src/helpers/v2-review-geometry-delivery.d.ts +22 -0
  86. package/dist/superdoc/src/internal/toolbar/built-in/ToolbarButton.vue.d.ts +1 -0
  87. package/dist/superdoc/src/public/content-control-ref.d.ts +12 -0
  88. package/dist/superdoc/src/public/index.d.cts +20 -0
  89. package/dist/superdoc/src/public/index.d.ts +4 -1
  90. package/dist/superdoc/src/public/ui/async-read-cache.d.ts +32 -0
  91. package/dist/superdoc/src/public/ui/block-commands.d.ts +39 -0
  92. package/dist/superdoc/src/public/ui/command-results.d.ts +39 -0
  93. package/dist/superdoc/src/public/ui/commands.d.ts +5 -1
  94. package/dist/superdoc/src/public/ui/content-controls-handle.d.ts +31 -0
  95. package/dist/superdoc/src/public/ui/create-super-doc-ui.d.ts +1 -17
  96. package/dist/superdoc/src/public/ui/document-change-records.d.ts +7 -0
  97. package/dist/superdoc/src/public/ui/entity-rows.d.ts +14 -0
  98. package/dist/superdoc/src/public/ui/font-options.d.ts +7 -0
  99. package/dist/superdoc/src/public/ui/format-painter-paragraph.d.ts +7 -0
  100. package/dist/superdoc/src/public/ui/format-painter-state.d.ts +29 -0
  101. package/dist/superdoc/src/public/ui/heavy-read-gate.d.ts +41 -0
  102. package/dist/superdoc/src/public/ui/heavy-read-policy.d.ts +11 -0
  103. package/dist/superdoc/src/public/ui/host-events.d.ts +3 -0
  104. package/dist/superdoc/src/public/ui/inline-values.d.ts +80 -0
  105. package/dist/superdoc/src/public/ui/loose-record.d.ts +4 -0
  106. package/dist/superdoc/src/public/ui/reasons.d.ts +1 -0
  107. package/dist/superdoc/src/public/ui/search-handle.d.ts +24 -0
  108. package/dist/superdoc/src/public/ui/selection-targets.d.ts +53 -0
  109. package/dist/superdoc/src/public/ui/shared-tracked-changes-catalog.d.ts +16 -0
  110. package/dist/superdoc/src/public/ui/styles-handle.d.ts +54 -0
  111. package/dist/superdoc/src/public/ui/tracked-change-ids.d.ts +51 -0
  112. package/dist/superdoc/src/public/ui/types.d.ts +31 -2
  113. package/dist/superdoc/src/public/ui/ui-bench-timing.d.ts +9 -0
  114. package/dist/superdoc/src/public/ui.d.cts +18 -0
  115. package/dist/superdoc/src/public/ui.d.ts +1 -1
  116. package/dist/superdoc/src/stores/comments-store.d.ts +23052 -537
  117. package/dist/superdoc/src/stores/helpers/comment-grouping.d.ts +1 -1
  118. package/dist/superdoc/src/stores/helpers/comment-identity.d.ts +10 -10
  119. package/dist/superdoc/src/stores/helpers/pending-selection-snapshot.d.ts +3 -3
  120. package/dist/superdoc/src/stores/helpers/review-presentation.d.ts +91 -0
  121. package/dist/superdoc/src/stores/helpers/tracked-change-identity.d.ts +4 -2
  122. package/dist/superdoc/src/stores/helpers/tracked-change-row-values.d.ts +1 -8
  123. package/dist/superdoc/src/stores/helpers/tracked-change-thread-index.d.ts +1 -1
  124. package/dist/superdoc/src/stores/helpers/v2-review-adapters.d.ts +148 -0
  125. package/dist/superdoc/src/stores/superdoc-store.d.ts +12 -2895
  126. package/dist/superdoc.cjs +1000 -566
  127. package/dist/superdoc.es.js +1000 -566
  128. package/dist/word-layout/src/marker-utils.d.ts +0 -5
  129. package/dist/word-layout/src/unit-conversions.d.ts +0 -1
  130. package/dist-cdn/style.layered.css +1 -1
  131. package/dist-cdn/superdoc.min.css +1 -1
  132. package/dist-cdn/superdoc.min.js +38 -42
  133. package/package.json +12 -3
  134. package/dist/document-api/src/diff/index.d.ts +0 -3
  135. package/dist/document-api/src/write/locator.d.ts +0 -24
  136. package/dist/layout-engine/painters/dom/src/features/feature-registry.d.ts +0 -41
  137. package/dist/superdoc/src/internal/toolbar/built-in/format-negation.d.ts +0 -11
  138. package/dist/word-layout/src/tab-layout.d.ts +0 -193
@@ -1,7 +1,7 @@
1
1
  import { i as __toESM$2 } from "./chunks/rolldown-runtime-0pSA04fp.es.js";
2
- import { $ as vModelSelect, A as onActivated, B as renderList, C as isRef$1, D as normalizeClass, E as nextTick, F as openBlock, G as toDisplayString, H as resolveDirective, I as provide$1, J as toRefs, K as toRaw$2, L as reactive, M as onDeactivated, N as onMounted, O as normalizeProps, P as onScopeDispose$1, Q as vModelCheckbox, R as readonly, S as isReactive$1, T as mergeProps, U as resolveDynamicComponent, V as renderSlot, W as shallowRef$1, X as useAttrs, Y as unref, Z as useSlots, _ as getCurrentInstance, a as computed$1, at as withModifiers, b as hasInjectionContext, c as createCommentVNode, d as createStaticVNode, et as vModelText, f as createTextVNode, g as effectScope, h as defineComponent, i as Transition, it as withKeys, j as onBeforeUnmount, k as normalizeStyle, l as createElementBlock, m as defineAsyncComponent, n as Fragment, nt as withCtx, o as createApp, ot as isPlainObject$1, p as createVNode, q as toRef, r as Teleport, rt as withDirectives, s as createBlock, st as isThenable$1, t as _plugin_vue_export_helper_default, tt as watch$1, u as createElementVNode, v as getCurrentScope, w as markRaw, x as inject$1, y as h, z as ref } from "./chunks/_plugin-vue_export-helper-DRPRMkeA.es.js";
2
+ import { $ as vModelSelect, A as onActivated, B as renderList, C as isRef$1, D as normalizeClass, E as nextTick, F as openBlock, G as toDisplayString, H as resolveDirective, I as provide$1, J as toRefs, K as toRaw$2, L as reactive, M as onDeactivated, N as onMounted, O as normalizeProps, P as onScopeDispose$1, Q as vModelCheckbox, R as readonly, S as isReactive$1, T as mergeProps, U as resolveDynamicComponent, V as renderSlot, W as shallowRef$1, X as useAttrs, Y as unref, Z as useSlots, _ as getCurrentInstance, a as computed$1, at as withModifiers, b as hasInjectionContext, c as createCommentVNode, ct as isThenable$1, d as createStaticVNode, et as vModelText, f as createTextVNode, g as effectScope, h as defineComponent, i as Transition, it as withKeys, j as onBeforeUnmount, k as normalizeStyle, l as createElementBlock, m as defineAsyncComponent, n as Fragment, nt as withCtx, o as createApp, p as createVNode, q as toRef, r as Teleport, rt as withDirectives, s as createBlock, st as isPlainObject$1, t as _plugin_vue_export_helper_default, tt as watch$1, u as createElementVNode, v as getCurrentScope, w as markRaw, x as inject$1, y as h, z as ref } from "./chunks/_plugin-vue_export-helper-CmkaJDnr.es.js";
3
3
  import { t as blank_default } from "./chunks/blank-docx-DzQccOlW.es.js";
4
- import { C as getWorkflowRecording, S as createInteractionHistory, T as recordInteraction, _ as createTrackedChangePermissionResolver, a as registerFirstPartyCommandMutation, b as DOM_CLASS_NAMES, c as getCommandDescriptor, d as mutationCommittedEventChangedDocument, f as createImageFilePicker, g as PERMISSIONS, h as BUILT_IN_PERMISSION_METHOD, i as readFirstPartyPageNumbers, l as applyV2CommentInvalidationFromMutation, n as executeFirstPartyCommandAsync, o as isRecord, p as resolveImageSrc, r as executeFirstPartyShortcutAsync, t as createSuperDocUI, u as getV2TrackedChangeMutationImpact, v as isAllowed, w as isWorkflowRecordingEnabled, x as closeInteractionHistory, y as isSyntheticTrackedChangeCommentLaneItem } from "./chunks/create-super-doc-ui-Dc8dQAMc.es.js";
4
+ import { C as createInteractionHistory, E as recordInteraction, S as closeInteractionHistory, T as isWorkflowRecordingEnabled, _ as isAllowed, a as registerFirstPartyCommandMutation, b as isRecord, c as applyV2CommentInvalidationFromMutation, d as createImageFilePicker, f as resolveImageSrc, g as createTrackedChangePermissionResolver, h as PERMISSIONS, i as readFirstPartyPageNumbers, l as getV2TrackedChangeMutationImpact, m as BUILT_IN_PERMISSION_METHOD, n as executeFirstPartyCommandAsync, r as executeFirstPartyShortcutAsync, s as getCommandDescriptor, t as createSuperDocUI, u as mutationCommittedEventChangedDocument, v as isSyntheticTrackedChangeCommentLaneItem, w as getWorkflowRecording, x as DOM_CLASS_NAMES, y as isStoryLocator } from "./chunks/create-super-doc-ui-_t6kMzLs.es.js";
5
5
  import { t as import_eventemitter3 } from "./chunks/eventemitter3-DEIiXiH2.es.js";
6
6
  import { t as v4 } from "./chunks/uuid-H0Xcmmhy.es.js";
7
7
  import { a as init_dist$1, i as global, n as init_dist$2, o as Buffer, r as process$1, s as init_dist, t as require_jszip_min } from "./chunks/jszip-BzJ3CyxR.es.js";
@@ -258,7 +258,7 @@ var v_click_outside_default = {
258
258
  var DEFAULT_ENDPOINT = "https://ingest.superdoc.dev/v1/collect";
259
259
  function getSuperdocVersion() {
260
260
  try {
261
- return "2.29.0";
261
+ return "2.30.0-next.19";
262
262
  } catch {
263
263
  return "unknown";
264
264
  }
@@ -5838,6 +5838,11 @@ function useCheckboxField(field) {
5838
5838
  }) : itemoptions) };
5839
5839
  }
5840
5840
  //#endregion
5841
+ //#region src/core/collaboration/collaboration-comment-map.ts
5842
+ function createCollaborationCommentMap(entries) {
5843
+ return new Map$1(entries);
5844
+ }
5845
+ //#endregion
5841
5846
  //#region src/core/collaboration/collaboration-comments.ts
5842
5847
  var readComment = (event) => {
5843
5848
  const { comment } = event;
@@ -5846,14 +5851,14 @@ var readComment = (event) => {
5846
5851
  };
5847
5852
  var addYComment = (yArray, ydoc, event, user) => {
5848
5853
  const comment = readComment(event);
5849
- const yComment = new Map$1(Object.entries(comment));
5854
+ const yComment = createCollaborationCommentMap(Object.entries(comment));
5850
5855
  ydoc.transact(() => {
5851
5856
  yArray.push([yComment]);
5852
5857
  }, { user });
5853
5858
  };
5854
5859
  var updateYComment = (yArray, ydoc, event, user) => {
5855
5860
  const comment = readComment(event);
5856
- const yComment = new Map$1(Object.entries(comment));
5861
+ const yComment = createCollaborationCommentMap(Object.entries(comment));
5857
5862
  const commentIndex = getCommentIndex(yArray, comment);
5858
5863
  if (commentIndex === -1) return;
5859
5864
  ydoc.transact(() => {
@@ -6750,6 +6755,136 @@ var useSuperdocStore = defineStore("superdoc", () => {
6750
6755
  };
6751
6756
  });
6752
6757
  //#endregion
6758
+ //#region ../document-api/src/errors.ts
6759
+ /**
6760
+ * Structured validation error thrown by document-api execute* functions.
6761
+ *
6762
+ * Consumers should prefer checking `error.code` over `instanceof` for resilience
6763
+ * across package boundaries and bundling scenarios.
6764
+ */
6765
+ var DocumentApiValidationError = class DocumentApiValidationError extends Error {
6766
+ constructor(code, message, details) {
6767
+ super(message);
6768
+ this.name = "DocumentApiValidationError";
6769
+ this.code = code;
6770
+ this.details = details;
6771
+ Object.setPrototypeOf(this, DocumentApiValidationError.prototype);
6772
+ }
6773
+ };
6774
+ //#endregion
6775
+ //#region ../document-api/src/export/export.types.ts
6776
+ /** Canonical tracked-change export modes accepted by every public export surface. */
6777
+ var SD_EXPORT_MODES = [
6778
+ "review-preserving",
6779
+ "final",
6780
+ "original"
6781
+ ];
6782
+ /** Default mode for modeful DOCX export. */
6783
+ var DEFAULT_SD_EXPORT_MODE = "review-preserving";
6784
+ var SD_COMMENT_EXPORT_POLICIES = [
6785
+ "preserve",
6786
+ "resolve",
6787
+ "remove"
6788
+ ];
6789
+ var SD_CONTENT_CONTROL_EXPORT_POLICIES = ["preserve", "flatten"];
6790
+ //#endregion
6791
+ //#region ../document-api/src/export/export.ts
6792
+ /**
6793
+ * Export namespace: engine-agnostic public API and adapter contract.
6794
+ *
6795
+ * Validates the requested mode at the public boundary so an invalid mode fails
6796
+ * closed with `INVALID_INPUT` before any document work happens, then delegates
6797
+ * byte production and degradation reporting to the engine adapter.
6798
+ */
6799
+ function resolveMode$1(input) {
6800
+ if (input != null && !isRecord(input)) throw new DocumentApiValidationError("INVALID_INPUT", "export.toDocx input must be an object.");
6801
+ const raw = input?.mode;
6802
+ if (raw === void 0) return DEFAULT_SD_EXPORT_MODE;
6803
+ if (typeof raw !== "string" || !SD_EXPORT_MODES.includes(raw)) throw new DocumentApiValidationError("INVALID_INPUT", `export.toDocx mode must be one of: ${SD_EXPORT_MODES.join(", ")}.`, {
6804
+ mode: raw,
6805
+ allowedModes: [...SD_EXPORT_MODES]
6806
+ });
6807
+ return raw;
6808
+ }
6809
+ function resolveDocxExportPolicies(input = {}) {
6810
+ if (!isRecord(input)) throw new DocumentApiValidationError("INVALID_INPUT", "DOCX export input must be an object.");
6811
+ const mode = resolveMode$1(input);
6812
+ const policies = input.policies;
6813
+ if (policies !== void 0 && !isRecord(policies)) throw new DocumentApiValidationError("INVALID_INPUT", "DOCX export policies must be an object.");
6814
+ const allowed = {
6815
+ trackedChanges: SD_EXPORT_MODES,
6816
+ comments: SD_COMMENT_EXPORT_POLICIES,
6817
+ contentControls: SD_CONTENT_CONTROL_EXPORT_POLICIES
6818
+ };
6819
+ for (const [key, value] of Object.entries(policies ?? {})) if (!Object.prototype.hasOwnProperty.call(allowed, key) || value !== void 0 && !allowed[key].includes(value)) throw new DocumentApiValidationError("INVALID_INPUT", `Unsupported DOCX export policy: ${key}.`, {
6820
+ policy: key,
6821
+ value
6822
+ });
6823
+ const requested = policies;
6824
+ if (input.mode !== void 0 && requested?.trackedChanges !== void 0 && mode !== requested.trackedChanges) throw new DocumentApiValidationError("INVALID_INPUT", "mode conflicts with policies.trackedChanges.");
6825
+ return {
6826
+ trackedChanges: requested?.trackedChanges ?? mode,
6827
+ comments: requested?.comments ?? "preserve",
6828
+ contentControls: requested?.contentControls ?? "preserve"
6829
+ };
6830
+ }
6831
+ new TextEncoder();
6832
+ function normalizeCommentIdentity(value) {
6833
+ if (value === null) return null;
6834
+ if (typeof value !== "object" || Array.isArray(value) || !("providerId" in value) || !("userId" in value) || !xmlIdentityString(value.providerId) || !xmlIdentityString(value.userId)) throw new TypeError("commentIdentity requires XML-safe non-empty providerId and userId strings.");
6835
+ return {
6836
+ providerId: value.providerId,
6837
+ userId: value.userId
6838
+ };
6839
+ }
6840
+ function xmlIdentityString(value) {
6841
+ return typeof value === "string" && value.trim().length > 0 && !/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/u.test(value);
6842
+ }
6843
+ //#endregion
6844
+ //#region ../document-api/src/permission-ranges/permission-ranges.ts
6845
+ function editorKey(editor) {
6846
+ return editor.id?.trim() ? editor.id : editor.email?.trim() || void 0;
6847
+ }
6848
+ function editorMatches(editor, id) {
6849
+ return Boolean(id.trim()) && (editor.id === id || Boolean(editor.email?.trim() && editor.email.trim().toLowerCase() === id.toLowerCase()));
6850
+ }
6851
+ /**
6852
+ * Resolve a host ID or imported Word email against an explicitly supplied roster.
6853
+ * Word receives the email when available; opaque IDs remain host-specific.
6854
+ * Duplicate IDs/emails are ambiguous. This does not authenticate a user.
6855
+ */
6856
+ function resolvePermissionRangeEditor(editors, id) {
6857
+ const matches = editors.filter((editor) => editorMatches(editor, id));
6858
+ if (matches.length === 0) return {
6859
+ status: "unknown",
6860
+ id
6861
+ };
6862
+ if (matches.length !== 1) return {
6863
+ status: "ambiguous",
6864
+ id
6865
+ };
6866
+ const editor = matches[0];
6867
+ const wordId = editor.email?.trim() || editor.id;
6868
+ if (editors.filter((candidate) => editorMatches(candidate, wordId)).length !== 1) return {
6869
+ status: "ambiguous",
6870
+ id
6871
+ };
6872
+ return {
6873
+ status: "resolved",
6874
+ id,
6875
+ editor: { ...editor },
6876
+ principal: {
6877
+ kind: "editor",
6878
+ id: wordId
6879
+ }
6880
+ };
6881
+ }
6882
+ /** Enumerate the host roster for an editor picker, including ambiguous entries. */
6883
+ function listPermissionRangeEditors(editors) {
6884
+ const ids = new Set(editors.map(editorKey).filter((id) => id !== void 0));
6885
+ return Array.from(ids, (id) => resolvePermissionRangeEditor(editors, id));
6886
+ }
6887
+ //#endregion
6753
6888
  //#region src/stores/helpers/floating-comment-instances.ts
6754
6889
  var HEADER_FOOTER_TRACKED_CHANGE_STORY_TYPE = "headerFooterPart";
6755
6890
  var toFinitePageIndex = (value) => {
@@ -7111,6 +7246,7 @@ var groupCommittedComments = (comments, presentations, isVisible) => {
7111
7246
  });
7112
7247
  const grouped = groupComments(comments.filter((row) => !presentations[String(row.fileId)]), isVisible);
7113
7248
  for (const [documentId, presentation] of Object.entries(presentations)) {
7249
+ if (!presentation) continue;
7114
7250
  grouped.parentComments.push(...visibleRowsInOrder(documentId, presentation.groupOrder.open));
7115
7251
  grouped.resolvedComments.push(...visibleRowsInOrder(documentId, presentation.groupOrder.resolved));
7116
7252
  }
@@ -7806,9 +7942,55 @@ var createDroppedCommentIdMap = (duplicates) => {
7806
7942
  return droppedCommentIds;
7807
7943
  };
7808
7944
  //#endregion
7809
- //#region src/stores/comments-store.js
7945
+ //#region src/stores/helpers/v2-review-adapters.ts
7946
+ var COMMENTS_ADAPTER_METHODS = [
7947
+ "setActiveComment",
7948
+ "commitPendingComment",
7949
+ "reply",
7950
+ "edit",
7951
+ "resolve",
7952
+ "reopen",
7953
+ "delete",
7954
+ "mapV2CommentToUseCommentInput"
7955
+ ];
7956
+ var COMMENTS_ADAPTER_OPTIONAL_METHODS = [
7957
+ "getCapabilityState",
7958
+ "captureCurrentSelection",
7959
+ "isPendingCommentCreation",
7960
+ "seedReviewCatalog",
7961
+ "selectVisibleReviewComments"
7962
+ ];
7963
+ var TRACKED_CHANGES_ADAPTER_METHODS = [
7964
+ "accept",
7965
+ "reject",
7966
+ "mapV2TrackedChangeToCommentParams"
7967
+ ];
7968
+ var TRACKED_CHANGES_ADAPTER_OPTIONAL_METHODS = ["clearActiveTrackedChangeTargetIfMatches"];
7969
+ var isAdapterDocumentId = (value) => value === void 0 || value === null || typeof value === "string" || typeof value === "number";
7970
+ var hasAdapterShape = (value, required, optional) => isPlainObject$1(value) && isAdapterDocumentId(value.documentId) && required.every((member) => typeof value[member] === "function") && optional.every((member) => value[member] === void 0 || typeof value[member] === "function");
7971
+ function isV2CommentsAdapter(value) {
7972
+ return hasAdapterShape(value, COMMENTS_ADAPTER_METHODS, COMMENTS_ADAPTER_OPTIONAL_METHODS);
7973
+ }
7974
+ function isV2TrackedChangesAdapter(value) {
7975
+ return hasAdapterShape(value, TRACKED_CHANGES_ADAPTER_METHODS, TRACKED_CHANGES_ADAPTER_OPTIONAL_METHODS);
7976
+ }
7977
+ //#endregion
7978
+ //#region src/stores/comments-store.ts
7979
+ var requireHost = (superdoc, action) => {
7980
+ if (!superdoc) throw new TypeError(`${action} requires superdoc`);
7981
+ return superdoc;
7982
+ };
7983
+ var PARENT_LINK_FIELDS = [
7984
+ "trackedChangeThreadParentId",
7985
+ "trackedChangeParentId",
7986
+ "threadingParentCommentId",
7987
+ "parentCommentId"
7988
+ ];
7989
+ var isPresent = (value) => Boolean(value);
7990
+ var errorDetail = (error) => (error == null ? void 0 : Reflect.get(Object(error), "message")) ?? String(error);
7810
7991
  var useCommentsStore = defineStore("comments", () => {
7811
7992
  const superdocStore = useSuperdocStore();
7993
+ const findDocument = (documentId) => typeof documentId === "string" ? superdocStore.getDocument(documentId) : void 0;
7812
7994
  const commentsConfig = reactive({
7813
7995
  name: "comments",
7814
7996
  readOnly: false,
@@ -7838,6 +8020,7 @@ var useCommentsStore = defineStore("comments", () => {
7838
8020
  });
7839
8021
  const debounceTimers = {};
7840
8022
  const trackedChangeResolutionSnapshots = /* @__PURE__ */ new WeakMap();
8023
+ let cachedTrackedChangeBatchIdentityIndex = null;
7841
8024
  const isBenchmarkCommentsDebugEnabled = () => {
7842
8025
  if (typeof window === "undefined") return false;
7843
8026
  return "__labsSuperDocV2BenchmarkDebug" in window;
@@ -7875,7 +8058,7 @@ var useCommentsStore = defineStore("comments", () => {
7875
8058
  });
7876
8059
  const getTrackedChangeThread = (parentComment) => {
7877
8060
  const id = parentComment?.commentId ?? null;
7878
- return (id != null ? trackedChangeThreadIndex.value.get(id) : null) ?? [parentComment].filter(Boolean);
8061
+ return (id != null ? trackedChangeThreadIndex.value.get(id) : null) ?? (parentComment ? [parentComment] : []);
7879
8062
  };
7880
8063
  let priorTrackedChangeDecisionLinkIndex = /* @__PURE__ */ new Map();
7881
8064
  const trackedChangeDecisionLinkIndex = computed$1(() => {
@@ -7886,6 +8069,7 @@ var useCommentsStore = defineStore("comments", () => {
7886
8069
  const isCommentsListVisible = ref(false);
7887
8070
  const isReviewDirectoryActive = ref(false);
7888
8071
  const isReviewDirectoryLoading = ref(false);
8072
+ const reviewAnalysisProgress = ref(null);
7889
8073
  const editorCommentIds = ref([]);
7890
8074
  const editorCommentPositions = ref({});
7891
8075
  const isCommentHighlighted = ref(false);
@@ -7907,29 +8091,28 @@ var useCommentsStore = defineStore("comments", () => {
7907
8091
  });
7908
8092
  const v2CommentsAdapter = shallowRef$1(null);
7909
8093
  const setV2CommentsAdapter = (adapter) => {
7910
- if (v2CommentsAdapter.value !== (adapter ?? null)) pendingCommentRequest += 1;
7911
- v2CommentsAdapter.value = adapter ?? null;
8094
+ const next = isV2CommentsAdapter(adapter) ? adapter : null;
8095
+ if (v2CommentsAdapter.value !== next) pendingCommentRequest += 1;
8096
+ v2CommentsAdapter.value = next;
7912
8097
  };
7913
8098
  const getV2CommentsAdapter = (superdoc) => {
7914
- const fromFacade = superdoc?.activeEditor?.v2Comments ?? null;
7915
- if (fromFacade) return fromFacade;
8099
+ const fromFacade = superdoc?.activeEditor?.v2Comments;
8100
+ if (isV2CommentsAdapter(fromFacade)) return fromFacade;
7916
8101
  return v2CommentsAdapter.value;
7917
8102
  };
7918
8103
  const isV2EditorActive = (superdoc) => superdoc?.activeEditor?.editorVersion === 2 || v2CommentsAdapter.value !== null;
7919
8104
  const v2TrackedChangesAdapter = shallowRef$1(null);
7920
8105
  const setV2TrackedChangesAdapter = (adapter) => {
7921
- v2TrackedChangesAdapter.value = adapter ?? null;
8106
+ cachedTrackedChangeBatchIdentityIndex = null;
8107
+ v2TrackedChangesAdapter.value = isV2TrackedChangesAdapter(adapter) ? adapter : null;
7922
8108
  };
7923
8109
  const getV2TrackedChangesAdapter = (superdoc) => {
7924
- const fromFacade = superdoc?.activeEditor?.v2TrackedChanges ?? null;
7925
- if (fromFacade) return fromFacade;
8110
+ const fromFacade = superdoc?.activeEditor?.v2TrackedChanges;
8111
+ if (isV2TrackedChangesAdapter(fromFacade)) return fromFacade;
7926
8112
  return v2TrackedChangesAdapter.value;
7927
8113
  };
7928
8114
  /**
7929
8115
  * Initialize the store
7930
- *
7931
- * @param {Object} config The comments module config from SuperDoc
7932
- * @returns {void}
7933
8116
  */
7934
8117
  const init = (config = {}) => {
7935
8118
  const updatedConfig = {
@@ -7961,12 +8144,17 @@ var useCommentsStore = defineStore("comments", () => {
7961
8144
  comment.trackedChangeDecision = null;
7962
8145
  };
7963
8146
  const getCommentEventPayload = (comment) => typeof comment?.getValues === "function" ? comment.getValues() : { ...comment };
8147
+ const getDeletedCommentChanges = (sources, fileId) => {
8148
+ const commentId = [...sources.map((source) => source?.commentId), ...sources.map((source) => source?.importedId)].map(normalizeCommentId).find(Boolean);
8149
+ return commentId ? [{
8150
+ key: "deleted",
8151
+ commentId,
8152
+ fileId
8153
+ }] : [];
8154
+ };
7964
8155
  /**
7965
8156
  * Check if a comment originated from the document editor (or has no explicit source).
7966
8157
  * Comments without a source are assumed to be editor-backed for backward compatibility.
7967
- *
7968
- * @param {Object} comment - The comment to check
7969
- * @returns {boolean} True if the comment is editor-backed
7970
8158
  */
7971
8159
  const isEditorBackedComment = (comment) => {
7972
8160
  const source = comment?.selection?.source;
@@ -8000,9 +8188,6 @@ var useCommentsStore = defineStore("comments", () => {
8000
8188
  }, { deep: false });
8001
8189
  /**
8002
8190
  * Get the editor position data for a comment.
8003
- *
8004
- * @param {Object | string} commentOrId The comment object or comment ID
8005
- * @returns {Object | null} The position data from editorCommentPositions
8006
8191
  */
8007
8192
  const getCommentPosition = (commentOrId) => {
8008
8193
  return resolveCommentPositionEntry(commentOrId).entry ?? null;
@@ -8015,10 +8200,6 @@ var useCommentsStore = defineStore("comments", () => {
8015
8200
  };
8016
8201
  /**
8017
8202
  * Set the active comment or clear all active comments
8018
- *
8019
- * @param {Object | undefined | null} superdoc The SuperDoc instance holding the active editor
8020
- * @param {string | undefined | null} id The comment ID
8021
- * @returns {void}
8022
8203
  */
8023
8204
  const setActiveComment = (superdoc, id) => {
8024
8205
  const activeEditor = superdoc?.activeEditor;
@@ -8029,7 +8210,7 @@ var useCommentsStore = defineStore("comments", () => {
8029
8210
  v2Adapter.setActiveComment(null);
8030
8211
  return;
8031
8212
  }
8032
- activeEditor?.commands?.setActiveComment({ commentId: null });
8213
+ activeEditor?.commands?.setActiveComment?.({ commentId: null });
8033
8214
  return;
8034
8215
  }
8035
8216
  const comment = getComment(id);
@@ -8040,18 +8221,13 @@ var useCommentsStore = defineStore("comments", () => {
8040
8221
  v2Adapter.setActiveComment(comment.commentId);
8041
8222
  return;
8042
8223
  }
8043
- activeEditor?.commands?.setActiveComment({ commentId: activeComment.value });
8224
+ activeEditor?.commands?.setActiveComment?.({ commentId: activeComment.value });
8044
8225
  };
8045
8226
  /**
8046
8227
  * Called when a tracked change is updated. Creates a new comment if necessary,
8047
8228
  * or updates an existing tracked-change comment.
8048
- *
8049
- * @param {Object} param0
8050
- * @param {Object} param0.superdoc The SuperDoc instance
8051
- * @param {Object} param0.params The tracked change params
8052
- * @returns {void}
8053
8229
  */
8054
- const handleTrackedChangeUpdate = ({ superdoc, params, broadcastChanges = true, trackedChangeIdentityIndex = null }) => {
8230
+ const handleTrackedChangeUpdate = ({ superdoc, params, broadcastChanges = true, trackedChangeIdentityIndex = null, preserveDraft = false }) => {
8055
8231
  const span = startInteractionSpan("store.trackedChanges.handleUpdate", "store-reconciliation", {
8056
8232
  event: params?.event ?? null,
8057
8233
  changeId: params?.changeId ?? null,
@@ -8076,9 +8252,11 @@ var useCommentsStore = defineStore("comments", () => {
8076
8252
  const normalizedTrackedChangeLabel = typeof trackedChangeLabel === "string" && trackedChangeLabel.length > 0 ? trackedChangeLabel : null;
8077
8253
  const normalizedTrackedChangeDetailLines = normalizeTrackedChangeDetailLines(trackedChangeDetailLines);
8078
8254
  const normalizedTrackedChangeImagePreview = normalizeTrackedChangeImagePreview(trackedChangeImagePreview);
8255
+ const hasDraft = preserveDraft && (pendingComment.value !== null || currentCommentText.value !== "" || currentCommentMentions.value.length > 0);
8079
8256
  const comment = getPendingComment({
8080
8257
  documentId,
8081
- commentId: changeId,
8258
+ ...hasDraft ? { commentText: "" } : {},
8259
+ commentId: normalizedChangeId ?? void 0,
8082
8260
  trackedChange: true,
8083
8261
  trackedChangeText,
8084
8262
  trackedChangeType,
@@ -8130,7 +8308,7 @@ var useCommentsStore = defineStore("comments", () => {
8130
8308
  commentCanonicalId,
8131
8309
  commentAnchorKey,
8132
8310
  ...commentAliasIds
8133
- ].filter(Boolean).some((id) => incomingAliasIds.has(id))) return true;
8311
+ ].some((id) => id != null && incomingAliasIds.has(id))) return true;
8134
8312
  const hasSyntheticSideIdentity = normalizedTrackedChangeCanonicalId && normalizedTrackedChangeCanonicalId !== normalizedChangeId || commentCanonicalId && commentCanonicalId !== commentId;
8135
8313
  if (hasCompatibleStory && commentId === normalizedChangeId) return true;
8136
8314
  if (hasCompatibleStory && !hasSyntheticSideIdentity && (importedId === normalizedChangeId || normalizedImportedId !== void 0 && normalizedImportedId != null && importedId === normalizedImportedId)) return true;
@@ -8154,7 +8332,7 @@ var useCommentsStore = defineStore("comments", () => {
8154
8332
  };
8155
8333
  const emitTrackedChangeEvent = (event) => {
8156
8334
  if (!broadcastChanges) return;
8157
- syncCommentsToClients(superdoc, event);
8335
+ syncCommentsToClients(requireHost(superdoc, "handleTrackedChangeUpdate"), event);
8158
8336
  debounceEmit(changeId, event, superdoc);
8159
8337
  };
8160
8338
  const setIfChanged = (target, key, value) => {
@@ -8197,8 +8375,9 @@ var useCommentsStore = defineStore("comments", () => {
8197
8375
  target?.trackedChangeCanonicalId,
8198
8376
  target?.trackedChangeAnchorKey,
8199
8377
  ...getCommentAliasIds(target)
8200
- ].map((id) => normalizeCommentId(id)).filter(Boolean));
8201
- fields.trackedChangePositionAliases = normalizedTrackedChangePositionAliases.some((alias) => targetIdentityIds.has(alias)) ? normalizeTrackedChangePositionAliases([...Array.isArray(target?.trackedChangePositionAliases) ? target.trackedChangePositionAliases : [], ...normalizedTrackedChangePositionAliases]) : normalizedTrackedChangePositionAliases;
8378
+ ].map((id) => normalizeCommentId(id)).filter(isPresent));
8379
+ const incomingAliases = normalizedTrackedChangePositionAliases ?? [];
8380
+ fields.trackedChangePositionAliases = incomingAliases.some((alias) => targetIdentityIds.has(alias)) ? normalizeTrackedChangePositionAliases([...Array.isArray(target?.trackedChangePositionAliases) ? target.trackedChangePositionAliases : [], ...incomingAliases]) : incomingAliases;
8202
8381
  }
8203
8382
  let didChange = false;
8204
8383
  for (const [key, value] of Object.entries(fields)) didChange = setIfChanged(target, key, value) || didChange;
@@ -8207,7 +8386,7 @@ var useCommentsStore = defineStore("comments", () => {
8207
8386
  didChange = true;
8208
8387
  }
8209
8388
  if (hasCustomAttributes && target && !trackedChangeCustomAttributesEqual(target.customAttributes, normalizedCustomAttributes)) {
8210
- target.customAttributes = normalizedCustomAttributes;
8389
+ target.customAttributes = normalizedCustomAttributes ?? null;
8211
8390
  didChange = true;
8212
8391
  }
8213
8392
  return applyStoryMetadata(target) || didChange;
@@ -8229,9 +8408,10 @@ var useCommentsStore = defineStore("comments", () => {
8229
8408
  return;
8230
8409
  }
8231
8410
  addHydratedComment({
8232
- superdoc,
8411
+ superdoc: requireHost(superdoc, "handleTrackedChangeUpdate"),
8233
8412
  comment,
8234
- broadcastChanges
8413
+ broadcastChanges,
8414
+ preserveDraft: hasDraft
8235
8415
  });
8236
8416
  trackedChangeIdentityIndex?.add(comment);
8237
8417
  } else if (event === "update") {
@@ -8249,11 +8429,12 @@ var useCommentsStore = defineStore("comments", () => {
8249
8429
  id: params.resolvedById ?? superdoc?.user?.id ?? null,
8250
8430
  email: params.resolvedByEmail ?? superdoc?.user?.email ?? null,
8251
8431
  name: params.resolvedByName ?? superdoc?.user?.name ?? null,
8252
- decision: params.decision ?? null,
8432
+ decision: params.decision ?? void 0,
8253
8433
  superdoc
8254
8434
  };
8255
8435
  if (existingTrackedChange && !existingTrackedChange.resolvedTime) existingTrackedChange.resolveComment({
8256
8436
  ...resolveArgs,
8437
+ superdoc: requireHost(superdoc, "handleTrackedChangeUpdate"),
8257
8438
  reconciliationToken: COMMENT_RECONCILIATION_TOKEN
8258
8439
  });
8259
8440
  }
@@ -8278,10 +8459,11 @@ var useCommentsStore = defineStore("comments", () => {
8278
8459
  instantSidebarAlignmentInstanceId.value = null;
8279
8460
  };
8280
8461
  const debounceEmit = (commentId, event, superdoc, delay = 1e3) => {
8281
- if (debounceTimers[commentId]) clearTimeout(debounceTimers[commentId]);
8282
- debounceTimers[commentId] = setTimeout(() => {
8462
+ const timerKey = String(commentId);
8463
+ if (debounceTimers[timerKey]) clearTimeout(debounceTimers[timerKey]);
8464
+ debounceTimers[timerKey] = setTimeout(() => {
8283
8465
  if (superdoc) superdoc.emit("comments-update", event);
8284
- delete debounceTimers[commentId];
8466
+ delete debounceTimers[timerKey];
8285
8467
  }, delay);
8286
8468
  };
8287
8469
  const showAddComment = (superdoc, targetClientY = null) => {
@@ -8293,7 +8475,7 @@ var useCommentsStore = defineStore("comments", () => {
8293
8475
  if (!ownsRequest()) return;
8294
8476
  superdoc.emit("comments-update", {
8295
8477
  type: COMMENT_EVENTS.PENDING,
8296
- pendingSelection: superdoc?.activeEditor === editor ? snapshot : null
8478
+ pendingSelection: superdoc?.activeEditor === editor && isPlainObject$1(snapshot) ? snapshot : null
8297
8479
  });
8298
8480
  };
8299
8481
  if (v2Adapter) {
@@ -8338,12 +8520,11 @@ var useCommentsStore = defineStore("comments", () => {
8338
8520
  }).catch((err) => ({
8339
8521
  ok: false,
8340
8522
  reason: "selection-capture-failed",
8341
- detail: err?.message ?? String(err)
8523
+ detail: errorDetail(err)
8342
8524
  }));
8343
8525
  }
8344
8526
  const pendingSelection = getPendingSelectionSnapshot(superdoc, superdocStore.activeSelection);
8345
- const asyncSnapshot = typeof pendingSelection?.then === "function";
8346
- if (!asyncSnapshot) emitPending(pendingSelection);
8527
+ if (!isThenable$1(pendingSelection)) emitPending(pendingSelection);
8347
8528
  const showLegacyPending = (selection) => {
8348
8529
  selection.selectionBounds = { ...selection.selectionBounds };
8349
8530
  if (superdocStore.selectionPosition?.source && superdocStore.selectionPosition.source !== "pdf") superdocStore.selectionPosition.source = null;
@@ -8353,7 +8534,7 @@ var useCommentsStore = defineStore("comments", () => {
8353
8534
  parentCommentId: null
8354
8535
  });
8355
8536
  if (!superdoc.config.isInternal) pendingComment.value.isInternal = false;
8356
- if (superdoc.activeEditor?.commands) superdoc.activeEditor.commands.insertComment({
8537
+ if (superdoc.activeEditor?.commands) superdoc.activeEditor.commands.insertComment?.({
8357
8538
  ...pendingComment.value.getValues(),
8358
8539
  commentId: "pending",
8359
8540
  skipEmit: true
@@ -8364,10 +8545,10 @@ var useCommentsStore = defineStore("comments", () => {
8364
8545
  activeComment.value = pendingComment.value.commentId;
8365
8546
  return { ok: true };
8366
8547
  };
8367
- if (asyncSnapshot) {
8548
+ if (isThenable$1(pendingSelection)) {
8368
8549
  const selection = { ...superdocStore.activeSelection };
8369
8550
  selection.selectionBounds = { ...selection.selectionBounds };
8370
- return pendingSelection.then((snapshot) => {
8551
+ return Promise.resolve(pendingSelection).then((snapshot) => {
8371
8552
  if (!ownsRequest()) return {
8372
8553
  ok: false,
8373
8554
  reason: "pending-comment-superseded"
@@ -8399,12 +8580,6 @@ var useCommentsStore = defineStore("comments", () => {
8399
8580
  });
8400
8581
  /**
8401
8582
  * Get a new pending comment
8402
- *
8403
- * @param {Object} param0
8404
- * @param {Object} [param0.selection] The selection object
8405
- * @param {string | null} [param0.documentId] The document ID
8406
- * @param {string | null} [param0.parentCommentId] The parent comment
8407
- * @returns {ReturnType<typeof useComment>} The new comment object
8408
8583
  */
8409
8584
  const getPendingComment = ({ selection, documentId, parentCommentId, ...options }) => {
8410
8585
  return _getNewcomment({
@@ -8416,17 +8591,11 @@ var useCommentsStore = defineStore("comments", () => {
8416
8591
  };
8417
8592
  /**
8418
8593
  * Get the new comment object
8419
- *
8420
- * @param {Object} param0
8421
- * @param {Object} param0.selection The selection object
8422
- * @param {String} param0.documentId The document ID
8423
- * @param {String} param0.parentCommentId The parent comment ID
8424
- * @returns {Object} The new comment object
8425
8594
  */
8426
8595
  const _getNewcomment = ({ selection, documentId, parentCommentId, ...options }) => {
8427
8596
  let activeDocument;
8428
- if (documentId) activeDocument = superdocStore.getDocument(documentId);
8429
- else if (selection) activeDocument = superdocStore.getDocument(selection.documentId);
8597
+ if (documentId) activeDocument = findDocument(documentId);
8598
+ else if (selection) activeDocument = findDocument(selection.documentId);
8430
8599
  if (!activeDocument) activeDocument = superdocStore.documents[0];
8431
8600
  return useComment({
8432
8601
  fileId: activeDocument.id,
@@ -8443,8 +8612,6 @@ var useCommentsStore = defineStore("comments", () => {
8443
8612
  };
8444
8613
  /**
8445
8614
  * Remove the pending comment
8446
- *
8447
- * @returns {void}
8448
8615
  */
8449
8616
  const resetPendingCommentState = (superdoc, clearViewingSelection = true) => {
8450
8617
  const hadPending = !!pendingComment.value;
@@ -8460,7 +8627,7 @@ var useCommentsStore = defineStore("comments", () => {
8460
8627
  } catch {}
8461
8628
  return;
8462
8629
  }
8463
- superdoc?.activeEditor?.commands?.removeComment({ commentId: "pending" });
8630
+ superdoc?.activeEditor?.commands?.removeComment?.({ commentId: "pending" });
8464
8631
  };
8465
8632
  const removePendingComment = (superdoc) => {
8466
8633
  pendingCommentRequest += 1;
@@ -8473,16 +8640,8 @@ var useCommentsStore = defineStore("comments", () => {
8473
8640
  };
8474
8641
  /**
8475
8642
  * Add a new comment to the document
8476
- *
8477
- * @param {Object} param0
8478
- * @param {Object} param0.superdoc The SuperDoc instance
8479
- * @param {ReturnType<typeof useComment>} param0.comment The comment to add
8480
- * @param {boolean} [param0.skipEditorUpdate] Skip writing the comment into the editor
8481
- * @param {boolean} [param0.broadcastChanges] Emit and sync the change
8482
- * @param {boolean} [param0.isHydration] The comment comes from import or collaboration
8483
- * @returns {{ ok: boolean, reason?: string, detail?: string, code?: string } | Promise<{ ok: boolean, reason?: string, detail?: string, code?: string }>} The mutation outcome
8484
8643
  */
8485
- const addCommentInternal = ({ superdoc, comment, skipEditorUpdate = false, broadcastChanges = true, isHydration = false }) => {
8644
+ const addCommentInternal = ({ superdoc, comment, skipEditorUpdate = false, broadcastChanges = true, isHydration = false, preserveDraft = false }) => {
8486
8645
  if (commentsAreReadOnly() && !isHydration) return readOnlyMutationOutcome();
8487
8646
  const v2Adapter = !skipEditorUpdate && !isHydration ? getV2CommentsAdapter(superdoc) : null;
8488
8647
  if (v2Adapter && !comment.trackedChange) {
@@ -8510,7 +8669,7 @@ var useCommentsStore = defineStore("comments", () => {
8510
8669
  return {
8511
8670
  ok: false,
8512
8671
  reason: "adapter-threw",
8513
- detail: err?.message ?? String(err)
8672
+ detail: errorDetail(err)
8514
8673
  };
8515
8674
  }
8516
8675
  })().then((outcome) => {
@@ -8561,28 +8720,30 @@ var useCommentsStore = defineStore("comments", () => {
8561
8720
  };
8562
8721
  });
8563
8722
  }
8564
- let parentComment = commentsList.value.find((c) => c.commentId === activeComment.value);
8723
+ const activeId = activeComment.value;
8724
+ const parent = toRaw$2(commentsList.value).find((c) => unref(c.commentId) === activeId);
8725
+ let parentComment = parent ? reactive(parent) : void 0;
8565
8726
  if (!parentComment) parentComment = comment;
8566
8727
  const newComment = useComment(comment.getValues());
8567
- if (pendingComment.value) newComment.setText({
8568
- text: currentCommentText.value,
8728
+ const text = pendingComment.value && !preserveDraft ? currentCommentText.value : comment.commentText;
8729
+ if (text === "") {
8730
+ newComment.commentText = "";
8731
+ newComment.mentions = [];
8732
+ } else newComment.setText({
8733
+ text,
8569
8734
  suppressUpdate: true
8570
8735
  });
8571
- else newComment.setText({
8572
- text: comment.commentText,
8573
- suppressUpdate: true
8574
- });
8575
- newComment.selection.source = pendingComment.value?.selection?.source ?? newComment.selection.source;
8736
+ if (!preserveDraft) newComment.selection.source = pendingComment.value?.selection?.source ?? newComment.selection.source;
8576
8737
  if (parentComment) newComment.isInternal = parentComment.isInternal;
8577
8738
  if (!superdoc.config.isInternal) newComment.isInternal = false;
8578
8739
  commentsList.value.push(newComment);
8579
- const hadPendingComment = !!pendingComment.value;
8580
- removePendingComment(superdoc);
8740
+ const hadPendingComment = !preserveDraft && !!pendingComment.value;
8741
+ if (!preserveDraft) removePendingComment(superdoc);
8581
8742
  if (hadPendingComment) {
8582
8743
  activeComment.value = newComment.commentId;
8583
8744
  setActiveFloatingCommentInstance(newComment.commentId);
8584
8745
  }
8585
- if (!skipEditorUpdate && !comment.trackedChange && superdoc.activeEditor?.commands && !comment.parentCommentId) superdoc.activeEditor.commands.insertComment({
8746
+ if (!skipEditorUpdate && !comment.trackedChange && superdoc.activeEditor?.commands && !comment.parentCommentId) superdoc.activeEditor.commands.insertComment?.({
8586
8747
  ...newComment.getValues(),
8587
8748
  skipEmit: true
8588
8749
  });
@@ -8605,12 +8766,13 @@ var useCommentsStore = defineStore("comments", () => {
8605
8766
  skipEditorUpdate,
8606
8767
  broadcastChanges
8607
8768
  });
8608
- const addHydratedComment = ({ superdoc, comment, skipEditorUpdate = false, broadcastChanges = true }) => addCommentInternal({
8769
+ const addHydratedComment = ({ superdoc, comment, skipEditorUpdate = false, broadcastChanges = true, preserveDraft = false }) => addCommentInternal({
8609
8770
  superdoc,
8610
8771
  comment,
8611
8772
  skipEditorUpdate,
8612
8773
  broadcastChanges,
8613
- isHydration: true
8774
+ isHydration: true,
8775
+ preserveDraft
8614
8776
  });
8615
8777
  const deleteComment = ({ commentId: commentIdToDelete, superdoc }) => {
8616
8778
  if (commentsAreReadOnly()) return Promise.resolve(readOnlyMutationOutcome());
@@ -8630,7 +8792,7 @@ var useCommentsStore = defineStore("comments", () => {
8630
8792
  return {
8631
8793
  ok: false,
8632
8794
  reason: "adapter-threw",
8633
- detail: err?.message ?? String(err)
8795
+ detail: errorDetail(err)
8634
8796
  };
8635
8797
  }
8636
8798
  })().then((outcome) => {
@@ -8650,16 +8812,16 @@ var useCommentsStore = defineStore("comments", () => {
8650
8812
  });
8651
8813
  return outcome;
8652
8814
  }
8653
- const removedIds = new Set((Array.isArray(outcome.removedCommentIds) ? outcome.removedCommentIds : []).map(normalizeCommentId).filter(Boolean));
8654
- if (removedIds.has(normalizeCommentId(commentId))) {
8655
- if (importedId != null) removedIds.add(normalizeCommentId(importedId));
8815
+ const removedIds = new Set((Array.isArray(outcome.removedCommentIds) ? outcome.removedCommentIds : []).map(normalizeCommentId).filter(isPresent));
8816
+ if (removedIds.has(normalizeCommentId(commentId) ?? "")) {
8817
+ if (importedId != null) removedIds.add(String(importedId));
8656
8818
  let addedChild = true;
8657
8819
  while (addedChild) {
8658
8820
  addedChild = false;
8659
8821
  for (const row of commentsList.value) {
8660
8822
  const parentId = normalizeCommentId(row?.parentCommentId);
8661
8823
  if (!parentId || !removedIds.has(parentId)) continue;
8662
- for (const alias of [row?.commentId, row?.importedId].map(normalizeCommentId).filter(Boolean)) {
8824
+ for (const alias of [row?.commentId, row?.importedId].map(normalizeCommentId).filter(isPresent)) {
8663
8825
  if (removedIds.has(alias)) continue;
8664
8826
  removedIds.add(alias);
8665
8827
  addedChild = true;
@@ -8667,9 +8829,9 @@ var useCommentsStore = defineStore("comments", () => {
8667
8829
  }
8668
8830
  }
8669
8831
  commentsList.value = commentsList.value.filter((row) => {
8670
- return ![row?.commentId, row?.importedId].map(normalizeCommentId).filter(Boolean).some((alias) => removedIds.has(alias));
8832
+ return ![row?.commentId, row?.importedId].map(normalizeCommentId).filter(isPresent).some((alias) => removedIds.has(alias));
8671
8833
  });
8672
- if (removedIds.has(normalizeCommentId(activeComment.value))) clearActiveCommentSelection();
8834
+ if (removedIds.has(normalizeCommentId(activeComment.value) ?? "")) clearActiveCommentSelection();
8673
8835
  } else reconcileCommentsFromV2({
8674
8836
  superdoc,
8675
8837
  adapter: v2Adapter,
@@ -8677,18 +8839,18 @@ var useCommentsStore = defineStore("comments", () => {
8677
8839
  items: outcome.items ?? [],
8678
8840
  pruneStale: outcome.complete !== false
8679
8841
  });
8842
+ const payload = comment.getValues();
8680
8843
  superdoc.emit("comments-update", {
8681
8844
  type: COMMENT_EVENTS.DELETED,
8682
- comment: comment.getValues(),
8683
- changes: [{
8684
- key: "deleted",
8845
+ comment: payload,
8846
+ changes: getDeletedCommentChanges([{
8685
8847
  commentId,
8686
- fileId
8687
- }]
8848
+ importedId
8849
+ }, payload], fileId)
8688
8850
  });
8689
8851
  return { ok: true };
8690
8852
  });
8691
- superdoc.activeEditor?.commands?.removeComment({
8853
+ superdoc.activeEditor?.commands?.removeComment?.({
8692
8854
  commentId,
8693
8855
  importedId
8694
8856
  });
@@ -8696,14 +8858,14 @@ var useCommentsStore = defineStore("comments", () => {
8696
8858
  const childCommentIds = commentsList.value.filter((c) => c.parentCommentId === commentId).map((c) => c.commentId || c.importedId);
8697
8859
  commentsList.value = commentsList.value.filter((c) => !childCommentIds.includes(c.commentId));
8698
8860
  if (activeComment.value === commentId || childCommentIds.includes(activeComment.value)) clearActiveCommentSelection();
8861
+ const payload = comment.getValues();
8699
8862
  const event = {
8700
8863
  type: COMMENT_EVENTS.DELETED,
8701
- comment: comment.getValues(),
8702
- changes: [{
8703
- key: "deleted",
8864
+ comment: payload,
8865
+ changes: getDeletedCommentChanges([{
8704
8866
  commentId,
8705
- fileId
8706
- }]
8867
+ importedId
8868
+ }, payload], fileId)
8707
8869
  };
8708
8870
  superdoc.emit("comments-update", event);
8709
8871
  syncCommentsToClients(superdoc, event);
@@ -8715,7 +8877,7 @@ var useCommentsStore = defineStore("comments", () => {
8715
8877
  * otherwise keep the source bubble after the document comment is gone.
8716
8878
  */
8717
8879
  const dropCommentsFromMutationImpact = ({ superdoc, documentId, removedIds } = {}) => {
8718
- const seedIds = new Set([...removedIds instanceof Set ? removedIds : removedIds ?? []].map((id) => normalizeCommentId(id)).filter(Boolean));
8880
+ const seedIds = new Set([...removedIds instanceof Set ? removedIds : removedIds ?? []].map((id) => normalizeCommentId(id)).filter(isPresent));
8719
8881
  if (!seedIds.size) return {
8720
8882
  ok: true,
8721
8883
  removedIds: []
@@ -8739,7 +8901,7 @@ var useCommentsStore = defineStore("comments", () => {
8739
8901
  if (!isDroppableCommentRow(row)) continue;
8740
8902
  const aliases = getCommentAliasIds(row);
8741
8903
  const alreadyDropped = aliases.some((alias) => droppedIds.has(alias));
8742
- const parentDropped = droppedIds.has(normalizeCommentId(row?.parentCommentId)) || droppedIds.has(normalizeCommentId(row?.threadingParentCommentId));
8904
+ const parentDropped = droppedIds.has(normalizeCommentId(row?.parentCommentId) ?? "") || droppedIds.has(normalizeCommentId(row?.threadingParentCommentId) ?? "");
8743
8905
  if (!alreadyDropped && !parentDropped) continue;
8744
8906
  for (const alias of aliases) {
8745
8907
  if (droppedIds.has(alias)) continue;
@@ -8767,11 +8929,7 @@ var useCommentsStore = defineStore("comments", () => {
8767
8929
  const event = {
8768
8930
  type: COMMENT_EVENTS.DELETED,
8769
8931
  comment: payload,
8770
- changes: [{
8771
- key: "deleted",
8772
- commentId: payload?.commentId,
8773
- fileId: payload?.fileId
8774
- }]
8932
+ changes: getDeletedCommentChanges([payload, comment], payload?.fileId)
8775
8933
  };
8776
8934
  superdoc?.emit?.("comments-update", event);
8777
8935
  });
@@ -8793,7 +8951,7 @@ var useCommentsStore = defineStore("comments", () => {
8793
8951
  const byAlias = /* @__PURE__ */ new Map();
8794
8952
  const childrenByParentAlias = /* @__PURE__ */ new Map();
8795
8953
  for (const row of rows) {
8796
- for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(Boolean)) if (!byAlias.has(alias)) byAlias.set(alias, row);
8954
+ for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(isPresent)) if (!byAlias.has(alias)) byAlias.set(alias, row);
8797
8955
  const parentId = normalizeCommentId(row.parentCommentId);
8798
8956
  if (parentId) {
8799
8957
  const children = childrenByParentAlias.get(parentId) ?? [];
@@ -8820,7 +8978,7 @@ var useCommentsStore = defineStore("comments", () => {
8820
8978
  if (!row || visited.has(row)) continue;
8821
8979
  visited.add(row);
8822
8980
  family.push(row);
8823
- for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(Boolean)) queue.push(...childrenByParentAlias.get(alias) ?? []);
8981
+ for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(isPresent)) queue.push(...childrenByParentAlias.get(alias) ?? []);
8824
8982
  }
8825
8983
  const isResolved = status === "resolved";
8826
8984
  const resolvedAt = Date.now();
@@ -8843,7 +9001,7 @@ var useCommentsStore = defineStore("comments", () => {
8843
9001
  outcome = {
8844
9002
  ok: false,
8845
9003
  reason: "adapter-threw",
8846
- detail: err?.message ?? String(err)
9004
+ detail: errorDetail(err)
8847
9005
  };
8848
9006
  }
8849
9007
  if (!isCurrentV2CommentsAdapter(adapter)) return {
@@ -8915,14 +9073,14 @@ var useCommentsStore = defineStore("comments", () => {
8915
9073
  return (outcome.items ?? []).map((item) => adapter.mapV2CommentToUseCommentInput(item, {
8916
9074
  fileId,
8917
9075
  fileType: null
8918
- })).filter(Boolean);
9076
+ })).filter(isPresent);
8919
9077
  };
8920
9078
  const isBlankDirectoryCommentText = (value) => typeof value !== "string" || value.replace(/<[^>]*>/g, "").replace(/&nbsp;/gi, " ").trim().length === 0;
8921
9079
  const trackedChangeDirectoryComment = (params) => {
8922
9080
  if (!params || params.event === "omit" || params.changeId == null) return null;
8923
9081
  return getPendingComment({
8924
9082
  documentId: params.documentId,
8925
- commentId: params.changeId,
9083
+ commentId: String(params.changeId),
8926
9084
  trackedChange: true,
8927
9085
  trackedChangeText: params.trackedChangeText,
8928
9086
  trackedChangeType: params.trackedChangeType,
@@ -8969,7 +9127,7 @@ var useCommentsStore = defineStore("comments", () => {
8969
9127
  const mappedCommentInputs = commentItems.filter((item) => !isSyntheticTrackedChangeCommentLaneItem(item)).map((item) => commentsAdapter.mapV2CommentToUseCommentInput(item, {
8970
9128
  fileId: documentId,
8971
9129
  fileType: document?.type ?? null
8972
- })).filter(Boolean);
9130
+ })).filter(isPresent);
8973
9131
  const trackedParentBySidecarId = /* @__PURE__ */ new Map();
8974
9132
  for (const input of mappedCommentInputs) if (input.trackedChange !== true && input.trackedChangeThreadParentId && !input.parentCommentId && isBlankDirectoryCommentText(input.commentText)) trackedParentBySidecarId.set(String(input.commentId), String(input.trackedChangeThreadParentId));
8975
9133
  const commentRows = mappedCommentInputs.filter((input) => !trackedParentBySidecarId.has(String(input.commentId))).map((input) => {
@@ -8980,7 +9138,7 @@ var useCommentsStore = defineStore("comments", () => {
8980
9138
  }
8981
9139
  return useComment(input);
8982
9140
  });
8983
- const trackedRows = trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).map((params) => trackedChangeDirectoryComment(params)).filter(Boolean);
9141
+ const trackedRows = trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).map((params) => trackedChangeDirectoryComment(params)).filter(isPresent);
8984
9142
  reviewDirectoryList.value = [...commentRows, ...trackedRows];
8985
9143
  return {
8986
9144
  ok: true,
@@ -9061,13 +9219,6 @@ var useCommentsStore = defineStore("comments", () => {
9061
9219
  * - successful mutation refreshes from v2 list and reconciles
9062
9220
  * - rejection preserves rows and emits a rejected `comments-update` event
9063
9221
  * - committed-but-refresh-failed surfaces honestly (no reconcile with `[]`)
9064
- *
9065
- * @param {Object} [param0]
9066
- * @param {Object} [param0.superdoc] The SuperDoc instance.
9067
- * @param {string | null} [param0.parentCommentId] The comment or tracked-change row being replied to.
9068
- * @param {string} [param0.text] The reply text.
9069
- * @param {Array<{ name?: string | null, email?: string | null }>} [param0.mentions] Users mentioned in the reply.
9070
- * @param {{ kind: 'trackedChange', trackedChangeId: string, story?: unknown, side?: import('./helpers/tracked-change-thread-index.js').TrackedChangeSideGroup }} [param0.trackedChangeTarget] The tracked change a first reply anchors to.
9071
9222
  */
9072
9223
  const replyCommentV2 = async ({ superdoc, parentCommentId, text, mentions = [], trackedChangeTarget } = {}) => {
9073
9224
  if (commentsAreReadOnly()) return readOnlyMutationOutcome();
@@ -9342,10 +9493,15 @@ var useCommentsStore = defineStore("comments", () => {
9342
9493
  row?.threadingParentCommentId,
9343
9494
  row?.trackedChangeParentId,
9344
9495
  row?.trackedChangeThreadParentId
9345
- ].map((value) => normalizeCommentId(value)).filter(Boolean));
9496
+ ].map((value) => normalizeCommentId(value)).filter(isPresent));
9346
9497
  /** Keep one mounted review window plus rows whose interaction is still live. */
9347
9498
  const boundReviewPresentationRows = ({ documentId, visibleIds }) => {
9348
9499
  const currentRows = commentsList.value;
9500
+ if (currentRows.every((row) => {
9501
+ if (!belongsToTrackedChangeSyncDocument(row, documentId)) return true;
9502
+ const id = normalizeCommentId(row.commentId);
9503
+ return Boolean(id && visibleIds.has(id));
9504
+ })) return;
9349
9505
  const identityIdsByRow = /* @__PURE__ */ new Map();
9350
9506
  const identityIdsFor = (row) => {
9351
9507
  let ids = identityIdsByRow.get(row);
@@ -9363,7 +9519,7 @@ var useCommentsStore = defineStore("comments", () => {
9363
9519
  pendingComment.value?.commentId,
9364
9520
  instantSidebarAlignmentThreadId.value,
9365
9521
  instantSidebarAlignmentInstanceId.value
9366
- ].map((value) => normalizeCommentId(value)).filter(Boolean));
9522
+ ].map((value) => normalizeCommentId(value)).filter(isPresent));
9367
9523
  let changed = true;
9368
9524
  while (changed) {
9369
9525
  changed = false;
@@ -9431,7 +9587,8 @@ var useCommentsStore = defineStore("comments", () => {
9431
9587
  if (preparedReview?.reuseCatalog === true && committedReviewPresentations.value[normalizedDocumentId]) {
9432
9588
  const previous = committedReviewPresentations.value[normalizedDocumentId];
9433
9589
  const next = preparedReview.presentation;
9434
- if (!next || !Number.isSafeInteger(preparedReview.catalogVersion) || preparedReview.catalogVersion < 1 || previous.catalogVersion > preparedReview.catalogVersion || next.catalogVersion !== preparedReview.catalogVersion || previous.coordinate?.documentEpoch !== next.coordinate?.documentEpoch || previous.coordinate?.mountId !== next.coordinate?.mountId || next.coordinate?.reviewVersion <= previous.coordinate?.reviewVersion || next.coordinate?.commitSequence < previous.coordinate?.commitSequence) return {
9590
+ const catalogVersion = preparedReview.catalogVersion ?? NaN;
9591
+ if (!next || !Number.isSafeInteger(catalogVersion) || catalogVersion < 1 || (previous.catalogVersion ?? 0) > catalogVersion || next.catalogVersion !== preparedReview.catalogVersion || previous.coordinate?.documentEpoch !== next.coordinate?.documentEpoch || previous.coordinate?.mountId !== next.coordinate?.mountId || next.coordinate?.reviewVersion <= previous.coordinate?.reviewVersion || next.coordinate?.commitSequence < previous.coordinate?.commitSequence) return {
9435
9592
  ok: false,
9436
9593
  reason: "review-catalog-stale"
9437
9594
  };
@@ -9477,8 +9634,8 @@ var useCommentsStore = defineStore("comments", () => {
9477
9634
  previousPresentation.updateContext = presentation.updateContext;
9478
9635
  return {
9479
9636
  ok: true,
9480
- commentItems: preparedReview.comments.length,
9481
- trackedItems: preparedReview.trackedChanges.length
9637
+ commentItems: preparedReview?.comments.length,
9638
+ trackedItems: preparedReview?.trackedChanges.length
9482
9639
  };
9483
9640
  }
9484
9641
  let effectiveCommentItems = commentItems;
@@ -9492,7 +9649,7 @@ var useCommentsStore = defineStore("comments", () => {
9492
9649
  reason: seeded?.reason ?? "comment-catalog-seed-failed"
9493
9650
  };
9494
9651
  const selected = commentsAdapter.selectVisibleReviewComments?.();
9495
- if (selected?.ok === true && Array.isArray(selected.items) && (!Array.isArray(selected.unresolvedIds) || selected.unresolvedIds.length === 0)) effectiveCommentItems = selected.items;
9652
+ if (!isThenable$1(selected) && selected?.ok === true && Array.isArray(selected.items) && (!Array.isArray(selected.unresolvedIds) || selected.unresolvedIds.length === 0)) effectiveCommentItems = selected.items;
9496
9653
  }
9497
9654
  const fileType = (normalizedDocumentId ? superdocStore.getDocument(normalizedDocumentId) : null)?.type ?? null;
9498
9655
  let preparedInputs;
@@ -9509,16 +9666,16 @@ var useCommentsStore = defineStore("comments", () => {
9509
9666
  })) : effectiveCommentItems.filter((item) => !isSyntheticTrackedChangeCommentLaneItem(item)).map((item) => commentsAdapter.mapV2CommentToUseCommentInput(item, {
9510
9667
  fileId: normalizedDocumentId,
9511
9668
  fileType
9512
- })).filter(Boolean);
9669
+ })).filter(isPresent);
9513
9670
  preparedParams = preparedReview ? preparedReview.trackedChanges.map((params) => ({
9514
9671
  ...params,
9515
9672
  documentId: normalizedDocumentId
9516
- })) : trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).filter(Boolean);
9673
+ })) : trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).filter(isPresent);
9517
9674
  } catch (error) {
9518
9675
  return {
9519
9676
  ok: false,
9520
9677
  reason: "mapper-failed",
9521
- detail: error?.message ?? String(error)
9678
+ detail: errorDetail(error)
9522
9679
  };
9523
9680
  }
9524
9681
  if (typeof patch !== "function") return {
@@ -9546,7 +9703,7 @@ var useCommentsStore = defineStore("comments", () => {
9546
9703
  params?.trackedChangeCanonicalId,
9547
9704
  params?.trackedChangeAnchorKey
9548
9705
  ])
9549
- ].map((value) => normalizeCommentId(value)).filter(Boolean));
9706
+ ].map((value) => normalizeCommentId(value)).filter(isPresent));
9550
9707
  patch(() => {
9551
9708
  const previousPreparedPresentation = committedReviewPresentations.value[normalizedDocumentId] ?? null;
9552
9709
  if (preparedReview?.presentation) committedReviewPresentations.value = {
@@ -9561,7 +9718,8 @@ var useCommentsStore = defineStore("comments", () => {
9561
9718
  preparedParams,
9562
9719
  preparedPresentation: preparedReview?.presentation ?? null,
9563
9720
  previousPreparedPresentation,
9564
- pruneStale: false
9721
+ pruneStale: false,
9722
+ preserveDraft: true
9565
9723
  });
9566
9724
  reconcileCommentsFromV2({
9567
9725
  superdoc,
@@ -9594,7 +9752,7 @@ var useCommentsStore = defineStore("comments", () => {
9594
9752
  };
9595
9753
  const effectiveDocumentId = documentId ?? adapter.documentId ?? null;
9596
9754
  const normalizedEffectiveDocumentId = effectiveDocumentId != null ? String(effectiveDocumentId) : null;
9597
- const fileType = (effectiveDocumentId ? superdocStore.getDocument(effectiveDocumentId) : null)?.type ?? null;
9755
+ const fileType = (effectiveDocumentId ? findDocument(effectiveDocumentId) : null)?.type ?? null;
9598
9756
  const isBlankV2CommentText = (value) => {
9599
9757
  if (typeof value !== "string") return true;
9600
9758
  return value.replace(/<[^>]*>/g, "").replace(/&nbsp;/gi, " ").trim().length === 0;
@@ -9614,7 +9772,7 @@ var useCommentsStore = defineStore("comments", () => {
9614
9772
  const candidateInputs = Array.isArray(preparedInputs) ? preparedInputs : items.filter((item) => !isSyntheticTrackedChangeCommentLaneItem(item)).map((item) => adapter.mapV2CommentToUseCommentInput(item, {
9615
9773
  fileId: effectiveDocumentId,
9616
9774
  fileType
9617
- })).filter(Boolean);
9775
+ })).filter(isPresent);
9618
9776
  for (const input of candidateInputs) {
9619
9777
  if (isV2TrackedChangeSidecarCommentInput(input)) {
9620
9778
  trackedChangeSidecarParentByCommentId.set(String(input.commentId), String(input.trackedChangeThreadParentId));
@@ -9667,7 +9825,7 @@ var useCommentsStore = defineStore("comments", () => {
9667
9825
  existing.mentions = Array.isArray(input.mentions) ? input.mentions : [];
9668
9826
  existing.isInternal = typeof input.isInternal === "boolean" ? input.isInternal : existing.isInternal;
9669
9827
  if (typeof input.resolvedTime === "number") {
9670
- existing.resolvedTime = input.resolvedTimeWasSynthesized === true && typeof existing.resolvedTime === "number" ? existing.resolvedTime : input.resolvedTime;
9828
+ existing.resolvedTime = input.resolvedTimeWasSynthesized === true && typeof existing.resolvedTime === "number" ? existing.resolvedTime : input.resolvedTime ?? null;
9671
9829
  existing.resolvedByEmail = input.resolvedByEmail ?? null;
9672
9830
  existing.resolvedByName = input.resolvedByName ?? null;
9673
9831
  } else {
@@ -9741,7 +9899,7 @@ var useCommentsStore = defineStore("comments", () => {
9741
9899
  return {
9742
9900
  ok: false,
9743
9901
  reason: "comment-read-failed",
9744
- detail: err?.message ?? String(err)
9902
+ detail: errorDetail(err)
9745
9903
  };
9746
9904
  }
9747
9905
  if (!isCurrentV2CommentsAdapter(adapter)) return {
@@ -9767,8 +9925,6 @@ var useCommentsStore = defineStore("comments", () => {
9767
9925
  };
9768
9926
  /**
9769
9927
  * Cancel the pending comment
9770
- *
9771
- * @returns {void}
9772
9928
  */
9773
9929
  const cancelComment = (superdoc) => {
9774
9930
  removePendingComment(superdoc);
@@ -9778,9 +9934,6 @@ var useCommentsStore = defineStore("comments", () => {
9778
9934
  *
9779
9935
  * Replace-file swaps call this before hydrating the replacement so stale
9780
9936
  * comments never survive long enough to render beside the new content.
9781
- *
9782
- * @param {string | null | undefined} documentId
9783
- * @returns {void}
9784
9937
  */
9785
9938
  function removeCommentsForDocument(documentId) {
9786
9939
  const activeDocumentId = documentId != null ? String(documentId) : null;
@@ -9811,20 +9964,16 @@ var useCommentsStore = defineStore("comments", () => {
9811
9964
  const remapTrackedChangeReferences = (droppedCommentIds, activeDocumentId) => {
9812
9965
  const remapSelection = (selection) => {
9813
9966
  const selectedId = normalizeCommentId(selection.value);
9814
- if (selectedId && droppedCommentIds.has(selectedId)) selection.value = droppedCommentIds.get(selectedId);
9967
+ const replacementId = selectedId ? droppedCommentIds.get(selectedId) : void 0;
9968
+ if (replacementId) selection.value = replacementId;
9815
9969
  };
9816
9970
  remapSelection(activeComment);
9817
9971
  remapSelection(activeFloatingCommentInstanceId);
9818
9972
  commentsList.value.forEach((row) => {
9819
9973
  if (!belongsToTrackedChangeSyncDocument(row, activeDocumentId)) return;
9820
9974
  const rowIdentityUpdates = {};
9821
- for (const field of [
9822
- "trackedChangeThreadParentId",
9823
- "trackedChangeParentId",
9824
- "threadingParentCommentId",
9825
- "parentCommentId"
9826
- ]) {
9827
- const replacementId = droppedCommentIds.get(normalizeCommentId(row?.[field]));
9975
+ for (const field of PARENT_LINK_FIELDS) {
9976
+ const replacementId = droppedCommentIds.get(normalizeCommentId(row?.[field]) ?? "");
9828
9977
  if (!replacementId) continue;
9829
9978
  row[field] = replacementId;
9830
9979
  rowIdentityUpdates[field] = replacementId;
@@ -9845,10 +9994,6 @@ var useCommentsStore = defineStore("comments", () => {
9845
9994
  *
9846
9995
  * Pruning is scoped to the active editor document so replay in one document does not
9847
9996
  * delete tracked-change comments from other open documents.
9848
- *
9849
- * @param {Set<string>} liveTrackedChangeIds IDs currently present in editor marks.
9850
- * @param {string | null} activeDocumentId Document currently being synced.
9851
- * @returns {void}
9852
9997
  */
9853
9998
  const pruneStaleTrackedChangeComments = (liveTrackedChangeIds, liveTrackedChangeAnchorKeys, activeDocumentId, superdoc = null, { broadcastChanges = true } = {}) => withInteractionSpan("store.trackedChanges.pruneStaleComments", "store-reconciliation", {
9854
9999
  activeDocumentId: activeDocumentId ?? null,
@@ -9893,7 +10038,7 @@ var useCommentsStore = defineStore("comments", () => {
9893
10038
  comment: payload
9894
10039
  };
9895
10040
  if (broadcastChanges) {
9896
- syncCommentsToClients(superdoc, event);
10041
+ syncCommentsToClients(requireHost(superdoc, "pruneStaleTrackedChangeComments"), event);
9897
10042
  superdoc?.emit?.("comments-update", event);
9898
10043
  }
9899
10044
  });
@@ -9925,14 +10070,10 @@ var useCommentsStore = defineStore("comments", () => {
9925
10070
  const event = {
9926
10071
  type: COMMENT_EVENTS.DELETED,
9927
10072
  comment: payload,
9928
- changes: [{
9929
- key: "deleted",
9930
- commentId: payload.commentId,
9931
- fileId: payload.fileId
9932
- }]
10073
+ changes: getDeletedCommentChanges([payload, comment], payload.fileId)
9933
10074
  };
9934
10075
  if (broadcastChanges) {
9935
- syncCommentsToClients(superdoc, event);
10076
+ syncCommentsToClients(requireHost(superdoc, "pruneStaleTrackedChangeComments"), event);
9936
10077
  superdoc?.emit?.("comments-update", event);
9937
10078
  }
9938
10079
  });
@@ -9955,7 +10096,6 @@ var useCommentsStore = defineStore("comments", () => {
9955
10096
  const resolveArgs = {
9956
10097
  email: superdoc?.user?.email ?? null,
9957
10098
  name: superdoc?.user?.name ?? null,
9958
- superdoc,
9959
10099
  reconciliationToken: COMMENT_RECONCILIATION_TOKEN
9960
10100
  };
9961
10101
  let resolvedCount = 0;
@@ -9966,9 +10106,12 @@ var useCommentsStore = defineStore("comments", () => {
9966
10106
  trackedChangeThreadParentIdForComment(linkedComment),
9967
10107
  linkedComment.threadingParentCommentId,
9968
10108
  linkedComment.parentCommentId
9969
- ].map((id) => normalizeCommentId(id)).filter(Boolean).some((id) => trackedChangeIds.has(id))) return;
10109
+ ].map((id) => normalizeCommentId(id)).filter(isPresent).some((id) => trackedChangeIds.has(id))) return;
9970
10110
  if (typeof linkedComment.resolveComment === "function") {
9971
- linkedComment.resolveComment(resolveArgs);
10111
+ linkedComment.resolveComment({
10112
+ ...resolveArgs,
10113
+ superdoc: requireHost(superdoc, "resolveLinkedCommentsForTrackedChangeDecision")
10114
+ });
9972
10115
  resolvedCount += 1;
9973
10116
  }
9974
10117
  });
@@ -10065,7 +10208,7 @@ var useCommentsStore = defineStore("comments", () => {
10065
10208
  if (previousCommentId) anchorSourceIds.add(previousCommentId);
10066
10209
  if (previousCanonicalId) anchorSourceIds.add(previousCanonicalId);
10067
10210
  }
10068
- if (comment.trackedChangeAnchorKey == null || anchorSourceIds.has(currentAnchorParts?.rawId)) comment.trackedChangeAnchorKey = toAnchor;
10211
+ if (comment.trackedChangeAnchorKey == null || anchorSourceIds.has(currentAnchorParts?.rawId ?? "")) comment.trackedChangeAnchorKey = toAnchor;
10069
10212
  comment.trackedChangePositionAliases = normalizeTrackedChangePositionAliases([...Array.isArray(comment.trackedChangePositionAliases) ? comment.trackedChangePositionAliases : [], fromId]);
10070
10213
  const identitySourceIds = /* @__PURE__ */ new Set([fromId]);
10071
10214
  if (advancesRemapLineage) {
@@ -10076,33 +10219,28 @@ var useCommentsStore = defineStore("comments", () => {
10076
10219
  if (row === comment) return;
10077
10220
  if (!belongsToTrackedChangeSyncDocument(row, normalizedDocumentId)) return;
10078
10221
  const rowIdentityUpdates = {};
10079
- for (const field of [
10080
- "trackedChangeThreadParentId",
10081
- "trackedChangeParentId",
10082
- "threadingParentCommentId",
10083
- "parentCommentId"
10084
- ]) {
10085
- if (!identitySourceIds.has(normalizeCommentId(row?.[field]))) continue;
10222
+ for (const field of PARENT_LINK_FIELDS) {
10223
+ if (!identitySourceIds.has(normalizeCommentId(row?.[field]) ?? "")) continue;
10086
10224
  row[field] = toId;
10087
10225
  rowIdentityUpdates[field] = toId;
10088
10226
  }
10089
10227
  if (Object.keys(rowIdentityUpdates).length && typeof row.updateIdentityValues === "function") row.updateIdentityValues(rowIdentityUpdates);
10090
10228
  });
10091
10229
  if (Object.keys(commentIdentityUpdates).length && typeof comment.updateIdentityValues === "function") comment.updateIdentityValues(commentIdentityUpdates);
10092
- const previousEntry = previousPositionEntry ?? [positions[fromId], positions[buildBodyTrackedChangeAnchorKey(fromId)]].find((entry) => entry && positionEntryMatchesTrackedChangeStory(entry, comment));
10230
+ const previousEntry = previousPositionEntry ?? [positions[fromId], positions[String(buildBodyTrackedChangeAnchorKey(fromId))]].find((entry) => entry && positionEntryMatchesTrackedChangeStory(entry, comment));
10093
10231
  if (previousEntry) {
10094
- for (const key of [toId, toAnchor]) if (positions[key] === void 0) {
10232
+ for (const key of [toId, String(toAnchor)]) if (positions[key] === void 0) {
10095
10233
  positions[key] = previousEntry;
10096
10234
  positionsChanged = true;
10097
10235
  }
10098
10236
  }
10099
- if (identitySourceIds.has(normalizeCommentId(activeComment.value))) activeComment.value = toId;
10100
- if (identitySourceIds.has(normalizeCommentId(activeFloatingCommentInstanceId.value))) activeFloatingCommentInstanceId.value = toId;
10237
+ if (identitySourceIds.has(normalizeCommentId(activeComment.value) ?? "")) activeComment.value = toId;
10238
+ if (identitySourceIds.has(normalizeCommentId(activeFloatingCommentInstanceId.value) ?? "")) activeFloatingCommentInstanceId.value = toId;
10101
10239
  }
10102
10240
  if (positionsChanged) editorCommentPositions.value = { ...positions };
10103
10241
  };
10104
10242
  const pruneDecidedTrackedChangeRow = ({ superdoc, comment: targetComment, decidedId, decidedIds, documentId } = {}) => {
10105
- const normalizedDecidedIds = new Set((Array.isArray(decidedIds) || decidedIds instanceof Set ? Array.from(decidedIds) : [decidedId]).map((id) => normalizeCommentId(id)).filter(Boolean));
10243
+ const normalizedDecidedIds = new Set((Array.isArray(decidedIds) || decidedIds instanceof Set ? Array.from(decidedIds) : [decidedId]).map((id) => normalizeCommentId(id)).filter(isPresent));
10106
10244
  const normalizedDocumentId = normalizeCommentId(documentId);
10107
10245
  if (!normalizedDecidedIds.size || !normalizedDocumentId) return {
10108
10246
  removed: false,
@@ -10162,12 +10300,6 @@ var useCommentsStore = defineStore("comments", () => {
10162
10300
  };
10163
10301
  /**
10164
10302
  * Accept or reject the tracked change behind a sidebar row.
10165
- *
10166
- * @param {Object} param0
10167
- * @param {Object} param0.superdoc The SuperDoc instance.
10168
- * @param {Object} param0.comment The tracked-change row.
10169
- * @param {string} param0.decision `'accept'` or `'reject'`.
10170
- * @returns {{ ok: boolean, success?: boolean, reason?: string } | Promise<{ ok: boolean, success?: boolean, reason?: string }>} The decision outcome.
10171
10303
  */
10172
10304
  const decideTrackedChangeFromSidebar = ({ superdoc, comment, decision }) => {
10173
10305
  const disabledReason = trackedChangeDecisionDisabledReason(superdoc);
@@ -10195,7 +10327,7 @@ var useCommentsStore = defineStore("comments", () => {
10195
10327
  return {
10196
10328
  ok: false,
10197
10329
  reason: "adapter-threw",
10198
- detail: err?.message ?? String(err)
10330
+ detail: errorDetail(err)
10199
10331
  };
10200
10332
  }
10201
10333
  })().then(async (outcome) => {
@@ -10248,9 +10380,11 @@ var useCommentsStore = defineStore("comments", () => {
10248
10380
  if (!activeEditor) return { ok: false };
10249
10381
  const id = comment.commentId ?? comment.importedId;
10250
10382
  if (!id) return { ok: false };
10251
- const story = comment.trackedChangeStory ?? void 0;
10383
+ const rawStory = comment.trackedChangeStory ?? void 0;
10384
+ const story = isStoryLocator(rawStory) ? rawStory : void 0;
10252
10385
  const documentApi = typeof activeEditor.doc === "object" ? activeEditor.doc : null;
10253
10386
  if (documentApi?.trackChanges?.decide) try {
10387
+ if (rawStory && !story) throw new TypeError("trackedChangeStory must be a valid StoryLocator");
10254
10388
  const target = story ? {
10255
10389
  id,
10256
10390
  story
@@ -10263,7 +10397,7 @@ var useCommentsStore = defineStore("comments", () => {
10263
10397
  success: Boolean(receipt?.success)
10264
10398
  }));
10265
10399
  } catch (error) {
10266
- if (story) return {
10400
+ if (rawStory) return {
10267
10401
  ok: false,
10268
10402
  error
10269
10403
  };
@@ -10310,9 +10444,9 @@ var useCommentsStore = defineStore("comments", () => {
10310
10444
  documentId: effectiveDocumentId
10311
10445
  })).removed
10312
10446
  };
10313
- const normalizedRemovedIds = new Set(Array.from(removedIds ?? []).map((id) => normalizeCommentId(id)).filter(Boolean));
10314
- const normalizedUpsertIds = [...new Set(Array.from(upsertIds ?? []).map((id) => normalizeCommentId(id)).filter(Boolean))];
10315
- const remappedSourceIds = new Set((Array.isArray(remappedPairs) ? remappedPairs : []).map((pair) => normalizeCommentId(pair?.from)).filter(Boolean));
10447
+ const normalizedRemovedIds = new Set(Array.from(removedIds ?? []).map((id) => normalizeCommentId(id)).filter(isPresent));
10448
+ const normalizedUpsertIds = [...new Set(Array.from(upsertIds ?? []).map((id) => normalizeCommentId(id)).filter(isPresent))];
10449
+ const remappedSourceIds = new Set((Array.isArray(remappedPairs) ? remappedPairs : []).map((pair) => normalizeCommentId(pair?.from)).filter(isPresent));
10316
10450
  const locallyRemovedIds = new Set([...normalizedRemovedIds].filter((id) => !remappedSourceIds.has(id)));
10317
10451
  if (locallyRemovedIds.size > 0 && effectiveDocumentId != null) pruneDecidedTrackedChangeRow({
10318
10452
  superdoc,
@@ -10331,6 +10465,10 @@ var useCommentsStore = defineStore("comments", () => {
10331
10465
  const byIdentityAlias = /* @__PURE__ */ new Map();
10332
10466
  const aliasesByRow = /* @__PURE__ */ new Map();
10333
10467
  const indexedRows = /* @__PURE__ */ new Set();
10468
+ const indexedIdentities = [];
10469
+ let building = true;
10470
+ let reusable = true;
10471
+ let reused = false;
10334
10472
  let aliasLookups = 0;
10335
10473
  let candidateVisits = 0;
10336
10474
  let rowAliasBuilds = 0;
@@ -10354,6 +10492,7 @@ var useCommentsStore = defineStore("comments", () => {
10354
10492
  index.set(alias, bucket);
10355
10493
  };
10356
10494
  const add = (comment) => {
10495
+ if (!building) reusable = false;
10357
10496
  if (!comment?.trackedChange) return;
10358
10497
  if (documentId && !belongsToTrackedChangeSyncDocument(comment, documentId)) return;
10359
10498
  indexedRows.add(comment);
@@ -10365,19 +10504,49 @@ var useCommentsStore = defineStore("comments", () => {
10365
10504
  rowAliasBuilds += 1;
10366
10505
  aliasesByRow.set(comment, aliases);
10367
10506
  aliases.forEach((alias) => addAlias(byIdentityAlias, alias, comment));
10507
+ if (building) indexedIdentities.push({
10508
+ row: comment,
10509
+ aliases,
10510
+ canonicalId: normalizeCommentId(comment.trackedChangeCanonicalId)
10511
+ });
10368
10512
  };
10369
10513
  commentsList.value.forEach(add);
10514
+ building = false;
10370
10515
  return {
10516
+ inUse: false,
10371
10517
  add,
10372
10518
  aliasesFor,
10373
- work: () => ({
10374
- trackedRowsIndexed: indexedRows.size,
10375
- invalidatedIdMembershipChecks: 0,
10376
- incomingAliasLookups: aliasLookups,
10377
- candidateVisits,
10378
- rowAliasBuilds,
10379
- rowAliasCacheHits
10380
- }),
10519
+ isCurrent: () => {
10520
+ if (!reusable) return false;
10521
+ let index = 0;
10522
+ for (const row of commentsList.value) {
10523
+ if (!row?.trackedChange || documentId && !belongsToTrackedChangeSyncDocument(row, documentId)) continue;
10524
+ const previous = indexedIdentities[index++];
10525
+ if (!previous || previous.row !== row || previous.canonicalId !== normalizeCommentId(row.trackedChangeCanonicalId)) return false;
10526
+ const aliases = getCommentAliasIds(row);
10527
+ rowAliasBuilds += 1;
10528
+ if (aliases.length !== previous.aliases.length || aliases.some((alias, i) => alias !== previous.aliases[i])) return false;
10529
+ }
10530
+ reused = index === indexedIdentities.length;
10531
+ return reused;
10532
+ },
10533
+ work: () => {
10534
+ const result = {
10535
+ trackedRowsIndexed: indexedRows.size,
10536
+ invalidatedIdMembershipChecks: 0,
10537
+ incomingAliasLookups: aliasLookups,
10538
+ candidateVisits,
10539
+ rowAliasBuilds,
10540
+ rowAliasCacheHits,
10541
+ indexReused: Number(reused)
10542
+ };
10543
+ aliasLookups = 0;
10544
+ candidateVisits = 0;
10545
+ rowAliasBuilds = 0;
10546
+ rowAliasCacheHits = 0;
10547
+ reused = false;
10548
+ return result;
10549
+ },
10381
10550
  candidates({ changeId, importedId, anchorKey, canonicalId, positionAliases = [] } = {}) {
10382
10551
  const out = /* @__PURE__ */ new Set();
10383
10552
  const include = (value) => {
@@ -10400,7 +10569,30 @@ var useCommentsStore = defineStore("comments", () => {
10400
10569
  }
10401
10570
  };
10402
10571
  };
10403
- const reconcileTrackedChangesFromV2 = ({ superdoc, adapter, documentId, items, pruneStale = true, preparedParams = null, preparedPresentation = null, previousPreparedPresentation = null, liveIds: suppliedLiveIds = null, liveAnchorKeys: suppliedLiveAnchorKeys = null, hydrationGeneration } = {}) => withInteractionSpan("store.trackedChanges.reconcile", "store-reconciliation", {
10572
+ const getTrackedChangeBatchIdentityIndex = (documentId) => {
10573
+ const cached = cachedTrackedChangeBatchIdentityIndex;
10574
+ if (cached?.documentId === documentId && !cached.index.inUse) {
10575
+ cached.index.inUse = true;
10576
+ let current = false;
10577
+ try {
10578
+ current = cached.index.isCurrent() && cachedTrackedChangeBatchIdentityIndex === cached;
10579
+ } finally {
10580
+ if (!current) {
10581
+ cached.index.inUse = false;
10582
+ if (cachedTrackedChangeBatchIdentityIndex === cached) cachedTrackedChangeBatchIdentityIndex = null;
10583
+ }
10584
+ }
10585
+ if (current) return cached.index;
10586
+ }
10587
+ const index = createTrackedChangeBatchIdentityIndex(documentId);
10588
+ index.inUse = true;
10589
+ cachedTrackedChangeBatchIdentityIndex = {
10590
+ documentId,
10591
+ index
10592
+ };
10593
+ return index;
10594
+ };
10595
+ const reconcileTrackedChangesFromV2 = ({ superdoc, adapter, documentId, items, pruneStale = true, preparedParams = null, preserveDraft = false, preparedPresentation = null, previousPreparedPresentation = null, liveIds: suppliedLiveIds = null, liveAnchorKeys: suppliedLiveAnchorKeys = null, hydrationGeneration } = {}) => withInteractionSpan("store.trackedChanges.reconcile", "store-reconciliation", {
10404
10596
  documentId: documentId ?? null,
10405
10597
  itemCount: Array.isArray(items) ? items.length : null,
10406
10598
  pruneStale,
@@ -10412,33 +10604,36 @@ var useCommentsStore = defineStore("comments", () => {
10412
10604
  const liveAnchorKeys = suppliedLiveAnchorKeys instanceof Set ? suppliedLiveAnchorKeys : /* @__PURE__ */ new Set();
10413
10605
  const liveIds = suppliedLiveIds instanceof Set ? suppliedLiveIds : /* @__PURE__ */ new Set();
10414
10606
  let appliedCount = 0;
10415
- const trackedChangeIdentityIndex = createTrackedChangeBatchIdentityIndex(effectiveDocumentId == null ? null : String(effectiveDocumentId));
10607
+ const trackedChangeIdentityIndex = getTrackedChangeBatchIdentityIndex(effectiveDocumentId == null ? null : String(effectiveDocumentId));
10416
10608
  const identitySpan = startInteractionSpan("store.trackedChanges.batchIdentity", "store-reconciliation", { itemCount: items.length });
10417
- const paramsList = Array.isArray(preparedParams) ? preparedParams : items.map((item) => adapter.mapV2TrackedChangeToCommentParams(item));
10418
- const previousCoordinate = previousPreparedPresentation?.coordinate;
10419
- const coordinate = preparedPresentation?.coordinate;
10420
- const retainUnchangedRows = !pruneStale && previousCoordinate && coordinate && coordinate.documentEpoch === preparedPresentation.epoch && coordinate.documentEpoch === previousPreparedPresentation.epoch && coordinate.documentEpoch === previousCoordinate.documentEpoch && typeof coordinate.mountId === "string" && coordinate.mountId.length > 0 && coordinate.mountId === previousCoordinate.mountId && preparedPresentation.baseVersion === previousPreparedPresentation.version && preparedPresentation.version === preparedPresentation.baseVersion + 1 && coordinate.reviewVersion === preparedPresentation.version && previousCoordinate.reviewVersion === previousPreparedPresentation.version && Number.isSafeInteger(coordinate.commitSequence) && Number.isSafeInteger(previousCoordinate.commitSequence) && coordinate.commitSequence >= previousCoordinate.commitSequence;
10609
+ let enteredBatch = false;
10421
10610
  try {
10611
+ const paramsList = Array.isArray(preparedParams) ? preparedParams : items.map((item) => adapter.mapV2TrackedChangeToCommentParams(item));
10612
+ const previousCoordinate = previousPreparedPresentation?.coordinate;
10613
+ const coordinate = preparedPresentation?.coordinate;
10614
+ const retainUnchangedRows = !pruneStale && previousCoordinate && coordinate && coordinate.documentEpoch === preparedPresentation.epoch && coordinate.documentEpoch === previousPreparedPresentation.epoch && coordinate.documentEpoch === previousCoordinate.documentEpoch && typeof coordinate.mountId === "string" && coordinate.mountId.length > 0 && coordinate.mountId === previousCoordinate.mountId && preparedPresentation.baseVersion === previousPreparedPresentation.version && preparedPresentation.version === preparedPresentation.baseVersion + 1 && coordinate.reviewVersion === preparedPresentation.version && previousCoordinate.reviewVersion === previousPreparedPresentation.version && Number.isSafeInteger(coordinate.commitSequence) && Number.isSafeInteger(previousCoordinate.commitSequence) && coordinate.commitSequence >= previousCoordinate.commitSequence;
10615
+ enteredBatch = true;
10422
10616
  for (const params of paramsList) {
10423
10617
  if (!params) continue;
10424
10618
  let retainUnchangedRow = false;
10619
+ const anchorParts = parseTrackedChangeAnchorKey(params.trackedChangeAnchorKey);
10620
+ const anchorStoryKey = anchorParts ? normalizeTrackedChangeStoryKey(anchorParts.storyKey) : void 0;
10425
10621
  if (preparedPresentation) {
10426
10622
  if (preparedPresentation !== committedReviewPresentations.value[String(effectiveDocumentId)]) continue;
10427
10623
  if (params.documentId != null && String(params.documentId) !== String(effectiveDocumentId)) continue;
10428
10624
  const id = normalizeCommentId(params.changeId);
10429
- const version = preparedPresentation.rowVersions?.[id];
10625
+ const version = preparedPresentation.rowVersions?.[String(id)] ?? NaN;
10430
10626
  if (!id || !Number.isSafeInteger(version) || version < 1) continue;
10431
10627
  const existing = [...trackedChangeIdentityIndex.candidates({ changeId: id })].filter((row) => String(row.fileId) === String(effectiveDocumentId) && String(row.commentId) === id);
10432
10628
  if (existing.length > 1) continue;
10433
10629
  const row = existing[0];
10434
10630
  if (row && row.trackedChangeCanonicalId !== params.trackedChangeCanonicalId) {
10435
- const previousVersion = previousPreparedPresentation?.rowVersions?.[id];
10436
- if (!row.trackedChange || params.event !== "add" || typeof params.trackedChangeCanonicalId !== "string" || !params.trackedChangeCanonicalId || !Number.isSafeInteger(previousVersion) || previousVersion < 1 || version <= previousVersion || normalizeTrackedChangeStoryKey(getTrackedChangeStoryKey(row)) !== normalizeTrackedChangeStoryKey(parseTrackedChangeAnchorKey(params.trackedChangeAnchorKey)?.storyKey) || row.trackedChangeType !== params.trackedChangeType || row.trackedChangeDisplayType !== params.trackedChangeDisplayType) continue;
10631
+ const previousVersion = previousPreparedPresentation?.rowVersions?.[id] ?? NaN;
10632
+ if (!row.trackedChange || params.event !== "add" || typeof params.trackedChangeCanonicalId !== "string" || !params.trackedChangeCanonicalId || !Number.isSafeInteger(previousVersion) || previousVersion < 1 || version <= previousVersion || normalizeTrackedChangeStoryKey(getTrackedChangeStoryKey(row)) !== anchorStoryKey || row.trackedChangeType !== params.trackedChangeType || row.trackedChangeDisplayType !== params.trackedChangeDisplayType) continue;
10437
10633
  row.trackedChangeCanonicalId = params.trackedChangeCanonicalId;
10438
- row.updateIdentityValues?.({ trackedChangeCanonicalId: params.trackedChangeCanonicalId });
10439
10634
  trackedChangeIdentityIndex.add(row);
10440
10635
  }
10441
- retainUnchangedRow = retainUnchangedRows && row?.trackedChange === true && previousPreparedPresentation.rowVersions?.[id] === version && row.trackedChangeCanonicalId === params.trackedChangeCanonicalId && row.trackedChangeType === params.trackedChangeType && row.trackedChangeDisplayType === params.trackedChangeDisplayType && normalizeTrackedChangeStoryKey(getTrackedChangeStoryKey(row)) === normalizeTrackedChangeStoryKey(parseTrackedChangeAnchorKey(params.trackedChangeAnchorKey)?.storyKey) && !row.resolvedTime && !row.resolvedById && !row.resolvedByEmail && !row.resolvedByName;
10636
+ retainUnchangedRow = retainUnchangedRows === true && row?.trackedChange === true && previousPreparedPresentation.rowVersions?.[id] === version && row.trackedChangeCanonicalId === params.trackedChangeCanonicalId && row.trackedChangeType === params.trackedChangeType && row.trackedChangeDisplayType === params.trackedChangeDisplayType && normalizeTrackedChangeStoryKey(getTrackedChangeStoryKey(row)) === anchorStoryKey && !row.resolvedTime && !row.resolvedById && !row.resolvedByEmail && !row.resolvedByName;
10442
10637
  }
10443
10638
  if (params.event === "omit") {
10444
10639
  if (params.changeId != null) liveIds.add(String(params.changeId));
@@ -10452,12 +10647,15 @@ var useCommentsStore = defineStore("comments", () => {
10452
10647
  superdoc,
10453
10648
  params,
10454
10649
  broadcastChanges: false,
10455
- trackedChangeIdentityIndex
10650
+ trackedChangeIdentityIndex,
10651
+ preserveDraft
10456
10652
  });
10457
10653
  appliedCount += 1;
10458
10654
  }
10459
10655
  } finally {
10460
- endInteractionSpan(identitySpan, trackedChangeIdentityIndex.work());
10656
+ const work = trackedChangeIdentityIndex.work();
10657
+ trackedChangeIdentityIndex.inUse = false;
10658
+ if (enteredBatch) endInteractionSpan(identitySpan, work);
10461
10659
  }
10462
10660
  if (!effectiveDocumentId || !pruneStale) {
10463
10661
  if (effectiveDocumentId) compactDuplicateBodyTrackedChangeComments(String(effectiveDocumentId));
@@ -10479,22 +10677,18 @@ var useCommentsStore = defineStore("comments", () => {
10479
10677
  return commentsList.value.filter((comment) => {
10480
10678
  if (comment?.trackedChange !== true) return false;
10481
10679
  if (normalizedDocumentId == null) return true;
10482
- const rowDocumentId = comment?.documentId ?? comment?.fileId ?? null;
10680
+ const rowDocumentId = Reflect.get(comment, "documentId") ?? comment?.fileId ?? null;
10483
10681
  return rowDocumentId != null && String(rowDocumentId) === normalizedDocumentId;
10484
10682
  }).length;
10485
10683
  };
10486
10684
  /**
10487
10685
  * Triggered when the editor locations are updated
10488
10686
  * Updates floating comment locations from the editor
10489
- *
10490
- * @param {Object} allCommentPositions The collected geometry keyed by comment / tracked-change id
10491
- * @param {{ retainMissingTrackedChangeGeometry?: boolean, retainedTrackedChangeIds?: Iterable<string> }} options Geometry reconciliation options
10492
- * @returns {void}
10493
10687
  */
10494
10688
  const handleEditorLocationsUpdate = (allCommentPositions, options = {}) => {
10495
10689
  if (allCommentPositions == null) return;
10496
10690
  const retainMissingTrackedChangeGeometry = options?.retainMissingTrackedChangeGeometry === true;
10497
- const retainedTrackedChangeIds = retainMissingTrackedChangeGeometry && options?.retainedTrackedChangeIds != null ? new Set(Array.from(options.retainedTrackedChangeIds, (id) => normalizeCommentId(id)).filter(Boolean)) : null;
10691
+ const retainedTrackedChangeIds = retainMissingTrackedChangeGeometry && options?.retainedTrackedChangeIds != null ? new Set(Array.from(options.retainedTrackedChangeIds, (id) => normalizeCommentId(id)).filter(isPresent)) : null;
10498
10692
  const previousPositions = editorCommentPositions.value || {};
10499
10693
  const normalizedPositions = {};
10500
10694
  Object.entries(allCommentPositions).forEach(([key, entry]) => {
@@ -10521,7 +10715,7 @@ var useCommentsStore = defineStore("comments", () => {
10521
10715
  return null;
10522
10716
  };
10523
10717
  const writeTrackedChangeAliases = (entry, comment, extraKeys = []) => {
10524
- const keys = [...aliasesFor(comment), ...extraKeys.map((id) => normalizeCommentId(id)).filter(Boolean)];
10718
+ const keys = [...aliasesFor(comment), ...extraKeys.map((id) => normalizeCommentId(id)).filter(isPresent)];
10525
10719
  for (const key of new Set(keys)) if (normalizedPositions[key] === void 0) normalizedPositions[key] = entry;
10526
10720
  };
10527
10721
  commentsList.value.forEach((comment) => {
@@ -10609,6 +10803,7 @@ var useCommentsStore = defineStore("comments", () => {
10609
10803
  isCommentsListVisible,
10610
10804
  isReviewDirectoryActive,
10611
10805
  isReviewDirectoryLoading,
10806
+ reviewAnalysisProgress,
10612
10807
  generalCommentIds,
10613
10808
  editorCommentIds,
10614
10809
  commentsParentElement,
@@ -10836,7 +11031,7 @@ var _hoisted_3$21 = {
10836
11031
  key: 0,
10837
11032
  class: "comments-dropdown__option-icon"
10838
11033
  };
10839
- var _hoisted_4$15 = ["innerHTML"];
11034
+ var _hoisted_4$16 = ["innerHTML"];
10840
11035
  var _hoisted_5$11 = { class: "comments-dropdown__option-label" };
10841
11036
  //#endregion
10842
11037
  //#region src/components/CommentsLayer/CommentsDropdown.vue
@@ -10982,7 +11177,7 @@ var CommentsDropdown_default = /* @__PURE__ */ _plugin_vue_export_helper_default
10982
11177
  }, [hasIcon(option) ? (openBlock(), createElementBlock("span", _hoisted_3$21, [option.iconString ? (openBlock(), createElementBlock("span", {
10983
11178
  key: 0,
10984
11179
  innerHTML: option.iconString
10985
- }, null, 8, _hoisted_4$15)) : (openBlock(), createBlock(unref(OptionIcon), {
11180
+ }, null, 8, _hoisted_4$16)) : (openBlock(), createBlock(unref(OptionIcon), {
10986
11181
  key: 1,
10987
11182
  option
10988
11183
  }, null, 8, ["option"]))])) : createCommentVNode("", true), createElementVNode("span", _hoisted_5$11, toDisplayString(option.label), 1)], 10, _hoisted_2$24);
@@ -10995,7 +11190,7 @@ var CommentsDropdown_default = /* @__PURE__ */ _plugin_vue_export_helper_default
10995
11190
  var _hoisted_1$30 = { class: "sd-comment-option" };
10996
11191
  var _hoisted_2$23 = ["innerHTML"];
10997
11192
  var _hoisted_3$20 = { class: "sd-option-state" };
10998
- var _hoisted_4$14 = ["innerHTML"];
11193
+ var _hoisted_4$15 = ["innerHTML"];
10999
11194
  //#endregion
11000
11195
  //#region src/components/CommentsLayer/InternalDropdown.vue
11001
11196
  var InternalDropdown_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -11074,7 +11269,7 @@ var InternalDropdown_default = /* @__PURE__ */ _plugin_vue_export_helper_default
11074
11269
  createElementVNode("div", {
11075
11270
  class: "sd-dropdown-caret",
11076
11271
  innerHTML: unref(superdocIcons).caretDown
11077
- }, null, 8, _hoisted_4$14)
11272
+ }, null, 8, _hoisted_4$15)
11078
11273
  ])]),
11079
11274
  _: 1
11080
11275
  }, 8, ["disabled", "content-style"])], 4);
@@ -11149,7 +11344,7 @@ var Avatar_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PUR
11149
11344
  var _hoisted_1$28 = { class: "card-section comment-header" };
11150
11345
  var _hoisted_2$21 = { class: "comment-header-left" };
11151
11346
  var _hoisted_3$18 = { class: "user-info" };
11152
- var _hoisted_4$13 = { class: "user-name" };
11347
+ var _hoisted_4$14 = { class: "user-name" };
11153
11348
  var _hoisted_5$10 = {
11154
11349
  key: 0,
11155
11350
  class: "imported-tag"
@@ -11412,7 +11607,7 @@ var CommentHeader_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
11412
11607
  return openBlock(), createElementBlock("div", _hoisted_1$28, [createElementVNode("div", _hoisted_2$21, [createVNode(Avatar_default, {
11413
11608
  user: getCurrentUser.value,
11414
11609
  class: "avatar"
11415
- }, null, 8, ["user"]), createElementVNode("div", _hoisted_3$18, [createElementVNode("div", _hoisted_4$13, [createTextVNode(toDisplayString(getCurrentUser.value.name), 1), isImported.value ? (openBlock(), createElementBlock("span", _hoisted_5$10, "IMPORTED")) : createCommentVNode("", true)]), props.comment.createdTime ? (openBlock(), createElementBlock("div", _hoisted_6$8, toDisplayString(unref(formatDate)(props.comment.createdTime)), 1)) : createCommentVNode("", true)])]), createElementVNode("div", { class: normalizeClass(["overflow-menu", { "is-visible": props.isActive }]) }, [
11610
+ }, null, 8, ["user"]), createElementVNode("div", _hoisted_3$18, [createElementVNode("div", _hoisted_4$14, [createTextVNode(toDisplayString(getCurrentUser.value.name), 1), isImported.value ? (openBlock(), createElementBlock("span", _hoisted_5$10, "IMPORTED")) : createCommentVNode("", true)]), props.comment.createdTime ? (openBlock(), createElementBlock("div", _hoisted_6$8, toDisplayString(unref(formatDate)(props.comment.createdTime)), 1)) : createCommentVNode("", true)])]), createElementVNode("div", { class: normalizeClass(["overflow-menu", { "is-visible": props.isActive }]) }, [
11416
11611
  allowResolve.value ? (openBlock(), createElementBlock("div", {
11417
11612
  key: 0,
11418
11613
  class: normalizeClass(["overflow-menu__icon", { "sd-is-disabled": Boolean(__props.resolveDisabledReason) }]),
@@ -11467,7 +11662,7 @@ var _hoisted_3$17 = [
11467
11662
  "aria-controls",
11468
11663
  "aria-activedescendant"
11469
11664
  ];
11470
- var _hoisted_4$12 = [
11665
+ var _hoisted_4$13 = [
11471
11666
  "id",
11472
11667
  "aria-selected",
11473
11668
  "data-sd-comment-mention-option",
@@ -11676,7 +11871,7 @@ var CommentInput_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
11676
11871
  "aria-selected": index === highlightedMentionIndex.value,
11677
11872
  "data-sd-comment-mention-option": userKey(user),
11678
11873
  onMousedown: withModifiers(($event) => selectMention(user), ["prevent"])
11679
- }, [createElementVNode("span", _hoisted_5$9, toDisplayString(displayName(user)), 1), user.email ? (openBlock(), createElementBlock("span", _hoisted_6$7, toDisplayString(user.email), 1)) : createCommentVNode("", true)], 42, _hoisted_4$12);
11874
+ }, [createElementVNode("span", _hoisted_5$9, toDisplayString(displayName(user)), 1), user.email ? (openBlock(), createElementBlock("span", _hoisted_6$7, toDisplayString(user.email), 1)) : createCommentVNode("", true)], 42, _hoisted_4$13);
11680
11875
  }), 128))])) : createCommentVNode("", true)])], 2)]);
11681
11876
  };
11682
11877
  }
@@ -11802,7 +11997,7 @@ var _hoisted_2$19 = {
11802
11997
  class: "existing-internal-input"
11803
11998
  };
11804
11999
  var _hoisted_3$16 = { class: "new-comment-input-wrapper" };
11805
- var _hoisted_4$11 = {
12000
+ var _hoisted_4$12 = {
11806
12001
  key: 1,
11807
12002
  class: "tracked-change-decision-alert",
11808
12003
  role: "alert"
@@ -11827,8 +12022,8 @@ var _hoisted_11$3 = {
11827
12022
  key: 0,
11828
12023
  class: "card-section comment-body"
11829
12024
  };
11830
- var _hoisted_12$2 = ["data-track-change-semantic-color-key"];
11831
- var _hoisted_13$2 = { key: 0 };
12025
+ var _hoisted_12$3 = ["data-track-change-semantic-color-key"];
12026
+ var _hoisted_13$3 = { key: 0 };
11832
12027
  var _hoisted_14$2 = { class: "change-type tracked-change-label" };
11833
12028
  var _hoisted_15$2 = {
11834
12029
  key: 0,
@@ -12613,6 +12808,16 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
12613
12808
  }
12614
12809
  handleResolve();
12615
12810
  };
12811
+ const ownsTrackedChangeDecision = (superdoc, editor) => editor !== null && proxy.$superdoc === superdoc && superdoc.activeEditor === editor;
12812
+ const restoreTrackedChangeDecisionFocus = (superdoc, editor) => {
12813
+ nextTick(() => {
12814
+ if (!ownsTrackedChangeDecision(superdoc, editor)) return;
12815
+ commentsStore.lastUpdate = /* @__PURE__ */ new Date();
12816
+ activeComment.value = null;
12817
+ commentsStore.setActiveComment(superdoc, activeComment.value);
12818
+ if (ownsTrackedChangeDecision(superdoc, editor)) superdoc.focus?.({ preventScroll: true });
12819
+ });
12820
+ };
12616
12821
  const handleReject = async () => {
12617
12822
  if (props.comment.trackedChange) {
12618
12823
  if (trackedChangeDecisionsAreDisabled()) return trackedChangeDecisionDisabledOutcome();
@@ -12620,23 +12825,21 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
12620
12825
  const customHandler = proxy.$superdoc.config.onTrackedChangeBubbleReject;
12621
12826
  if (props.comment.trackedChange) {
12622
12827
  if (isV2Mode.value) {
12828
+ const superdoc = proxy.$superdoc;
12829
+ const editor = superdoc.activeEditor;
12623
12830
  const outcome = await commentsStore.decideTrackedChangeFromSidebar({
12624
- superdoc: proxy.$superdoc,
12831
+ superdoc,
12625
12832
  comment: props.comment,
12626
12833
  decision: "reject"
12627
12834
  });
12835
+ if (!ownsTrackedChangeDecision(superdoc, editor)) return;
12628
12836
  if (!outcome?.ok || outcome.success === false) {
12629
12837
  reportTrackedChangeDecisionFailure("reject", outcome);
12630
12838
  return;
12631
12839
  }
12632
12840
  clearTrackedChangeDecisionFailure();
12633
- if (typeof customHandler === "function") customHandler(props.comment, proxy.$superdoc.activeEditor);
12634
- nextTick(() => {
12635
- commentsStore.lastUpdate = /* @__PURE__ */ new Date();
12636
- activeComment.value = null;
12637
- commentsStore.setActiveComment(proxy.$superdoc, activeComment.value);
12638
- proxy.$superdoc.focus?.({ preventScroll: true });
12639
- });
12841
+ if (typeof customHandler === "function") customHandler(props.comment, editor);
12842
+ restoreTrackedChangeDecisionFocus(superdoc, editor);
12640
12843
  return;
12641
12844
  }
12642
12845
  let decisionApplied = true;
@@ -12682,23 +12885,21 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
12682
12885
  }
12683
12886
  const customHandler = proxy.$superdoc.config.onTrackedChangeBubbleAccept;
12684
12887
  if (props.comment.trackedChange && isV2Mode.value) {
12888
+ const superdoc = proxy.$superdoc;
12889
+ const editor = superdoc.activeEditor;
12685
12890
  const outcome = await commentsStore.decideTrackedChangeFromSidebar({
12686
- superdoc: proxy.$superdoc,
12891
+ superdoc,
12687
12892
  comment: props.comment,
12688
12893
  decision: "accept"
12689
12894
  });
12895
+ if (!ownsTrackedChangeDecision(superdoc, editor)) return;
12690
12896
  if (!outcome?.ok || outcome.success === false) {
12691
12897
  reportTrackedChangeDecisionFailure("accept", outcome);
12692
12898
  return;
12693
12899
  }
12694
12900
  clearTrackedChangeDecisionFailure();
12695
- if (typeof customHandler === "function") customHandler(props.comment, proxy.$superdoc.activeEditor);
12696
- nextTick(() => {
12697
- commentsStore.lastUpdate = /* @__PURE__ */ new Date();
12698
- activeComment.value = null;
12699
- commentsStore.setActiveComment(proxy.$superdoc, activeComment.value);
12700
- proxy.$superdoc.focus?.({ preventScroll: true });
12701
- });
12901
+ if (typeof customHandler === "function") customHandler(props.comment, editor);
12902
+ restoreTrackedChangeDecisionFocus(superdoc, editor);
12702
12903
  return;
12703
12904
  }
12704
12905
  let v1TrackedChangeDecisionApplied = true;
@@ -12797,14 +12998,15 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
12797
12998
  if (session?.owner === editSessionOwner) session.submission = submission;
12798
12999
  const editorOwner = captureCommentEditorOwner();
12799
13000
  const documentId = editorOwner.adapter.documentId;
12800
- const thread = props.comment;
13001
+ const threadId = props.comment.commentId;
13002
+ const fileId = props.comment.fileId;
12801
13003
  const draft = currentCommentText.value;
12802
13004
  const outcome = await commentsStore.editCommentV2({
12803
13005
  superdoc: proxy.$superdoc,
12804
13006
  commentId: comment.commentId,
12805
13007
  text: draft
12806
13008
  });
12807
- if (session?.owner !== editSessionOwner || commentEditSessions.get(commentsStore) !== session || session.submission !== submission || !editorOwner.isCurrent() || editorOwner.adapter.documentId !== documentId || props.comment !== thread || editingCommentId.value !== comment.commentId || currentCommentText.value !== draft) return outcome;
13009
+ if (session?.owner !== editSessionOwner || commentEditSessions.get(commentsStore) !== session || session.submission !== submission || !editorOwner.isCurrent() || editorOwner.adapter.documentId !== documentId || props.comment.commentId !== threadId || props.comment.fileId !== fileId || editingCommentId.value !== comment.commentId || currentCommentText.value !== draft) return outcome;
12808
13010
  if (!outcome?.ok) {
12809
13011
  commentSaveFailure.value = commentSaveFailureMessage(outcome);
12810
13012
  return outcome;
@@ -12943,7 +13145,7 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
12943
13145
  "config",
12944
13146
  "comment"
12945
13147
  ])]),
12946
- commentSaveFailure.value ? (openBlock(), createElementBlock("p", _hoisted_4$11, toDisplayString(commentSaveFailure.value), 1)) : createCommentVNode("", true),
13148
+ commentSaveFailure.value ? (openBlock(), createElementBlock("p", _hoisted_4$12, toDisplayString(commentSaveFailure.value), 1)) : createCommentVNode("", true),
12947
13149
  createElementVNode("div", _hoisted_5$8, [createElementVNode("button", {
12948
13150
  class: "sd-button reply-btn-cancel",
12949
13151
  onClick: withModifiers(handleCancel, ["stop", "prevent"])
@@ -13002,7 +13204,7 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13002
13204
  "data-track-change-semantic-color-key": comment.semanticColorKey ?? void 0,
13003
13205
  ref_for: true,
13004
13206
  ref: index === 0 ? setParentBodyRef : void 0
13005
- }, [comment.trackedChangeLabel ? (openBlock(), createElementBlock("div", _hoisted_13$2, [createElementVNode("span", _hoisted_14$2, toDisplayString(comment.trackedChangeLabel), 1), comment.trackedChangeDetailLines && comment.trackedChangeDetailLines.length ? (openBlock(), createElementBlock("div", _hoisted_15$2, [(openBlock(true), createElementBlock(Fragment, null, renderList(comment.trackedChangeDetailLines, (line, lineIndex) => {
13207
+ }, [comment.trackedChangeLabel ? (openBlock(), createElementBlock("div", _hoisted_13$3, [createElementVNode("span", _hoisted_14$2, toDisplayString(comment.trackedChangeLabel), 1), comment.trackedChangeDetailLines && comment.trackedChangeDetailLines.length ? (openBlock(), createElementBlock("div", _hoisted_15$2, [(openBlock(true), createElementBlock(Fragment, null, renderList(comment.trackedChangeDetailLines, (line, lineIndex) => {
13006
13208
  return openBlock(), createElementBlock("div", {
13007
13209
  key: lineIndex,
13008
13210
  class: "tracked-change-detail-line"
@@ -13027,7 +13229,7 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13027
13229
  style: normalizeStyle(trackedChangeImagePreviewStyle(comment.trackedChangeImagePreview)),
13028
13230
  loading: "lazy",
13029
13231
  decoding: "async"
13030
- }, null, 12, _hoisted_38)], 8, _hoisted_37)) : createCommentVNode("", true)], 10, _hoisted_12$2),
13232
+ }, null, 12, _hoisted_38)], 8, _hoisted_37)) : createCommentVNode("", true)], 10, _hoisted_12$3),
13031
13233
  shouldTruncate.value && isTextOverflowing.value && index === 0 ? (openBlock(), createElementBlock("div", {
13032
13234
  key: 0,
13033
13235
  class: "show-more-toggle",
@@ -13121,7 +13323,7 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13121
13323
  ], 64))], 14, _hoisted_1$26)), [[_directive_click_outside, handleClickOutside]]);
13122
13324
  };
13123
13325
  }
13124
- }), [["__scopeId", "data-v-4220ce17"]]);
13326
+ }), [["__scopeId", "data-v-919d450b"]]);
13125
13327
  //#endregion
13126
13328
  //#region src/components/CommentsLayer/commentsList/ReviewDirectoryListItem.vue?vue&type=script&setup=true&lang.ts
13127
13329
  var _hoisted_1$25 = [
@@ -13276,8 +13478,14 @@ var ReviewDirectoryListItem_default = /* @__PURE__ */ _plugin_vue_export_helper_
13276
13478
  //#endregion
13277
13479
  //#region src/components/CommentsLayer/commentsList/commentsList.vue?vue&type=script&setup=true&lang.ts
13278
13480
  var _hoisted_1$24 = ["aria-busy"];
13279
- var _hoisted_2$18 = { key: 0 };
13481
+ var _hoisted_2$18 = {
13482
+ key: 0,
13483
+ class: "review-analysis",
13484
+ role: "status",
13485
+ "data-testid": "review-analysis-progress"
13486
+ };
13280
13487
  var _hoisted_3$15 = { key: 1 };
13488
+ var _hoisted_4$11 = { key: 2 };
13281
13489
  var REPEATED_HEADER_FOOTER_STORY_TYPE = "headerFooterPart";
13282
13490
  //#endregion
13283
13491
  //#region src/components/CommentsLayer/commentsList/commentsList.vue
@@ -13301,7 +13509,12 @@ var commentsList_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13301
13509
  const rovingIndex = ref(0);
13302
13510
  const isCommentsListVisible = toRef(commentsStore, "isCommentsListVisible");
13303
13511
  const floatingCommentInstances = computed$1(() => commentsStore.getFloatingCommentInstances);
13304
- const { activeComment, activeFloatingCommentInstanceId, commentsList, reviewDirectoryList, isReviewDirectoryActive, isReviewDirectoryLoading } = storeToRefs(piniaCommentsStore);
13512
+ const { activeComment, activeFloatingCommentInstanceId, commentsList, reviewDirectoryList, isReviewDirectoryActive, isReviewDirectoryLoading, reviewAnalysisProgress } = storeToRefs(piniaCommentsStore);
13513
+ const reviewAnalysisLabel = computed$1(() => {
13514
+ const progress = reviewAnalysisProgress.value;
13515
+ if (!isReviewDirectoryActive.value || !isReviewDirectoryLoading.value || !progress) return null;
13516
+ return `Analysing changes… ${Math.min(progress.done, progress.total)} of ${progress.total}`;
13517
+ });
13305
13518
  const groupedComments = computed$1(() => isReviewDirectoryActive.value ? commentsStore.getGroupedReviewDirectory : commentsStore.getGroupedComments);
13306
13519
  const reviewRows = computed$1(() => isReviewDirectoryActive.value ? reviewDirectoryList.value : commentsList.value);
13307
13520
  const commentAliases = (comment) => [
@@ -13414,42 +13627,50 @@ var commentsList_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13414
13627
  class: "comments-list",
13415
13628
  role: "list",
13416
13629
  "aria-busy": unref(isReviewDirectoryActive) && unref(isReviewDirectoryLoading) ? "true" : "false"
13417
- }, [__props.showMainComments ? (openBlock(), createElementBlock("div", _hoisted_2$18, [(openBlock(true), createElementBlock(Fragment, null, renderList(parentCommentDisplayItems.value, (item, index) => {
13418
- return openBlock(), createBlock(ReviewDirectoryListItem_default, {
13419
- key: item.id,
13420
- item,
13421
- index,
13422
- total: totalDisplayItems.value,
13423
- "tab-index": index === rovingIndex.value ? 0 : -1,
13424
- onFocusItem: _cache[0] || (_cache[0] = ($event) => rovingIndex.value = $event),
13425
- onNavigate: focusDirectoryItem
13426
- }, null, 8, [
13427
- "item",
13428
- "index",
13429
- "total",
13430
- "tab-index"
13431
- ]);
13432
- }), 128))])) : createCommentVNode("", true), shouldShowResolvedComments.value ? (openBlock(), createElementBlock("div", _hoisted_3$15, [_cache[2] || (_cache[2] = createElementVNode("div", { class: "comment-title" }, "Resolved", -1)), (openBlock(true), createElementBlock(Fragment, null, renderList(resolvedCommentDisplayItems.value, (item, index) => {
13433
- return openBlock(), createBlock(ReviewDirectoryListItem_default, {
13434
- key: item.id,
13435
- item,
13436
- index: displayedMainCount.value + index,
13437
- total: totalDisplayItems.value,
13438
- "tab-index": displayedMainCount.value + index === rovingIndex.value ? 0 : -1,
13439
- onFocusItem: _cache[1] || (_cache[1] = ($event) => rovingIndex.value = $event),
13440
- onNavigate: focusDirectoryItem
13441
- }, null, 8, [
13442
- "item",
13443
- "index",
13444
- "total",
13445
- "tab-index"
13446
- ]);
13447
- }), 128))])) : createCommentVNode("", true)], 8, _hoisted_1$24);
13630
+ }, [
13631
+ reviewAnalysisLabel.value ? (openBlock(), createElementBlock("div", _hoisted_2$18, toDisplayString(reviewAnalysisLabel.value), 1)) : createCommentVNode("", true),
13632
+ __props.showMainComments ? (openBlock(), createElementBlock("div", _hoisted_3$15, [(openBlock(true), createElementBlock(Fragment, null, renderList(parentCommentDisplayItems.value, (item, index) => {
13633
+ return openBlock(), createBlock(ReviewDirectoryListItem_default, {
13634
+ key: item.id,
13635
+ item,
13636
+ index,
13637
+ total: totalDisplayItems.value,
13638
+ "tab-index": index === rovingIndex.value ? 0 : -1,
13639
+ onFocusItem: _cache[0] || (_cache[0] = ($event) => rovingIndex.value = $event),
13640
+ onNavigate: focusDirectoryItem
13641
+ }, null, 8, [
13642
+ "item",
13643
+ "index",
13644
+ "total",
13645
+ "tab-index"
13646
+ ]);
13647
+ }), 128))])) : createCommentVNode("", true),
13648
+ shouldShowResolvedComments.value ? (openBlock(), createElementBlock("div", _hoisted_4$11, [_cache[2] || (_cache[2] = createElementVNode("div", { class: "comment-title" }, "Resolved", -1)), (openBlock(true), createElementBlock(Fragment, null, renderList(resolvedCommentDisplayItems.value, (item, index) => {
13649
+ return openBlock(), createBlock(ReviewDirectoryListItem_default, {
13650
+ key: item.id,
13651
+ item,
13652
+ index: displayedMainCount.value + index,
13653
+ total: totalDisplayItems.value,
13654
+ "tab-index": displayedMainCount.value + index === rovingIndex.value ? 0 : -1,
13655
+ onFocusItem: _cache[1] || (_cache[1] = ($event) => rovingIndex.value = $event),
13656
+ onNavigate: focusDirectoryItem
13657
+ }, null, 8, [
13658
+ "item",
13659
+ "index",
13660
+ "total",
13661
+ "tab-index"
13662
+ ]);
13663
+ }), 128))])) : createCommentVNode("", true)
13664
+ ], 8, _hoisted_1$24);
13448
13665
  };
13449
13666
  }
13450
- }), [["__scopeId", "data-v-8eca48e1"]]);
13667
+ }), [["__scopeId", "data-v-641eba1e"]]);
13451
13668
  //#endregion
13452
13669
  //#region src/components/CommentsLayer/commentsList/super-comments-list.ts
13670
+ var reviewAnalysisOf = (snapshot) => {
13671
+ const analysis = snapshot?.analysis;
13672
+ return analysis && typeof analysis.done === "number" && typeof analysis.total === "number" && analysis.total > 0 ? analysis : null;
13673
+ };
13453
13674
  var isReviewDirectoryStore = (value) => isPlainObject$1(value) && typeof value.setReviewDirectoryFromV2 === "function" && typeof value.clearReviewDirectory === "function";
13454
13675
  var readCommentsStore = (superdoc) => {
13455
13676
  const store = superdoc?.["commentsStore"];
@@ -13507,9 +13728,16 @@ var SuperComments = class extends import_eventemitter3.default {
13507
13728
  if (generation !== this.directoryGeneration) return;
13508
13729
  if ((snapshot?.listStatus ?? snapshot?.status) !== "ready") {
13509
13730
  const store = readCommentsStore(this.superdoc);
13510
- if (store) store.isReviewDirectoryLoading = true;
13731
+ if (store) {
13732
+ store.isReviewDirectoryLoading = true;
13733
+ if (family === "trackChanges") store.reviewAnalysisProgress = reviewAnalysisOf(snapshot);
13734
+ }
13511
13735
  return;
13512
13736
  }
13737
+ if (family === "trackChanges") {
13738
+ const store = readCommentsStore(this.superdoc);
13739
+ if (store) store.reviewAnalysisProgress = null;
13740
+ }
13513
13741
  const items = Array.isArray(snapshot.items) ? snapshot.items : [];
13514
13742
  if (this.directoryItemRefs[family] === items) {
13515
13743
  const store = readCommentsStore(this.superdoc);
@@ -13568,6 +13796,7 @@ var SuperComments = class extends import_eventemitter3.default {
13568
13796
  store.clearReviewDirectory();
13569
13797
  store.isReviewDirectoryActive = false;
13570
13798
  store.isReviewDirectoryLoading = false;
13799
+ store.reviewAnalysisProgress = null;
13571
13800
  }
13572
13801
  if (this.app) {
13573
13802
  this.app.unmount();
@@ -13888,124 +14117,6 @@ var composeAuthorColorResolver = (config) => {
13888
14117
  };
13889
14118
  };
13890
14119
  //#endregion
13891
- //#region ../document-api/src/errors.ts
13892
- /**
13893
- * Structured validation error thrown by document-api execute* functions.
13894
- *
13895
- * Consumers should prefer checking `error.code` over `instanceof` for resilience
13896
- * across package boundaries and bundling scenarios.
13897
- */
13898
- var DocumentApiValidationError = class DocumentApiValidationError extends Error {
13899
- constructor(code, message, details) {
13900
- super(message);
13901
- this.name = "DocumentApiValidationError";
13902
- this.code = code;
13903
- this.details = details;
13904
- Object.setPrototypeOf(this, DocumentApiValidationError.prototype);
13905
- }
13906
- };
13907
- //#endregion
13908
- //#region ../document-api/src/export/export.types.ts
13909
- /** Canonical tracked-change export modes accepted by every public export surface. */
13910
- var SD_EXPORT_MODES = [
13911
- "review-preserving",
13912
- "final",
13913
- "original"
13914
- ];
13915
- /** Default mode for modeful DOCX export. */
13916
- var DEFAULT_SD_EXPORT_MODE = "review-preserving";
13917
- var SD_COMMENT_EXPORT_POLICIES = [
13918
- "preserve",
13919
- "resolve",
13920
- "remove"
13921
- ];
13922
- var SD_CONTENT_CONTROL_EXPORT_POLICIES = ["preserve", "flatten"];
13923
- //#endregion
13924
- //#region ../document-api/src/export/export.ts
13925
- /**
13926
- * Export namespace: engine-agnostic public API and adapter contract.
13927
- *
13928
- * Validates the requested mode at the public boundary so an invalid mode fails
13929
- * closed with `INVALID_INPUT` before any document work happens, then delegates
13930
- * byte production and degradation reporting to the engine adapter.
13931
- */
13932
- function resolveMode$1(input) {
13933
- if (input != null && !isRecord(input)) throw new DocumentApiValidationError("INVALID_INPUT", "export.toDocx input must be an object.");
13934
- const raw = input?.mode;
13935
- if (raw === void 0) return DEFAULT_SD_EXPORT_MODE;
13936
- if (typeof raw !== "string" || !SD_EXPORT_MODES.includes(raw)) throw new DocumentApiValidationError("INVALID_INPUT", `export.toDocx mode must be one of: ${SD_EXPORT_MODES.join(", ")}.`, {
13937
- mode: raw,
13938
- allowedModes: [...SD_EXPORT_MODES]
13939
- });
13940
- return raw;
13941
- }
13942
- function resolveDocxExportPolicies(input = {}) {
13943
- if (!isRecord(input)) throw new DocumentApiValidationError("INVALID_INPUT", "DOCX export input must be an object.");
13944
- const mode = resolveMode$1(input);
13945
- const policies = input.policies;
13946
- if (policies !== void 0 && !isRecord(policies)) throw new DocumentApiValidationError("INVALID_INPUT", "DOCX export policies must be an object.");
13947
- const allowed = {
13948
- trackedChanges: SD_EXPORT_MODES,
13949
- comments: SD_COMMENT_EXPORT_POLICIES,
13950
- contentControls: SD_CONTENT_CONTROL_EXPORT_POLICIES
13951
- };
13952
- for (const [key, value] of Object.entries(policies ?? {})) if (!Object.prototype.hasOwnProperty.call(allowed, key) || value !== void 0 && !allowed[key].includes(value)) throw new DocumentApiValidationError("INVALID_INPUT", `Unsupported DOCX export policy: ${key}.`, {
13953
- policy: key,
13954
- value
13955
- });
13956
- const requested = policies;
13957
- if (input.mode !== void 0 && requested?.trackedChanges !== void 0 && mode !== requested.trackedChanges) throw new DocumentApiValidationError("INVALID_INPUT", "mode conflicts with policies.trackedChanges.");
13958
- return {
13959
- trackedChanges: requested?.trackedChanges ?? mode,
13960
- comments: requested?.comments ?? "preserve",
13961
- contentControls: requested?.contentControls ?? "preserve"
13962
- };
13963
- }
13964
- //#endregion
13965
- //#region ../document-api/src/permission-ranges/permission-ranges.ts
13966
- function editorKey(editor) {
13967
- return editor.id?.trim() ? editor.id : editor.email?.trim() || void 0;
13968
- }
13969
- function editorMatches(editor, id) {
13970
- return Boolean(id.trim()) && (editor.id === id || Boolean(editor.email?.trim() && editor.email.trim().toLowerCase() === id.toLowerCase()));
13971
- }
13972
- /**
13973
- * Resolve a host ID or imported Word email against an explicitly supplied roster.
13974
- * Word receives the email when available; opaque IDs remain host-specific.
13975
- * Duplicate IDs/emails are ambiguous. This does not authenticate a user.
13976
- */
13977
- function resolvePermissionRangeEditor(editors, id) {
13978
- const matches = editors.filter((editor) => editorMatches(editor, id));
13979
- if (matches.length === 0) return {
13980
- status: "unknown",
13981
- id
13982
- };
13983
- if (matches.length !== 1) return {
13984
- status: "ambiguous",
13985
- id
13986
- };
13987
- const editor = matches[0];
13988
- const wordId = editor.email?.trim() || editor.id;
13989
- if (editors.filter((candidate) => editorMatches(candidate, wordId)).length !== 1) return {
13990
- status: "ambiguous",
13991
- id
13992
- };
13993
- return {
13994
- status: "resolved",
13995
- id,
13996
- editor: { ...editor },
13997
- principal: {
13998
- kind: "editor",
13999
- id: wordId
14000
- }
14001
- };
14002
- }
14003
- /** Enumerate the host roster for an editor picker, including ambiguous entries. */
14004
- function listPermissionRangeEditors(editors) {
14005
- const ids = new Set(editors.map(editorKey).filter((id) => id !== void 0));
14006
- return Array.from(ids, (id) => resolvePermissionRangeEditor(editors, id));
14007
- }
14008
- //#endregion
14009
14120
  //#region src/core/superdoc-ui-owner.ts
14010
14121
  var ownerIds = /* @__PURE__ */ new WeakMap();
14011
14122
  function getSuperDocUiOwnerId(superdoc) {
@@ -14118,18 +14229,42 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
14118
14229
  const { activeComment, activeFloatingCommentInstanceId, editorCommentPositions, pendingComment, editingCommentId, instantSidebarAlignmentTargetY, instantSidebarAlignmentThreadId, instantSidebarAlignmentInstanceId } = storeToRefs(piniaCommentsStore);
14119
14230
  const { activeZoom } = storeToRefs(superdocStore);
14120
14231
  const committedPresentation = computed$1(() => commentsStore.committedReviewPresentations?.[String(props.currentDocument?.id)] ?? null);
14232
+ const committedInstanceCache = /* @__PURE__ */ new Map();
14233
+ let committedInstanceCacheOwner = "";
14234
+ const sameReviewInstance = (cached, next) => {
14235
+ const before = Object.entries(cached).filter(([key]) => key !== "comment" && key !== "rowVersion");
14236
+ const after = Object.entries(next);
14237
+ if (before.length !== after.length) return false;
14238
+ const previous = new Map(before);
14239
+ return after.every(([key, value]) => previous.has(key) && (key === "positionEntry" ? JSON.stringify(previous.get(key)) === JSON.stringify(value) : Object.is(previous.get(key), value)));
14240
+ };
14121
14241
  const committedInstances = computed$1(() => {
14122
14242
  const presentation = committedPresentation.value;
14123
14243
  if (!presentation) return null;
14124
14244
  const rows = new Map(commentsStore.commentsList.filter((row) => String(row.fileId) === String(props.currentDocument?.id)).map((row) => [String(row.commentId), row]));
14125
- return presentation.instances.flatMap((instance) => {
14245
+ const owner = `${presentation.epoch}\u0000${presentation.coordinate?.mountId ?? ""}`;
14246
+ if (owner !== committedInstanceCacheOwner) {
14247
+ committedInstanceCache.clear();
14248
+ committedInstanceCacheOwner = owner;
14249
+ }
14250
+ const live = /* @__PURE__ */ new Set();
14251
+ const instances = presentation.instances.flatMap((instance) => {
14126
14252
  const comment = instance.rowId === "pending" ? pendingComment.value : rows.get(instance.rowId);
14127
- return comment ? [{
14253
+ if (!comment) return [];
14254
+ const rowVersion = presentation.rowVersions[instance.rowId] ?? 0;
14255
+ live.add(instance.id);
14256
+ const cached = committedInstanceCache.get(instance.id);
14257
+ if (cached && cached.comment === comment && cached.rowVersion === rowVersion && sameReviewInstance(cached, instance)) return [cached];
14258
+ const merged = {
14128
14259
  ...instance,
14129
14260
  comment,
14130
- rowVersion: presentation.rowVersions[instance.rowId] ?? 0
14131
- }] : [];
14261
+ rowVersion
14262
+ };
14263
+ committedInstanceCache.set(instance.id, merged);
14264
+ return [merged];
14132
14265
  });
14266
+ for (const id of committedInstanceCache.keys()) if (!live.has(id)) committedInstanceCache.delete(id);
14267
+ return instances;
14133
14268
  });
14134
14269
  const floatingCommentInstances = computed$1(() => {
14135
14270
  if (committedInstances.value) return committedInstances.value.filter((instance) => instance.rowId !== "pending");
@@ -14545,6 +14680,18 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
14545
14680
  scheduleDeferredRemeasure(() => allPositions.value.map((pos) => pos.id));
14546
14681
  });
14547
14682
  };
14683
+ const resizeHandlers = /* @__PURE__ */ new Map();
14684
+ const resizeHandlerFor = (id) => {
14685
+ let handler = resizeHandlers.get(id);
14686
+ if (!handler) {
14687
+ handler = () => {
14688
+ const position = allPositions.value.find((candidate) => candidate.id === id);
14689
+ if (position) handleResize(position);
14690
+ };
14691
+ resizeHandlers.set(id, handler);
14692
+ }
14693
+ return handler;
14694
+ };
14548
14695
  const setInstantLayoutTransitionsDisabled = (disabled) => {
14549
14696
  if (!disabled && directDecisionContinuityTargetId.value != null) return;
14550
14697
  disableInstantLayoutTransitions.value = disabled;
@@ -14949,6 +15096,7 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
14949
15096
  visibilityChanged = true;
14950
15097
  }
14951
15098
  if (visibilityChanged) visibleIds.value = newVisible;
15099
+ for (const id of resizeHandlers.keys()) if (!currentIds.has(id)) resizeHandlers.delete(id);
14952
15100
  for (const id of prevPositionIds) if (!currentIds.has(id)) delete _heightsCache[id];
14953
15101
  const setChanged = prevPositionIds.size !== currentIds.size || [...prevPositionIds].some((id) => !currentIds.has(id));
14954
15102
  const orderChanged = renderedPositionOrder.length !== positionOrder.length || renderedPositionOrder.some((id, index) => id !== positionOrder[index]);
@@ -15041,12 +15189,13 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
15041
15189
  style: normalizeStyle({
15042
15190
  top: (pos.isOffscreenReviewCard && !committedPresentation.value ? pos.top - sidebarOffsetY.value : pos.top) + "px",
15043
15191
  height: pos.height + "px",
15044
- transition: committedPresentation.value || disableInstantLayoutTransitions.value ? "none" : void 0
15192
+ transition: committedPresentation.value || disableInstantLayoutTransitions.value ? "none" : void 0,
15193
+ visibility: pos.instanceRef?.provisional ? "hidden" : void 0
15045
15194
  })
15046
15195
  }, [shouldRenderDialog(pos) ? (openBlock(), createBlock(CommentDialog_default, {
15047
15196
  key: pos.id + commentsRenderKey.value,
15048
15197
  onReady: handleDialog,
15049
- onResize: ($event) => handleResize(pos),
15198
+ onResize: resizeHandlerFor(pos.id),
15050
15199
  class: "floating-comment",
15051
15200
  parent: __props.parent,
15052
15201
  comment: pos.commentRef,
@@ -15066,7 +15215,7 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
15066
15215
  }), 128))], 4)], 36);
15067
15216
  };
15068
15217
  }
15069
- }), [["__scopeId", "data-v-aaf2c537"]]);
15218
+ }), [["__scopeId", "data-v-1070f5a1"]]);
15070
15219
  //#endregion
15071
15220
  //#region src/components/CommentsLayer/PdfCommentsLayer.vue?vue&type=script&setup=true&lang.ts
15072
15221
  var _hoisted_1$22 = {
@@ -15199,7 +15348,7 @@ var PdfCommentsLayer_default = /* @__PURE__ */ _plugin_vue_export_helper_default
15199
15348
  }), 128))]);
15200
15349
  };
15201
15350
  }
15202
- }), [["__scopeId", "data-v-86166837"]]);
15351
+ }), [["__scopeId", "data-v-e3ae65fa"]]);
15203
15352
  //#endregion
15204
15353
  //#region src/components/Whiteboard/use-whiteboard.ts
15205
15354
  var useWhiteboard = ({ proxy, layers, documents, modules }) => {
@@ -15791,6 +15940,21 @@ function useCommentSmallScreen({ commentsModuleConfig, superdocRoot, layers }) {
15791
15940
  };
15792
15941
  }
15793
15942
  //#endregion
15943
+ //#region src/composables/use-comment-sidebar.ts
15944
+ function useCommentSidebar({ documentKey, enabled, hasContent }) {
15945
+ const reserved = ref(false);
15946
+ watch$1(documentKey, () => {
15947
+ reserved.value = false;
15948
+ }, { flush: "sync" });
15949
+ watch$1(() => enabled.value && hasContent.value, (visible) => {
15950
+ if (visible) reserved.value = true;
15951
+ }, {
15952
+ immediate: true,
15953
+ flush: "sync"
15954
+ });
15955
+ return computed$1(() => enabled.value && (hasContent.value || reserved.value));
15956
+ }
15957
+ //#endregion
15794
15958
  //#region src/composables/use-compact-comment-popover.ts
15795
15959
  var POPOVER_WIDTH_PX = 320;
15796
15960
  var SAFE_MARGIN_PX = 12;
@@ -16169,6 +16333,117 @@ function resolveV2ReviewTargetCommentId(target, getComment) {
16169
16333
  return commentId != null ? String(commentId) : null;
16170
16334
  }
16171
16335
  //#endregion
16336
+ //#region src/helpers/v2-review-geometry-delivery.ts
16337
+ function createReviewGeometryDelivery(deliver) {
16338
+ const packets = /* @__PURE__ */ new Map();
16339
+ let stageOwners = /* @__PURE__ */ new WeakMap();
16340
+ let active = null;
16341
+ const documentOf = (current) => current.documentId != null ? String(current.documentId) : current.mountContainer?.dataset.superdocV2DocumentId ?? null;
16342
+ const matches = (documentId, current) => current.mountContainer?.dataset.superdocV2DocumentId === documentId && (current.documentId == null || String(current.documentId) === documentId);
16343
+ const matchesPaint = (packet, current) => typeof packet.payload.paintEpoch !== "number" || packet.payload.paintEpoch === current.epoch;
16344
+ const observe = (current) => {
16345
+ const stage = current.mountContainer;
16346
+ const documentId = documentOf(current);
16347
+ if (!stage || !documentId || !matches(documentId, current)) return;
16348
+ const previousOwner = stageOwners.get(stage);
16349
+ if (previousOwner && previousOwner !== documentId) packets.delete(previousOwner);
16350
+ stageOwners.set(stage, documentId);
16351
+ const packet = packets.get(documentId);
16352
+ if (packet?.stage && packet.stage !== stage) packets.delete(documentId);
16353
+ else if (packet && (!active || active.stage !== stage || active.documentId !== documentId || active.epoch !== current.epoch)) packet.delivered = false;
16354
+ active = {
16355
+ documentId,
16356
+ stage,
16357
+ epoch: current.epoch
16358
+ };
16359
+ };
16360
+ const dispatch = (documentId, packet, current, replay = false) => {
16361
+ if (!current.mountContainer || !matches(documentId, current) || !matchesPaint(packet, current)) return false;
16362
+ if (packet.stage && packet.stage !== current.mountContainer || packet.delivering || packet.delivered && !replay) return false;
16363
+ const wasDelivered = packet.delivered;
16364
+ const previousStage = packet.stage;
16365
+ packet.stage = current.mountContainer;
16366
+ packet.delivered = true;
16367
+ packet.delivering = true;
16368
+ try {
16369
+ deliver(current, packet.payload);
16370
+ } catch (error) {
16371
+ packet.delivered = wasDelivered;
16372
+ packet.stage = previousStage;
16373
+ throw error;
16374
+ } finally {
16375
+ packet.delivering = false;
16376
+ }
16377
+ return true;
16378
+ };
16379
+ const notify = (documentId, payload, current, ready = true) => {
16380
+ if (!payload.reviewGeometry || !Array.isArray(payload.pageIndices)) return false;
16381
+ let key;
16382
+ try {
16383
+ key = JSON.stringify([
16384
+ documentId,
16385
+ payload.documentEpoch,
16386
+ payload.paintEpoch,
16387
+ payload.commitSequence,
16388
+ payload.generation,
16389
+ payload.pageIndices,
16390
+ payload.zoomScale,
16391
+ payload.reviewGeometry
16392
+ ]);
16393
+ } catch {
16394
+ return false;
16395
+ }
16396
+ const isCurrent = current != null && matches(documentId, current);
16397
+ if (isCurrent) observe(current);
16398
+ const previous = packets.get(documentId);
16399
+ if (previous && previous.payload.documentEpoch === payload.documentEpoch && typeof previous.payload.paintEpoch === "number" && typeof payload.paintEpoch === "number" && payload.paintEpoch < previous.payload.paintEpoch) return false;
16400
+ if (previous && previous.payload.documentEpoch === payload.documentEpoch && typeof previous.payload.commitSequence === "number" && typeof payload.commitSequence === "number" && payload.commitSequence < previous.payload.commitSequence) return false;
16401
+ if (previous && previous.payload.documentEpoch === payload.documentEpoch && typeof previous.payload.generation === "number" && typeof payload.generation === "number" && payload.generation < previous.payload.generation) return false;
16402
+ if (isCurrent && typeof payload.paintEpoch === "number" && typeof current.epoch === "number" && payload.paintEpoch < current.epoch) return false;
16403
+ if (previous?.key === key) {
16404
+ previous.payload = payload;
16405
+ return isCurrent && ready ? dispatch(documentId, previous, current) : false;
16406
+ }
16407
+ const packet = {
16408
+ payload,
16409
+ key,
16410
+ stage: isCurrent ? current.mountContainer : previous && previous.payload.documentEpoch === payload.documentEpoch ? previous.stage : null,
16411
+ delivered: false,
16412
+ delivering: false
16413
+ };
16414
+ packets.set(documentId, packet);
16415
+ if (!isCurrent || !ready) return false;
16416
+ try {
16417
+ return dispatch(documentId, packet, current);
16418
+ } catch (error) {
16419
+ if (previous) packets.set(documentId, previous);
16420
+ else packets.delete(documentId);
16421
+ throw error;
16422
+ }
16423
+ };
16424
+ return {
16425
+ notify,
16426
+ observe,
16427
+ flush(current) {
16428
+ observe(current);
16429
+ const documentId = documentOf(current);
16430
+ const packet = documentId == null ? null : packets.get(documentId);
16431
+ return documentId && packet ? dispatch(documentId, packet, current) : false;
16432
+ },
16433
+ replay(current) {
16434
+ observe(current);
16435
+ const documentId = documentOf(current);
16436
+ const packet = documentId == null ? null : packets.get(documentId);
16437
+ return documentId && packet ? dispatch(documentId, packet, current, true) : false;
16438
+ },
16439
+ reset() {
16440
+ packets.clear();
16441
+ stageOwners = /* @__PURE__ */ new WeakMap();
16442
+ active = null;
16443
+ }
16444
+ };
16445
+ }
16446
+ //#endregion
16172
16447
  //#region src/helpers/v2-mutation-exception.ts
16173
16448
  var FAILURE_SOURCES = /* @__PURE__ */ new Set(["shell", "receipt"]);
16174
16449
  var DIAGNOSTIC_CODE = /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/;
@@ -16538,7 +16813,7 @@ function usePasswordPrompt({ getSurfaceManager, getPasswordPromptConfig, onUnhan
16538
16813
  })
16539
16814
  });
16540
16815
  } else {
16541
- const mod = await import("./chunks/PasswordPromptSurface-CZqhDQQ1.es.js");
16816
+ const mod = await import("./chunks/PasswordPromptSurface-DjV1JgjD.es.js");
16542
16817
  const surfaceId = `password-prompt-${doc.id}`;
16543
16818
  handle = manager.open({
16544
16819
  id: surfaceId,
@@ -17198,7 +17473,7 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
17198
17473
  } else {
17199
17474
  let FindReplaceSurface;
17200
17475
  try {
17201
- FindReplaceSurface = (await import("./chunks/FindReplaceSurface-B_5ggdWg.es.js")).default;
17476
+ FindReplaceSurface = (await import("./chunks/FindReplaceSurface-BEnRLpD9.es.js")).default;
17202
17477
  } catch {
17203
17478
  opening = false;
17204
17479
  return;
@@ -17572,11 +17847,11 @@ var _hoisted_8$3 = { class: "input-row url-input-row" };
17572
17847
  var _hoisted_9$2 = ["innerHTML"];
17573
17848
  var _hoisted_10$2 = ["readonly", "onKeydown"];
17574
17849
  var _hoisted_11$2 = ["innerHTML"];
17575
- var _hoisted_12$1 = {
17850
+ var _hoisted_12$2 = {
17576
17851
  key: 0,
17577
17852
  class: "input-row link-buttons"
17578
17853
  };
17579
- var _hoisted_13$1 = ["disabled"];
17854
+ var _hoisted_13$2 = ["disabled"];
17580
17855
  var _hoisted_14$1 = ["innerHTML"];
17581
17856
  var _hoisted_15$1 = ["disabled"];
17582
17857
  var _hoisted_16$1 = {
@@ -17835,7 +18110,7 @@ var LinkInput_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
17835
18110
  "data-item": "btn-link-open"
17836
18111
  }, null, 10, _hoisted_11$2)
17837
18112
  ]),
17838
- !isViewingMode.value ? (openBlock(), createElementBlock("div", _hoisted_12$1, [isEditing.value ? (openBlock(), createElementBlock("button", {
18113
+ !isViewingMode.value ? (openBlock(), createElementBlock("div", _hoisted_12$2, [isEditing.value ? (openBlock(), createElementBlock("button", {
17839
18114
  key: 0,
17840
18115
  class: "remove-btn",
17841
18116
  onClick: handleRemove,
@@ -17844,7 +18119,7 @@ var LinkInput_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
17844
18119
  }, [createElementVNode("div", {
17845
18120
  class: "remove-btn__icon",
17846
18121
  innerHTML: unref(toolbarIcons).removeLink
17847
- }, null, 8, _hoisted_14$1), _cache[6] || (_cache[6] = createTextVNode(" Remove ", -1))], 8, _hoisted_13$1)) : createCommentVNode("", true), createElementVNode("button", {
18122
+ }, null, 8, _hoisted_14$1), _cache[6] || (_cache[6] = createTextVNode(" Remove ", -1))], 8, _hoisted_13$2)) : createCommentVNode("", true), createElementVNode("button", {
17848
18123
  class: normalizeClass(["sd-submit-btn", { "disable-btn": isDisabled.value }]),
17849
18124
  onClick: handleSubmit,
17850
18125
  disabled: isSubmitting.value,
@@ -18754,6 +19029,7 @@ function createV2EditorRuntimeAdapter(options) {
18754
19029
  const { id, documentId, root, host, getLegacyEditorProjection, onUnregister } = options;
18755
19030
  let snapshot = host.getSnapshot();
18756
19031
  let didDispose = false;
19032
+ let detached = false;
18757
19033
  let unregistered = false;
18758
19034
  let mountedFocusHandle = null;
18759
19035
  let tokenRevision = 0;
@@ -19050,7 +19326,17 @@ function createV2EditorRuntimeAdapter(options) {
19050
19326
  };
19051
19327
  }
19052
19328
  }
19329
+ function releaseHostSubscriptions() {
19330
+ hostUnsubscribe?.();
19331
+ hostUnsubscribe = null;
19332
+ pageMetricsUnsubscribe?.();
19333
+ pageMetricsUnsubscribe = null;
19334
+ selectionUnsubscribe?.();
19335
+ selectionUnsubscribe = null;
19336
+ activeSelectionController = null;
19337
+ }
19053
19338
  function handleHostSnapshot(next) {
19339
+ if (detached || didDispose) return;
19054
19340
  snapshot = next;
19055
19341
  invalidatePositionTokens();
19056
19342
  syncSelectionSubscription();
@@ -19064,10 +19350,7 @@ function createV2EditorRuntimeAdapter(options) {
19064
19350
  });
19065
19351
  if (next.state === "disposed" && !didDispose) {
19066
19352
  didDispose = true;
19067
- hostUnsubscribe?.();
19068
- pageMetricsUnsubscribe?.();
19069
- selectionUnsubscribe?.();
19070
- selectionUnsubscribe = null;
19353
+ releaseHostSubscriptions();
19071
19354
  emit({ type: "disposed" });
19072
19355
  listeners.clear();
19073
19356
  if (!unregistered) {
@@ -19214,10 +19497,7 @@ function createV2EditorRuntimeAdapter(options) {
19214
19497
  await host.dispose();
19215
19498
  if (!didDispose) {
19216
19499
  didDispose = true;
19217
- hostUnsubscribe?.();
19218
- pageMetricsUnsubscribe?.();
19219
- selectionUnsubscribe?.();
19220
- selectionUnsubscribe = null;
19500
+ releaseHostSubscriptions();
19221
19501
  emit({ type: "disposed" });
19222
19502
  listeners.clear();
19223
19503
  if (!unregistered) {
@@ -19296,8 +19576,17 @@ function createV2EditorRuntimeAdapter(options) {
19296
19576
  }
19297
19577
  },
19298
19578
  attachMountHandle(handle) {
19579
+ if (detached || didDispose) return;
19299
19580
  mountedFocusHandle = handle;
19300
19581
  syncSelectionSubscription();
19582
+ },
19583
+ detach() {
19584
+ if (detached) return;
19585
+ detached = true;
19586
+ unregistered = true;
19587
+ releaseHostSubscriptions();
19588
+ mountedFocusHandle = null;
19589
+ listeners.clear();
19301
19590
  }
19302
19591
  };
19303
19592
  }
@@ -20460,7 +20749,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20460
20749
  __name: "SuperDoc",
20461
20750
  emits: ["selection-update"],
20462
20751
  setup(__props, { emit: __emit }) {
20463
- const PdfViewer = defineAsyncComponent(() => import("./chunks/PdfViewer-CW09MNrB.es.js"));
20752
+ const PdfViewer = defineAsyncComponent(() => import("./chunks/PdfViewer-C2r-K8zC.es.js"));
20464
20753
  const getDocumentLoadPassword = (doc) => doc.password ?? proxy.$superdoc.config.password;
20465
20754
  const resolveDocumentV2Collaboration = (doc) => {
20466
20755
  const rawConfig = doc?.v2Collaboration;
@@ -20545,7 +20834,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20545
20834
  if (commentsConfig.highlightHoverColor) vars["--sd-comments-highlight-hover"] = commentsConfig.highlightHoverColor;
20546
20835
  const activeTrackChangeColors = {
20547
20836
  ...commentsConfig.trackChangeHighlightColors || {},
20548
- ...commentsConfig.trackChangeActiveHighlightColors || {}
20837
+ ...commentsConfig.trackChangeActiveHighlightColors
20549
20838
  };
20550
20839
  if (activeTrackChangeColors.insertBorder) vars["--sd-tracked-changes-insert-border"] = activeTrackChangeColors.insertBorder;
20551
20840
  if (activeTrackChangeColors.insertBackground) vars["--sd-tracked-changes-insert-background"] = activeTrackChangeColors.insertBackground;
@@ -20574,6 +20863,8 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20574
20863
  const v2ReviewSidebarUnlocked = ref(false);
20575
20864
  const v2GeometryEpoch = ref(null);
20576
20865
  let v2GeometryRafHandle = 0;
20866
+ let v2GeometryReplayRafHandle = 0;
20867
+ let v2GeometryReplayGeneration = 0;
20577
20868
  let v2TrackedChangeRestampRetainPaints = 0;
20578
20869
  let v2TrackedChangeRestampRetentionReason = null;
20579
20870
  const activeEditorRef = computed$1(() => proxy.$superdoc.activeEditor);
@@ -20757,6 +21048,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20757
21048
  if (document && provider?.sendStateless) document.provider = null;
20758
21049
  const entry = v2Runtimes.get(documentId);
20759
21050
  if (!entry) return;
21051
+ entry.adapter.detach();
20760
21052
  proxy.$superdoc["unregisterEditorRuntime"](entry.runtimeId);
20761
21053
  v2Runtimes.delete(documentId);
20762
21054
  const hostRoot = subDocumentRoots.get(documentId);
@@ -20910,7 +21202,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20910
21202
  if (loadingDocument?.loadScope && !loadingDocument.loadScope.finish()) return;
20911
21203
  clearV2EditorFailure(payload.documentId ?? null);
20912
21204
  clearV2MutationRejectionNotifications(payload.documentId ?? null);
20913
- const { host, firstPartyCommandMutation, mount, documentId, capabilities, editCommands, bindEditShortcuts, bindSessionShortcuts, commentsAdapter, trackedChangesAdapter, contextMenu, documentApi, documentMutationReadiness, documentApiUnavailableReason, pageMetrics, pageLayout, pageFurniture, presence, lock, connection, collaborationProvider, fonts, replaceFile, upgradeToCollaboration, documentOpenToken } = payload;
21205
+ const { host, firstPartyCommandMutation, mount, documentId, capabilities, editCommands, bindEditShortcuts, bindSessionShortcuts, commentsAdapter, trackedChangesAdapter, contextMenu, viewportQuery, documentApi, documentMutationReadiness, documentApiUnavailableReason, pageMetrics, pageLayout, pageFurniture, presence, lock, connection, collaborationProvider, fonts, replaceFile, upgradeToCollaboration, documentOpenToken } = payload;
20914
21206
  documentOpenTelemetry?.trackDocumentOpen(documentOpenToken ?? null, documentId ?? null);
20915
21207
  const saveV2Bytes = createV2SaveQueue(async (saveOptions = {}) => {
20916
21208
  if (!host || typeof host.save !== "function") throw new Error("v2-editor: save unavailable");
@@ -21006,21 +21298,21 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21006
21298
  };
21007
21299
  };
21008
21300
  const currentV2AuthoringMode = () => proxy.$superdoc.config.documentMode === "suggesting" ? "tracked" : "direct";
21009
- const readV2CurrentSelectionTarget = () => {
21010
- const selection = (host?.getHandles?.()?.editing ?? null)?.selection ?? null;
21011
- if (typeof selection?.toSelectionTarget !== "function") return createV2AuthoringFailure("selection-target-unavailable", "editing.selection.toSelectionTarget is not available");
21012
- const result = selection.toSelectionTarget();
21013
- if (!result || result.kind === "rejected") {
21014
- const rejection = result?.rejection ?? {};
21015
- return createV2AuthoringFailure(rejection.reason ?? rejection.code ?? "selection-target-rejected", rejection.detail);
21301
+ const confirmedV2SelectionTarget = async () => {
21302
+ const selectionTargets = host?.getHandles?.()?.editing?.selectionTargets;
21303
+ if (typeof selectionTargets?.confirmedSelectionTarget !== "function") return createV2AuthoringFailure("selection-confirmation-unavailable");
21304
+ try {
21305
+ const result = await selectionTargets.confirmedSelectionTarget();
21306
+ if (!result?.ok || !result.target) return createV2AuthoringFailure(result?.reason ?? "selection-unconfirmed");
21307
+ return {
21308
+ ok: true,
21309
+ target: result.target,
21310
+ mode: result.mode,
21311
+ story: result.story
21312
+ };
21313
+ } catch (error) {
21314
+ return createV2AuthoringFailure("selection-unconfirmed", error instanceof Error ? error.message : String(error));
21016
21315
  }
21017
- if (!result.target || typeof result.target !== "object") return createV2AuthoringFailure("selection-target-rejected", "selection target was not produced");
21018
- return {
21019
- ok: true,
21020
- target: result.target,
21021
- mode: result.mode ?? "range",
21022
- story: result.story
21023
- };
21024
21316
  };
21025
21317
  const failureFromV2Receipt = (receipt, fallbackReason) => {
21026
21318
  const failure = receipt?.failure ?? receipt;
@@ -21094,7 +21386,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21094
21386
  },
21095
21387
  serializeSelectionToClipboard: async ({ includeHtml = true } = {}) => {
21096
21388
  if (!documentApi?.clipboard || typeof documentApi.clipboard.serializeSelection !== "function") return createV2AuthoringFailure("clipboard-serialize-unavailable", "activeEditor.doc.clipboard.serializeSelection is not available");
21097
- const selected = readV2CurrentSelectionTarget();
21389
+ const selected = await confirmedV2SelectionTarget();
21098
21390
  if (!selected.ok) return selected;
21099
21391
  let serialized;
21100
21392
  try {
@@ -21120,7 +21412,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21120
21412
  let pasteTarget = target;
21121
21413
  let selectionMode = null;
21122
21414
  if (!pasteTarget) {
21123
- const selected = readV2CurrentSelectionTarget();
21415
+ const selected = await confirmedV2SelectionTarget();
21124
21416
  if (!selected.ok) return selected;
21125
21417
  pasteTarget = selected.target;
21126
21418
  selectionMode = selected.mode;
@@ -21181,6 +21473,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21181
21473
  v2Comments: commentsAdapter ?? null,
21182
21474
  v2TrackedChanges: trackedChangesAdapter ?? null,
21183
21475
  contextMenu: contextMenu ?? null,
21476
+ viewportQuery: viewportQuery ?? null,
21184
21477
  pageMetrics: pageMetrics ?? null,
21185
21478
  pageLayout: pageLayout ?? null,
21186
21479
  pageFurniture: pageFurniture ?? null,
@@ -21522,10 +21815,11 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21522
21815
  const publishV2PendingPositionEntry = (entry) => {
21523
21816
  if (!entry) return;
21524
21817
  handleEditorLocationsUpdate({
21525
- ...editorCommentPositions.value ?? {},
21818
+ ...editorCommentPositions.value,
21526
21819
  pending: entry
21527
21820
  });
21528
21821
  };
21822
+ let scheduleV2GeometryReplay = () => {};
21529
21823
  const v2GeometryPublisher = resolvedEditorIntegration.createGeometryPublisher({
21530
21824
  getLayersContainer: () => layers.value ?? null,
21531
21825
  isCommentsEnabled: () => Boolean(commentsModuleConfig.value) && shouldRenderCommentsInViewing.value,
@@ -21541,7 +21835,8 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21541
21835
  v2ReviewSidebarUnlocked.value = true;
21542
21836
  v2GeometryEpoch.value = v2GeometryPublisher.getLastEpoch();
21543
21837
  }
21544
- }
21838
+ },
21839
+ onCommittedPagePaint: ({ payload }) => scheduleV2GeometryReplay(payload)
21545
21840
  });
21546
21841
  const armV2TrackedChangeRestampGeometryRetention = (reason = "tracked-change-restamp") => {
21547
21842
  v2TrackedChangeRestampRetainPaints = Math.max(v2TrackedChangeRestampRetainPaints, 3);
@@ -21561,20 +21856,53 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21561
21856
  v2TrackedChangeRestampRetentionReason = null;
21562
21857
  };
21563
21858
  const resolveV2GeometryPublishOptions = (options) => typeof options === "function" ? options() : options;
21859
+ const isV2GeometryReady = (current) => {
21860
+ if (!current?.mountContainer || !commentsModuleConfig.value || !shouldRenderCommentsInViewing.value) return false;
21861
+ const mount = current.mount;
21862
+ let liveEpoch;
21863
+ try {
21864
+ liveEpoch = mount?.getRenderEpoch?.();
21865
+ } catch {
21866
+ return false;
21867
+ }
21868
+ return v2GeometryPublisher.getLastPayload() === current && v2GeometryPublisher.getLastEpoch() != null && v2GeometryPublisher.getLastEpoch() === current.epoch && (liveEpoch == null || liveEpoch === current.epoch);
21869
+ };
21564
21870
  const scheduleV2GeometryPublish = (payload, options = void 0) => {
21565
21871
  v2GeometryRender.value = payload;
21566
21872
  if (v2GeometryRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryRafHandle);
21873
+ const publish = () => {
21874
+ const current = v2GeometryRender.value;
21875
+ Promise.resolve(v2GeometryPublisher.publish(current, resolveV2GeometryPublishOptions(options))).then(() => {
21876
+ if (current === v2GeometryRender.value && isV2GeometryReady(current)) v2ReviewGeometryDelivery.flush(current);
21877
+ });
21878
+ };
21567
21879
  if (typeof requestAnimationFrame !== "function") {
21568
- v2GeometryPublisher.publish(v2GeometryRender.value, resolveV2GeometryPublishOptions(options));
21880
+ publish();
21569
21881
  return;
21570
21882
  }
21571
21883
  v2GeometryRafHandle = requestAnimationFrame(() => {
21572
21884
  v2GeometryRafHandle = 0;
21573
- v2GeometryPublisher.publish(v2GeometryRender.value, resolveV2GeometryPublishOptions(options));
21885
+ publish();
21574
21886
  });
21575
21887
  };
21888
+ const v2ReviewGeometryDelivery = createReviewGeometryDelivery((current, packet) => {
21889
+ v2GeometryPublisher.noteCommittedPageWindow?.(packet);
21890
+ scheduleV2GeometryPublish(current);
21891
+ });
21892
+ scheduleV2GeometryReplay = (payload) => {
21893
+ if (!payload || payload !== v2GeometryRender.value) return;
21894
+ const generation = ++v2GeometryReplayGeneration;
21895
+ if (v2GeometryReplayRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryReplayRafHandle);
21896
+ const replay = () => {
21897
+ v2GeometryReplayRafHandle = 0;
21898
+ if (generation === v2GeometryReplayGeneration && payload === v2GeometryRender.value && isV2GeometryReady(payload)) v2ReviewGeometryDelivery.replay(payload);
21899
+ };
21900
+ if (typeof requestAnimationFrame === "function") v2GeometryReplayRafHandle = requestAnimationFrame(replay);
21901
+ else queueMicrotask(replay);
21902
+ };
21576
21903
  const onV2Render = (payload) => {
21577
21904
  if (!payload) return;
21905
+ v2ReviewGeometryDelivery.observe(payload);
21578
21906
  scheduleV2GeometryPublish(payload, () => takeV2TrackedChangeRestampGeometryRetention());
21579
21907
  if (v2HasRangeSelection.value) scheduleV2SelectionToolbarStateSync();
21580
21908
  };
@@ -21607,7 +21935,11 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21607
21935
  cancelScheduledV2SelectionToolbarSync();
21608
21936
  if (v2GeometryRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryRafHandle);
21609
21937
  v2GeometryRafHandle = 0;
21938
+ if (v2GeometryReplayRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryReplayRafHandle);
21939
+ v2GeometryReplayRafHandle = 0;
21940
+ v2GeometryReplayGeneration++;
21610
21941
  v2GeometryRender.value = null;
21942
+ v2ReviewGeometryDelivery.reset();
21611
21943
  v2GeometryEpoch.value = null;
21612
21944
  v2GeometryAvailable.value = false;
21613
21945
  v2ReviewSidebarUnlocked.value = false;
@@ -21755,14 +22087,15 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21755
22087
  if (!isV2Mode.value) return;
21756
22088
  if (!v2GeometryPublisher.getLastPayload()) return;
21757
22089
  if (v2GeometryRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryRafHandle);
21758
- if (typeof requestAnimationFrame !== "function") {
21759
- v2GeometryPublisher.recollect(resolveV2GeometryPublishOptions(options));
21760
- return;
21761
- }
21762
- v2GeometryRafHandle = requestAnimationFrame(() => {
22090
+ const recollect = () => {
21763
22091
  v2GeometryRafHandle = 0;
21764
- v2GeometryPublisher.recollect(resolveV2GeometryPublishOptions(options));
21765
- });
22092
+ const current = v2GeometryRender.value;
22093
+ Promise.resolve(v2GeometryPublisher.recollect(resolveV2GeometryPublishOptions(options))).then(() => {
22094
+ if (current === v2GeometryRender.value && isV2GeometryReady(current)) v2ReviewGeometryDelivery.replay(current);
22095
+ });
22096
+ };
22097
+ if (typeof requestAnimationFrame !== "function") recollect();
22098
+ else v2GeometryRafHandle = requestAnimationFrame(recollect);
21766
22099
  };
21767
22100
  const handleV2DocumentModeChange = () => {
21768
22101
  if (!isV2Mode.value) return;
@@ -21985,18 +22318,25 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21985
22318
  onCommentsUpdate: onEditorCommentsUpdate,
21986
22319
  onFontsResolved: onFontsResolvedFn,
21987
22320
  onPageCountKnown: proxy.$superdoc.config.onPageCountKnown ?? null,
22321
+ onReviewAnalysisProgress: (progress) => {
22322
+ v2ReviewWindowController.noteAnalysisProgress?.(progress);
22323
+ },
21988
22324
  onReviewWindowCommitted: (payload) => {
21989
22325
  v2ReviewWindowController.onCommittedPagePaint?.({
21990
22326
  ...payload,
21991
22327
  documentId: doc.id
21992
22328
  });
21993
- v2GeometryPublisher.noteCommittedPageWindow?.(payload);
22329
+ if (payload.reviewGeometry && Array.isArray(payload.pageIndices)) {
22330
+ const current = v2GeometryRender.value;
22331
+ v2ReviewGeometryDelivery.notify(String(doc.id), payload, current, isV2GeometryReady(current));
22332
+ } else v2GeometryPublisher.noteCommittedPageWindow?.(payload);
21994
22333
  },
21995
22334
  fontAssets: proxy.$superdoc.config.fonts,
21996
22335
  workerUrls: proxy.$superdoc.config.workerUrls,
21997
22336
  fieldContext: doc.fieldContext,
21998
22337
  fieldUpdatePolicy: proxy.$superdoc.config.fieldUpdatePolicy,
21999
22338
  workerStartupTimeoutMs: proxy.$superdoc.config.workerStartupTimeoutMs,
22339
+ ...proxy.$superdoc.config.changes ? { changes: proxy.$superdoc.config.changes } : {},
22000
22340
  proofing: resolvedProofingConfig.value,
22001
22341
  isNewFile,
22002
22342
  password: getDocumentLoadPassword(doc),
@@ -22017,7 +22357,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
22017
22357
  documentLoading: proxy.$superdoc.uiConfig.loading.enabled,
22018
22358
  linkPopoverResolver: getHyperlinkActivationHandler(),
22019
22359
  layoutEngineOptions: useLayoutEngine ? {
22020
- ...proxy.$superdoc.config.layoutEngineOptions || {},
22360
+ ...proxy.$superdoc.config.layoutEngineOptions,
22021
22361
  proofing: resolvedProofingConfig.value,
22022
22362
  debugLabel: proxy.$superdoc.config.layoutEngineOptions?.debugLabel ?? doc.name ?? doc.id,
22023
22363
  zoom: (activeZoom.value ?? 100) / 100,
@@ -22280,11 +22620,16 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
22280
22620
  if (layout !== "auto") return true;
22281
22621
  return !isCompactCommentsMode.value;
22282
22622
  });
22283
- const showCommentsSidebar = computed$1(() => {
22623
+ const sidebarEnabled = computed$1(() => {
22284
22624
  if (isV2Mode.value && !v2GeometryAvailable.value && !v2ReviewSidebarUnlocked.value && !hasPdfFloatingComments.value) return false;
22285
22625
  if (!shouldRenderCommentsInViewing.value) return false;
22286
22626
  if (!shouldUseSidebarComments.value) return false;
22287
- return pendingComment.value || (floatingComments.value.length > 0 || hasOpenTrackedChanges.value) && isReady.value && layers.value && isCommentsEnabled.value && !isCommentsListVisible.value;
22627
+ return Boolean(pendingComment.value || isReady.value && layers.value && isCommentsEnabled.value && !isCommentsListVisible.value);
22628
+ });
22629
+ const showCommentsSidebar = useCommentSidebar({
22630
+ documentKey: computed$1(() => JSON.stringify(documents.value.map((document) => document.id))),
22631
+ enabled: sidebarEnabled,
22632
+ hasContent: computed$1(() => Boolean(pendingComment.value || floatingComments.value.length || hasOpenTrackedChanges.value))
22288
22633
  });
22289
22634
  const activeCompactComment = computed$1(() => {
22290
22635
  if (showCommentsSidebar.value) return null;
@@ -22908,7 +23253,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
22908
23253
  ref_key: "superdocRoot",
22909
23254
  ref: superdocRoot,
22910
23255
  class: normalizeClass(["superdoc", {
22911
- "superdoc--with-sidebar": showCommentsSidebar.value,
23256
+ "superdoc--with-sidebar": unref(showCommentsSidebar),
22912
23257
  "superdoc--web-layout": unref(proxy).$superdoc.config.viewOptions?.layout === "web",
22913
23258
  "superdoc--contained": unref(proxy).$superdoc.config.contained,
22914
23259
  "high-contrast": unref(isHighContrastMode)
@@ -23056,12 +23401,12 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
23056
23401
  onAnchorActivate: handleHighlightClick
23057
23402
  })) : createCommentVNode("", true)
23058
23403
  ])], 512),
23059
- showCommentsSidebar.value ? (openBlock(), createElementBlock("div", {
23404
+ unref(showCommentsSidebar) ? (openBlock(), createElementBlock("div", {
23060
23405
  key: 0,
23061
23406
  ref_key: "rightSidebarRef",
23062
23407
  ref: rightSidebarRef,
23063
23408
  class: "superdoc__right-sidebar right-sidebar"
23064
- }, [createElementVNode("div", _hoisted_4$9, [unref(hasInitializedLocations) && (floatingComments.value.length > 0 || unref(pendingComment)) ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(unref(documentsWithConverations), (doc) => {
23409
+ }, [createElementVNode("div", _hoisted_4$9, [unref(hasInitializedLocations) ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(unref(documentsWithConverations), (doc) => {
23065
23410
  return openBlock(), createBlock(FloatingComments_default, {
23066
23411
  parent: layers.value,
23067
23412
  "current-document": doc
@@ -23079,7 +23424,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
23079
23424
  ], 46, _hoisted_1$17);
23080
23425
  };
23081
23426
  }
23082
- }), [["__scopeId", "data-v-d2540ad3"]]);
23427
+ }), [["__scopeId", "data-v-9bae9abb"]]);
23083
23428
  //#endregion
23084
23429
  //#region src/core/create-app.ts
23085
23430
  var PINIA_DEVTOOLS_SETUP_EVENT = "devtools-plugin:setup";
@@ -24560,6 +24905,16 @@ var SurfaceManager = class {
24560
24905
  };
24561
24906
  //#endregion
24562
24907
  //#region src/helpers/deprecation.ts
24908
+ /**
24909
+ * Deprecation utilities for steering consumers toward the Document API.
24910
+ *
24911
+ * ProseMirror will be removed in a future major version. These helpers emit
24912
+ * one-time console warnings when deprecated properties are accessed on an
24913
+ * editor instance handed to consumer callbacks (onEditorCreate, etc.).
24914
+ *
24915
+ * Internal code continues to use the raw Editor — only the consumer-facing
24916
+ * boundary wraps the editor with a Proxy.
24917
+ */
24563
24918
  var MIGRATION_URL = "https://docs.superdoc.dev/guides/migration/document-api";
24564
24919
  var _warned = /* @__PURE__ */ new Set();
24565
24920
  /**
@@ -24748,7 +25103,8 @@ function normalizeSuperDocUser(user) {
24748
25103
  if (!user || typeof user !== "object") return { ...DEFAULT_SUPERDOC_USER };
24749
25104
  return {
24750
25105
  ...DEFAULT_SUPERDOC_USER,
24751
- ...user
25106
+ ...user,
25107
+ ...user.commentIdentity !== void 0 ? { commentIdentity: normalizeCommentIdentity(user.commentIdentity) } : {}
24752
25108
  };
24753
25109
  }
24754
25110
  //#endregion
@@ -25267,6 +25623,7 @@ function normalizeUiConfig(config = {}) {
25267
25623
  * Keeping policy separate means an application can disable the built-in
25268
25624
  * comments UI without losing the rules enforced by its custom UI.
25269
25625
  */
25626
+ var isAutocapitalize = (value) => value === "off" || value === "sentences" || value === "words" || value === "characters";
25270
25627
  var isCommentInteractionLevel = (value) => value === "read" || value === "write" || value === "resolve";
25271
25628
  /**
25272
25629
  * Resolve the effective interaction policy.
@@ -25286,14 +25643,26 @@ function normalizeInteractionConfig(config = {}) {
25286
25643
  const canonicalLevel = interactionComments.level;
25287
25644
  const hasCanonicalLevel = isCommentInteractionLevel(canonicalLevel);
25288
25645
  const level = hasCanonicalLevel ? canonicalLevel : legacyReadOnly ? "read" : legacyAllowResolve ? "resolve" : "write";
25646
+ const interactionWritingAssistance = isPlainObject$1(interaction.writingAssistance) ? interaction.writingAssistance : {};
25647
+ const autocorrect = interactionWritingAssistance.autocorrect === true;
25648
+ const autocapitalize = isAutocapitalize(interactionWritingAssistance.autocapitalize) ? interactionWritingAssistance.autocapitalize : "off";
25649
+ const spellcheck = interactionWritingAssistance.spellcheck === true;
25289
25650
  const canonicalAllowDecisions = interactionTrackedChanges.allowDecisions;
25651
+ const interactionTouch = isPlainObject$1(interaction.touch) ? interaction.touch : {};
25290
25652
  return {
25291
25653
  comments: {
25292
25654
  level,
25293
25655
  readOnly: hasCanonicalLevel ? level === "read" : legacyReadOnly,
25294
25656
  allowResolve: hasCanonicalLevel ? level === "resolve" : legacyAllowResolve
25295
25657
  },
25296
- trackedChanges: { allowDecisions: typeof canonicalAllowDecisions === "boolean" ? canonicalAllowDecisions : !legacyReadOnly }
25658
+ trackedChanges: { allowDecisions: typeof canonicalAllowDecisions === "boolean" ? canonicalAllowDecisions : !legacyReadOnly },
25659
+ touch: { gestures: interactionTouch.gestures === true },
25660
+ writingAssistance: {
25661
+ enabled: autocorrect || autocapitalize !== "off" || spellcheck,
25662
+ autocorrect,
25663
+ autocapitalize,
25664
+ spellcheck
25665
+ }
25297
25666
  };
25298
25667
  }
25299
25668
  //#endregion
@@ -25949,26 +26318,39 @@ var ToolbarButtonIcon_default = /* @__PURE__ */ _plugin_vue_export_helper_defaul
25949
26318
  var _hoisted_1$15 = [
25950
26319
  "role",
25951
26320
  "aria-label",
25952
- "aria-disabled"
26321
+ "aria-disabled",
26322
+ "aria-busy"
25953
26323
  ];
25954
26324
  var _hoisted_2$11 = ["data-item"];
25955
26325
  var _hoisted_3$10 = ["data-item"];
25956
26326
  var _hoisted_4$8 = {
25957
- key: 1,
26327
+ key: 0,
26328
+ role: "status",
26329
+ "aria-label": "Applying list",
26330
+ style: { "font-weight": "700" }
26331
+ };
26332
+ var _hoisted_5$6 = {
26333
+ key: 2,
25958
26334
  class: "sd-button-label"
25959
26335
  };
25960
- var _hoisted_5$6 = ["data-item", "aria-label"];
25961
- var _hoisted_6$4 = ["innerHTML"];
25962
- var _hoisted_7$2 = { key: 2 };
25963
- var _hoisted_8$2 = ["onKeydown", "id"];
25964
- var _hoisted_9$1 = [
26336
+ var _hoisted_6$4 = ["data-item", "aria-label"];
26337
+ var _hoisted_7$2 = ["innerHTML"];
26338
+ var _hoisted_8$2 = {
26339
+ key: 0,
26340
+ role: "status",
26341
+ "aria-label": "Applying list",
26342
+ style: { "font-weight": "700" }
26343
+ };
26344
+ var _hoisted_9$1 = { key: 3 };
26345
+ var _hoisted_10$1 = ["onKeydown", "id"];
26346
+ var _hoisted_11$1 = [
25965
26347
  "placeholder",
25966
26348
  "onKeydown",
25967
26349
  "id",
25968
26350
  "data-item"
25969
26351
  ];
25970
- var _hoisted_10$1 = ["data-item", "innerHTML"];
25971
- var _hoisted_11$1 = {
26352
+ var _hoisted_12$1 = ["data-item", "innerHTML"];
26353
+ var _hoisted_13$1 = {
25972
26354
  "aria-live": "polite",
25973
26355
  class: "sd-visually-hidden"
25974
26356
  };
@@ -26141,6 +26523,7 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26141
26523
  role: __props.isOverflowItem ? "menuitem" : "button",
26142
26524
  "aria-label": unref(attributes).ariaLabel,
26143
26525
  "aria-disabled": unref(disabled) ? "true" : void 0,
26526
+ "aria-busy": unref(attributes).ariaBusy || void 0,
26144
26527
  "data-sd-part": "toolbar-item",
26145
26528
  onClick: handleOuterClick,
26146
26529
  onKeydown: _cache[9] || (_cache[9] = withKeys(($event) => onEnterKeydown($event), ["enter"])),
@@ -26162,17 +26545,21 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26162
26545
  class: "sd-toolbar-button__main",
26163
26546
  "data-item": `btn-${unref(name) || ""}-main`,
26164
26547
  onClick: _cache[0] || (_cache[0] = ($event) => handleSplitMainClick($event))
26165
- }, [unref(icon) ? (openBlock(), createBlock(ToolbarButtonIcon_default, {
26166
- key: 0,
26167
- color: unref(iconColor),
26168
- class: "sd-toolbar-icon",
26169
- icon: unref(icon),
26170
- name: unref(name)
26171
- }, null, 8, [
26172
- "color",
26173
- "icon",
26174
- "name"
26175
- ])) : createCommentVNode("", true), unref(label) && !unref(hideLabel) && !unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("div", _hoisted_4$8, toDisplayString(unref(label)), 1)) : createCommentVNode("", true)], 8, _hoisted_3$10)) : createCommentVNode("", true),
26548
+ }, [
26549
+ unref(attributes).ariaBusy && isSplit.value ? (openBlock(), createElementBlock("span", _hoisted_4$8, "…")) : createCommentVNode("", true),
26550
+ unref(icon) ? (openBlock(), createBlock(ToolbarButtonIcon_default, {
26551
+ key: 1,
26552
+ color: unref(iconColor),
26553
+ class: "sd-toolbar-icon",
26554
+ icon: unref(icon),
26555
+ name: unref(name)
26556
+ }, null, 8, [
26557
+ "color",
26558
+ "icon",
26559
+ "name"
26560
+ ])) : createCommentVNode("", true),
26561
+ unref(label) && !unref(hideLabel) && !unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("div", _hoisted_5$6, toDisplayString(unref(label)), 1)) : createCommentVNode("", true)
26562
+ ], 8, _hoisted_3$10)) : createCommentVNode("", true),
26176
26563
  isSplit.value ? (openBlock(), createElementBlock("div", {
26177
26564
  key: 1,
26178
26565
  class: "sd-toolbar-button__caret",
@@ -26183,9 +26570,10 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26183
26570
  class: "sd-dropdown-caret",
26184
26571
  innerHTML: caretIcon.value,
26185
26572
  style: normalizeStyle({ opacity: unref(disabled) ? .6 : 1 })
26186
- }, null, 12, _hoisted_6$4)], 8, _hoisted_5$6)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
26573
+ }, null, 12, _hoisted_7$2)], 8, _hoisted_6$4)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
26574
+ unref(attributes).ariaBusy ? (openBlock(), createElementBlock("span", _hoisted_8$2, "…")) : createCommentVNode("", true),
26187
26575
  unref(icon) ? (openBlock(), createBlock(ToolbarButtonIcon_default, {
26188
- key: 0,
26576
+ key: 1,
26189
26577
  color: unref(iconColor),
26190
26578
  class: "sd-toolbar-icon",
26191
26579
  icon: unref(icon),
@@ -26196,10 +26584,10 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26196
26584
  "name"
26197
26585
  ])) : createCommentVNode("", true),
26198
26586
  unref(label) && !unref(hideLabel) && (!unref(inlineTextInputVisible) || itemName.value === "fontFamily") ? (openBlock(), createElementBlock("div", {
26199
- key: 1,
26587
+ key: 2,
26200
26588
  class: normalizeClass(["sd-button-label", { "sd-visually-hidden": unref(inlineTextInputVisible) && itemName.value === "fontFamily" }])
26201
26589
  }, toDisplayString(unref(label)), 3)) : createCommentVNode("", true),
26202
- unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("span", _hoisted_7$2, [unref(name) === "fontSize" ? withDirectives((openBlock(), createElementBlock("input", {
26590
+ unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("span", _hoisted_9$1, [unref(name) === "fontSize" ? withDirectives((openBlock(), createElementBlock("input", {
26203
26591
  key: 0,
26204
26592
  "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => inlineTextInput.value = $event),
26205
26593
  onKeydown: [withKeys(withModifiers(handleInlineInputEnter, ["stop", "prevent"]), ["enter"]), _cache[2] || (_cache[2] = withKeys(($event) => handleInlineInputTab($event), ["tab"]))],
@@ -26211,7 +26599,7 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26211
26599
  autocomplete: "off",
26212
26600
  ref_key: "inlineInput",
26213
26601
  ref: inlineInput
26214
- }, null, 42, _hoisted_8$2)), [[vModelText, inlineTextInput.value]]) : withDirectives((openBlock(), createElementBlock("input", {
26602
+ }, null, 42, _hoisted_10$1)), [[vModelText, inlineTextInput.value]]) : withDirectives((openBlock(), createElementBlock("input", {
26215
26603
  key: 1,
26216
26604
  "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => inlineTextInput.value = $event),
26217
26605
  placeholder: unref(label),
@@ -26228,20 +26616,20 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26228
26616
  autocomplete: "off",
26229
26617
  ref_key: "inlineInput",
26230
26618
  ref: inlineInput
26231
- }, null, 42, _hoisted_9$1)), [[vModelText, inlineTextInput.value]])])) : createCommentVNode("", true),
26619
+ }, null, 42, _hoisted_11$1)), [[vModelText, inlineTextInput.value]])])) : createCommentVNode("", true),
26232
26620
  unref(hasCaret) ? (openBlock(), createElementBlock("div", {
26233
- key: 3,
26621
+ key: 4,
26234
26622
  class: "sd-dropdown-caret",
26235
26623
  "data-item": `btn-${unref(name) || ""}-toggle`,
26236
26624
  innerHTML: caretIcon.value,
26237
26625
  style: normalizeStyle({ opacity: unref(disabled) ? .6 : 1 })
26238
- }, null, 12, _hoisted_10$1)) : createCommentVNode("", true)
26626
+ }, null, 12, _hoisted_12$1)) : createCommentVNode("", true)
26239
26627
  ], 64)),
26240
- createElementVNode("div", _hoisted_11$1, toDisplayString(`${unref(attributes).ariaLabel} ${unref(active) ? "selected" : "unset"}`), 1)
26628
+ createElementVNode("div", _hoisted_13$1, toDisplayString(`${unref(attributes).ariaLabel} ${unref(active) ? "selected" : "unset"}`), 1)
26241
26629
  ], 10, _hoisted_2$11)], 46, _hoisted_1$15);
26242
26630
  };
26243
26631
  }
26244
- }), [["__scopeId", "data-v-e599674c"]]);
26632
+ }), [["__scopeId", "data-v-b49ede12"]]);
26245
26633
  //#endregion
26246
26634
  //#region src/internal/toolbar/built-in/ToolbarSeparator.vue?vue&type=script&setup=true&lang.ts
26247
26635
  var _hoisted_1$14 = {
@@ -27533,7 +27921,7 @@ var SdTooltip_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
27533
27921
  ]);
27534
27922
  const contentStyle = computed$1(() => ({
27535
27923
  ...props.contentStyle,
27536
- ...attrs.style || {},
27924
+ ...attrs.style,
27537
27925
  position: "fixed",
27538
27926
  top: position.value.top,
27539
27927
  left: position.value.left,
@@ -27702,7 +28090,7 @@ var SdTooltip_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
27702
28090
  }, 8, ["css"])]))], 64);
27703
28091
  };
27704
28092
  }
27705
- }), [["__scopeId", "data-v-dfd6f1d1"]]);
28093
+ }), [["__scopeId", "data-v-87967ab1"]]);
27706
28094
  //#endregion
27707
28095
  //#region src/internal/toolbar/built-in/ButtonGroup.vue?vue&type=script&setup=true&lang.ts
27708
28096
  var _hoisted_1$11 = [
@@ -30526,6 +30914,8 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
30526
30914
  attributes: { ariaLabel: "Bold" }
30527
30915
  });
30528
30916
  const fontOptions = (toolbarFonts ?? TOOLBAR_FONTS).map(normalizeFontOption);
30917
+ const isFontOption = (option) => typeof option.label === "string" && typeof option.key === "string";
30918
+ const currentFontOptions = () => fontButton.nestedOptions.value.filter(isFontOption);
30529
30919
  const fontButton = useToolbarItem({
30530
30920
  type: "dropdown",
30531
30921
  name: "fontFamily",
@@ -30552,8 +30942,9 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
30552
30942
  if (typeof fontFamily !== "string" || !fontFamily) return;
30553
30943
  const family = fontFamily.split(",")[0];
30554
30944
  const optionValue = (option) => option.value ?? option.label;
30555
- const defaultFont = fontOptions.find((option) => optionValue(option) === fontButton.defaultLabel.value);
30556
- const foundFont = fontOptions.find((option) => optionValue(option) === family);
30945
+ const options = currentFontOptions();
30946
+ const defaultFont = options.find((option) => optionValue(option) === fontButton.defaultLabel.value);
30947
+ const foundFont = options.find((option) => optionValue(option) === family);
30557
30948
  if (foundFont) {
30558
30949
  fontButton.label.value = foundFont.label;
30559
30950
  fontButton.selectedValue.value = foundFont.key;
@@ -30566,7 +30957,7 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
30566
30957
  }
30567
30958
  },
30568
30959
  onDeactivate: () => {
30569
- const defaultFont = fontOptions.find((option) => (option.value ?? option.label) === fontButton.defaultLabel.value);
30960
+ const defaultFont = currentFontOptions().find((option) => (option.value ?? option.label) === fontButton.defaultLabel.value);
30570
30961
  fontButton.label.value = defaultFont?.label ?? fontButton.defaultLabel.value;
30571
30962
  fontButton.selectedValue.value = defaultFont?.key ?? "";
30572
30963
  }
@@ -31778,7 +32169,7 @@ var V1_ENABLED_ON_SELECTION_REASON = /* @__PURE__ */ new Set([
31778
32169
  ]);
31779
32170
  var V1_ENABLED_ON_EMPTY_REASON = /* @__PURE__ */ new Set(["list", "numberedlist"]);
31780
32171
  function shouldKeepEnabledForV1ToolbarParity(name, commandState) {
31781
- if (name === "fontFamily" || name === "fontSize") return true;
32172
+ if (name === "fontFamily" || name === "fontSize") return commandState?.reason !== "document-readonly" && commandState?.reason !== "deleted-text-formatting-unsupported";
31782
32173
  if (V1_ENABLED_ON_EMPTY_REASON.has(name) && commandState?.disabled && commandState?.supported !== false && commandState?.reason == null) return true;
31783
32174
  if (!V1_ENABLED_ON_SELECTION_REASON.has(name)) return false;
31784
32175
  const reason = commandState?.reason;
@@ -31812,6 +32203,7 @@ var RESERVED_TOOLBAR_ITEM_NAMES = /* @__PURE__ */ new Set([
31812
32203
  * proof lane, and behavior tests resolve against a real object.
31813
32204
  */
31814
32205
  var BuiltInToolbar = class extends import_eventemitter3.default {
32206
+ #formattingRefusalTooltips;
31815
32207
  #imagePicker;
31816
32208
  #groupItems;
31817
32209
  #highlightOptions;
@@ -31843,6 +32235,7 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
31843
32235
  showPageNumberButton: false
31844
32236
  };
31845
32237
  this.toolbarItems = [];
32238
+ this.#formattingRefusalTooltips = /* @__PURE__ */ new WeakMap();
31846
32239
  this.overflowItems = [];
31847
32240
  this.pendingSelectionCapture = void 0;
31848
32241
  this.#imagePicker = null;
@@ -31969,7 +32362,7 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
31969
32362
  const signature = this.#fontOptionsSignature();
31970
32363
  if (signature !== this._lastFontOptionsSignature) {
31971
32364
  this._lastFontOptionsSignature = signature;
31972
- this.#rebuildToolbarItems();
32365
+ if (!this.#refreshFontFamilyOptions()) this.#rebuildToolbarItems();
31973
32366
  }
31974
32367
  this.updateToolbarState();
31975
32368
  };
@@ -32117,6 +32510,13 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
32117
32510
  this.#makeToolbarItems();
32118
32511
  this.emit("toolbar-items-changed");
32119
32512
  }
32513
+ #refreshFontFamilyOptions() {
32514
+ const items = this.#getAllToolbarItems().filter((item) => item.name.value === "fontFamily");
32515
+ if (!items.length) return false;
32516
+ const options = (this.#resolveToolbarFonts(this.config.fonts) ?? TOOLBAR_FONTS).map(normalizeFontOption);
32517
+ for (const item of items) item.nestedOptions.value = options.map((option) => ({ ...option }));
32518
+ return true;
32519
+ }
32120
32520
  #fontOptionsSignature() {
32121
32521
  if (this.config.fonts) return "custom-fonts";
32122
32522
  const options = this.superdoc?.fonts?.getFontFamilyOptions?.() ?? [];
@@ -32296,6 +32696,17 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
32296
32696
  const commandId = resolveBuiltInItemCommandId(name);
32297
32697
  if (!commandId) return false;
32298
32698
  const commandState = this.snapshot?.commands?.[commandId];
32699
+ if (commandState?.reason === "deleted-text-formatting-unsupported") {
32700
+ if (!this.#formattingRefusalTooltips.has(item)) this.#formattingRefusalTooltips.set(item, item.tooltip.value);
32701
+ item.tooltip.value = "Select kept text, or accept or reject the deletion before formatting.";
32702
+ } else if (this.#formattingRefusalTooltips.has(item)) {
32703
+ item.tooltip.value = this.#formattingRefusalTooltips.get(item);
32704
+ this.#formattingRefusalTooltips.delete(item);
32705
+ }
32706
+ if ((name === "list" || name === "numberedlist") && item.attributes.value.ariaBusy !== Boolean(commandState?.pending)) item.attributes.value = {
32707
+ ...item.attributes.value,
32708
+ ariaBusy: Boolean(commandState?.pending)
32709
+ };
32299
32710
  if (shouldKeepEnabledForV1ToolbarParity(name, commandState)) item.setDisabled(false);
32300
32711
  else item.setDisabled(Boolean(commandState?.disabled));
32301
32712
  const handlers = {
@@ -32763,6 +33174,9 @@ function deriveCollaborationConnectionKind(snapshot, hasSyncedBefore) {
32763
33174
  default: return hasSyncedBefore ? "reconnecting" : "initial";
32764
33175
  }
32765
33176
  }
33177
+ function destroyedDuringUpgradeError() {
33178
+ return /* @__PURE__ */ new Error("SuperDoc: instance was destroyed during upgrade");
33179
+ }
32766
33180
  function isV2ActiveEditorFacade(editor) {
32767
33181
  return Boolean(editor && typeof editor === "object" && editor.editorVersion === 2);
32768
33182
  }
@@ -32812,6 +33226,7 @@ function runtimeUserOf(user) {
32812
33226
  const text = (value) => typeof value === "string" && value ? value : void 0;
32813
33227
  return {
32814
33228
  name: text(user.name) ?? "",
33229
+ ...user.commentIdentity !== void 0 ? { commentIdentity: user.commentIdentity ? { ...user.commentIdentity } : null } : {},
32815
33230
  ...text(user.id) ? { id: text(user.id) } : {},
32816
33231
  ...text(user.email) ? { email: text(user.email) } : {},
32817
33232
  ...text(user.initials) ? { initials: text(user.initials) } : {}
@@ -32823,10 +33238,7 @@ var SuperDoc = class extends import_eventemitter3.default {
32823
33238
  }
32824
33239
  #destroyed;
32825
33240
  #diagnostics;
32826
- #isUpgrading;
32827
33241
  #pendingUpgrade;
32828
- /** Aborts an in-flight upgrade (sync wait or ready wait). */
32829
- #abortUpgrade;
32830
33242
  /**
32831
33243
  * Unsubscribe handle for the v2 presence → `awareness-update` bridge. Set
32832
33244
  * while a v2 collaborative editor is mounted and torn down on remount,
@@ -32840,6 +33252,7 @@ var SuperDoc = class extends import_eventemitter3.default {
32840
33252
  #v2ConnectionUnsub;
32841
33253
  /** Latest `collaboration-connection-change` payload, for late readers. */
32842
33254
  #v2ConnectionState;
33255
+ #v2ConnectionFailureReported;
32843
33256
  /** Whether the current collaboration room has completed a sync at least once. */
32844
33257
  #v2ConnectionHasSynced;
32845
33258
  /** Lock state captured before a local → v2 collaboration promotion remount. */
@@ -32935,13 +33348,12 @@ var SuperDoc = class extends import_eventemitter3.default {
32935
33348
  constructor(config) {
32936
33349
  super();
32937
33350
  this.#destroyed = false;
32938
- this.#isUpgrading = false;
32939
33351
  this.#pendingUpgrade = null;
32940
- this.#abortUpgrade = null;
32941
33352
  this.#v2AwarenessUnsub = null;
32942
33353
  this.#v2LockUnsub = null;
32943
33354
  this.#v2ConnectionUnsub = null;
32944
33355
  this.#v2ConnectionState = null;
33356
+ this.#v2ConnectionFailureReported = false;
32945
33357
  this.#v2ConnectionHasSynced = false;
32946
33358
  this.#pendingV2LockSeed = null;
32947
33359
  this.#mountWrapper = null;
@@ -33056,6 +33468,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33056
33468
  if (!config.selector) throw new Error("SuperDoc: selector is required");
33057
33469
  const container = typeof config.selector === "string" ? document.querySelector(config.selector) : config.selector;
33058
33470
  if (!(container instanceof HTMLElement)) throw new Error("SuperDoc: selector must be a valid CSS selector string or DOM element");
33471
+ const user = normalizeSuperDocUser(config.user);
33059
33472
  this.#diagnostics = createInteractionHistory(this, config, () => this.version, container);
33060
33473
  this.#surfaceManager = new SurfaceManager({ getModuleConfig: () => this.#surfacesConfig });
33061
33474
  this.#contentControlClickRoot = container;
@@ -33065,7 +33478,10 @@ var SuperDoc = class extends import_eventemitter3.default {
33065
33478
  container.addEventListener("keydown", this.#handleContentControlKeyDown, true);
33066
33479
  container.addEventListener("click", this.#handleContentControlClick, true);
33067
33480
  this.#onConfig("content-control:active-change", config.onContentControlActiveChange);
33068
- this.#init(config, container);
33481
+ this.#init({
33482
+ ...config,
33483
+ user
33484
+ }, container);
33069
33485
  this.#contentControlEventBridgeReady = true;
33070
33486
  if (this.listenerCount("content-control:active-change") > 0) this.ui;
33071
33487
  }
@@ -33118,7 +33534,6 @@ var SuperDoc = class extends import_eventemitter3.default {
33118
33534
  }
33119
33535
  this.editorVersion = 2;
33120
33536
  this.#validateLegacyConfig();
33121
- this.config.user = normalizeSuperDocUser(this.config.user);
33122
33537
  if (!this.config.layoutEngineOptions.virtualization) this.config.layoutEngineOptions.virtualization = {
33123
33538
  enabled: true,
33124
33539
  window: 5,
@@ -33144,7 +33559,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33144
33559
  }
33145
33560
  this.userColorMap = /* @__PURE__ */ new Map();
33146
33561
  this.colorIndex = 0;
33147
- this.version = "2.29.0";
33562
+ this.version = "2.30.0-next.19";
33148
33563
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
33149
33564
  this.superdocId = config.superdocId || v4();
33150
33565
  this.colors = this.config.colors ?? [];
@@ -33563,6 +33978,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33563
33978
  if (this.#v2ConnectionState?.documentId !== documentId) {
33564
33979
  this.#v2ConnectionState = null;
33565
33980
  this.#v2ConnectionHasSynced = false;
33981
+ this.#v2ConnectionFailureReported = false;
33566
33982
  }
33567
33983
  const publish = (snapshot) => {
33568
33984
  if (this.#destroyed) return;
@@ -33577,8 +33993,21 @@ var SuperDoc = class extends import_eventemitter3.default {
33577
33993
  superdoc: this
33578
33994
  };
33579
33995
  this.#v2ConnectionState = payload;
33580
- if (snapshot.state === "synced") this.#v2ConnectionHasSynced = true;
33581
- this.emit("collaboration-connection-change", payload);
33996
+ if (snapshot.state === "synced") {
33997
+ this.#v2ConnectionHasSynced = true;
33998
+ this.#v2ConnectionFailureReported = false;
33999
+ }
34000
+ const firstFailure = snapshot.state === "failed" && !this.#v2ConnectionFailureReported;
34001
+ if (firstFailure) this.#v2ConnectionFailureReported = true;
34002
+ const reportFailure = firstFailure && snapshot.detail !== "worker-transport-failed";
34003
+ try {
34004
+ this.emit("collaboration-connection-change", payload);
34005
+ } finally {
34006
+ if (reportFailure) {
34007
+ const exception = createCollaborationException("collaboration-connection-failed", documentId);
34008
+ if (exception) this.emit("exception", exception);
34009
+ }
34010
+ }
33582
34011
  };
33583
34012
  try {
33584
34013
  const initialSnapshot = connection.getSnapshot?.();
@@ -33691,41 +34120,43 @@ var SuperDoc = class extends import_eventemitter3.default {
33691
34120
  throw Object.assign(/* @__PURE__ */ new Error("SuperDoc: a different collaboration target is already upgrading"), { code: "collaboration-upgrade-target-conflict" });
33692
34121
  }
33693
34122
  const { target } = this.#validateUpgradePrerequisites(options);
33694
- this.#isUpgrading = true;
33695
- const operation = (async () => {
33696
- try {
33697
- const localSource = await this.#captureCurrentDocxSourceForUpgrade();
33698
- const rollback = this.#snapshotV2UpgradeState();
33699
- const lockSeed = this.#snapshotV2LockSeed();
33700
- this.#assertNotDestroyed();
33701
- try {
33702
- this.#pendingV2LockSeed = lockSeed;
33703
- const promotion = await this.#promoteSingleDocumentToV2Collaboration(target, localSource);
33704
- this.#finalizeSingleDocumentV2Collaboration(promotion, target);
33705
- this.#pendingV2LockSeed = null;
33706
- const attachedDocumentId = this.activeEditor?.options?.documentId;
33707
- const attachedRoomId = promotion.configDoc.v2Collaboration?.documentId;
33708
- if (typeof attachedDocumentId !== "string" || attachedDocumentId !== promotion.configDoc.id || typeof attachedRoomId !== "string" || attachedRoomId !== target.documentId) throw new Error("SuperDoc: collaboration upgrade did not attach the expected room and document");
33709
- return {
33710
- roomId: attachedRoomId,
33711
- documentId: attachedDocumentId
33712
- };
33713
- } catch (err) {
33714
- this.#rollbackV2UpgradeState(rollback);
33715
- throw err;
33716
- }
33717
- } finally {
33718
- this.#abortUpgrade = null;
33719
- this.#isUpgrading = false;
33720
- this.#pendingUpgrade = null;
33721
- }
33722
- })();
34123
+ let rejectOnDestroy = () => {};
34124
+ const destroyed = new Promise((_resolve, reject) => {
34125
+ rejectOnDestroy = reject;
34126
+ });
34127
+ const operation = Promise.race([this.#runUpgrade(target), destroyed]).finally(() => {
34128
+ this.#pendingUpgrade = null;
34129
+ });
33723
34130
  this.#pendingUpgrade = {
33724
34131
  target,
33725
- promise: operation
34132
+ promise: operation,
34133
+ rejectOnDestroy
33726
34134
  };
33727
34135
  return operation;
33728
34136
  }
34137
+ async #runUpgrade(target) {
34138
+ const localSource = await this.#captureCurrentDocxSourceForUpgrade();
34139
+ const rollback = this.#snapshotV2UpgradeState();
34140
+ const lockSeed = this.#snapshotV2LockSeed();
34141
+ this.#assertNotDestroyed();
34142
+ try {
34143
+ this.#pendingV2LockSeed = lockSeed;
34144
+ const promotion = await this.#promoteSingleDocumentToV2Collaboration(target, localSource);
34145
+ this.#assertNotDestroyed();
34146
+ this.#finalizeSingleDocumentV2Collaboration(promotion, target);
34147
+ this.#pendingV2LockSeed = null;
34148
+ const attachedDocumentId = this.activeEditor?.options?.documentId;
34149
+ const attachedRoomId = promotion.configDoc.v2Collaboration?.documentId;
34150
+ if (typeof attachedDocumentId !== "string" || attachedDocumentId !== promotion.configDoc.id || typeof attachedRoomId !== "string" || attachedRoomId !== target.documentId) throw new Error("SuperDoc: collaboration upgrade did not attach the expected room and document");
34151
+ return {
34152
+ roomId: attachedRoomId,
34153
+ documentId: attachedDocumentId
34154
+ };
34155
+ } catch (err) {
34156
+ if (!this.#destroyed) this.#rollbackV2UpgradeState(rollback);
34157
+ throw err;
34158
+ }
34159
+ }
33729
34160
  #sameUpgradeTarget(left, right) {
33730
34161
  if (left.token !== right.token) return false;
33731
34162
  const seen = /* @__PURE__ */ new WeakMap();
@@ -33790,7 +34221,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33790
34221
  * async waits inside upgradeToCollaboration().
33791
34222
  */
33792
34223
  #assertNotDestroyed() {
33793
- if (this.#destroyed) throw new Error("SuperDoc: instance was destroyed during upgrade");
34224
+ if (this.#destroyed) throw destroyedDuringUpgradeError();
33794
34225
  }
33795
34226
  /**
33796
34227
  * Return the superdoc store, throwing a clear lifecycle error if
@@ -33889,6 +34320,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33889
34320
  this.#writeStoreDocV2Collaboration(storeDoc, v2Collaboration);
33890
34321
  this.isCollaborative = true;
33891
34322
  await editor.upgradeToCollaboration(localSource, v2Collaboration);
34323
+ this.#assertNotDestroyed();
33892
34324
  configDoc.data = localSource;
33893
34325
  storeDoc.data = localSource;
33894
34326
  return {
@@ -33948,7 +34380,6 @@ var SuperDoc = class extends import_eventemitter3.default {
33948
34380
  */
33949
34381
  #validateUpgradePrerequisites(options) {
33950
34382
  if (this.#destroyed) throw new Error("SuperDoc: cannot upgrade a destroyed instance");
33951
- if (this.#isUpgrading) throw new Error("SuperDoc: upgrade already in progress");
33952
34383
  if (this.isCollaborative) throw new Error("SuperDoc: instance is already collaborative");
33953
34384
  const cfg = this.config;
33954
34385
  const docxDocs = cfg.documents.filter((d) => d.type === DOCX);
@@ -34003,10 +34434,15 @@ var SuperDoc = class extends import_eventemitter3.default {
34003
34434
  this.#requireReady("setUser");
34004
34435
  const name = typeof user?.name === "string" ? user.name.trim() : "";
34005
34436
  if (!name) return Promise.reject(/* @__PURE__ */ new TypeError("SuperDoc.setUser requires a non-empty user name."));
34006
- const next = normalizeSuperDocUser({
34007
- ...user,
34008
- name
34009
- });
34437
+ let next;
34438
+ try {
34439
+ next = normalizeSuperDocUser({
34440
+ ...user,
34441
+ name
34442
+ });
34443
+ } catch (error) {
34444
+ return Promise.reject(error);
34445
+ }
34010
34446
  const applied = this.#userSwitch.then(() => this.#applyUser(next));
34011
34447
  this.#userSwitch = applied.catch(() => {});
34012
34448
  return applied;
@@ -34020,14 +34456,15 @@ var SuperDoc = class extends import_eventemitter3.default {
34020
34456
  "email",
34021
34457
  "image",
34022
34458
  "initials"
34023
- ].every((key) => (current[key] ?? null) === (next[key] ?? null))) return;
34459
+ ].every((key) => (current[key] ?? null) === (next[key] ?? null)) && current.commentIdentity === void 0 === (next.commentIdentity === void 0) && (current.commentIdentity?.providerId ?? null) === (next.commentIdentity?.providerId ?? null) && (current.commentIdentity?.userId ?? null) === (next.commentIdentity?.userId ?? null)) return;
34024
34460
  const runtimes = this.#editorRuntimeRegistry.getAll();
34025
34461
  const results = await Promise.allSettled(runtimes.map((runtime) => runtime.setUser?.(runtimeUserOf(next))));
34026
34462
  const failure = results.find((result) => result.status === "rejected");
34027
34463
  if (failure) {
34028
- if (current) await Promise.allSettled(runtimes.filter((_, index) => results[index].status === "fulfilled").map((runtime) => runtime.setUser?.(runtimeUserOf(current))));
34464
+ if (current && !this.#destroyed) await Promise.allSettled(runtimes.filter((_, index) => results[index].status === "fulfilled").map((runtime) => runtime.setUser?.(runtimeUserOf(current))));
34029
34465
  throw failure.reason;
34030
34466
  }
34467
+ if (this.#destroyed) return;
34031
34468
  const target = this.config.user ?? (this.config.user = {});
34032
34469
  for (const key of Object.keys(target)) delete target[key];
34033
34470
  Object.assign(target, next);
@@ -35199,7 +35636,7 @@ var SuperDoc = class extends import_eventemitter3.default {
35199
35636
  const result = await activeEditor.replaceFile(replacementSource);
35200
35637
  const state = result && typeof result === "object" ? result.state : null;
35201
35638
  const confirmed = state === null || state === "review-ready" || state === "editing-ready";
35202
- if (confirmed) {
35639
+ if (confirmed && !this.#destroyed) {
35203
35640
  this.#replaceActiveDocumentData(activeEditor, replacementSource);
35204
35641
  this.emit("document-replaced", {
35205
35642
  editor: activeEditor,
@@ -35355,10 +35792,7 @@ var SuperDoc = class extends import_eventemitter3.default {
35355
35792
  this.#contentControlClickRoot = null;
35356
35793
  this.#contentControlActiveChangeUnsub?.();
35357
35794
  this.#contentControlActiveChangeUnsub = null;
35358
- if (this.#abortUpgrade) {
35359
- this.#abortUpgrade();
35360
- this.#abortUpgrade = null;
35361
- }
35795
+ this.#pendingUpgrade?.rejectOnDestroy(destroyedDuringUpgradeError());
35362
35796
  if (this.#surfaceManager) this.#surfaceManager.destroy();
35363
35797
  this.toolbar?.destroy?.();
35364
35798
  this.commentsList?.close();