superdoc 2.29.0 → 2.30.0-next.13

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 (131) 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-Dc8dQAMc.es.js → create-super-doc-ui--o8TcYYv.es.js} +6080 -5456
  11. package/dist/chunks/{create-super-doc-ui-L60GhVAX.cjs → create-super-doc-ui-CvK1JdZv.cjs} +3567 -2937
  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/contract/command-catalog.d.ts +1 -1
  19. package/dist/document-api/src/contract/operation-definitions.d.ts +76 -8
  20. package/dist/document-api/src/contract/operation-registry.d.ts +2 -2
  21. package/dist/document-api/src/contract/schemas.d.ts +7 -1
  22. package/dist/document-api/src/create/create.d.ts +0 -4
  23. package/dist/document-api/src/diff/diff.types.d.ts +6 -6
  24. package/dist/document-api/src/header-footers/header-footers.d.ts +1 -3
  25. package/dist/document-api/src/index.d.ts +13 -1
  26. package/dist/document-api/src/inline-semantics/token-sets.d.ts +0 -2
  27. package/dist/document-api/src/insert/insert.d.ts +0 -1
  28. package/dist/document-api/src/paragraphs/paragraphs.d.ts +8 -3
  29. package/dist/document-api/src/paragraphs/paragraphs.types.d.ts +49 -5
  30. package/dist/document-api/src/tables/data/table-preset-appearance.generated.d.ts +2 -0
  31. package/dist/document-api/src/tables/table-style-presets.d.ts +73 -0
  32. package/dist/document-api/src/tables/tables.d.ts +4 -1
  33. package/dist/document-api/src/types/sd-props.d.ts +7 -0
  34. package/dist/document-api/src/types/table-operations.types.d.ts +12 -15
  35. package/dist/layout-engine/contracts/src/index.d.ts +4 -1
  36. package/dist/layout-engine/contracts/src/table-cell-vector-overlay.d.ts +2 -0
  37. package/dist/layout-engine/dom-contract/src/data-attrs.d.ts +4 -0
  38. package/dist/layout-engine/layout-bridge/src/coupled-footnote-pagination.d.ts +1 -0
  39. package/dist/layout-engine/layout-bridge/src/footnote-page-certificate.d.ts +1 -0
  40. package/dist/layout-engine/layout-bridge/src/footnote-page-planner.d.ts +5 -0
  41. package/dist/layout-engine/layout-bridge/src/incrementalLayout.d.ts +1 -0
  42. package/dist/layout-engine/layout-engine/src/footnote-page-flow.d.ts +3 -0
  43. package/dist/layout-engine/layout-engine/src/index.d.ts +7 -0
  44. package/dist/layout-engine/layout-engine/src/layout-paragraph.d.ts +5 -0
  45. package/dist/layout-engine/layout-engine/src/layout-table.d.ts +3 -1
  46. package/dist/layout-engine/layout-engine/src/layout-utils.d.ts +0 -4
  47. package/dist/layout-engine/layout-engine/src/paginator.d.ts +4 -0
  48. package/dist/layout-engine/painters/dom/src/index.d.ts +2 -0
  49. package/dist/layout-engine/painters/dom/src/paragraph/list-marker.d.ts +1 -1
  50. package/dist/layout-engine/painters/dom/src/paragraph/renderParagraphContent.d.ts +1 -1
  51. package/dist/layout-engine/painters/dom/src/styles.d.ts +0 -10
  52. package/dist/layout-engine/painters/dom/src/svg-utils.d.ts +1 -13
  53. package/dist/public/ui-react.cjs +1 -1
  54. package/dist/public/ui-react.es.js +1 -1
  55. package/dist/public/ui-vue.cjs +1 -1
  56. package/dist/public/ui-vue.es.js +1 -1
  57. package/dist/public/ui.cjs +1 -1
  58. package/dist/public/ui.es.js +1 -1
  59. package/dist/shared/common/Telemetry.d.ts +50 -0
  60. package/dist/shared/common/event-types.d.ts +13 -0
  61. package/dist/shared/font-system/src/bundled-face-names.d.ts +1 -0
  62. package/dist/shared/font-system/src/resolver.d.ts +1 -0
  63. package/dist/style.css +1 -1
  64. package/dist/style.layered.css +1 -1
  65. package/dist/superdoc/src/components/CommentsLayer/CommentDialog.vue.d.ts +17 -33
  66. package/dist/superdoc/src/components/CommentsLayer/collect-tracked-change-thread.d.ts +6 -4
  67. package/dist/superdoc/src/components/CommentsLayer/comment-params.d.ts +84 -0
  68. package/dist/superdoc/src/components/CommentsLayer/use-comment.d.ts +15 -95
  69. package/dist/superdoc/src/components/Whiteboard/use-whiteboard.d.ts +30 -9
  70. package/dist/superdoc/src/composables/use-comment-sidebar.d.ts +6 -0
  71. package/dist/superdoc/src/composables/use-document.d.ts +51 -99
  72. package/dist/superdoc/src/core/SuperDoc.d.ts +12 -3
  73. package/dist/superdoc/src/core/collaboration/cdn-collaboration-comment-map.d.ts +1 -0
  74. package/dist/superdoc/src/core/collaboration/collaboration-comment-map.d.ts +2 -0
  75. package/dist/superdoc/src/core/collaboration/helpers.d.ts +1 -1
  76. package/dist/superdoc/src/core/config/normalize-interaction-config.d.ts +10 -1
  77. package/dist/superdoc/src/core/document-open-telemetry.d.ts +1 -1
  78. package/dist/superdoc/src/core/editor-runtime/types.d.ts +4 -0
  79. package/dist/superdoc/src/core/editor-runtime/v2/v2-editor-runtime-adapter.d.ts +1 -0
  80. package/dist/superdoc/src/core/types/index.d.ts +110 -39
  81. package/dist/superdoc/src/helpers/deprecation.d.ts +12 -3
  82. package/dist/superdoc/src/helpers/object-guards.d.ts +1 -0
  83. package/dist/superdoc/src/helpers/v2-review-geometry-delivery.d.ts +22 -0
  84. package/dist/superdoc/src/internal/toolbar/built-in/ToolbarButton.vue.d.ts +1 -0
  85. package/dist/superdoc/src/public/content-control-ref.d.ts +12 -0
  86. package/dist/superdoc/src/public/index.d.cts +20 -0
  87. package/dist/superdoc/src/public/index.d.ts +4 -1
  88. package/dist/superdoc/src/public/ui/async-read-cache.d.ts +32 -0
  89. package/dist/superdoc/src/public/ui/block-commands.d.ts +39 -0
  90. package/dist/superdoc/src/public/ui/command-results.d.ts +39 -0
  91. package/dist/superdoc/src/public/ui/commands.d.ts +5 -1
  92. package/dist/superdoc/src/public/ui/create-super-doc-ui.d.ts +1 -17
  93. package/dist/superdoc/src/public/ui/document-change-records.d.ts +7 -0
  94. package/dist/superdoc/src/public/ui/entity-rows.d.ts +14 -0
  95. package/dist/superdoc/src/public/ui/font-options.d.ts +7 -0
  96. package/dist/superdoc/src/public/ui/format-painter-paragraph.d.ts +7 -0
  97. package/dist/superdoc/src/public/ui/format-painter-state.d.ts +29 -0
  98. package/dist/superdoc/src/public/ui/heavy-read-policy.d.ts +11 -0
  99. package/dist/superdoc/src/public/ui/host-events.d.ts +3 -0
  100. package/dist/superdoc/src/public/ui/inline-values.d.ts +80 -0
  101. package/dist/superdoc/src/public/ui/loose-record.d.ts +4 -0
  102. package/dist/superdoc/src/public/ui/reasons.d.ts +1 -0
  103. package/dist/superdoc/src/public/ui/selection-targets.d.ts +53 -0
  104. package/dist/superdoc/src/public/ui/shared-tracked-changes-catalog.d.ts +16 -0
  105. package/dist/superdoc/src/public/ui/tracked-change-ids.d.ts +51 -0
  106. package/dist/superdoc/src/public/ui/types.d.ts +25 -2
  107. package/dist/superdoc/src/public/ui/ui-bench-timing.d.ts +9 -0
  108. package/dist/superdoc/src/public/ui.d.cts +18 -0
  109. package/dist/superdoc/src/public/ui.d.ts +1 -1
  110. package/dist/superdoc/src/stores/comments-store.d.ts +23022 -546
  111. package/dist/superdoc/src/stores/helpers/comment-grouping.d.ts +1 -1
  112. package/dist/superdoc/src/stores/helpers/comment-identity.d.ts +10 -10
  113. package/dist/superdoc/src/stores/helpers/review-presentation.d.ts +91 -0
  114. package/dist/superdoc/src/stores/helpers/tracked-change-identity.d.ts +4 -2
  115. package/dist/superdoc/src/stores/helpers/tracked-change-row-values.d.ts +1 -8
  116. package/dist/superdoc/src/stores/helpers/tracked-change-thread-index.d.ts +1 -1
  117. package/dist/superdoc/src/stores/helpers/v2-review-adapters.d.ts +147 -0
  118. package/dist/superdoc/src/stores/superdoc-store.d.ts +12 -2895
  119. package/dist/superdoc.cjs +915 -516
  120. package/dist/superdoc.es.js +915 -516
  121. package/dist/word-layout/src/marker-utils.d.ts +0 -5
  122. package/dist/word-layout/src/unit-conversions.d.ts +0 -1
  123. package/dist-cdn/style.layered.css +1 -1
  124. package/dist-cdn/superdoc.min.css +1 -1
  125. package/dist-cdn/superdoc.min.js +38 -42
  126. package/package.json +12 -3
  127. package/dist/document-api/src/diff/index.d.ts +0 -3
  128. package/dist/document-api/src/write/locator.d.ts +0 -24
  129. package/dist/layout-engine/painters/dom/src/features/feature-registry.d.ts +0 -41
  130. package/dist/superdoc/src/internal/toolbar/built-in/format-negation.d.ts +0 -11
  131. 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--o8TcYYv.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.13";
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(() => {
@@ -7907,29 +8090,28 @@ var useCommentsStore = defineStore("comments", () => {
7907
8090
  });
7908
8091
  const v2CommentsAdapter = shallowRef$1(null);
7909
8092
  const setV2CommentsAdapter = (adapter) => {
7910
- if (v2CommentsAdapter.value !== (adapter ?? null)) pendingCommentRequest += 1;
7911
- v2CommentsAdapter.value = adapter ?? null;
8093
+ const next = isV2CommentsAdapter(adapter) ? adapter : null;
8094
+ if (v2CommentsAdapter.value !== next) pendingCommentRequest += 1;
8095
+ v2CommentsAdapter.value = next;
7912
8096
  };
7913
8097
  const getV2CommentsAdapter = (superdoc) => {
7914
- const fromFacade = superdoc?.activeEditor?.v2Comments ?? null;
7915
- if (fromFacade) return fromFacade;
8098
+ const fromFacade = superdoc?.activeEditor?.v2Comments;
8099
+ if (isV2CommentsAdapter(fromFacade)) return fromFacade;
7916
8100
  return v2CommentsAdapter.value;
7917
8101
  };
7918
8102
  const isV2EditorActive = (superdoc) => superdoc?.activeEditor?.editorVersion === 2 || v2CommentsAdapter.value !== null;
7919
8103
  const v2TrackedChangesAdapter = shallowRef$1(null);
7920
8104
  const setV2TrackedChangesAdapter = (adapter) => {
7921
- v2TrackedChangesAdapter.value = adapter ?? null;
8105
+ cachedTrackedChangeBatchIdentityIndex = null;
8106
+ v2TrackedChangesAdapter.value = isV2TrackedChangesAdapter(adapter) ? adapter : null;
7922
8107
  };
7923
8108
  const getV2TrackedChangesAdapter = (superdoc) => {
7924
- const fromFacade = superdoc?.activeEditor?.v2TrackedChanges ?? null;
7925
- if (fromFacade) return fromFacade;
8109
+ const fromFacade = superdoc?.activeEditor?.v2TrackedChanges;
8110
+ if (isV2TrackedChangesAdapter(fromFacade)) return fromFacade;
7926
8111
  return v2TrackedChangesAdapter.value;
7927
8112
  };
7928
8113
  /**
7929
8114
  * Initialize the store
7930
- *
7931
- * @param {Object} config The comments module config from SuperDoc
7932
- * @returns {void}
7933
8115
  */
7934
8116
  const init = (config = {}) => {
7935
8117
  const updatedConfig = {
@@ -7961,12 +8143,17 @@ var useCommentsStore = defineStore("comments", () => {
7961
8143
  comment.trackedChangeDecision = null;
7962
8144
  };
7963
8145
  const getCommentEventPayload = (comment) => typeof comment?.getValues === "function" ? comment.getValues() : { ...comment };
8146
+ const getDeletedCommentChanges = (sources, fileId) => {
8147
+ const commentId = [...sources.map((source) => source?.commentId), ...sources.map((source) => source?.importedId)].map(normalizeCommentId).find(Boolean);
8148
+ return commentId ? [{
8149
+ key: "deleted",
8150
+ commentId,
8151
+ fileId
8152
+ }] : [];
8153
+ };
7964
8154
  /**
7965
8155
  * Check if a comment originated from the document editor (or has no explicit source).
7966
8156
  * 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
8157
  */
7971
8158
  const isEditorBackedComment = (comment) => {
7972
8159
  const source = comment?.selection?.source;
@@ -8000,9 +8187,6 @@ var useCommentsStore = defineStore("comments", () => {
8000
8187
  }, { deep: false });
8001
8188
  /**
8002
8189
  * 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
8190
  */
8007
8191
  const getCommentPosition = (commentOrId) => {
8008
8192
  return resolveCommentPositionEntry(commentOrId).entry ?? null;
@@ -8015,10 +8199,6 @@ var useCommentsStore = defineStore("comments", () => {
8015
8199
  };
8016
8200
  /**
8017
8201
  * 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
8202
  */
8023
8203
  const setActiveComment = (superdoc, id) => {
8024
8204
  const activeEditor = superdoc?.activeEditor;
@@ -8029,7 +8209,7 @@ var useCommentsStore = defineStore("comments", () => {
8029
8209
  v2Adapter.setActiveComment(null);
8030
8210
  return;
8031
8211
  }
8032
- activeEditor?.commands?.setActiveComment({ commentId: null });
8212
+ activeEditor?.commands?.setActiveComment?.({ commentId: null });
8033
8213
  return;
8034
8214
  }
8035
8215
  const comment = getComment(id);
@@ -8040,18 +8220,13 @@ var useCommentsStore = defineStore("comments", () => {
8040
8220
  v2Adapter.setActiveComment(comment.commentId);
8041
8221
  return;
8042
8222
  }
8043
- activeEditor?.commands?.setActiveComment({ commentId: activeComment.value });
8223
+ activeEditor?.commands?.setActiveComment?.({ commentId: activeComment.value });
8044
8224
  };
8045
8225
  /**
8046
8226
  * Called when a tracked change is updated. Creates a new comment if necessary,
8047
8227
  * 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
8228
  */
8054
- const handleTrackedChangeUpdate = ({ superdoc, params, broadcastChanges = true, trackedChangeIdentityIndex = null }) => {
8229
+ const handleTrackedChangeUpdate = ({ superdoc, params, broadcastChanges = true, trackedChangeIdentityIndex = null, preserveDraft = false }) => {
8055
8230
  const span = startInteractionSpan("store.trackedChanges.handleUpdate", "store-reconciliation", {
8056
8231
  event: params?.event ?? null,
8057
8232
  changeId: params?.changeId ?? null,
@@ -8076,9 +8251,11 @@ var useCommentsStore = defineStore("comments", () => {
8076
8251
  const normalizedTrackedChangeLabel = typeof trackedChangeLabel === "string" && trackedChangeLabel.length > 0 ? trackedChangeLabel : null;
8077
8252
  const normalizedTrackedChangeDetailLines = normalizeTrackedChangeDetailLines(trackedChangeDetailLines);
8078
8253
  const normalizedTrackedChangeImagePreview = normalizeTrackedChangeImagePreview(trackedChangeImagePreview);
8254
+ const hasDraft = preserveDraft && (pendingComment.value !== null || currentCommentText.value !== "" || currentCommentMentions.value.length > 0);
8079
8255
  const comment = getPendingComment({
8080
8256
  documentId,
8081
- commentId: changeId,
8257
+ ...hasDraft ? { commentText: "" } : {},
8258
+ commentId: normalizedChangeId ?? void 0,
8082
8259
  trackedChange: true,
8083
8260
  trackedChangeText,
8084
8261
  trackedChangeType,
@@ -8130,7 +8307,7 @@ var useCommentsStore = defineStore("comments", () => {
8130
8307
  commentCanonicalId,
8131
8308
  commentAnchorKey,
8132
8309
  ...commentAliasIds
8133
- ].filter(Boolean).some((id) => incomingAliasIds.has(id))) return true;
8310
+ ].some((id) => id != null && incomingAliasIds.has(id))) return true;
8134
8311
  const hasSyntheticSideIdentity = normalizedTrackedChangeCanonicalId && normalizedTrackedChangeCanonicalId !== normalizedChangeId || commentCanonicalId && commentCanonicalId !== commentId;
8135
8312
  if (hasCompatibleStory && commentId === normalizedChangeId) return true;
8136
8313
  if (hasCompatibleStory && !hasSyntheticSideIdentity && (importedId === normalizedChangeId || normalizedImportedId !== void 0 && normalizedImportedId != null && importedId === normalizedImportedId)) return true;
@@ -8154,7 +8331,7 @@ var useCommentsStore = defineStore("comments", () => {
8154
8331
  };
8155
8332
  const emitTrackedChangeEvent = (event) => {
8156
8333
  if (!broadcastChanges) return;
8157
- syncCommentsToClients(superdoc, event);
8334
+ syncCommentsToClients(requireHost(superdoc, "handleTrackedChangeUpdate"), event);
8158
8335
  debounceEmit(changeId, event, superdoc);
8159
8336
  };
8160
8337
  const setIfChanged = (target, key, value) => {
@@ -8197,8 +8374,9 @@ var useCommentsStore = defineStore("comments", () => {
8197
8374
  target?.trackedChangeCanonicalId,
8198
8375
  target?.trackedChangeAnchorKey,
8199
8376
  ...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;
8377
+ ].map((id) => normalizeCommentId(id)).filter(isPresent));
8378
+ const incomingAliases = normalizedTrackedChangePositionAliases ?? [];
8379
+ fields.trackedChangePositionAliases = incomingAliases.some((alias) => targetIdentityIds.has(alias)) ? normalizeTrackedChangePositionAliases([...Array.isArray(target?.trackedChangePositionAliases) ? target.trackedChangePositionAliases : [], ...incomingAliases]) : incomingAliases;
8202
8380
  }
8203
8381
  let didChange = false;
8204
8382
  for (const [key, value] of Object.entries(fields)) didChange = setIfChanged(target, key, value) || didChange;
@@ -8207,7 +8385,7 @@ var useCommentsStore = defineStore("comments", () => {
8207
8385
  didChange = true;
8208
8386
  }
8209
8387
  if (hasCustomAttributes && target && !trackedChangeCustomAttributesEqual(target.customAttributes, normalizedCustomAttributes)) {
8210
- target.customAttributes = normalizedCustomAttributes;
8388
+ target.customAttributes = normalizedCustomAttributes ?? null;
8211
8389
  didChange = true;
8212
8390
  }
8213
8391
  return applyStoryMetadata(target) || didChange;
@@ -8229,9 +8407,10 @@ var useCommentsStore = defineStore("comments", () => {
8229
8407
  return;
8230
8408
  }
8231
8409
  addHydratedComment({
8232
- superdoc,
8410
+ superdoc: requireHost(superdoc, "handleTrackedChangeUpdate"),
8233
8411
  comment,
8234
- broadcastChanges
8412
+ broadcastChanges,
8413
+ preserveDraft: hasDraft
8235
8414
  });
8236
8415
  trackedChangeIdentityIndex?.add(comment);
8237
8416
  } else if (event === "update") {
@@ -8249,11 +8428,12 @@ var useCommentsStore = defineStore("comments", () => {
8249
8428
  id: params.resolvedById ?? superdoc?.user?.id ?? null,
8250
8429
  email: params.resolvedByEmail ?? superdoc?.user?.email ?? null,
8251
8430
  name: params.resolvedByName ?? superdoc?.user?.name ?? null,
8252
- decision: params.decision ?? null,
8431
+ decision: params.decision ?? void 0,
8253
8432
  superdoc
8254
8433
  };
8255
8434
  if (existingTrackedChange && !existingTrackedChange.resolvedTime) existingTrackedChange.resolveComment({
8256
8435
  ...resolveArgs,
8436
+ superdoc: requireHost(superdoc, "handleTrackedChangeUpdate"),
8257
8437
  reconciliationToken: COMMENT_RECONCILIATION_TOKEN
8258
8438
  });
8259
8439
  }
@@ -8278,10 +8458,11 @@ var useCommentsStore = defineStore("comments", () => {
8278
8458
  instantSidebarAlignmentInstanceId.value = null;
8279
8459
  };
8280
8460
  const debounceEmit = (commentId, event, superdoc, delay = 1e3) => {
8281
- if (debounceTimers[commentId]) clearTimeout(debounceTimers[commentId]);
8282
- debounceTimers[commentId] = setTimeout(() => {
8461
+ const timerKey = String(commentId);
8462
+ if (debounceTimers[timerKey]) clearTimeout(debounceTimers[timerKey]);
8463
+ debounceTimers[timerKey] = setTimeout(() => {
8283
8464
  if (superdoc) superdoc.emit("comments-update", event);
8284
- delete debounceTimers[commentId];
8465
+ delete debounceTimers[timerKey];
8285
8466
  }, delay);
8286
8467
  };
8287
8468
  const showAddComment = (superdoc, targetClientY = null) => {
@@ -8293,7 +8474,7 @@ var useCommentsStore = defineStore("comments", () => {
8293
8474
  if (!ownsRequest()) return;
8294
8475
  superdoc.emit("comments-update", {
8295
8476
  type: COMMENT_EVENTS.PENDING,
8296
- pendingSelection: superdoc?.activeEditor === editor ? snapshot : null
8477
+ pendingSelection: superdoc?.activeEditor === editor && isPlainObject$1(snapshot) ? snapshot : null
8297
8478
  });
8298
8479
  };
8299
8480
  if (v2Adapter) {
@@ -8338,12 +8519,11 @@ var useCommentsStore = defineStore("comments", () => {
8338
8519
  }).catch((err) => ({
8339
8520
  ok: false,
8340
8521
  reason: "selection-capture-failed",
8341
- detail: err?.message ?? String(err)
8522
+ detail: errorDetail(err)
8342
8523
  }));
8343
8524
  }
8344
8525
  const pendingSelection = getPendingSelectionSnapshot(superdoc, superdocStore.activeSelection);
8345
- const asyncSnapshot = typeof pendingSelection?.then === "function";
8346
- if (!asyncSnapshot) emitPending(pendingSelection);
8526
+ if (!isThenable$1(pendingSelection)) emitPending(pendingSelection);
8347
8527
  const showLegacyPending = (selection) => {
8348
8528
  selection.selectionBounds = { ...selection.selectionBounds };
8349
8529
  if (superdocStore.selectionPosition?.source && superdocStore.selectionPosition.source !== "pdf") superdocStore.selectionPosition.source = null;
@@ -8353,7 +8533,7 @@ var useCommentsStore = defineStore("comments", () => {
8353
8533
  parentCommentId: null
8354
8534
  });
8355
8535
  if (!superdoc.config.isInternal) pendingComment.value.isInternal = false;
8356
- if (superdoc.activeEditor?.commands) superdoc.activeEditor.commands.insertComment({
8536
+ if (superdoc.activeEditor?.commands) superdoc.activeEditor.commands.insertComment?.({
8357
8537
  ...pendingComment.value.getValues(),
8358
8538
  commentId: "pending",
8359
8539
  skipEmit: true
@@ -8364,10 +8544,10 @@ var useCommentsStore = defineStore("comments", () => {
8364
8544
  activeComment.value = pendingComment.value.commentId;
8365
8545
  return { ok: true };
8366
8546
  };
8367
- if (asyncSnapshot) {
8547
+ if (isThenable$1(pendingSelection)) {
8368
8548
  const selection = { ...superdocStore.activeSelection };
8369
8549
  selection.selectionBounds = { ...selection.selectionBounds };
8370
- return pendingSelection.then((snapshot) => {
8550
+ return Promise.resolve(pendingSelection).then((snapshot) => {
8371
8551
  if (!ownsRequest()) return {
8372
8552
  ok: false,
8373
8553
  reason: "pending-comment-superseded"
@@ -8399,12 +8579,6 @@ var useCommentsStore = defineStore("comments", () => {
8399
8579
  });
8400
8580
  /**
8401
8581
  * 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
8582
  */
8409
8583
  const getPendingComment = ({ selection, documentId, parentCommentId, ...options }) => {
8410
8584
  return _getNewcomment({
@@ -8416,17 +8590,11 @@ var useCommentsStore = defineStore("comments", () => {
8416
8590
  };
8417
8591
  /**
8418
8592
  * 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
8593
  */
8426
8594
  const _getNewcomment = ({ selection, documentId, parentCommentId, ...options }) => {
8427
8595
  let activeDocument;
8428
- if (documentId) activeDocument = superdocStore.getDocument(documentId);
8429
- else if (selection) activeDocument = superdocStore.getDocument(selection.documentId);
8596
+ if (documentId) activeDocument = findDocument(documentId);
8597
+ else if (selection) activeDocument = findDocument(selection.documentId);
8430
8598
  if (!activeDocument) activeDocument = superdocStore.documents[0];
8431
8599
  return useComment({
8432
8600
  fileId: activeDocument.id,
@@ -8443,8 +8611,6 @@ var useCommentsStore = defineStore("comments", () => {
8443
8611
  };
8444
8612
  /**
8445
8613
  * Remove the pending comment
8446
- *
8447
- * @returns {void}
8448
8614
  */
8449
8615
  const resetPendingCommentState = (superdoc, clearViewingSelection = true) => {
8450
8616
  const hadPending = !!pendingComment.value;
@@ -8460,7 +8626,7 @@ var useCommentsStore = defineStore("comments", () => {
8460
8626
  } catch {}
8461
8627
  return;
8462
8628
  }
8463
- superdoc?.activeEditor?.commands?.removeComment({ commentId: "pending" });
8629
+ superdoc?.activeEditor?.commands?.removeComment?.({ commentId: "pending" });
8464
8630
  };
8465
8631
  const removePendingComment = (superdoc) => {
8466
8632
  pendingCommentRequest += 1;
@@ -8473,16 +8639,8 @@ var useCommentsStore = defineStore("comments", () => {
8473
8639
  };
8474
8640
  /**
8475
8641
  * 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
8642
  */
8485
- const addCommentInternal = ({ superdoc, comment, skipEditorUpdate = false, broadcastChanges = true, isHydration = false }) => {
8643
+ const addCommentInternal = ({ superdoc, comment, skipEditorUpdate = false, broadcastChanges = true, isHydration = false, preserveDraft = false }) => {
8486
8644
  if (commentsAreReadOnly() && !isHydration) return readOnlyMutationOutcome();
8487
8645
  const v2Adapter = !skipEditorUpdate && !isHydration ? getV2CommentsAdapter(superdoc) : null;
8488
8646
  if (v2Adapter && !comment.trackedChange) {
@@ -8510,7 +8668,7 @@ var useCommentsStore = defineStore("comments", () => {
8510
8668
  return {
8511
8669
  ok: false,
8512
8670
  reason: "adapter-threw",
8513
- detail: err?.message ?? String(err)
8671
+ detail: errorDetail(err)
8514
8672
  };
8515
8673
  }
8516
8674
  })().then((outcome) => {
@@ -8561,28 +8719,30 @@ var useCommentsStore = defineStore("comments", () => {
8561
8719
  };
8562
8720
  });
8563
8721
  }
8564
- let parentComment = commentsList.value.find((c) => c.commentId === activeComment.value);
8722
+ const activeId = activeComment.value;
8723
+ const parent = toRaw$2(commentsList.value).find((c) => unref(c.commentId) === activeId);
8724
+ let parentComment = parent ? reactive(parent) : void 0;
8565
8725
  if (!parentComment) parentComment = comment;
8566
8726
  const newComment = useComment(comment.getValues());
8567
- if (pendingComment.value) newComment.setText({
8568
- text: currentCommentText.value,
8569
- suppressUpdate: true
8570
- });
8571
- else newComment.setText({
8572
- text: comment.commentText,
8727
+ const text = pendingComment.value && !preserveDraft ? currentCommentText.value : comment.commentText;
8728
+ if (text === "") {
8729
+ newComment.commentText = "";
8730
+ newComment.mentions = [];
8731
+ } else newComment.setText({
8732
+ text,
8573
8733
  suppressUpdate: true
8574
8734
  });
8575
- newComment.selection.source = pendingComment.value?.selection?.source ?? newComment.selection.source;
8735
+ if (!preserveDraft) newComment.selection.source = pendingComment.value?.selection?.source ?? newComment.selection.source;
8576
8736
  if (parentComment) newComment.isInternal = parentComment.isInternal;
8577
8737
  if (!superdoc.config.isInternal) newComment.isInternal = false;
8578
8738
  commentsList.value.push(newComment);
8579
- const hadPendingComment = !!pendingComment.value;
8580
- removePendingComment(superdoc);
8739
+ const hadPendingComment = !preserveDraft && !!pendingComment.value;
8740
+ if (!preserveDraft) removePendingComment(superdoc);
8581
8741
  if (hadPendingComment) {
8582
8742
  activeComment.value = newComment.commentId;
8583
8743
  setActiveFloatingCommentInstance(newComment.commentId);
8584
8744
  }
8585
- if (!skipEditorUpdate && !comment.trackedChange && superdoc.activeEditor?.commands && !comment.parentCommentId) superdoc.activeEditor.commands.insertComment({
8745
+ if (!skipEditorUpdate && !comment.trackedChange && superdoc.activeEditor?.commands && !comment.parentCommentId) superdoc.activeEditor.commands.insertComment?.({
8586
8746
  ...newComment.getValues(),
8587
8747
  skipEmit: true
8588
8748
  });
@@ -8605,12 +8765,13 @@ var useCommentsStore = defineStore("comments", () => {
8605
8765
  skipEditorUpdate,
8606
8766
  broadcastChanges
8607
8767
  });
8608
- const addHydratedComment = ({ superdoc, comment, skipEditorUpdate = false, broadcastChanges = true }) => addCommentInternal({
8768
+ const addHydratedComment = ({ superdoc, comment, skipEditorUpdate = false, broadcastChanges = true, preserveDraft = false }) => addCommentInternal({
8609
8769
  superdoc,
8610
8770
  comment,
8611
8771
  skipEditorUpdate,
8612
8772
  broadcastChanges,
8613
- isHydration: true
8773
+ isHydration: true,
8774
+ preserveDraft
8614
8775
  });
8615
8776
  const deleteComment = ({ commentId: commentIdToDelete, superdoc }) => {
8616
8777
  if (commentsAreReadOnly()) return Promise.resolve(readOnlyMutationOutcome());
@@ -8630,7 +8791,7 @@ var useCommentsStore = defineStore("comments", () => {
8630
8791
  return {
8631
8792
  ok: false,
8632
8793
  reason: "adapter-threw",
8633
- detail: err?.message ?? String(err)
8794
+ detail: errorDetail(err)
8634
8795
  };
8635
8796
  }
8636
8797
  })().then((outcome) => {
@@ -8650,16 +8811,16 @@ var useCommentsStore = defineStore("comments", () => {
8650
8811
  });
8651
8812
  return outcome;
8652
8813
  }
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));
8814
+ const removedIds = new Set((Array.isArray(outcome.removedCommentIds) ? outcome.removedCommentIds : []).map(normalizeCommentId).filter(isPresent));
8815
+ if (removedIds.has(normalizeCommentId(commentId) ?? "")) {
8816
+ if (importedId != null) removedIds.add(String(importedId));
8656
8817
  let addedChild = true;
8657
8818
  while (addedChild) {
8658
8819
  addedChild = false;
8659
8820
  for (const row of commentsList.value) {
8660
8821
  const parentId = normalizeCommentId(row?.parentCommentId);
8661
8822
  if (!parentId || !removedIds.has(parentId)) continue;
8662
- for (const alias of [row?.commentId, row?.importedId].map(normalizeCommentId).filter(Boolean)) {
8823
+ for (const alias of [row?.commentId, row?.importedId].map(normalizeCommentId).filter(isPresent)) {
8663
8824
  if (removedIds.has(alias)) continue;
8664
8825
  removedIds.add(alias);
8665
8826
  addedChild = true;
@@ -8667,9 +8828,9 @@ var useCommentsStore = defineStore("comments", () => {
8667
8828
  }
8668
8829
  }
8669
8830
  commentsList.value = commentsList.value.filter((row) => {
8670
- return ![row?.commentId, row?.importedId].map(normalizeCommentId).filter(Boolean).some((alias) => removedIds.has(alias));
8831
+ return ![row?.commentId, row?.importedId].map(normalizeCommentId).filter(isPresent).some((alias) => removedIds.has(alias));
8671
8832
  });
8672
- if (removedIds.has(normalizeCommentId(activeComment.value))) clearActiveCommentSelection();
8833
+ if (removedIds.has(normalizeCommentId(activeComment.value) ?? "")) clearActiveCommentSelection();
8673
8834
  } else reconcileCommentsFromV2({
8674
8835
  superdoc,
8675
8836
  adapter: v2Adapter,
@@ -8677,18 +8838,18 @@ var useCommentsStore = defineStore("comments", () => {
8677
8838
  items: outcome.items ?? [],
8678
8839
  pruneStale: outcome.complete !== false
8679
8840
  });
8841
+ const payload = comment.getValues();
8680
8842
  superdoc.emit("comments-update", {
8681
8843
  type: COMMENT_EVENTS.DELETED,
8682
- comment: comment.getValues(),
8683
- changes: [{
8684
- key: "deleted",
8844
+ comment: payload,
8845
+ changes: getDeletedCommentChanges([{
8685
8846
  commentId,
8686
- fileId
8687
- }]
8847
+ importedId
8848
+ }, payload], fileId)
8688
8849
  });
8689
8850
  return { ok: true };
8690
8851
  });
8691
- superdoc.activeEditor?.commands?.removeComment({
8852
+ superdoc.activeEditor?.commands?.removeComment?.({
8692
8853
  commentId,
8693
8854
  importedId
8694
8855
  });
@@ -8696,14 +8857,14 @@ var useCommentsStore = defineStore("comments", () => {
8696
8857
  const childCommentIds = commentsList.value.filter((c) => c.parentCommentId === commentId).map((c) => c.commentId || c.importedId);
8697
8858
  commentsList.value = commentsList.value.filter((c) => !childCommentIds.includes(c.commentId));
8698
8859
  if (activeComment.value === commentId || childCommentIds.includes(activeComment.value)) clearActiveCommentSelection();
8860
+ const payload = comment.getValues();
8699
8861
  const event = {
8700
8862
  type: COMMENT_EVENTS.DELETED,
8701
- comment: comment.getValues(),
8702
- changes: [{
8703
- key: "deleted",
8863
+ comment: payload,
8864
+ changes: getDeletedCommentChanges([{
8704
8865
  commentId,
8705
- fileId
8706
- }]
8866
+ importedId
8867
+ }, payload], fileId)
8707
8868
  };
8708
8869
  superdoc.emit("comments-update", event);
8709
8870
  syncCommentsToClients(superdoc, event);
@@ -8715,7 +8876,7 @@ var useCommentsStore = defineStore("comments", () => {
8715
8876
  * otherwise keep the source bubble after the document comment is gone.
8716
8877
  */
8717
8878
  const dropCommentsFromMutationImpact = ({ superdoc, documentId, removedIds } = {}) => {
8718
- const seedIds = new Set([...removedIds instanceof Set ? removedIds : removedIds ?? []].map((id) => normalizeCommentId(id)).filter(Boolean));
8879
+ const seedIds = new Set([...removedIds instanceof Set ? removedIds : removedIds ?? []].map((id) => normalizeCommentId(id)).filter(isPresent));
8719
8880
  if (!seedIds.size) return {
8720
8881
  ok: true,
8721
8882
  removedIds: []
@@ -8739,7 +8900,7 @@ var useCommentsStore = defineStore("comments", () => {
8739
8900
  if (!isDroppableCommentRow(row)) continue;
8740
8901
  const aliases = getCommentAliasIds(row);
8741
8902
  const alreadyDropped = aliases.some((alias) => droppedIds.has(alias));
8742
- const parentDropped = droppedIds.has(normalizeCommentId(row?.parentCommentId)) || droppedIds.has(normalizeCommentId(row?.threadingParentCommentId));
8903
+ const parentDropped = droppedIds.has(normalizeCommentId(row?.parentCommentId) ?? "") || droppedIds.has(normalizeCommentId(row?.threadingParentCommentId) ?? "");
8743
8904
  if (!alreadyDropped && !parentDropped) continue;
8744
8905
  for (const alias of aliases) {
8745
8906
  if (droppedIds.has(alias)) continue;
@@ -8767,11 +8928,7 @@ var useCommentsStore = defineStore("comments", () => {
8767
8928
  const event = {
8768
8929
  type: COMMENT_EVENTS.DELETED,
8769
8930
  comment: payload,
8770
- changes: [{
8771
- key: "deleted",
8772
- commentId: payload?.commentId,
8773
- fileId: payload?.fileId
8774
- }]
8931
+ changes: getDeletedCommentChanges([payload, comment], payload?.fileId)
8775
8932
  };
8776
8933
  superdoc?.emit?.("comments-update", event);
8777
8934
  });
@@ -8793,7 +8950,7 @@ var useCommentsStore = defineStore("comments", () => {
8793
8950
  const byAlias = /* @__PURE__ */ new Map();
8794
8951
  const childrenByParentAlias = /* @__PURE__ */ new Map();
8795
8952
  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);
8953
+ for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(isPresent)) if (!byAlias.has(alias)) byAlias.set(alias, row);
8797
8954
  const parentId = normalizeCommentId(row.parentCommentId);
8798
8955
  if (parentId) {
8799
8956
  const children = childrenByParentAlias.get(parentId) ?? [];
@@ -8820,7 +8977,7 @@ var useCommentsStore = defineStore("comments", () => {
8820
8977
  if (!row || visited.has(row)) continue;
8821
8978
  visited.add(row);
8822
8979
  family.push(row);
8823
- for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(Boolean)) queue.push(...childrenByParentAlias.get(alias) ?? []);
8980
+ for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(isPresent)) queue.push(...childrenByParentAlias.get(alias) ?? []);
8824
8981
  }
8825
8982
  const isResolved = status === "resolved";
8826
8983
  const resolvedAt = Date.now();
@@ -8843,7 +9000,7 @@ var useCommentsStore = defineStore("comments", () => {
8843
9000
  outcome = {
8844
9001
  ok: false,
8845
9002
  reason: "adapter-threw",
8846
- detail: err?.message ?? String(err)
9003
+ detail: errorDetail(err)
8847
9004
  };
8848
9005
  }
8849
9006
  if (!isCurrentV2CommentsAdapter(adapter)) return {
@@ -8915,14 +9072,14 @@ var useCommentsStore = defineStore("comments", () => {
8915
9072
  return (outcome.items ?? []).map((item) => adapter.mapV2CommentToUseCommentInput(item, {
8916
9073
  fileId,
8917
9074
  fileType: null
8918
- })).filter(Boolean);
9075
+ })).filter(isPresent);
8919
9076
  };
8920
9077
  const isBlankDirectoryCommentText = (value) => typeof value !== "string" || value.replace(/<[^>]*>/g, "").replace(/&nbsp;/gi, " ").trim().length === 0;
8921
9078
  const trackedChangeDirectoryComment = (params) => {
8922
9079
  if (!params || params.event === "omit" || params.changeId == null) return null;
8923
9080
  return getPendingComment({
8924
9081
  documentId: params.documentId,
8925
- commentId: params.changeId,
9082
+ commentId: String(params.changeId),
8926
9083
  trackedChange: true,
8927
9084
  trackedChangeText: params.trackedChangeText,
8928
9085
  trackedChangeType: params.trackedChangeType,
@@ -8969,7 +9126,7 @@ var useCommentsStore = defineStore("comments", () => {
8969
9126
  const mappedCommentInputs = commentItems.filter((item) => !isSyntheticTrackedChangeCommentLaneItem(item)).map((item) => commentsAdapter.mapV2CommentToUseCommentInput(item, {
8970
9127
  fileId: documentId,
8971
9128
  fileType: document?.type ?? null
8972
- })).filter(Boolean);
9129
+ })).filter(isPresent);
8973
9130
  const trackedParentBySidecarId = /* @__PURE__ */ new Map();
8974
9131
  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
9132
  const commentRows = mappedCommentInputs.filter((input) => !trackedParentBySidecarId.has(String(input.commentId))).map((input) => {
@@ -8980,7 +9137,7 @@ var useCommentsStore = defineStore("comments", () => {
8980
9137
  }
8981
9138
  return useComment(input);
8982
9139
  });
8983
- const trackedRows = trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).map((params) => trackedChangeDirectoryComment(params)).filter(Boolean);
9140
+ const trackedRows = trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).map((params) => trackedChangeDirectoryComment(params)).filter(isPresent);
8984
9141
  reviewDirectoryList.value = [...commentRows, ...trackedRows];
8985
9142
  return {
8986
9143
  ok: true,
@@ -9061,13 +9218,6 @@ var useCommentsStore = defineStore("comments", () => {
9061
9218
  * - successful mutation refreshes from v2 list and reconciles
9062
9219
  * - rejection preserves rows and emits a rejected `comments-update` event
9063
9220
  * - 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
9221
  */
9072
9222
  const replyCommentV2 = async ({ superdoc, parentCommentId, text, mentions = [], trackedChangeTarget } = {}) => {
9073
9223
  if (commentsAreReadOnly()) return readOnlyMutationOutcome();
@@ -9342,10 +9492,15 @@ var useCommentsStore = defineStore("comments", () => {
9342
9492
  row?.threadingParentCommentId,
9343
9493
  row?.trackedChangeParentId,
9344
9494
  row?.trackedChangeThreadParentId
9345
- ].map((value) => normalizeCommentId(value)).filter(Boolean));
9495
+ ].map((value) => normalizeCommentId(value)).filter(isPresent));
9346
9496
  /** Keep one mounted review window plus rows whose interaction is still live. */
9347
9497
  const boundReviewPresentationRows = ({ documentId, visibleIds }) => {
9348
9498
  const currentRows = commentsList.value;
9499
+ if (currentRows.every((row) => {
9500
+ if (!belongsToTrackedChangeSyncDocument(row, documentId)) return true;
9501
+ const id = normalizeCommentId(row.commentId);
9502
+ return Boolean(id && visibleIds.has(id));
9503
+ })) return;
9349
9504
  const identityIdsByRow = /* @__PURE__ */ new Map();
9350
9505
  const identityIdsFor = (row) => {
9351
9506
  let ids = identityIdsByRow.get(row);
@@ -9363,7 +9518,7 @@ var useCommentsStore = defineStore("comments", () => {
9363
9518
  pendingComment.value?.commentId,
9364
9519
  instantSidebarAlignmentThreadId.value,
9365
9520
  instantSidebarAlignmentInstanceId.value
9366
- ].map((value) => normalizeCommentId(value)).filter(Boolean));
9521
+ ].map((value) => normalizeCommentId(value)).filter(isPresent));
9367
9522
  let changed = true;
9368
9523
  while (changed) {
9369
9524
  changed = false;
@@ -9431,7 +9586,8 @@ var useCommentsStore = defineStore("comments", () => {
9431
9586
  if (preparedReview?.reuseCatalog === true && committedReviewPresentations.value[normalizedDocumentId]) {
9432
9587
  const previous = committedReviewPresentations.value[normalizedDocumentId];
9433
9588
  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 {
9589
+ const catalogVersion = preparedReview.catalogVersion ?? NaN;
9590
+ 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
9591
  ok: false,
9436
9592
  reason: "review-catalog-stale"
9437
9593
  };
@@ -9477,8 +9633,8 @@ var useCommentsStore = defineStore("comments", () => {
9477
9633
  previousPresentation.updateContext = presentation.updateContext;
9478
9634
  return {
9479
9635
  ok: true,
9480
- commentItems: preparedReview.comments.length,
9481
- trackedItems: preparedReview.trackedChanges.length
9636
+ commentItems: preparedReview?.comments.length,
9637
+ trackedItems: preparedReview?.trackedChanges.length
9482
9638
  };
9483
9639
  }
9484
9640
  let effectiveCommentItems = commentItems;
@@ -9492,7 +9648,7 @@ var useCommentsStore = defineStore("comments", () => {
9492
9648
  reason: seeded?.reason ?? "comment-catalog-seed-failed"
9493
9649
  };
9494
9650
  const selected = commentsAdapter.selectVisibleReviewComments?.();
9495
- if (selected?.ok === true && Array.isArray(selected.items) && (!Array.isArray(selected.unresolvedIds) || selected.unresolvedIds.length === 0)) effectiveCommentItems = selected.items;
9651
+ if (!isThenable$1(selected) && selected?.ok === true && Array.isArray(selected.items) && (!Array.isArray(selected.unresolvedIds) || selected.unresolvedIds.length === 0)) effectiveCommentItems = selected.items;
9496
9652
  }
9497
9653
  const fileType = (normalizedDocumentId ? superdocStore.getDocument(normalizedDocumentId) : null)?.type ?? null;
9498
9654
  let preparedInputs;
@@ -9509,16 +9665,16 @@ var useCommentsStore = defineStore("comments", () => {
9509
9665
  })) : effectiveCommentItems.filter((item) => !isSyntheticTrackedChangeCommentLaneItem(item)).map((item) => commentsAdapter.mapV2CommentToUseCommentInput(item, {
9510
9666
  fileId: normalizedDocumentId,
9511
9667
  fileType
9512
- })).filter(Boolean);
9668
+ })).filter(isPresent);
9513
9669
  preparedParams = preparedReview ? preparedReview.trackedChanges.map((params) => ({
9514
9670
  ...params,
9515
9671
  documentId: normalizedDocumentId
9516
- })) : trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).filter(Boolean);
9672
+ })) : trackedChangeItems.map((item) => trackedChangesAdapter.mapV2TrackedChangeToCommentParams(item)).filter(isPresent);
9517
9673
  } catch (error) {
9518
9674
  return {
9519
9675
  ok: false,
9520
9676
  reason: "mapper-failed",
9521
- detail: error?.message ?? String(error)
9677
+ detail: errorDetail(error)
9522
9678
  };
9523
9679
  }
9524
9680
  if (typeof patch !== "function") return {
@@ -9546,7 +9702,7 @@ var useCommentsStore = defineStore("comments", () => {
9546
9702
  params?.trackedChangeCanonicalId,
9547
9703
  params?.trackedChangeAnchorKey
9548
9704
  ])
9549
- ].map((value) => normalizeCommentId(value)).filter(Boolean));
9705
+ ].map((value) => normalizeCommentId(value)).filter(isPresent));
9550
9706
  patch(() => {
9551
9707
  const previousPreparedPresentation = committedReviewPresentations.value[normalizedDocumentId] ?? null;
9552
9708
  if (preparedReview?.presentation) committedReviewPresentations.value = {
@@ -9561,7 +9717,8 @@ var useCommentsStore = defineStore("comments", () => {
9561
9717
  preparedParams,
9562
9718
  preparedPresentation: preparedReview?.presentation ?? null,
9563
9719
  previousPreparedPresentation,
9564
- pruneStale: false
9720
+ pruneStale: false,
9721
+ preserveDraft: true
9565
9722
  });
9566
9723
  reconcileCommentsFromV2({
9567
9724
  superdoc,
@@ -9594,7 +9751,7 @@ var useCommentsStore = defineStore("comments", () => {
9594
9751
  };
9595
9752
  const effectiveDocumentId = documentId ?? adapter.documentId ?? null;
9596
9753
  const normalizedEffectiveDocumentId = effectiveDocumentId != null ? String(effectiveDocumentId) : null;
9597
- const fileType = (effectiveDocumentId ? superdocStore.getDocument(effectiveDocumentId) : null)?.type ?? null;
9754
+ const fileType = (effectiveDocumentId ? findDocument(effectiveDocumentId) : null)?.type ?? null;
9598
9755
  const isBlankV2CommentText = (value) => {
9599
9756
  if (typeof value !== "string") return true;
9600
9757
  return value.replace(/<[^>]*>/g, "").replace(/&nbsp;/gi, " ").trim().length === 0;
@@ -9614,7 +9771,7 @@ var useCommentsStore = defineStore("comments", () => {
9614
9771
  const candidateInputs = Array.isArray(preparedInputs) ? preparedInputs : items.filter((item) => !isSyntheticTrackedChangeCommentLaneItem(item)).map((item) => adapter.mapV2CommentToUseCommentInput(item, {
9615
9772
  fileId: effectiveDocumentId,
9616
9773
  fileType
9617
- })).filter(Boolean);
9774
+ })).filter(isPresent);
9618
9775
  for (const input of candidateInputs) {
9619
9776
  if (isV2TrackedChangeSidecarCommentInput(input)) {
9620
9777
  trackedChangeSidecarParentByCommentId.set(String(input.commentId), String(input.trackedChangeThreadParentId));
@@ -9667,7 +9824,7 @@ var useCommentsStore = defineStore("comments", () => {
9667
9824
  existing.mentions = Array.isArray(input.mentions) ? input.mentions : [];
9668
9825
  existing.isInternal = typeof input.isInternal === "boolean" ? input.isInternal : existing.isInternal;
9669
9826
  if (typeof input.resolvedTime === "number") {
9670
- existing.resolvedTime = input.resolvedTimeWasSynthesized === true && typeof existing.resolvedTime === "number" ? existing.resolvedTime : input.resolvedTime;
9827
+ existing.resolvedTime = input.resolvedTimeWasSynthesized === true && typeof existing.resolvedTime === "number" ? existing.resolvedTime : input.resolvedTime ?? null;
9671
9828
  existing.resolvedByEmail = input.resolvedByEmail ?? null;
9672
9829
  existing.resolvedByName = input.resolvedByName ?? null;
9673
9830
  } else {
@@ -9741,7 +9898,7 @@ var useCommentsStore = defineStore("comments", () => {
9741
9898
  return {
9742
9899
  ok: false,
9743
9900
  reason: "comment-read-failed",
9744
- detail: err?.message ?? String(err)
9901
+ detail: errorDetail(err)
9745
9902
  };
9746
9903
  }
9747
9904
  if (!isCurrentV2CommentsAdapter(adapter)) return {
@@ -9767,8 +9924,6 @@ var useCommentsStore = defineStore("comments", () => {
9767
9924
  };
9768
9925
  /**
9769
9926
  * Cancel the pending comment
9770
- *
9771
- * @returns {void}
9772
9927
  */
9773
9928
  const cancelComment = (superdoc) => {
9774
9929
  removePendingComment(superdoc);
@@ -9778,9 +9933,6 @@ var useCommentsStore = defineStore("comments", () => {
9778
9933
  *
9779
9934
  * Replace-file swaps call this before hydrating the replacement so stale
9780
9935
  * comments never survive long enough to render beside the new content.
9781
- *
9782
- * @param {string | null | undefined} documentId
9783
- * @returns {void}
9784
9936
  */
9785
9937
  function removeCommentsForDocument(documentId) {
9786
9938
  const activeDocumentId = documentId != null ? String(documentId) : null;
@@ -9811,20 +9963,16 @@ var useCommentsStore = defineStore("comments", () => {
9811
9963
  const remapTrackedChangeReferences = (droppedCommentIds, activeDocumentId) => {
9812
9964
  const remapSelection = (selection) => {
9813
9965
  const selectedId = normalizeCommentId(selection.value);
9814
- if (selectedId && droppedCommentIds.has(selectedId)) selection.value = droppedCommentIds.get(selectedId);
9966
+ const replacementId = selectedId ? droppedCommentIds.get(selectedId) : void 0;
9967
+ if (replacementId) selection.value = replacementId;
9815
9968
  };
9816
9969
  remapSelection(activeComment);
9817
9970
  remapSelection(activeFloatingCommentInstanceId);
9818
9971
  commentsList.value.forEach((row) => {
9819
9972
  if (!belongsToTrackedChangeSyncDocument(row, activeDocumentId)) return;
9820
9973
  const rowIdentityUpdates = {};
9821
- for (const field of [
9822
- "trackedChangeThreadParentId",
9823
- "trackedChangeParentId",
9824
- "threadingParentCommentId",
9825
- "parentCommentId"
9826
- ]) {
9827
- const replacementId = droppedCommentIds.get(normalizeCommentId(row?.[field]));
9974
+ for (const field of PARENT_LINK_FIELDS) {
9975
+ const replacementId = droppedCommentIds.get(normalizeCommentId(row?.[field]) ?? "");
9828
9976
  if (!replacementId) continue;
9829
9977
  row[field] = replacementId;
9830
9978
  rowIdentityUpdates[field] = replacementId;
@@ -9845,10 +9993,6 @@ var useCommentsStore = defineStore("comments", () => {
9845
9993
  *
9846
9994
  * Pruning is scoped to the active editor document so replay in one document does not
9847
9995
  * 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
9996
  */
9853
9997
  const pruneStaleTrackedChangeComments = (liveTrackedChangeIds, liveTrackedChangeAnchorKeys, activeDocumentId, superdoc = null, { broadcastChanges = true } = {}) => withInteractionSpan("store.trackedChanges.pruneStaleComments", "store-reconciliation", {
9854
9998
  activeDocumentId: activeDocumentId ?? null,
@@ -9893,7 +10037,7 @@ var useCommentsStore = defineStore("comments", () => {
9893
10037
  comment: payload
9894
10038
  };
9895
10039
  if (broadcastChanges) {
9896
- syncCommentsToClients(superdoc, event);
10040
+ syncCommentsToClients(requireHost(superdoc, "pruneStaleTrackedChangeComments"), event);
9897
10041
  superdoc?.emit?.("comments-update", event);
9898
10042
  }
9899
10043
  });
@@ -9925,14 +10069,10 @@ var useCommentsStore = defineStore("comments", () => {
9925
10069
  const event = {
9926
10070
  type: COMMENT_EVENTS.DELETED,
9927
10071
  comment: payload,
9928
- changes: [{
9929
- key: "deleted",
9930
- commentId: payload.commentId,
9931
- fileId: payload.fileId
9932
- }]
10072
+ changes: getDeletedCommentChanges([payload, comment], payload.fileId)
9933
10073
  };
9934
10074
  if (broadcastChanges) {
9935
- syncCommentsToClients(superdoc, event);
10075
+ syncCommentsToClients(requireHost(superdoc, "pruneStaleTrackedChangeComments"), event);
9936
10076
  superdoc?.emit?.("comments-update", event);
9937
10077
  }
9938
10078
  });
@@ -9955,7 +10095,6 @@ var useCommentsStore = defineStore("comments", () => {
9955
10095
  const resolveArgs = {
9956
10096
  email: superdoc?.user?.email ?? null,
9957
10097
  name: superdoc?.user?.name ?? null,
9958
- superdoc,
9959
10098
  reconciliationToken: COMMENT_RECONCILIATION_TOKEN
9960
10099
  };
9961
10100
  let resolvedCount = 0;
@@ -9966,9 +10105,12 @@ var useCommentsStore = defineStore("comments", () => {
9966
10105
  trackedChangeThreadParentIdForComment(linkedComment),
9967
10106
  linkedComment.threadingParentCommentId,
9968
10107
  linkedComment.parentCommentId
9969
- ].map((id) => normalizeCommentId(id)).filter(Boolean).some((id) => trackedChangeIds.has(id))) return;
10108
+ ].map((id) => normalizeCommentId(id)).filter(isPresent).some((id) => trackedChangeIds.has(id))) return;
9970
10109
  if (typeof linkedComment.resolveComment === "function") {
9971
- linkedComment.resolveComment(resolveArgs);
10110
+ linkedComment.resolveComment({
10111
+ ...resolveArgs,
10112
+ superdoc: requireHost(superdoc, "resolveLinkedCommentsForTrackedChangeDecision")
10113
+ });
9972
10114
  resolvedCount += 1;
9973
10115
  }
9974
10116
  });
@@ -10065,7 +10207,7 @@ var useCommentsStore = defineStore("comments", () => {
10065
10207
  if (previousCommentId) anchorSourceIds.add(previousCommentId);
10066
10208
  if (previousCanonicalId) anchorSourceIds.add(previousCanonicalId);
10067
10209
  }
10068
- if (comment.trackedChangeAnchorKey == null || anchorSourceIds.has(currentAnchorParts?.rawId)) comment.trackedChangeAnchorKey = toAnchor;
10210
+ if (comment.trackedChangeAnchorKey == null || anchorSourceIds.has(currentAnchorParts?.rawId ?? "")) comment.trackedChangeAnchorKey = toAnchor;
10069
10211
  comment.trackedChangePositionAliases = normalizeTrackedChangePositionAliases([...Array.isArray(comment.trackedChangePositionAliases) ? comment.trackedChangePositionAliases : [], fromId]);
10070
10212
  const identitySourceIds = /* @__PURE__ */ new Set([fromId]);
10071
10213
  if (advancesRemapLineage) {
@@ -10076,33 +10218,28 @@ var useCommentsStore = defineStore("comments", () => {
10076
10218
  if (row === comment) return;
10077
10219
  if (!belongsToTrackedChangeSyncDocument(row, normalizedDocumentId)) return;
10078
10220
  const rowIdentityUpdates = {};
10079
- for (const field of [
10080
- "trackedChangeThreadParentId",
10081
- "trackedChangeParentId",
10082
- "threadingParentCommentId",
10083
- "parentCommentId"
10084
- ]) {
10085
- if (!identitySourceIds.has(normalizeCommentId(row?.[field]))) continue;
10221
+ for (const field of PARENT_LINK_FIELDS) {
10222
+ if (!identitySourceIds.has(normalizeCommentId(row?.[field]) ?? "")) continue;
10086
10223
  row[field] = toId;
10087
10224
  rowIdentityUpdates[field] = toId;
10088
10225
  }
10089
10226
  if (Object.keys(rowIdentityUpdates).length && typeof row.updateIdentityValues === "function") row.updateIdentityValues(rowIdentityUpdates);
10090
10227
  });
10091
10228
  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));
10229
+ const previousEntry = previousPositionEntry ?? [positions[fromId], positions[String(buildBodyTrackedChangeAnchorKey(fromId))]].find((entry) => entry && positionEntryMatchesTrackedChangeStory(entry, comment));
10093
10230
  if (previousEntry) {
10094
- for (const key of [toId, toAnchor]) if (positions[key] === void 0) {
10231
+ for (const key of [toId, String(toAnchor)]) if (positions[key] === void 0) {
10095
10232
  positions[key] = previousEntry;
10096
10233
  positionsChanged = true;
10097
10234
  }
10098
10235
  }
10099
- if (identitySourceIds.has(normalizeCommentId(activeComment.value))) activeComment.value = toId;
10100
- if (identitySourceIds.has(normalizeCommentId(activeFloatingCommentInstanceId.value))) activeFloatingCommentInstanceId.value = toId;
10236
+ if (identitySourceIds.has(normalizeCommentId(activeComment.value) ?? "")) activeComment.value = toId;
10237
+ if (identitySourceIds.has(normalizeCommentId(activeFloatingCommentInstanceId.value) ?? "")) activeFloatingCommentInstanceId.value = toId;
10101
10238
  }
10102
10239
  if (positionsChanged) editorCommentPositions.value = { ...positions };
10103
10240
  };
10104
10241
  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));
10242
+ const normalizedDecidedIds = new Set((Array.isArray(decidedIds) || decidedIds instanceof Set ? Array.from(decidedIds) : [decidedId]).map((id) => normalizeCommentId(id)).filter(isPresent));
10106
10243
  const normalizedDocumentId = normalizeCommentId(documentId);
10107
10244
  if (!normalizedDecidedIds.size || !normalizedDocumentId) return {
10108
10245
  removed: false,
@@ -10162,12 +10299,6 @@ var useCommentsStore = defineStore("comments", () => {
10162
10299
  };
10163
10300
  /**
10164
10301
  * 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
10302
  */
10172
10303
  const decideTrackedChangeFromSidebar = ({ superdoc, comment, decision }) => {
10173
10304
  const disabledReason = trackedChangeDecisionDisabledReason(superdoc);
@@ -10195,7 +10326,7 @@ var useCommentsStore = defineStore("comments", () => {
10195
10326
  return {
10196
10327
  ok: false,
10197
10328
  reason: "adapter-threw",
10198
- detail: err?.message ?? String(err)
10329
+ detail: errorDetail(err)
10199
10330
  };
10200
10331
  }
10201
10332
  })().then(async (outcome) => {
@@ -10248,9 +10379,11 @@ var useCommentsStore = defineStore("comments", () => {
10248
10379
  if (!activeEditor) return { ok: false };
10249
10380
  const id = comment.commentId ?? comment.importedId;
10250
10381
  if (!id) return { ok: false };
10251
- const story = comment.trackedChangeStory ?? void 0;
10382
+ const rawStory = comment.trackedChangeStory ?? void 0;
10383
+ const story = isStoryLocator(rawStory) ? rawStory : void 0;
10252
10384
  const documentApi = typeof activeEditor.doc === "object" ? activeEditor.doc : null;
10253
10385
  if (documentApi?.trackChanges?.decide) try {
10386
+ if (rawStory && !story) throw new TypeError("trackedChangeStory must be a valid StoryLocator");
10254
10387
  const target = story ? {
10255
10388
  id,
10256
10389
  story
@@ -10263,7 +10396,7 @@ var useCommentsStore = defineStore("comments", () => {
10263
10396
  success: Boolean(receipt?.success)
10264
10397
  }));
10265
10398
  } catch (error) {
10266
- if (story) return {
10399
+ if (rawStory) return {
10267
10400
  ok: false,
10268
10401
  error
10269
10402
  };
@@ -10310,9 +10443,9 @@ var useCommentsStore = defineStore("comments", () => {
10310
10443
  documentId: effectiveDocumentId
10311
10444
  })).removed
10312
10445
  };
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));
10446
+ const normalizedRemovedIds = new Set(Array.from(removedIds ?? []).map((id) => normalizeCommentId(id)).filter(isPresent));
10447
+ const normalizedUpsertIds = [...new Set(Array.from(upsertIds ?? []).map((id) => normalizeCommentId(id)).filter(isPresent))];
10448
+ const remappedSourceIds = new Set((Array.isArray(remappedPairs) ? remappedPairs : []).map((pair) => normalizeCommentId(pair?.from)).filter(isPresent));
10316
10449
  const locallyRemovedIds = new Set([...normalizedRemovedIds].filter((id) => !remappedSourceIds.has(id)));
10317
10450
  if (locallyRemovedIds.size > 0 && effectiveDocumentId != null) pruneDecidedTrackedChangeRow({
10318
10451
  superdoc,
@@ -10331,6 +10464,10 @@ var useCommentsStore = defineStore("comments", () => {
10331
10464
  const byIdentityAlias = /* @__PURE__ */ new Map();
10332
10465
  const aliasesByRow = /* @__PURE__ */ new Map();
10333
10466
  const indexedRows = /* @__PURE__ */ new Set();
10467
+ const indexedIdentities = [];
10468
+ let building = true;
10469
+ let reusable = true;
10470
+ let reused = false;
10334
10471
  let aliasLookups = 0;
10335
10472
  let candidateVisits = 0;
10336
10473
  let rowAliasBuilds = 0;
@@ -10354,6 +10491,7 @@ var useCommentsStore = defineStore("comments", () => {
10354
10491
  index.set(alias, bucket);
10355
10492
  };
10356
10493
  const add = (comment) => {
10494
+ if (!building) reusable = false;
10357
10495
  if (!comment?.trackedChange) return;
10358
10496
  if (documentId && !belongsToTrackedChangeSyncDocument(comment, documentId)) return;
10359
10497
  indexedRows.add(comment);
@@ -10365,19 +10503,49 @@ var useCommentsStore = defineStore("comments", () => {
10365
10503
  rowAliasBuilds += 1;
10366
10504
  aliasesByRow.set(comment, aliases);
10367
10505
  aliases.forEach((alias) => addAlias(byIdentityAlias, alias, comment));
10506
+ if (building) indexedIdentities.push({
10507
+ row: comment,
10508
+ aliases,
10509
+ canonicalId: normalizeCommentId(comment.trackedChangeCanonicalId)
10510
+ });
10368
10511
  };
10369
10512
  commentsList.value.forEach(add);
10513
+ building = false;
10370
10514
  return {
10515
+ inUse: false,
10371
10516
  add,
10372
10517
  aliasesFor,
10373
- work: () => ({
10374
- trackedRowsIndexed: indexedRows.size,
10375
- invalidatedIdMembershipChecks: 0,
10376
- incomingAliasLookups: aliasLookups,
10377
- candidateVisits,
10378
- rowAliasBuilds,
10379
- rowAliasCacheHits
10380
- }),
10518
+ isCurrent: () => {
10519
+ if (!reusable) return false;
10520
+ let index = 0;
10521
+ for (const row of commentsList.value) {
10522
+ if (!row?.trackedChange || documentId && !belongsToTrackedChangeSyncDocument(row, documentId)) continue;
10523
+ const previous = indexedIdentities[index++];
10524
+ if (!previous || previous.row !== row || previous.canonicalId !== normalizeCommentId(row.trackedChangeCanonicalId)) return false;
10525
+ const aliases = getCommentAliasIds(row);
10526
+ rowAliasBuilds += 1;
10527
+ if (aliases.length !== previous.aliases.length || aliases.some((alias, i) => alias !== previous.aliases[i])) return false;
10528
+ }
10529
+ reused = index === indexedIdentities.length;
10530
+ return reused;
10531
+ },
10532
+ work: () => {
10533
+ const result = {
10534
+ trackedRowsIndexed: indexedRows.size,
10535
+ invalidatedIdMembershipChecks: 0,
10536
+ incomingAliasLookups: aliasLookups,
10537
+ candidateVisits,
10538
+ rowAliasBuilds,
10539
+ rowAliasCacheHits,
10540
+ indexReused: Number(reused)
10541
+ };
10542
+ aliasLookups = 0;
10543
+ candidateVisits = 0;
10544
+ rowAliasBuilds = 0;
10545
+ rowAliasCacheHits = 0;
10546
+ reused = false;
10547
+ return result;
10548
+ },
10381
10549
  candidates({ changeId, importedId, anchorKey, canonicalId, positionAliases = [] } = {}) {
10382
10550
  const out = /* @__PURE__ */ new Set();
10383
10551
  const include = (value) => {
@@ -10400,7 +10568,30 @@ var useCommentsStore = defineStore("comments", () => {
10400
10568
  }
10401
10569
  };
10402
10570
  };
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", {
10571
+ const getTrackedChangeBatchIdentityIndex = (documentId) => {
10572
+ const cached = cachedTrackedChangeBatchIdentityIndex;
10573
+ if (cached?.documentId === documentId && !cached.index.inUse) {
10574
+ cached.index.inUse = true;
10575
+ let current = false;
10576
+ try {
10577
+ current = cached.index.isCurrent() && cachedTrackedChangeBatchIdentityIndex === cached;
10578
+ } finally {
10579
+ if (!current) {
10580
+ cached.index.inUse = false;
10581
+ if (cachedTrackedChangeBatchIdentityIndex === cached) cachedTrackedChangeBatchIdentityIndex = null;
10582
+ }
10583
+ }
10584
+ if (current) return cached.index;
10585
+ }
10586
+ const index = createTrackedChangeBatchIdentityIndex(documentId);
10587
+ index.inUse = true;
10588
+ cachedTrackedChangeBatchIdentityIndex = {
10589
+ documentId,
10590
+ index
10591
+ };
10592
+ return index;
10593
+ };
10594
+ 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
10595
  documentId: documentId ?? null,
10405
10596
  itemCount: Array.isArray(items) ? items.length : null,
10406
10597
  pruneStale,
@@ -10412,33 +10603,36 @@ var useCommentsStore = defineStore("comments", () => {
10412
10603
  const liveAnchorKeys = suppliedLiveAnchorKeys instanceof Set ? suppliedLiveAnchorKeys : /* @__PURE__ */ new Set();
10413
10604
  const liveIds = suppliedLiveIds instanceof Set ? suppliedLiveIds : /* @__PURE__ */ new Set();
10414
10605
  let appliedCount = 0;
10415
- const trackedChangeIdentityIndex = createTrackedChangeBatchIdentityIndex(effectiveDocumentId == null ? null : String(effectiveDocumentId));
10606
+ const trackedChangeIdentityIndex = getTrackedChangeBatchIdentityIndex(effectiveDocumentId == null ? null : String(effectiveDocumentId));
10416
10607
  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;
10608
+ let enteredBatch = false;
10421
10609
  try {
10610
+ const paramsList = Array.isArray(preparedParams) ? preparedParams : items.map((item) => adapter.mapV2TrackedChangeToCommentParams(item));
10611
+ const previousCoordinate = previousPreparedPresentation?.coordinate;
10612
+ const coordinate = preparedPresentation?.coordinate;
10613
+ 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;
10614
+ enteredBatch = true;
10422
10615
  for (const params of paramsList) {
10423
10616
  if (!params) continue;
10424
10617
  let retainUnchangedRow = false;
10618
+ const anchorParts = parseTrackedChangeAnchorKey(params.trackedChangeAnchorKey);
10619
+ const anchorStoryKey = anchorParts ? normalizeTrackedChangeStoryKey(anchorParts.storyKey) : void 0;
10425
10620
  if (preparedPresentation) {
10426
10621
  if (preparedPresentation !== committedReviewPresentations.value[String(effectiveDocumentId)]) continue;
10427
10622
  if (params.documentId != null && String(params.documentId) !== String(effectiveDocumentId)) continue;
10428
10623
  const id = normalizeCommentId(params.changeId);
10429
- const version = preparedPresentation.rowVersions?.[id];
10624
+ const version = preparedPresentation.rowVersions?.[String(id)] ?? NaN;
10430
10625
  if (!id || !Number.isSafeInteger(version) || version < 1) continue;
10431
10626
  const existing = [...trackedChangeIdentityIndex.candidates({ changeId: id })].filter((row) => String(row.fileId) === String(effectiveDocumentId) && String(row.commentId) === id);
10432
10627
  if (existing.length > 1) continue;
10433
10628
  const row = existing[0];
10434
10629
  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;
10630
+ const previousVersion = previousPreparedPresentation?.rowVersions?.[id] ?? NaN;
10631
+ 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
10632
  row.trackedChangeCanonicalId = params.trackedChangeCanonicalId;
10438
- row.updateIdentityValues?.({ trackedChangeCanonicalId: params.trackedChangeCanonicalId });
10439
10633
  trackedChangeIdentityIndex.add(row);
10440
10634
  }
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;
10635
+ 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
10636
  }
10443
10637
  if (params.event === "omit") {
10444
10638
  if (params.changeId != null) liveIds.add(String(params.changeId));
@@ -10452,12 +10646,15 @@ var useCommentsStore = defineStore("comments", () => {
10452
10646
  superdoc,
10453
10647
  params,
10454
10648
  broadcastChanges: false,
10455
- trackedChangeIdentityIndex
10649
+ trackedChangeIdentityIndex,
10650
+ preserveDraft
10456
10651
  });
10457
10652
  appliedCount += 1;
10458
10653
  }
10459
10654
  } finally {
10460
- endInteractionSpan(identitySpan, trackedChangeIdentityIndex.work());
10655
+ const work = trackedChangeIdentityIndex.work();
10656
+ trackedChangeIdentityIndex.inUse = false;
10657
+ if (enteredBatch) endInteractionSpan(identitySpan, work);
10461
10658
  }
10462
10659
  if (!effectiveDocumentId || !pruneStale) {
10463
10660
  if (effectiveDocumentId) compactDuplicateBodyTrackedChangeComments(String(effectiveDocumentId));
@@ -10479,22 +10676,18 @@ var useCommentsStore = defineStore("comments", () => {
10479
10676
  return commentsList.value.filter((comment) => {
10480
10677
  if (comment?.trackedChange !== true) return false;
10481
10678
  if (normalizedDocumentId == null) return true;
10482
- const rowDocumentId = comment?.documentId ?? comment?.fileId ?? null;
10679
+ const rowDocumentId = Reflect.get(comment, "documentId") ?? comment?.fileId ?? null;
10483
10680
  return rowDocumentId != null && String(rowDocumentId) === normalizedDocumentId;
10484
10681
  }).length;
10485
10682
  };
10486
10683
  /**
10487
10684
  * Triggered when the editor locations are updated
10488
10685
  * 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
10686
  */
10494
10687
  const handleEditorLocationsUpdate = (allCommentPositions, options = {}) => {
10495
10688
  if (allCommentPositions == null) return;
10496
10689
  const retainMissingTrackedChangeGeometry = options?.retainMissingTrackedChangeGeometry === true;
10497
- const retainedTrackedChangeIds = retainMissingTrackedChangeGeometry && options?.retainedTrackedChangeIds != null ? new Set(Array.from(options.retainedTrackedChangeIds, (id) => normalizeCommentId(id)).filter(Boolean)) : null;
10690
+ const retainedTrackedChangeIds = retainMissingTrackedChangeGeometry && options?.retainedTrackedChangeIds != null ? new Set(Array.from(options.retainedTrackedChangeIds, (id) => normalizeCommentId(id)).filter(isPresent)) : null;
10498
10691
  const previousPositions = editorCommentPositions.value || {};
10499
10692
  const normalizedPositions = {};
10500
10693
  Object.entries(allCommentPositions).forEach(([key, entry]) => {
@@ -10521,7 +10714,7 @@ var useCommentsStore = defineStore("comments", () => {
10521
10714
  return null;
10522
10715
  };
10523
10716
  const writeTrackedChangeAliases = (entry, comment, extraKeys = []) => {
10524
- const keys = [...aliasesFor(comment), ...extraKeys.map((id) => normalizeCommentId(id)).filter(Boolean)];
10717
+ const keys = [...aliasesFor(comment), ...extraKeys.map((id) => normalizeCommentId(id)).filter(isPresent)];
10525
10718
  for (const key of new Set(keys)) if (normalizedPositions[key] === void 0) normalizedPositions[key] = entry;
10526
10719
  };
10527
10720
  commentsList.value.forEach((comment) => {
@@ -11827,8 +12020,8 @@ var _hoisted_11$3 = {
11827
12020
  key: 0,
11828
12021
  class: "card-section comment-body"
11829
12022
  };
11830
- var _hoisted_12$2 = ["data-track-change-semantic-color-key"];
11831
- var _hoisted_13$2 = { key: 0 };
12023
+ var _hoisted_12$3 = ["data-track-change-semantic-color-key"];
12024
+ var _hoisted_13$3 = { key: 0 };
11832
12025
  var _hoisted_14$2 = { class: "change-type tracked-change-label" };
11833
12026
  var _hoisted_15$2 = {
11834
12027
  key: 0,
@@ -12613,6 +12806,16 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
12613
12806
  }
12614
12807
  handleResolve();
12615
12808
  };
12809
+ const ownsTrackedChangeDecision = (superdoc, editor) => editor !== null && proxy.$superdoc === superdoc && superdoc.activeEditor === editor;
12810
+ const restoreTrackedChangeDecisionFocus = (superdoc, editor) => {
12811
+ nextTick(() => {
12812
+ if (!ownsTrackedChangeDecision(superdoc, editor)) return;
12813
+ commentsStore.lastUpdate = /* @__PURE__ */ new Date();
12814
+ activeComment.value = null;
12815
+ commentsStore.setActiveComment(superdoc, activeComment.value);
12816
+ if (ownsTrackedChangeDecision(superdoc, editor)) superdoc.focus?.({ preventScroll: true });
12817
+ });
12818
+ };
12616
12819
  const handleReject = async () => {
12617
12820
  if (props.comment.trackedChange) {
12618
12821
  if (trackedChangeDecisionsAreDisabled()) return trackedChangeDecisionDisabledOutcome();
@@ -12620,23 +12823,21 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
12620
12823
  const customHandler = proxy.$superdoc.config.onTrackedChangeBubbleReject;
12621
12824
  if (props.comment.trackedChange) {
12622
12825
  if (isV2Mode.value) {
12826
+ const superdoc = proxy.$superdoc;
12827
+ const editor = superdoc.activeEditor;
12623
12828
  const outcome = await commentsStore.decideTrackedChangeFromSidebar({
12624
- superdoc: proxy.$superdoc,
12829
+ superdoc,
12625
12830
  comment: props.comment,
12626
12831
  decision: "reject"
12627
12832
  });
12833
+ if (!ownsTrackedChangeDecision(superdoc, editor)) return;
12628
12834
  if (!outcome?.ok || outcome.success === false) {
12629
12835
  reportTrackedChangeDecisionFailure("reject", outcome);
12630
12836
  return;
12631
12837
  }
12632
12838
  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
- });
12839
+ if (typeof customHandler === "function") customHandler(props.comment, editor);
12840
+ restoreTrackedChangeDecisionFocus(superdoc, editor);
12640
12841
  return;
12641
12842
  }
12642
12843
  let decisionApplied = true;
@@ -12682,23 +12883,21 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
12682
12883
  }
12683
12884
  const customHandler = proxy.$superdoc.config.onTrackedChangeBubbleAccept;
12684
12885
  if (props.comment.trackedChange && isV2Mode.value) {
12886
+ const superdoc = proxy.$superdoc;
12887
+ const editor = superdoc.activeEditor;
12685
12888
  const outcome = await commentsStore.decideTrackedChangeFromSidebar({
12686
- superdoc: proxy.$superdoc,
12889
+ superdoc,
12687
12890
  comment: props.comment,
12688
12891
  decision: "accept"
12689
12892
  });
12893
+ if (!ownsTrackedChangeDecision(superdoc, editor)) return;
12690
12894
  if (!outcome?.ok || outcome.success === false) {
12691
12895
  reportTrackedChangeDecisionFailure("accept", outcome);
12692
12896
  return;
12693
12897
  }
12694
12898
  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
- });
12899
+ if (typeof customHandler === "function") customHandler(props.comment, editor);
12900
+ restoreTrackedChangeDecisionFocus(superdoc, editor);
12702
12901
  return;
12703
12902
  }
12704
12903
  let v1TrackedChangeDecisionApplied = true;
@@ -13002,7 +13201,7 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13002
13201
  "data-track-change-semantic-color-key": comment.semanticColorKey ?? void 0,
13003
13202
  ref_for: true,
13004
13203
  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) => {
13204
+ }, [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
13205
  return openBlock(), createElementBlock("div", {
13007
13206
  key: lineIndex,
13008
13207
  class: "tracked-change-detail-line"
@@ -13027,7 +13226,7 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13027
13226
  style: normalizeStyle(trackedChangeImagePreviewStyle(comment.trackedChangeImagePreview)),
13028
13227
  loading: "lazy",
13029
13228
  decoding: "async"
13030
- }, null, 12, _hoisted_38)], 8, _hoisted_37)) : createCommentVNode("", true)], 10, _hoisted_12$2),
13229
+ }, null, 12, _hoisted_38)], 8, _hoisted_37)) : createCommentVNode("", true)], 10, _hoisted_12$3),
13031
13230
  shouldTruncate.value && isTextOverflowing.value && index === 0 ? (openBlock(), createElementBlock("div", {
13032
13231
  key: 0,
13033
13232
  class: "show-more-toggle",
@@ -13121,7 +13320,7 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13121
13320
  ], 64))], 14, _hoisted_1$26)), [[_directive_click_outside, handleClickOutside]]);
13122
13321
  };
13123
13322
  }
13124
- }), [["__scopeId", "data-v-4220ce17"]]);
13323
+ }), [["__scopeId", "data-v-04c17a1a"]]);
13125
13324
  //#endregion
13126
13325
  //#region src/components/CommentsLayer/commentsList/ReviewDirectoryListItem.vue?vue&type=script&setup=true&lang.ts
13127
13326
  var _hoisted_1$25 = [
@@ -13888,124 +14087,6 @@ var composeAuthorColorResolver = (config) => {
13888
14087
  };
13889
14088
  };
13890
14089
  //#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
14090
  //#region src/core/superdoc-ui-owner.ts
14010
14091
  var ownerIds = /* @__PURE__ */ new WeakMap();
14011
14092
  function getSuperDocUiOwnerId(superdoc) {
@@ -14118,18 +14199,42 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
14118
14199
  const { activeComment, activeFloatingCommentInstanceId, editorCommentPositions, pendingComment, editingCommentId, instantSidebarAlignmentTargetY, instantSidebarAlignmentThreadId, instantSidebarAlignmentInstanceId } = storeToRefs(piniaCommentsStore);
14119
14200
  const { activeZoom } = storeToRefs(superdocStore);
14120
14201
  const committedPresentation = computed$1(() => commentsStore.committedReviewPresentations?.[String(props.currentDocument?.id)] ?? null);
14202
+ const committedInstanceCache = /* @__PURE__ */ new Map();
14203
+ let committedInstanceCacheOwner = "";
14204
+ const sameReviewInstance = (cached, next) => {
14205
+ const before = Object.entries(cached).filter(([key]) => key !== "comment" && key !== "rowVersion");
14206
+ const after = Object.entries(next);
14207
+ if (before.length !== after.length) return false;
14208
+ const previous = new Map(before);
14209
+ return after.every(([key, value]) => previous.has(key) && (key === "positionEntry" ? JSON.stringify(previous.get(key)) === JSON.stringify(value) : Object.is(previous.get(key), value)));
14210
+ };
14121
14211
  const committedInstances = computed$1(() => {
14122
14212
  const presentation = committedPresentation.value;
14123
14213
  if (!presentation) return null;
14124
14214
  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) => {
14215
+ const owner = `${presentation.epoch}\u0000${presentation.coordinate?.mountId ?? ""}`;
14216
+ if (owner !== committedInstanceCacheOwner) {
14217
+ committedInstanceCache.clear();
14218
+ committedInstanceCacheOwner = owner;
14219
+ }
14220
+ const live = /* @__PURE__ */ new Set();
14221
+ const instances = presentation.instances.flatMap((instance) => {
14126
14222
  const comment = instance.rowId === "pending" ? pendingComment.value : rows.get(instance.rowId);
14127
- return comment ? [{
14223
+ if (!comment) return [];
14224
+ const rowVersion = presentation.rowVersions[instance.rowId] ?? 0;
14225
+ live.add(instance.id);
14226
+ const cached = committedInstanceCache.get(instance.id);
14227
+ if (cached && cached.comment === comment && cached.rowVersion === rowVersion && sameReviewInstance(cached, instance)) return [cached];
14228
+ const merged = {
14128
14229
  ...instance,
14129
14230
  comment,
14130
- rowVersion: presentation.rowVersions[instance.rowId] ?? 0
14131
- }] : [];
14231
+ rowVersion
14232
+ };
14233
+ committedInstanceCache.set(instance.id, merged);
14234
+ return [merged];
14132
14235
  });
14236
+ for (const id of committedInstanceCache.keys()) if (!live.has(id)) committedInstanceCache.delete(id);
14237
+ return instances;
14133
14238
  });
14134
14239
  const floatingCommentInstances = computed$1(() => {
14135
14240
  if (committedInstances.value) return committedInstances.value.filter((instance) => instance.rowId !== "pending");
@@ -14545,6 +14650,18 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
14545
14650
  scheduleDeferredRemeasure(() => allPositions.value.map((pos) => pos.id));
14546
14651
  });
14547
14652
  };
14653
+ const resizeHandlers = /* @__PURE__ */ new Map();
14654
+ const resizeHandlerFor = (id) => {
14655
+ let handler = resizeHandlers.get(id);
14656
+ if (!handler) {
14657
+ handler = () => {
14658
+ const position = allPositions.value.find((candidate) => candidate.id === id);
14659
+ if (position) handleResize(position);
14660
+ };
14661
+ resizeHandlers.set(id, handler);
14662
+ }
14663
+ return handler;
14664
+ };
14548
14665
  const setInstantLayoutTransitionsDisabled = (disabled) => {
14549
14666
  if (!disabled && directDecisionContinuityTargetId.value != null) return;
14550
14667
  disableInstantLayoutTransitions.value = disabled;
@@ -14949,6 +15066,7 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
14949
15066
  visibilityChanged = true;
14950
15067
  }
14951
15068
  if (visibilityChanged) visibleIds.value = newVisible;
15069
+ for (const id of resizeHandlers.keys()) if (!currentIds.has(id)) resizeHandlers.delete(id);
14952
15070
  for (const id of prevPositionIds) if (!currentIds.has(id)) delete _heightsCache[id];
14953
15071
  const setChanged = prevPositionIds.size !== currentIds.size || [...prevPositionIds].some((id) => !currentIds.has(id));
14954
15072
  const orderChanged = renderedPositionOrder.length !== positionOrder.length || renderedPositionOrder.some((id, index) => id !== positionOrder[index]);
@@ -15041,12 +15159,13 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
15041
15159
  style: normalizeStyle({
15042
15160
  top: (pos.isOffscreenReviewCard && !committedPresentation.value ? pos.top - sidebarOffsetY.value : pos.top) + "px",
15043
15161
  height: pos.height + "px",
15044
- transition: committedPresentation.value || disableInstantLayoutTransitions.value ? "none" : void 0
15162
+ transition: committedPresentation.value || disableInstantLayoutTransitions.value ? "none" : void 0,
15163
+ visibility: pos.instanceRef?.provisional ? "hidden" : void 0
15045
15164
  })
15046
15165
  }, [shouldRenderDialog(pos) ? (openBlock(), createBlock(CommentDialog_default, {
15047
15166
  key: pos.id + commentsRenderKey.value,
15048
15167
  onReady: handleDialog,
15049
- onResize: ($event) => handleResize(pos),
15168
+ onResize: resizeHandlerFor(pos.id),
15050
15169
  class: "floating-comment",
15051
15170
  parent: __props.parent,
15052
15171
  comment: pos.commentRef,
@@ -15066,7 +15185,7 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
15066
15185
  }), 128))], 4)], 36);
15067
15186
  };
15068
15187
  }
15069
- }), [["__scopeId", "data-v-aaf2c537"]]);
15188
+ }), [["__scopeId", "data-v-1070f5a1"]]);
15070
15189
  //#endregion
15071
15190
  //#region src/components/CommentsLayer/PdfCommentsLayer.vue?vue&type=script&setup=true&lang.ts
15072
15191
  var _hoisted_1$22 = {
@@ -15199,7 +15318,7 @@ var PdfCommentsLayer_default = /* @__PURE__ */ _plugin_vue_export_helper_default
15199
15318
  }), 128))]);
15200
15319
  };
15201
15320
  }
15202
- }), [["__scopeId", "data-v-86166837"]]);
15321
+ }), [["__scopeId", "data-v-e3ae65fa"]]);
15203
15322
  //#endregion
15204
15323
  //#region src/components/Whiteboard/use-whiteboard.ts
15205
15324
  var useWhiteboard = ({ proxy, layers, documents, modules }) => {
@@ -15791,6 +15910,21 @@ function useCommentSmallScreen({ commentsModuleConfig, superdocRoot, layers }) {
15791
15910
  };
15792
15911
  }
15793
15912
  //#endregion
15913
+ //#region src/composables/use-comment-sidebar.ts
15914
+ function useCommentSidebar({ documentKey, enabled, hasContent }) {
15915
+ const reserved = ref(false);
15916
+ watch$1(documentKey, () => {
15917
+ reserved.value = false;
15918
+ }, { flush: "sync" });
15919
+ watch$1(() => enabled.value && hasContent.value, (visible) => {
15920
+ if (visible) reserved.value = true;
15921
+ }, {
15922
+ immediate: true,
15923
+ flush: "sync"
15924
+ });
15925
+ return computed$1(() => enabled.value && (hasContent.value || reserved.value));
15926
+ }
15927
+ //#endregion
15794
15928
  //#region src/composables/use-compact-comment-popover.ts
15795
15929
  var POPOVER_WIDTH_PX = 320;
15796
15930
  var SAFE_MARGIN_PX = 12;
@@ -16169,6 +16303,116 @@ function resolveV2ReviewTargetCommentId(target, getComment) {
16169
16303
  return commentId != null ? String(commentId) : null;
16170
16304
  }
16171
16305
  //#endregion
16306
+ //#region src/helpers/v2-review-geometry-delivery.ts
16307
+ function createReviewGeometryDelivery(deliver) {
16308
+ const packets = /* @__PURE__ */ new Map();
16309
+ let stageOwners = /* @__PURE__ */ new WeakMap();
16310
+ let active = null;
16311
+ const documentOf = (current) => current.documentId != null ? String(current.documentId) : current.mountContainer?.dataset.superdocV2DocumentId ?? null;
16312
+ const matches = (documentId, current) => current.mountContainer?.dataset.superdocV2DocumentId === documentId && (current.documentId == null || String(current.documentId) === documentId);
16313
+ const matchesPaint = (packet, current) => typeof packet.payload.paintEpoch !== "number" || packet.payload.paintEpoch === current.epoch;
16314
+ const observe = (current) => {
16315
+ const stage = current.mountContainer;
16316
+ const documentId = documentOf(current);
16317
+ if (!stage || !documentId || !matches(documentId, current)) return;
16318
+ const previousOwner = stageOwners.get(stage);
16319
+ if (previousOwner && previousOwner !== documentId) packets.delete(previousOwner);
16320
+ stageOwners.set(stage, documentId);
16321
+ const packet = packets.get(documentId);
16322
+ if (packet?.stage && packet.stage !== stage) packets.delete(documentId);
16323
+ else if (packet && (!active || active.stage !== stage || active.documentId !== documentId || active.epoch !== current.epoch)) packet.delivered = false;
16324
+ active = {
16325
+ documentId,
16326
+ stage,
16327
+ epoch: current.epoch
16328
+ };
16329
+ };
16330
+ const dispatch = (documentId, packet, current, replay = false) => {
16331
+ if (!current.mountContainer || !matches(documentId, current) || !matchesPaint(packet, current)) return false;
16332
+ if (packet.stage && packet.stage !== current.mountContainer || packet.delivering || packet.delivered && !replay) return false;
16333
+ const wasDelivered = packet.delivered;
16334
+ const previousStage = packet.stage;
16335
+ packet.stage = current.mountContainer;
16336
+ packet.delivered = true;
16337
+ packet.delivering = true;
16338
+ try {
16339
+ deliver(current, packet.payload);
16340
+ } catch (error) {
16341
+ packet.delivered = wasDelivered;
16342
+ packet.stage = previousStage;
16343
+ throw error;
16344
+ } finally {
16345
+ packet.delivering = false;
16346
+ }
16347
+ return true;
16348
+ };
16349
+ const notify = (documentId, payload, current, ready = true) => {
16350
+ if (!payload.reviewGeometry || !Array.isArray(payload.pageIndices)) return false;
16351
+ let key;
16352
+ try {
16353
+ key = JSON.stringify([
16354
+ documentId,
16355
+ payload.documentEpoch,
16356
+ payload.paintEpoch,
16357
+ payload.commitSequence,
16358
+ payload.pageIndices,
16359
+ payload.zoomScale,
16360
+ payload.reviewGeometry
16361
+ ]);
16362
+ } catch {
16363
+ return false;
16364
+ }
16365
+ const isCurrent = current != null && matches(documentId, current);
16366
+ if (isCurrent) observe(current);
16367
+ const previous = packets.get(documentId);
16368
+ if (previous && previous.payload.documentEpoch === payload.documentEpoch && typeof previous.payload.paintEpoch === "number" && typeof payload.paintEpoch === "number" && payload.paintEpoch < previous.payload.paintEpoch) return false;
16369
+ if (previous && previous.payload.documentEpoch === payload.documentEpoch && typeof previous.payload.commitSequence === "number" && typeof payload.commitSequence === "number" && payload.commitSequence < previous.payload.commitSequence) return false;
16370
+ if (previous && previous.payload.documentEpoch === payload.documentEpoch && typeof previous.payload.generation === "number" && typeof payload.generation === "number" && payload.generation < previous.payload.generation) return false;
16371
+ if (isCurrent && typeof payload.paintEpoch === "number" && typeof current.epoch === "number" && payload.paintEpoch < current.epoch) return false;
16372
+ if (previous?.key === key) {
16373
+ previous.payload = payload;
16374
+ return isCurrent && ready ? dispatch(documentId, previous, current) : false;
16375
+ }
16376
+ const packet = {
16377
+ payload,
16378
+ key,
16379
+ stage: isCurrent ? current.mountContainer : previous && previous.payload.documentEpoch === payload.documentEpoch ? previous.stage : null,
16380
+ delivered: false,
16381
+ delivering: false
16382
+ };
16383
+ packets.set(documentId, packet);
16384
+ if (!isCurrent || !ready) return false;
16385
+ try {
16386
+ return dispatch(documentId, packet, current);
16387
+ } catch (error) {
16388
+ if (previous) packets.set(documentId, previous);
16389
+ else packets.delete(documentId);
16390
+ throw error;
16391
+ }
16392
+ };
16393
+ return {
16394
+ notify,
16395
+ observe,
16396
+ flush(current) {
16397
+ observe(current);
16398
+ const documentId = documentOf(current);
16399
+ const packet = documentId == null ? null : packets.get(documentId);
16400
+ return documentId && packet ? dispatch(documentId, packet, current) : false;
16401
+ },
16402
+ replay(current) {
16403
+ observe(current);
16404
+ const documentId = documentOf(current);
16405
+ const packet = documentId == null ? null : packets.get(documentId);
16406
+ return documentId && packet ? dispatch(documentId, packet, current, true) : false;
16407
+ },
16408
+ reset() {
16409
+ packets.clear();
16410
+ stageOwners = /* @__PURE__ */ new WeakMap();
16411
+ active = null;
16412
+ }
16413
+ };
16414
+ }
16415
+ //#endregion
16172
16416
  //#region src/helpers/v2-mutation-exception.ts
16173
16417
  var FAILURE_SOURCES = /* @__PURE__ */ new Set(["shell", "receipt"]);
16174
16418
  var DIAGNOSTIC_CODE = /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/;
@@ -16538,7 +16782,7 @@ function usePasswordPrompt({ getSurfaceManager, getPasswordPromptConfig, onUnhan
16538
16782
  })
16539
16783
  });
16540
16784
  } else {
16541
- const mod = await import("./chunks/PasswordPromptSurface-CZqhDQQ1.es.js");
16785
+ const mod = await import("./chunks/PasswordPromptSurface-DjV1JgjD.es.js");
16542
16786
  const surfaceId = `password-prompt-${doc.id}`;
16543
16787
  handle = manager.open({
16544
16788
  id: surfaceId,
@@ -17198,7 +17442,7 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
17198
17442
  } else {
17199
17443
  let FindReplaceSurface;
17200
17444
  try {
17201
- FindReplaceSurface = (await import("./chunks/FindReplaceSurface-B_5ggdWg.es.js")).default;
17445
+ FindReplaceSurface = (await import("./chunks/FindReplaceSurface-BEnRLpD9.es.js")).default;
17202
17446
  } catch {
17203
17447
  opening = false;
17204
17448
  return;
@@ -17572,11 +17816,11 @@ var _hoisted_8$3 = { class: "input-row url-input-row" };
17572
17816
  var _hoisted_9$2 = ["innerHTML"];
17573
17817
  var _hoisted_10$2 = ["readonly", "onKeydown"];
17574
17818
  var _hoisted_11$2 = ["innerHTML"];
17575
- var _hoisted_12$1 = {
17819
+ var _hoisted_12$2 = {
17576
17820
  key: 0,
17577
17821
  class: "input-row link-buttons"
17578
17822
  };
17579
- var _hoisted_13$1 = ["disabled"];
17823
+ var _hoisted_13$2 = ["disabled"];
17580
17824
  var _hoisted_14$1 = ["innerHTML"];
17581
17825
  var _hoisted_15$1 = ["disabled"];
17582
17826
  var _hoisted_16$1 = {
@@ -17835,7 +18079,7 @@ var LinkInput_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
17835
18079
  "data-item": "btn-link-open"
17836
18080
  }, null, 10, _hoisted_11$2)
17837
18081
  ]),
17838
- !isViewingMode.value ? (openBlock(), createElementBlock("div", _hoisted_12$1, [isEditing.value ? (openBlock(), createElementBlock("button", {
18082
+ !isViewingMode.value ? (openBlock(), createElementBlock("div", _hoisted_12$2, [isEditing.value ? (openBlock(), createElementBlock("button", {
17839
18083
  key: 0,
17840
18084
  class: "remove-btn",
17841
18085
  onClick: handleRemove,
@@ -17844,7 +18088,7 @@ var LinkInput_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
17844
18088
  }, [createElementVNode("div", {
17845
18089
  class: "remove-btn__icon",
17846
18090
  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", {
18091
+ }, null, 8, _hoisted_14$1), _cache[6] || (_cache[6] = createTextVNode(" Remove ", -1))], 8, _hoisted_13$2)) : createCommentVNode("", true), createElementVNode("button", {
17848
18092
  class: normalizeClass(["sd-submit-btn", { "disable-btn": isDisabled.value }]),
17849
18093
  onClick: handleSubmit,
17850
18094
  disabled: isSubmitting.value,
@@ -18754,6 +18998,7 @@ function createV2EditorRuntimeAdapter(options) {
18754
18998
  const { id, documentId, root, host, getLegacyEditorProjection, onUnregister } = options;
18755
18999
  let snapshot = host.getSnapshot();
18756
19000
  let didDispose = false;
19001
+ let detached = false;
18757
19002
  let unregistered = false;
18758
19003
  let mountedFocusHandle = null;
18759
19004
  let tokenRevision = 0;
@@ -19050,7 +19295,17 @@ function createV2EditorRuntimeAdapter(options) {
19050
19295
  };
19051
19296
  }
19052
19297
  }
19298
+ function releaseHostSubscriptions() {
19299
+ hostUnsubscribe?.();
19300
+ hostUnsubscribe = null;
19301
+ pageMetricsUnsubscribe?.();
19302
+ pageMetricsUnsubscribe = null;
19303
+ selectionUnsubscribe?.();
19304
+ selectionUnsubscribe = null;
19305
+ activeSelectionController = null;
19306
+ }
19053
19307
  function handleHostSnapshot(next) {
19308
+ if (detached || didDispose) return;
19054
19309
  snapshot = next;
19055
19310
  invalidatePositionTokens();
19056
19311
  syncSelectionSubscription();
@@ -19064,10 +19319,7 @@ function createV2EditorRuntimeAdapter(options) {
19064
19319
  });
19065
19320
  if (next.state === "disposed" && !didDispose) {
19066
19321
  didDispose = true;
19067
- hostUnsubscribe?.();
19068
- pageMetricsUnsubscribe?.();
19069
- selectionUnsubscribe?.();
19070
- selectionUnsubscribe = null;
19322
+ releaseHostSubscriptions();
19071
19323
  emit({ type: "disposed" });
19072
19324
  listeners.clear();
19073
19325
  if (!unregistered) {
@@ -19214,10 +19466,7 @@ function createV2EditorRuntimeAdapter(options) {
19214
19466
  await host.dispose();
19215
19467
  if (!didDispose) {
19216
19468
  didDispose = true;
19217
- hostUnsubscribe?.();
19218
- pageMetricsUnsubscribe?.();
19219
- selectionUnsubscribe?.();
19220
- selectionUnsubscribe = null;
19469
+ releaseHostSubscriptions();
19221
19470
  emit({ type: "disposed" });
19222
19471
  listeners.clear();
19223
19472
  if (!unregistered) {
@@ -19296,8 +19545,17 @@ function createV2EditorRuntimeAdapter(options) {
19296
19545
  }
19297
19546
  },
19298
19547
  attachMountHandle(handle) {
19548
+ if (detached || didDispose) return;
19299
19549
  mountedFocusHandle = handle;
19300
19550
  syncSelectionSubscription();
19551
+ },
19552
+ detach() {
19553
+ if (detached) return;
19554
+ detached = true;
19555
+ unregistered = true;
19556
+ releaseHostSubscriptions();
19557
+ mountedFocusHandle = null;
19558
+ listeners.clear();
19301
19559
  }
19302
19560
  };
19303
19561
  }
@@ -20460,7 +20718,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20460
20718
  __name: "SuperDoc",
20461
20719
  emits: ["selection-update"],
20462
20720
  setup(__props, { emit: __emit }) {
20463
- const PdfViewer = defineAsyncComponent(() => import("./chunks/PdfViewer-CW09MNrB.es.js"));
20721
+ const PdfViewer = defineAsyncComponent(() => import("./chunks/PdfViewer-C2r-K8zC.es.js"));
20464
20722
  const getDocumentLoadPassword = (doc) => doc.password ?? proxy.$superdoc.config.password;
20465
20723
  const resolveDocumentV2Collaboration = (doc) => {
20466
20724
  const rawConfig = doc?.v2Collaboration;
@@ -20545,7 +20803,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20545
20803
  if (commentsConfig.highlightHoverColor) vars["--sd-comments-highlight-hover"] = commentsConfig.highlightHoverColor;
20546
20804
  const activeTrackChangeColors = {
20547
20805
  ...commentsConfig.trackChangeHighlightColors || {},
20548
- ...commentsConfig.trackChangeActiveHighlightColors || {}
20806
+ ...commentsConfig.trackChangeActiveHighlightColors
20549
20807
  };
20550
20808
  if (activeTrackChangeColors.insertBorder) vars["--sd-tracked-changes-insert-border"] = activeTrackChangeColors.insertBorder;
20551
20809
  if (activeTrackChangeColors.insertBackground) vars["--sd-tracked-changes-insert-background"] = activeTrackChangeColors.insertBackground;
@@ -20574,6 +20832,8 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20574
20832
  const v2ReviewSidebarUnlocked = ref(false);
20575
20833
  const v2GeometryEpoch = ref(null);
20576
20834
  let v2GeometryRafHandle = 0;
20835
+ let v2GeometryReplayRafHandle = 0;
20836
+ let v2GeometryReplayGeneration = 0;
20577
20837
  let v2TrackedChangeRestampRetainPaints = 0;
20578
20838
  let v2TrackedChangeRestampRetentionReason = null;
20579
20839
  const activeEditorRef = computed$1(() => proxy.$superdoc.activeEditor);
@@ -20757,6 +21017,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20757
21017
  if (document && provider?.sendStateless) document.provider = null;
20758
21018
  const entry = v2Runtimes.get(documentId);
20759
21019
  if (!entry) return;
21020
+ entry.adapter.detach();
20760
21021
  proxy.$superdoc["unregisterEditorRuntime"](entry.runtimeId);
20761
21022
  v2Runtimes.delete(documentId);
20762
21023
  const hostRoot = subDocumentRoots.get(documentId);
@@ -20910,7 +21171,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20910
21171
  if (loadingDocument?.loadScope && !loadingDocument.loadScope.finish()) return;
20911
21172
  clearV2EditorFailure(payload.documentId ?? null);
20912
21173
  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;
21174
+ 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
21175
  documentOpenTelemetry?.trackDocumentOpen(documentOpenToken ?? null, documentId ?? null);
20915
21176
  const saveV2Bytes = createV2SaveQueue(async (saveOptions = {}) => {
20916
21177
  if (!host || typeof host.save !== "function") throw new Error("v2-editor: save unavailable");
@@ -21006,21 +21267,21 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21006
21267
  };
21007
21268
  };
21008
21269
  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);
21270
+ const confirmedV2SelectionTarget = async () => {
21271
+ const selectionTargets = host?.getHandles?.()?.editing?.selectionTargets;
21272
+ if (typeof selectionTargets?.confirmedSelectionTarget !== "function") return createV2AuthoringFailure("selection-confirmation-unavailable");
21273
+ try {
21274
+ const result = await selectionTargets.confirmedSelectionTarget();
21275
+ if (!result?.ok || !result.target) return createV2AuthoringFailure(result?.reason ?? "selection-unconfirmed");
21276
+ return {
21277
+ ok: true,
21278
+ target: result.target,
21279
+ mode: result.mode,
21280
+ story: result.story
21281
+ };
21282
+ } catch (error) {
21283
+ return createV2AuthoringFailure("selection-unconfirmed", error instanceof Error ? error.message : String(error));
21016
21284
  }
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
21285
  };
21025
21286
  const failureFromV2Receipt = (receipt, fallbackReason) => {
21026
21287
  const failure = receipt?.failure ?? receipt;
@@ -21094,7 +21355,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21094
21355
  },
21095
21356
  serializeSelectionToClipboard: async ({ includeHtml = true } = {}) => {
21096
21357
  if (!documentApi?.clipboard || typeof documentApi.clipboard.serializeSelection !== "function") return createV2AuthoringFailure("clipboard-serialize-unavailable", "activeEditor.doc.clipboard.serializeSelection is not available");
21097
- const selected = readV2CurrentSelectionTarget();
21358
+ const selected = await confirmedV2SelectionTarget();
21098
21359
  if (!selected.ok) return selected;
21099
21360
  let serialized;
21100
21361
  try {
@@ -21120,7 +21381,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21120
21381
  let pasteTarget = target;
21121
21382
  let selectionMode = null;
21122
21383
  if (!pasteTarget) {
21123
- const selected = readV2CurrentSelectionTarget();
21384
+ const selected = await confirmedV2SelectionTarget();
21124
21385
  if (!selected.ok) return selected;
21125
21386
  pasteTarget = selected.target;
21126
21387
  selectionMode = selected.mode;
@@ -21181,6 +21442,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21181
21442
  v2Comments: commentsAdapter ?? null,
21182
21443
  v2TrackedChanges: trackedChangesAdapter ?? null,
21183
21444
  contextMenu: contextMenu ?? null,
21445
+ viewportQuery: viewportQuery ?? null,
21184
21446
  pageMetrics: pageMetrics ?? null,
21185
21447
  pageLayout: pageLayout ?? null,
21186
21448
  pageFurniture: pageFurniture ?? null,
@@ -21522,10 +21784,11 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21522
21784
  const publishV2PendingPositionEntry = (entry) => {
21523
21785
  if (!entry) return;
21524
21786
  handleEditorLocationsUpdate({
21525
- ...editorCommentPositions.value ?? {},
21787
+ ...editorCommentPositions.value,
21526
21788
  pending: entry
21527
21789
  });
21528
21790
  };
21791
+ let scheduleV2GeometryReplay = () => {};
21529
21792
  const v2GeometryPublisher = resolvedEditorIntegration.createGeometryPublisher({
21530
21793
  getLayersContainer: () => layers.value ?? null,
21531
21794
  isCommentsEnabled: () => Boolean(commentsModuleConfig.value) && shouldRenderCommentsInViewing.value,
@@ -21541,7 +21804,8 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21541
21804
  v2ReviewSidebarUnlocked.value = true;
21542
21805
  v2GeometryEpoch.value = v2GeometryPublisher.getLastEpoch();
21543
21806
  }
21544
- }
21807
+ },
21808
+ onCommittedPagePaint: ({ payload }) => scheduleV2GeometryReplay(payload)
21545
21809
  });
21546
21810
  const armV2TrackedChangeRestampGeometryRetention = (reason = "tracked-change-restamp") => {
21547
21811
  v2TrackedChangeRestampRetainPaints = Math.max(v2TrackedChangeRestampRetainPaints, 3);
@@ -21561,20 +21825,53 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21561
21825
  v2TrackedChangeRestampRetentionReason = null;
21562
21826
  };
21563
21827
  const resolveV2GeometryPublishOptions = (options) => typeof options === "function" ? options() : options;
21828
+ const isV2GeometryReady = (current) => {
21829
+ if (!current?.mountContainer || !commentsModuleConfig.value || !shouldRenderCommentsInViewing.value) return false;
21830
+ const mount = current.mount;
21831
+ let liveEpoch;
21832
+ try {
21833
+ liveEpoch = mount?.getRenderEpoch?.();
21834
+ } catch {
21835
+ return false;
21836
+ }
21837
+ return v2GeometryPublisher.getLastPayload() === current && v2GeometryPublisher.getLastEpoch() != null && v2GeometryPublisher.getLastEpoch() === current.epoch && (liveEpoch == null || liveEpoch === current.epoch);
21838
+ };
21564
21839
  const scheduleV2GeometryPublish = (payload, options = void 0) => {
21565
21840
  v2GeometryRender.value = payload;
21566
21841
  if (v2GeometryRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryRafHandle);
21842
+ const publish = () => {
21843
+ const current = v2GeometryRender.value;
21844
+ Promise.resolve(v2GeometryPublisher.publish(current, resolveV2GeometryPublishOptions(options))).then(() => {
21845
+ if (current === v2GeometryRender.value && isV2GeometryReady(current)) v2ReviewGeometryDelivery.flush(current);
21846
+ });
21847
+ };
21567
21848
  if (typeof requestAnimationFrame !== "function") {
21568
- v2GeometryPublisher.publish(v2GeometryRender.value, resolveV2GeometryPublishOptions(options));
21849
+ publish();
21569
21850
  return;
21570
21851
  }
21571
21852
  v2GeometryRafHandle = requestAnimationFrame(() => {
21572
21853
  v2GeometryRafHandle = 0;
21573
- v2GeometryPublisher.publish(v2GeometryRender.value, resolveV2GeometryPublishOptions(options));
21854
+ publish();
21574
21855
  });
21575
21856
  };
21857
+ const v2ReviewGeometryDelivery = createReviewGeometryDelivery((current, packet) => {
21858
+ v2GeometryPublisher.noteCommittedPageWindow?.(packet);
21859
+ scheduleV2GeometryPublish(current);
21860
+ });
21861
+ scheduleV2GeometryReplay = (payload) => {
21862
+ if (!payload || payload !== v2GeometryRender.value) return;
21863
+ const generation = ++v2GeometryReplayGeneration;
21864
+ if (v2GeometryReplayRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryReplayRafHandle);
21865
+ const replay = () => {
21866
+ v2GeometryReplayRafHandle = 0;
21867
+ if (generation === v2GeometryReplayGeneration && payload === v2GeometryRender.value && isV2GeometryReady(payload)) v2ReviewGeometryDelivery.replay(payload);
21868
+ };
21869
+ if (typeof requestAnimationFrame === "function") v2GeometryReplayRafHandle = requestAnimationFrame(replay);
21870
+ else queueMicrotask(replay);
21871
+ };
21576
21872
  const onV2Render = (payload) => {
21577
21873
  if (!payload) return;
21874
+ v2ReviewGeometryDelivery.observe(payload);
21578
21875
  scheduleV2GeometryPublish(payload, () => takeV2TrackedChangeRestampGeometryRetention());
21579
21876
  if (v2HasRangeSelection.value) scheduleV2SelectionToolbarStateSync();
21580
21877
  };
@@ -21607,7 +21904,11 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21607
21904
  cancelScheduledV2SelectionToolbarSync();
21608
21905
  if (v2GeometryRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryRafHandle);
21609
21906
  v2GeometryRafHandle = 0;
21907
+ if (v2GeometryReplayRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryReplayRafHandle);
21908
+ v2GeometryReplayRafHandle = 0;
21909
+ v2GeometryReplayGeneration++;
21610
21910
  v2GeometryRender.value = null;
21911
+ v2ReviewGeometryDelivery.reset();
21611
21912
  v2GeometryEpoch.value = null;
21612
21913
  v2GeometryAvailable.value = false;
21613
21914
  v2ReviewSidebarUnlocked.value = false;
@@ -21755,14 +22056,15 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21755
22056
  if (!isV2Mode.value) return;
21756
22057
  if (!v2GeometryPublisher.getLastPayload()) return;
21757
22058
  if (v2GeometryRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryRafHandle);
21758
- if (typeof requestAnimationFrame !== "function") {
21759
- v2GeometryPublisher.recollect(resolveV2GeometryPublishOptions(options));
21760
- return;
21761
- }
21762
- v2GeometryRafHandle = requestAnimationFrame(() => {
22059
+ const recollect = () => {
21763
22060
  v2GeometryRafHandle = 0;
21764
- v2GeometryPublisher.recollect(resolveV2GeometryPublishOptions(options));
21765
- });
22061
+ const current = v2GeometryRender.value;
22062
+ Promise.resolve(v2GeometryPublisher.recollect(resolveV2GeometryPublishOptions(options))).then(() => {
22063
+ if (current === v2GeometryRender.value && isV2GeometryReady(current)) v2ReviewGeometryDelivery.replay(current);
22064
+ });
22065
+ };
22066
+ if (typeof requestAnimationFrame !== "function") recollect();
22067
+ else v2GeometryRafHandle = requestAnimationFrame(recollect);
21766
22068
  };
21767
22069
  const handleV2DocumentModeChange = () => {
21768
22070
  if (!isV2Mode.value) return;
@@ -21990,13 +22292,17 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21990
22292
  ...payload,
21991
22293
  documentId: doc.id
21992
22294
  });
21993
- v2GeometryPublisher.noteCommittedPageWindow?.(payload);
22295
+ if (payload.reviewGeometry && Array.isArray(payload.pageIndices)) {
22296
+ const current = v2GeometryRender.value;
22297
+ v2ReviewGeometryDelivery.notify(String(doc.id), payload, current, isV2GeometryReady(current));
22298
+ } else v2GeometryPublisher.noteCommittedPageWindow?.(payload);
21994
22299
  },
21995
22300
  fontAssets: proxy.$superdoc.config.fonts,
21996
22301
  workerUrls: proxy.$superdoc.config.workerUrls,
21997
22302
  fieldContext: doc.fieldContext,
21998
22303
  fieldUpdatePolicy: proxy.$superdoc.config.fieldUpdatePolicy,
21999
22304
  workerStartupTimeoutMs: proxy.$superdoc.config.workerStartupTimeoutMs,
22305
+ ...proxy.$superdoc.config.changes ? { changes: proxy.$superdoc.config.changes } : {},
22000
22306
  proofing: resolvedProofingConfig.value,
22001
22307
  isNewFile,
22002
22308
  password: getDocumentLoadPassword(doc),
@@ -22017,7 +22323,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
22017
22323
  documentLoading: proxy.$superdoc.uiConfig.loading.enabled,
22018
22324
  linkPopoverResolver: getHyperlinkActivationHandler(),
22019
22325
  layoutEngineOptions: useLayoutEngine ? {
22020
- ...proxy.$superdoc.config.layoutEngineOptions || {},
22326
+ ...proxy.$superdoc.config.layoutEngineOptions,
22021
22327
  proofing: resolvedProofingConfig.value,
22022
22328
  debugLabel: proxy.$superdoc.config.layoutEngineOptions?.debugLabel ?? doc.name ?? doc.id,
22023
22329
  zoom: (activeZoom.value ?? 100) / 100,
@@ -22280,11 +22586,16 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
22280
22586
  if (layout !== "auto") return true;
22281
22587
  return !isCompactCommentsMode.value;
22282
22588
  });
22283
- const showCommentsSidebar = computed$1(() => {
22589
+ const sidebarEnabled = computed$1(() => {
22284
22590
  if (isV2Mode.value && !v2GeometryAvailable.value && !v2ReviewSidebarUnlocked.value && !hasPdfFloatingComments.value) return false;
22285
22591
  if (!shouldRenderCommentsInViewing.value) return false;
22286
22592
  if (!shouldUseSidebarComments.value) return false;
22287
- return pendingComment.value || (floatingComments.value.length > 0 || hasOpenTrackedChanges.value) && isReady.value && layers.value && isCommentsEnabled.value && !isCommentsListVisible.value;
22593
+ return Boolean(pendingComment.value || isReady.value && layers.value && isCommentsEnabled.value && !isCommentsListVisible.value);
22594
+ });
22595
+ const showCommentsSidebar = useCommentSidebar({
22596
+ documentKey: computed$1(() => JSON.stringify(documents.value.map((document) => document.id))),
22597
+ enabled: sidebarEnabled,
22598
+ hasContent: computed$1(() => Boolean(pendingComment.value || floatingComments.value.length || hasOpenTrackedChanges.value))
22288
22599
  });
22289
22600
  const activeCompactComment = computed$1(() => {
22290
22601
  if (showCommentsSidebar.value) return null;
@@ -22908,7 +23219,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
22908
23219
  ref_key: "superdocRoot",
22909
23220
  ref: superdocRoot,
22910
23221
  class: normalizeClass(["superdoc", {
22911
- "superdoc--with-sidebar": showCommentsSidebar.value,
23222
+ "superdoc--with-sidebar": unref(showCommentsSidebar),
22912
23223
  "superdoc--web-layout": unref(proxy).$superdoc.config.viewOptions?.layout === "web",
22913
23224
  "superdoc--contained": unref(proxy).$superdoc.config.contained,
22914
23225
  "high-contrast": unref(isHighContrastMode)
@@ -23056,7 +23367,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
23056
23367
  onAnchorActivate: handleHighlightClick
23057
23368
  })) : createCommentVNode("", true)
23058
23369
  ])], 512),
23059
- showCommentsSidebar.value ? (openBlock(), createElementBlock("div", {
23370
+ unref(showCommentsSidebar) ? (openBlock(), createElementBlock("div", {
23060
23371
  key: 0,
23061
23372
  ref_key: "rightSidebarRef",
23062
23373
  ref: rightSidebarRef,
@@ -23079,7 +23390,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
23079
23390
  ], 46, _hoisted_1$17);
23080
23391
  };
23081
23392
  }
23082
- }), [["__scopeId", "data-v-d2540ad3"]]);
23393
+ }), [["__scopeId", "data-v-b5da778c"]]);
23083
23394
  //#endregion
23084
23395
  //#region src/core/create-app.ts
23085
23396
  var PINIA_DEVTOOLS_SETUP_EVENT = "devtools-plugin:setup";
@@ -24560,6 +24871,16 @@ var SurfaceManager = class {
24560
24871
  };
24561
24872
  //#endregion
24562
24873
  //#region src/helpers/deprecation.ts
24874
+ /**
24875
+ * Deprecation utilities for steering consumers toward the Document API.
24876
+ *
24877
+ * ProseMirror will be removed in a future major version. These helpers emit
24878
+ * one-time console warnings when deprecated properties are accessed on an
24879
+ * editor instance handed to consumer callbacks (onEditorCreate, etc.).
24880
+ *
24881
+ * Internal code continues to use the raw Editor — only the consumer-facing
24882
+ * boundary wraps the editor with a Proxy.
24883
+ */
24563
24884
  var MIGRATION_URL = "https://docs.superdoc.dev/guides/migration/document-api";
24564
24885
  var _warned = /* @__PURE__ */ new Set();
24565
24886
  /**
@@ -24748,7 +25069,8 @@ function normalizeSuperDocUser(user) {
24748
25069
  if (!user || typeof user !== "object") return { ...DEFAULT_SUPERDOC_USER };
24749
25070
  return {
24750
25071
  ...DEFAULT_SUPERDOC_USER,
24751
- ...user
25072
+ ...user,
25073
+ ...user.commentIdentity !== void 0 ? { commentIdentity: normalizeCommentIdentity(user.commentIdentity) } : {}
24752
25074
  };
24753
25075
  }
24754
25076
  //#endregion
@@ -25267,6 +25589,7 @@ function normalizeUiConfig(config = {}) {
25267
25589
  * Keeping policy separate means an application can disable the built-in
25268
25590
  * comments UI without losing the rules enforced by its custom UI.
25269
25591
  */
25592
+ var isAutocapitalize = (value) => value === "off" || value === "sentences" || value === "words" || value === "characters";
25270
25593
  var isCommentInteractionLevel = (value) => value === "read" || value === "write" || value === "resolve";
25271
25594
  /**
25272
25595
  * Resolve the effective interaction policy.
@@ -25286,14 +25609,26 @@ function normalizeInteractionConfig(config = {}) {
25286
25609
  const canonicalLevel = interactionComments.level;
25287
25610
  const hasCanonicalLevel = isCommentInteractionLevel(canonicalLevel);
25288
25611
  const level = hasCanonicalLevel ? canonicalLevel : legacyReadOnly ? "read" : legacyAllowResolve ? "resolve" : "write";
25612
+ const interactionWritingAssistance = isPlainObject$1(interaction.writingAssistance) ? interaction.writingAssistance : {};
25613
+ const autocorrect = interactionWritingAssistance.autocorrect === true;
25614
+ const autocapitalize = isAutocapitalize(interactionWritingAssistance.autocapitalize) ? interactionWritingAssistance.autocapitalize : "off";
25615
+ const spellcheck = interactionWritingAssistance.spellcheck === true;
25289
25616
  const canonicalAllowDecisions = interactionTrackedChanges.allowDecisions;
25617
+ const interactionTouch = isPlainObject$1(interaction.touch) ? interaction.touch : {};
25290
25618
  return {
25291
25619
  comments: {
25292
25620
  level,
25293
25621
  readOnly: hasCanonicalLevel ? level === "read" : legacyReadOnly,
25294
25622
  allowResolve: hasCanonicalLevel ? level === "resolve" : legacyAllowResolve
25295
25623
  },
25296
- trackedChanges: { allowDecisions: typeof canonicalAllowDecisions === "boolean" ? canonicalAllowDecisions : !legacyReadOnly }
25624
+ trackedChanges: { allowDecisions: typeof canonicalAllowDecisions === "boolean" ? canonicalAllowDecisions : !legacyReadOnly },
25625
+ touch: { gestures: interactionTouch.gestures === true },
25626
+ writingAssistance: {
25627
+ enabled: autocorrect || autocapitalize !== "off" || spellcheck,
25628
+ autocorrect,
25629
+ autocapitalize,
25630
+ spellcheck
25631
+ }
25297
25632
  };
25298
25633
  }
25299
25634
  //#endregion
@@ -25949,26 +26284,39 @@ var ToolbarButtonIcon_default = /* @__PURE__ */ _plugin_vue_export_helper_defaul
25949
26284
  var _hoisted_1$15 = [
25950
26285
  "role",
25951
26286
  "aria-label",
25952
- "aria-disabled"
26287
+ "aria-disabled",
26288
+ "aria-busy"
25953
26289
  ];
25954
26290
  var _hoisted_2$11 = ["data-item"];
25955
26291
  var _hoisted_3$10 = ["data-item"];
25956
26292
  var _hoisted_4$8 = {
25957
- key: 1,
26293
+ key: 0,
26294
+ role: "status",
26295
+ "aria-label": "Applying list",
26296
+ style: { "font-weight": "700" }
26297
+ };
26298
+ var _hoisted_5$6 = {
26299
+ key: 2,
25958
26300
  class: "sd-button-label"
25959
26301
  };
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 = [
26302
+ var _hoisted_6$4 = ["data-item", "aria-label"];
26303
+ var _hoisted_7$2 = ["innerHTML"];
26304
+ var _hoisted_8$2 = {
26305
+ key: 0,
26306
+ role: "status",
26307
+ "aria-label": "Applying list",
26308
+ style: { "font-weight": "700" }
26309
+ };
26310
+ var _hoisted_9$1 = { key: 3 };
26311
+ var _hoisted_10$1 = ["onKeydown", "id"];
26312
+ var _hoisted_11$1 = [
25965
26313
  "placeholder",
25966
26314
  "onKeydown",
25967
26315
  "id",
25968
26316
  "data-item"
25969
26317
  ];
25970
- var _hoisted_10$1 = ["data-item", "innerHTML"];
25971
- var _hoisted_11$1 = {
26318
+ var _hoisted_12$1 = ["data-item", "innerHTML"];
26319
+ var _hoisted_13$1 = {
25972
26320
  "aria-live": "polite",
25973
26321
  class: "sd-visually-hidden"
25974
26322
  };
@@ -26141,6 +26489,7 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26141
26489
  role: __props.isOverflowItem ? "menuitem" : "button",
26142
26490
  "aria-label": unref(attributes).ariaLabel,
26143
26491
  "aria-disabled": unref(disabled) ? "true" : void 0,
26492
+ "aria-busy": unref(attributes).ariaBusy || void 0,
26144
26493
  "data-sd-part": "toolbar-item",
26145
26494
  onClick: handleOuterClick,
26146
26495
  onKeydown: _cache[9] || (_cache[9] = withKeys(($event) => onEnterKeydown($event), ["enter"])),
@@ -26162,17 +26511,21 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26162
26511
  class: "sd-toolbar-button__main",
26163
26512
  "data-item": `btn-${unref(name) || ""}-main`,
26164
26513
  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),
26514
+ }, [
26515
+ unref(attributes).ariaBusy && isSplit.value ? (openBlock(), createElementBlock("span", _hoisted_4$8, "…")) : createCommentVNode("", true),
26516
+ unref(icon) ? (openBlock(), createBlock(ToolbarButtonIcon_default, {
26517
+ key: 1,
26518
+ color: unref(iconColor),
26519
+ class: "sd-toolbar-icon",
26520
+ icon: unref(icon),
26521
+ name: unref(name)
26522
+ }, null, 8, [
26523
+ "color",
26524
+ "icon",
26525
+ "name"
26526
+ ])) : createCommentVNode("", true),
26527
+ unref(label) && !unref(hideLabel) && !unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("div", _hoisted_5$6, toDisplayString(unref(label)), 1)) : createCommentVNode("", true)
26528
+ ], 8, _hoisted_3$10)) : createCommentVNode("", true),
26176
26529
  isSplit.value ? (openBlock(), createElementBlock("div", {
26177
26530
  key: 1,
26178
26531
  class: "sd-toolbar-button__caret",
@@ -26183,9 +26536,10 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26183
26536
  class: "sd-dropdown-caret",
26184
26537
  innerHTML: caretIcon.value,
26185
26538
  style: normalizeStyle({ opacity: unref(disabled) ? .6 : 1 })
26186
- }, null, 12, _hoisted_6$4)], 8, _hoisted_5$6)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
26539
+ }, null, 12, _hoisted_7$2)], 8, _hoisted_6$4)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
26540
+ unref(attributes).ariaBusy ? (openBlock(), createElementBlock("span", _hoisted_8$2, "…")) : createCommentVNode("", true),
26187
26541
  unref(icon) ? (openBlock(), createBlock(ToolbarButtonIcon_default, {
26188
- key: 0,
26542
+ key: 1,
26189
26543
  color: unref(iconColor),
26190
26544
  class: "sd-toolbar-icon",
26191
26545
  icon: unref(icon),
@@ -26196,10 +26550,10 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26196
26550
  "name"
26197
26551
  ])) : createCommentVNode("", true),
26198
26552
  unref(label) && !unref(hideLabel) && (!unref(inlineTextInputVisible) || itemName.value === "fontFamily") ? (openBlock(), createElementBlock("div", {
26199
- key: 1,
26553
+ key: 2,
26200
26554
  class: normalizeClass(["sd-button-label", { "sd-visually-hidden": unref(inlineTextInputVisible) && itemName.value === "fontFamily" }])
26201
26555
  }, toDisplayString(unref(label)), 3)) : createCommentVNode("", true),
26202
- unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("span", _hoisted_7$2, [unref(name) === "fontSize" ? withDirectives((openBlock(), createElementBlock("input", {
26556
+ unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("span", _hoisted_9$1, [unref(name) === "fontSize" ? withDirectives((openBlock(), createElementBlock("input", {
26203
26557
  key: 0,
26204
26558
  "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => inlineTextInput.value = $event),
26205
26559
  onKeydown: [withKeys(withModifiers(handleInlineInputEnter, ["stop", "prevent"]), ["enter"]), _cache[2] || (_cache[2] = withKeys(($event) => handleInlineInputTab($event), ["tab"]))],
@@ -26211,7 +26565,7 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26211
26565
  autocomplete: "off",
26212
26566
  ref_key: "inlineInput",
26213
26567
  ref: inlineInput
26214
- }, null, 42, _hoisted_8$2)), [[vModelText, inlineTextInput.value]]) : withDirectives((openBlock(), createElementBlock("input", {
26568
+ }, null, 42, _hoisted_10$1)), [[vModelText, inlineTextInput.value]]) : withDirectives((openBlock(), createElementBlock("input", {
26215
26569
  key: 1,
26216
26570
  "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => inlineTextInput.value = $event),
26217
26571
  placeholder: unref(label),
@@ -26228,20 +26582,20 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26228
26582
  autocomplete: "off",
26229
26583
  ref_key: "inlineInput",
26230
26584
  ref: inlineInput
26231
- }, null, 42, _hoisted_9$1)), [[vModelText, inlineTextInput.value]])])) : createCommentVNode("", true),
26585
+ }, null, 42, _hoisted_11$1)), [[vModelText, inlineTextInput.value]])])) : createCommentVNode("", true),
26232
26586
  unref(hasCaret) ? (openBlock(), createElementBlock("div", {
26233
- key: 3,
26587
+ key: 4,
26234
26588
  class: "sd-dropdown-caret",
26235
26589
  "data-item": `btn-${unref(name) || ""}-toggle`,
26236
26590
  innerHTML: caretIcon.value,
26237
26591
  style: normalizeStyle({ opacity: unref(disabled) ? .6 : 1 })
26238
- }, null, 12, _hoisted_10$1)) : createCommentVNode("", true)
26592
+ }, null, 12, _hoisted_12$1)) : createCommentVNode("", true)
26239
26593
  ], 64)),
26240
- createElementVNode("div", _hoisted_11$1, toDisplayString(`${unref(attributes).ariaLabel} ${unref(active) ? "selected" : "unset"}`), 1)
26594
+ createElementVNode("div", _hoisted_13$1, toDisplayString(`${unref(attributes).ariaLabel} ${unref(active) ? "selected" : "unset"}`), 1)
26241
26595
  ], 10, _hoisted_2$11)], 46, _hoisted_1$15);
26242
26596
  };
26243
26597
  }
26244
- }), [["__scopeId", "data-v-e599674c"]]);
26598
+ }), [["__scopeId", "data-v-b49ede12"]]);
26245
26599
  //#endregion
26246
26600
  //#region src/internal/toolbar/built-in/ToolbarSeparator.vue?vue&type=script&setup=true&lang.ts
26247
26601
  var _hoisted_1$14 = {
@@ -27533,7 +27887,7 @@ var SdTooltip_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
27533
27887
  ]);
27534
27888
  const contentStyle = computed$1(() => ({
27535
27889
  ...props.contentStyle,
27536
- ...attrs.style || {},
27890
+ ...attrs.style,
27537
27891
  position: "fixed",
27538
27892
  top: position.value.top,
27539
27893
  left: position.value.left,
@@ -27702,7 +28056,7 @@ var SdTooltip_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
27702
28056
  }, 8, ["css"])]))], 64);
27703
28057
  };
27704
28058
  }
27705
- }), [["__scopeId", "data-v-dfd6f1d1"]]);
28059
+ }), [["__scopeId", "data-v-87967ab1"]]);
27706
28060
  //#endregion
27707
28061
  //#region src/internal/toolbar/built-in/ButtonGroup.vue?vue&type=script&setup=true&lang.ts
27708
28062
  var _hoisted_1$11 = [
@@ -30526,6 +30880,8 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
30526
30880
  attributes: { ariaLabel: "Bold" }
30527
30881
  });
30528
30882
  const fontOptions = (toolbarFonts ?? TOOLBAR_FONTS).map(normalizeFontOption);
30883
+ const isFontOption = (option) => typeof option.label === "string" && typeof option.key === "string";
30884
+ const currentFontOptions = () => fontButton.nestedOptions.value.filter(isFontOption);
30529
30885
  const fontButton = useToolbarItem({
30530
30886
  type: "dropdown",
30531
30887
  name: "fontFamily",
@@ -30552,8 +30908,9 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
30552
30908
  if (typeof fontFamily !== "string" || !fontFamily) return;
30553
30909
  const family = fontFamily.split(",")[0];
30554
30910
  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);
30911
+ const options = currentFontOptions();
30912
+ const defaultFont = options.find((option) => optionValue(option) === fontButton.defaultLabel.value);
30913
+ const foundFont = options.find((option) => optionValue(option) === family);
30557
30914
  if (foundFont) {
30558
30915
  fontButton.label.value = foundFont.label;
30559
30916
  fontButton.selectedValue.value = foundFont.key;
@@ -30566,7 +30923,7 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
30566
30923
  }
30567
30924
  },
30568
30925
  onDeactivate: () => {
30569
- const defaultFont = fontOptions.find((option) => (option.value ?? option.label) === fontButton.defaultLabel.value);
30926
+ const defaultFont = currentFontOptions().find((option) => (option.value ?? option.label) === fontButton.defaultLabel.value);
30570
30927
  fontButton.label.value = defaultFont?.label ?? fontButton.defaultLabel.value;
30571
30928
  fontButton.selectedValue.value = defaultFont?.key ?? "";
30572
30929
  }
@@ -31778,7 +32135,7 @@ var V1_ENABLED_ON_SELECTION_REASON = /* @__PURE__ */ new Set([
31778
32135
  ]);
31779
32136
  var V1_ENABLED_ON_EMPTY_REASON = /* @__PURE__ */ new Set(["list", "numberedlist"]);
31780
32137
  function shouldKeepEnabledForV1ToolbarParity(name, commandState) {
31781
- if (name === "fontFamily" || name === "fontSize") return true;
32138
+ if (name === "fontFamily" || name === "fontSize") return commandState?.reason !== "document-readonly" && commandState?.reason !== "deleted-text-formatting-unsupported";
31782
32139
  if (V1_ENABLED_ON_EMPTY_REASON.has(name) && commandState?.disabled && commandState?.supported !== false && commandState?.reason == null) return true;
31783
32140
  if (!V1_ENABLED_ON_SELECTION_REASON.has(name)) return false;
31784
32141
  const reason = commandState?.reason;
@@ -31812,6 +32169,7 @@ var RESERVED_TOOLBAR_ITEM_NAMES = /* @__PURE__ */ new Set([
31812
32169
  * proof lane, and behavior tests resolve against a real object.
31813
32170
  */
31814
32171
  var BuiltInToolbar = class extends import_eventemitter3.default {
32172
+ #formattingRefusalTooltips;
31815
32173
  #imagePicker;
31816
32174
  #groupItems;
31817
32175
  #highlightOptions;
@@ -31843,6 +32201,7 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
31843
32201
  showPageNumberButton: false
31844
32202
  };
31845
32203
  this.toolbarItems = [];
32204
+ this.#formattingRefusalTooltips = /* @__PURE__ */ new WeakMap();
31846
32205
  this.overflowItems = [];
31847
32206
  this.pendingSelectionCapture = void 0;
31848
32207
  this.#imagePicker = null;
@@ -31969,7 +32328,7 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
31969
32328
  const signature = this.#fontOptionsSignature();
31970
32329
  if (signature !== this._lastFontOptionsSignature) {
31971
32330
  this._lastFontOptionsSignature = signature;
31972
- this.#rebuildToolbarItems();
32331
+ if (!this.#refreshFontFamilyOptions()) this.#rebuildToolbarItems();
31973
32332
  }
31974
32333
  this.updateToolbarState();
31975
32334
  };
@@ -32117,6 +32476,13 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
32117
32476
  this.#makeToolbarItems();
32118
32477
  this.emit("toolbar-items-changed");
32119
32478
  }
32479
+ #refreshFontFamilyOptions() {
32480
+ const items = this.#getAllToolbarItems().filter((item) => item.name.value === "fontFamily");
32481
+ if (!items.length) return false;
32482
+ const options = (this.#resolveToolbarFonts(this.config.fonts) ?? TOOLBAR_FONTS).map(normalizeFontOption);
32483
+ for (const item of items) item.nestedOptions.value = options.map((option) => ({ ...option }));
32484
+ return true;
32485
+ }
32120
32486
  #fontOptionsSignature() {
32121
32487
  if (this.config.fonts) return "custom-fonts";
32122
32488
  const options = this.superdoc?.fonts?.getFontFamilyOptions?.() ?? [];
@@ -32296,6 +32662,17 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
32296
32662
  const commandId = resolveBuiltInItemCommandId(name);
32297
32663
  if (!commandId) return false;
32298
32664
  const commandState = this.snapshot?.commands?.[commandId];
32665
+ if (commandState?.reason === "deleted-text-formatting-unsupported") {
32666
+ if (!this.#formattingRefusalTooltips.has(item)) this.#formattingRefusalTooltips.set(item, item.tooltip.value);
32667
+ item.tooltip.value = "Select kept text, or accept or reject the deletion before formatting.";
32668
+ } else if (this.#formattingRefusalTooltips.has(item)) {
32669
+ item.tooltip.value = this.#formattingRefusalTooltips.get(item);
32670
+ this.#formattingRefusalTooltips.delete(item);
32671
+ }
32672
+ if ((name === "list" || name === "numberedlist") && item.attributes.value.ariaBusy !== Boolean(commandState?.pending)) item.attributes.value = {
32673
+ ...item.attributes.value,
32674
+ ariaBusy: Boolean(commandState?.pending)
32675
+ };
32299
32676
  if (shouldKeepEnabledForV1ToolbarParity(name, commandState)) item.setDisabled(false);
32300
32677
  else item.setDisabled(Boolean(commandState?.disabled));
32301
32678
  const handlers = {
@@ -32763,6 +33140,9 @@ function deriveCollaborationConnectionKind(snapshot, hasSyncedBefore) {
32763
33140
  default: return hasSyncedBefore ? "reconnecting" : "initial";
32764
33141
  }
32765
33142
  }
33143
+ function destroyedDuringUpgradeError() {
33144
+ return /* @__PURE__ */ new Error("SuperDoc: instance was destroyed during upgrade");
33145
+ }
32766
33146
  function isV2ActiveEditorFacade(editor) {
32767
33147
  return Boolean(editor && typeof editor === "object" && editor.editorVersion === 2);
32768
33148
  }
@@ -32812,6 +33192,7 @@ function runtimeUserOf(user) {
32812
33192
  const text = (value) => typeof value === "string" && value ? value : void 0;
32813
33193
  return {
32814
33194
  name: text(user.name) ?? "",
33195
+ ...user.commentIdentity !== void 0 ? { commentIdentity: user.commentIdentity ? { ...user.commentIdentity } : null } : {},
32815
33196
  ...text(user.id) ? { id: text(user.id) } : {},
32816
33197
  ...text(user.email) ? { email: text(user.email) } : {},
32817
33198
  ...text(user.initials) ? { initials: text(user.initials) } : {}
@@ -32823,10 +33204,7 @@ var SuperDoc = class extends import_eventemitter3.default {
32823
33204
  }
32824
33205
  #destroyed;
32825
33206
  #diagnostics;
32826
- #isUpgrading;
32827
33207
  #pendingUpgrade;
32828
- /** Aborts an in-flight upgrade (sync wait or ready wait). */
32829
- #abortUpgrade;
32830
33208
  /**
32831
33209
  * Unsubscribe handle for the v2 presence → `awareness-update` bridge. Set
32832
33210
  * while a v2 collaborative editor is mounted and torn down on remount,
@@ -32840,6 +33218,7 @@ var SuperDoc = class extends import_eventemitter3.default {
32840
33218
  #v2ConnectionUnsub;
32841
33219
  /** Latest `collaboration-connection-change` payload, for late readers. */
32842
33220
  #v2ConnectionState;
33221
+ #v2ConnectionFailureReported;
32843
33222
  /** Whether the current collaboration room has completed a sync at least once. */
32844
33223
  #v2ConnectionHasSynced;
32845
33224
  /** Lock state captured before a local → v2 collaboration promotion remount. */
@@ -32935,13 +33314,12 @@ var SuperDoc = class extends import_eventemitter3.default {
32935
33314
  constructor(config) {
32936
33315
  super();
32937
33316
  this.#destroyed = false;
32938
- this.#isUpgrading = false;
32939
33317
  this.#pendingUpgrade = null;
32940
- this.#abortUpgrade = null;
32941
33318
  this.#v2AwarenessUnsub = null;
32942
33319
  this.#v2LockUnsub = null;
32943
33320
  this.#v2ConnectionUnsub = null;
32944
33321
  this.#v2ConnectionState = null;
33322
+ this.#v2ConnectionFailureReported = false;
32945
33323
  this.#v2ConnectionHasSynced = false;
32946
33324
  this.#pendingV2LockSeed = null;
32947
33325
  this.#mountWrapper = null;
@@ -33056,6 +33434,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33056
33434
  if (!config.selector) throw new Error("SuperDoc: selector is required");
33057
33435
  const container = typeof config.selector === "string" ? document.querySelector(config.selector) : config.selector;
33058
33436
  if (!(container instanceof HTMLElement)) throw new Error("SuperDoc: selector must be a valid CSS selector string or DOM element");
33437
+ const user = normalizeSuperDocUser(config.user);
33059
33438
  this.#diagnostics = createInteractionHistory(this, config, () => this.version, container);
33060
33439
  this.#surfaceManager = new SurfaceManager({ getModuleConfig: () => this.#surfacesConfig });
33061
33440
  this.#contentControlClickRoot = container;
@@ -33065,7 +33444,10 @@ var SuperDoc = class extends import_eventemitter3.default {
33065
33444
  container.addEventListener("keydown", this.#handleContentControlKeyDown, true);
33066
33445
  container.addEventListener("click", this.#handleContentControlClick, true);
33067
33446
  this.#onConfig("content-control:active-change", config.onContentControlActiveChange);
33068
- this.#init(config, container);
33447
+ this.#init({
33448
+ ...config,
33449
+ user
33450
+ }, container);
33069
33451
  this.#contentControlEventBridgeReady = true;
33070
33452
  if (this.listenerCount("content-control:active-change") > 0) this.ui;
33071
33453
  }
@@ -33118,7 +33500,6 @@ var SuperDoc = class extends import_eventemitter3.default {
33118
33500
  }
33119
33501
  this.editorVersion = 2;
33120
33502
  this.#validateLegacyConfig();
33121
- this.config.user = normalizeSuperDocUser(this.config.user);
33122
33503
  if (!this.config.layoutEngineOptions.virtualization) this.config.layoutEngineOptions.virtualization = {
33123
33504
  enabled: true,
33124
33505
  window: 5,
@@ -33144,7 +33525,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33144
33525
  }
33145
33526
  this.userColorMap = /* @__PURE__ */ new Map();
33146
33527
  this.colorIndex = 0;
33147
- this.version = "2.29.0";
33528
+ this.version = "2.30.0-next.13";
33148
33529
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
33149
33530
  this.superdocId = config.superdocId || v4();
33150
33531
  this.colors = this.config.colors ?? [];
@@ -33563,6 +33944,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33563
33944
  if (this.#v2ConnectionState?.documentId !== documentId) {
33564
33945
  this.#v2ConnectionState = null;
33565
33946
  this.#v2ConnectionHasSynced = false;
33947
+ this.#v2ConnectionFailureReported = false;
33566
33948
  }
33567
33949
  const publish = (snapshot) => {
33568
33950
  if (this.#destroyed) return;
@@ -33577,8 +33959,21 @@ var SuperDoc = class extends import_eventemitter3.default {
33577
33959
  superdoc: this
33578
33960
  };
33579
33961
  this.#v2ConnectionState = payload;
33580
- if (snapshot.state === "synced") this.#v2ConnectionHasSynced = true;
33581
- this.emit("collaboration-connection-change", payload);
33962
+ if (snapshot.state === "synced") {
33963
+ this.#v2ConnectionHasSynced = true;
33964
+ this.#v2ConnectionFailureReported = false;
33965
+ }
33966
+ const firstFailure = snapshot.state === "failed" && !this.#v2ConnectionFailureReported;
33967
+ if (firstFailure) this.#v2ConnectionFailureReported = true;
33968
+ const reportFailure = firstFailure && snapshot.detail !== "worker-transport-failed";
33969
+ try {
33970
+ this.emit("collaboration-connection-change", payload);
33971
+ } finally {
33972
+ if (reportFailure) {
33973
+ const exception = createCollaborationException("collaboration-connection-failed", documentId);
33974
+ if (exception) this.emit("exception", exception);
33975
+ }
33976
+ }
33582
33977
  };
33583
33978
  try {
33584
33979
  const initialSnapshot = connection.getSnapshot?.();
@@ -33691,41 +34086,43 @@ var SuperDoc = class extends import_eventemitter3.default {
33691
34086
  throw Object.assign(/* @__PURE__ */ new Error("SuperDoc: a different collaboration target is already upgrading"), { code: "collaboration-upgrade-target-conflict" });
33692
34087
  }
33693
34088
  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
- })();
34089
+ let rejectOnDestroy = () => {};
34090
+ const destroyed = new Promise((_resolve, reject) => {
34091
+ rejectOnDestroy = reject;
34092
+ });
34093
+ const operation = Promise.race([this.#runUpgrade(target), destroyed]).finally(() => {
34094
+ this.#pendingUpgrade = null;
34095
+ });
33723
34096
  this.#pendingUpgrade = {
33724
34097
  target,
33725
- promise: operation
34098
+ promise: operation,
34099
+ rejectOnDestroy
33726
34100
  };
33727
34101
  return operation;
33728
34102
  }
34103
+ async #runUpgrade(target) {
34104
+ const localSource = await this.#captureCurrentDocxSourceForUpgrade();
34105
+ const rollback = this.#snapshotV2UpgradeState();
34106
+ const lockSeed = this.#snapshotV2LockSeed();
34107
+ this.#assertNotDestroyed();
34108
+ try {
34109
+ this.#pendingV2LockSeed = lockSeed;
34110
+ const promotion = await this.#promoteSingleDocumentToV2Collaboration(target, localSource);
34111
+ this.#assertNotDestroyed();
34112
+ this.#finalizeSingleDocumentV2Collaboration(promotion, target);
34113
+ this.#pendingV2LockSeed = null;
34114
+ const attachedDocumentId = this.activeEditor?.options?.documentId;
34115
+ const attachedRoomId = promotion.configDoc.v2Collaboration?.documentId;
34116
+ 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");
34117
+ return {
34118
+ roomId: attachedRoomId,
34119
+ documentId: attachedDocumentId
34120
+ };
34121
+ } catch (err) {
34122
+ if (!this.#destroyed) this.#rollbackV2UpgradeState(rollback);
34123
+ throw err;
34124
+ }
34125
+ }
33729
34126
  #sameUpgradeTarget(left, right) {
33730
34127
  if (left.token !== right.token) return false;
33731
34128
  const seen = /* @__PURE__ */ new WeakMap();
@@ -33790,7 +34187,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33790
34187
  * async waits inside upgradeToCollaboration().
33791
34188
  */
33792
34189
  #assertNotDestroyed() {
33793
- if (this.#destroyed) throw new Error("SuperDoc: instance was destroyed during upgrade");
34190
+ if (this.#destroyed) throw destroyedDuringUpgradeError();
33794
34191
  }
33795
34192
  /**
33796
34193
  * Return the superdoc store, throwing a clear lifecycle error if
@@ -33889,6 +34286,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33889
34286
  this.#writeStoreDocV2Collaboration(storeDoc, v2Collaboration);
33890
34287
  this.isCollaborative = true;
33891
34288
  await editor.upgradeToCollaboration(localSource, v2Collaboration);
34289
+ this.#assertNotDestroyed();
33892
34290
  configDoc.data = localSource;
33893
34291
  storeDoc.data = localSource;
33894
34292
  return {
@@ -33948,7 +34346,6 @@ var SuperDoc = class extends import_eventemitter3.default {
33948
34346
  */
33949
34347
  #validateUpgradePrerequisites(options) {
33950
34348
  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
34349
  if (this.isCollaborative) throw new Error("SuperDoc: instance is already collaborative");
33953
34350
  const cfg = this.config;
33954
34351
  const docxDocs = cfg.documents.filter((d) => d.type === DOCX);
@@ -34003,10 +34400,15 @@ var SuperDoc = class extends import_eventemitter3.default {
34003
34400
  this.#requireReady("setUser");
34004
34401
  const name = typeof user?.name === "string" ? user.name.trim() : "";
34005
34402
  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
- });
34403
+ let next;
34404
+ try {
34405
+ next = normalizeSuperDocUser({
34406
+ ...user,
34407
+ name
34408
+ });
34409
+ } catch (error) {
34410
+ return Promise.reject(error);
34411
+ }
34010
34412
  const applied = this.#userSwitch.then(() => this.#applyUser(next));
34011
34413
  this.#userSwitch = applied.catch(() => {});
34012
34414
  return applied;
@@ -34020,7 +34422,7 @@ var SuperDoc = class extends import_eventemitter3.default {
34020
34422
  "email",
34021
34423
  "image",
34022
34424
  "initials"
34023
- ].every((key) => (current[key] ?? null) === (next[key] ?? null))) return;
34425
+ ].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
34426
  const runtimes = this.#editorRuntimeRegistry.getAll();
34025
34427
  const results = await Promise.allSettled(runtimes.map((runtime) => runtime.setUser?.(runtimeUserOf(next))));
34026
34428
  const failure = results.find((result) => result.status === "rejected");
@@ -35355,10 +35757,7 @@ var SuperDoc = class extends import_eventemitter3.default {
35355
35757
  this.#contentControlClickRoot = null;
35356
35758
  this.#contentControlActiveChangeUnsub?.();
35357
35759
  this.#contentControlActiveChangeUnsub = null;
35358
- if (this.#abortUpgrade) {
35359
- this.#abortUpgrade();
35360
- this.#abortUpgrade = null;
35361
- }
35760
+ this.#pendingUpgrade?.rejectOnDestroy(destroyedDuringUpgradeError());
35362
35761
  if (this.#surfaceManager) this.#surfaceManager.destroy();
35363
35762
  this.toolbar?.destroy?.();
35364
35763
  this.commentsList?.close();