superdoc 2.29.0 → 2.30.0-next.9

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 (130) 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-Cq8trPVC.es.js} +3179 -2639
  11. package/dist/chunks/{create-super-doc-ui-L60GhVAX.cjs → create-super-doc-ui-FH5GSHph.cjs} +3197 -2651
  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/create-super-doc-ui.d.ts +1 -17
  92. package/dist/superdoc/src/public/ui/document-change-records.d.ts +7 -0
  93. package/dist/superdoc/src/public/ui/entity-rows.d.ts +14 -0
  94. package/dist/superdoc/src/public/ui/font-options.d.ts +7 -0
  95. package/dist/superdoc/src/public/ui/format-painter-paragraph.d.ts +7 -0
  96. package/dist/superdoc/src/public/ui/format-painter-state.d.ts +29 -0
  97. package/dist/superdoc/src/public/ui/heavy-read-policy.d.ts +11 -0
  98. package/dist/superdoc/src/public/ui/host-events.d.ts +3 -0
  99. package/dist/superdoc/src/public/ui/inline-values.d.ts +79 -0
  100. package/dist/superdoc/src/public/ui/loose-record.d.ts +4 -0
  101. package/dist/superdoc/src/public/ui/reasons.d.ts +1 -0
  102. package/dist/superdoc/src/public/ui/selection-targets.d.ts +47 -0
  103. package/dist/superdoc/src/public/ui/shared-tracked-changes-catalog.d.ts +16 -0
  104. package/dist/superdoc/src/public/ui/tracked-change-ids.d.ts +51 -0
  105. package/dist/superdoc/src/public/ui/types.d.ts +12 -2
  106. package/dist/superdoc/src/public/ui/ui-bench-timing.d.ts +9 -0
  107. package/dist/superdoc/src/public/ui.d.cts +14 -0
  108. package/dist/superdoc/src/public/ui.d.ts +1 -1
  109. package/dist/superdoc/src/stores/comments-store.d.ts +23022 -546
  110. package/dist/superdoc/src/stores/helpers/comment-grouping.d.ts +1 -1
  111. package/dist/superdoc/src/stores/helpers/comment-identity.d.ts +10 -10
  112. package/dist/superdoc/src/stores/helpers/review-presentation.d.ts +91 -0
  113. package/dist/superdoc/src/stores/helpers/tracked-change-identity.d.ts +4 -2
  114. package/dist/superdoc/src/stores/helpers/tracked-change-row-values.d.ts +1 -8
  115. package/dist/superdoc/src/stores/helpers/tracked-change-thread-index.d.ts +1 -1
  116. package/dist/superdoc/src/stores/helpers/v2-review-adapters.d.ts +147 -0
  117. package/dist/superdoc/src/stores/superdoc-store.d.ts +12 -2895
  118. package/dist/superdoc.cjs +837 -442
  119. package/dist/superdoc.es.js +837 -442
  120. package/dist/word-layout/src/marker-utils.d.ts +0 -5
  121. package/dist/word-layout/src/unit-conversions.d.ts +0 -1
  122. package/dist-cdn/style.layered.css +1 -1
  123. package/dist-cdn/superdoc.min.css +1 -1
  124. package/dist-cdn/superdoc.min.js +38 -42
  125. package/package.json +12 -3
  126. package/dist/document-api/src/diff/index.d.ts +0 -3
  127. package/dist/document-api/src/write/locator.d.ts +0 -24
  128. package/dist/layout-engine/painters/dom/src/features/feature-registry.d.ts +0 -41
  129. package/dist/superdoc/src/internal/toolbar/built-in/format-negation.d.ts +0 -11
  130. 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-Cq8trPVC.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.9";
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,
@@ -13002,7 +13195,7 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13002
13195
  "data-track-change-semantic-color-key": comment.semanticColorKey ?? void 0,
13003
13196
  ref_for: true,
13004
13197
  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) => {
13198
+ }, [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
13199
  return openBlock(), createElementBlock("div", {
13007
13200
  key: lineIndex,
13008
13201
  class: "tracked-change-detail-line"
@@ -13027,7 +13220,7 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13027
13220
  style: normalizeStyle(trackedChangeImagePreviewStyle(comment.trackedChangeImagePreview)),
13028
13221
  loading: "lazy",
13029
13222
  decoding: "async"
13030
- }, null, 12, _hoisted_38)], 8, _hoisted_37)) : createCommentVNode("", true)], 10, _hoisted_12$2),
13223
+ }, null, 12, _hoisted_38)], 8, _hoisted_37)) : createCommentVNode("", true)], 10, _hoisted_12$3),
13031
13224
  shouldTruncate.value && isTextOverflowing.value && index === 0 ? (openBlock(), createElementBlock("div", {
13032
13225
  key: 0,
13033
13226
  class: "show-more-toggle",
@@ -13121,7 +13314,7 @@ var CommentDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
13121
13314
  ], 64))], 14, _hoisted_1$26)), [[_directive_click_outside, handleClickOutside]]);
13122
13315
  };
13123
13316
  }
13124
- }), [["__scopeId", "data-v-4220ce17"]]);
13317
+ }), [["__scopeId", "data-v-c0cc448b"]]);
13125
13318
  //#endregion
13126
13319
  //#region src/components/CommentsLayer/commentsList/ReviewDirectoryListItem.vue?vue&type=script&setup=true&lang.ts
13127
13320
  var _hoisted_1$25 = [
@@ -13888,124 +14081,6 @@ var composeAuthorColorResolver = (config) => {
13888
14081
  };
13889
14082
  };
13890
14083
  //#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
14084
  //#region src/core/superdoc-ui-owner.ts
14010
14085
  var ownerIds = /* @__PURE__ */ new WeakMap();
14011
14086
  function getSuperDocUiOwnerId(superdoc) {
@@ -14118,18 +14193,42 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
14118
14193
  const { activeComment, activeFloatingCommentInstanceId, editorCommentPositions, pendingComment, editingCommentId, instantSidebarAlignmentTargetY, instantSidebarAlignmentThreadId, instantSidebarAlignmentInstanceId } = storeToRefs(piniaCommentsStore);
14119
14194
  const { activeZoom } = storeToRefs(superdocStore);
14120
14195
  const committedPresentation = computed$1(() => commentsStore.committedReviewPresentations?.[String(props.currentDocument?.id)] ?? null);
14196
+ const committedInstanceCache = /* @__PURE__ */ new Map();
14197
+ let committedInstanceCacheOwner = "";
14198
+ const sameReviewInstance = (cached, next) => {
14199
+ const before = Object.entries(cached).filter(([key]) => key !== "comment" && key !== "rowVersion");
14200
+ const after = Object.entries(next);
14201
+ if (before.length !== after.length) return false;
14202
+ const previous = new Map(before);
14203
+ return after.every(([key, value]) => previous.has(key) && (key === "positionEntry" ? JSON.stringify(previous.get(key)) === JSON.stringify(value) : Object.is(previous.get(key), value)));
14204
+ };
14121
14205
  const committedInstances = computed$1(() => {
14122
14206
  const presentation = committedPresentation.value;
14123
14207
  if (!presentation) return null;
14124
14208
  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) => {
14209
+ const owner = `${presentation.epoch}\u0000${presentation.coordinate?.mountId ?? ""}`;
14210
+ if (owner !== committedInstanceCacheOwner) {
14211
+ committedInstanceCache.clear();
14212
+ committedInstanceCacheOwner = owner;
14213
+ }
14214
+ const live = /* @__PURE__ */ new Set();
14215
+ const instances = presentation.instances.flatMap((instance) => {
14126
14216
  const comment = instance.rowId === "pending" ? pendingComment.value : rows.get(instance.rowId);
14127
- return comment ? [{
14217
+ if (!comment) return [];
14218
+ const rowVersion = presentation.rowVersions[instance.rowId] ?? 0;
14219
+ live.add(instance.id);
14220
+ const cached = committedInstanceCache.get(instance.id);
14221
+ if (cached && cached.comment === comment && cached.rowVersion === rowVersion && sameReviewInstance(cached, instance)) return [cached];
14222
+ const merged = {
14128
14223
  ...instance,
14129
14224
  comment,
14130
- rowVersion: presentation.rowVersions[instance.rowId] ?? 0
14131
- }] : [];
14225
+ rowVersion
14226
+ };
14227
+ committedInstanceCache.set(instance.id, merged);
14228
+ return [merged];
14132
14229
  });
14230
+ for (const id of committedInstanceCache.keys()) if (!live.has(id)) committedInstanceCache.delete(id);
14231
+ return instances;
14133
14232
  });
14134
14233
  const floatingCommentInstances = computed$1(() => {
14135
14234
  if (committedInstances.value) return committedInstances.value.filter((instance) => instance.rowId !== "pending");
@@ -14545,6 +14644,18 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
14545
14644
  scheduleDeferredRemeasure(() => allPositions.value.map((pos) => pos.id));
14546
14645
  });
14547
14646
  };
14647
+ const resizeHandlers = /* @__PURE__ */ new Map();
14648
+ const resizeHandlerFor = (id) => {
14649
+ let handler = resizeHandlers.get(id);
14650
+ if (!handler) {
14651
+ handler = () => {
14652
+ const position = allPositions.value.find((candidate) => candidate.id === id);
14653
+ if (position) handleResize(position);
14654
+ };
14655
+ resizeHandlers.set(id, handler);
14656
+ }
14657
+ return handler;
14658
+ };
14548
14659
  const setInstantLayoutTransitionsDisabled = (disabled) => {
14549
14660
  if (!disabled && directDecisionContinuityTargetId.value != null) return;
14550
14661
  disableInstantLayoutTransitions.value = disabled;
@@ -14949,6 +15060,7 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
14949
15060
  visibilityChanged = true;
14950
15061
  }
14951
15062
  if (visibilityChanged) visibleIds.value = newVisible;
15063
+ for (const id of resizeHandlers.keys()) if (!currentIds.has(id)) resizeHandlers.delete(id);
14952
15064
  for (const id of prevPositionIds) if (!currentIds.has(id)) delete _heightsCache[id];
14953
15065
  const setChanged = prevPositionIds.size !== currentIds.size || [...prevPositionIds].some((id) => !currentIds.has(id));
14954
15066
  const orderChanged = renderedPositionOrder.length !== positionOrder.length || renderedPositionOrder.some((id, index) => id !== positionOrder[index]);
@@ -15041,12 +15153,13 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
15041
15153
  style: normalizeStyle({
15042
15154
  top: (pos.isOffscreenReviewCard && !committedPresentation.value ? pos.top - sidebarOffsetY.value : pos.top) + "px",
15043
15155
  height: pos.height + "px",
15044
- transition: committedPresentation.value || disableInstantLayoutTransitions.value ? "none" : void 0
15156
+ transition: committedPresentation.value || disableInstantLayoutTransitions.value ? "none" : void 0,
15157
+ visibility: pos.instanceRef?.provisional ? "hidden" : void 0
15045
15158
  })
15046
15159
  }, [shouldRenderDialog(pos) ? (openBlock(), createBlock(CommentDialog_default, {
15047
15160
  key: pos.id + commentsRenderKey.value,
15048
15161
  onReady: handleDialog,
15049
- onResize: ($event) => handleResize(pos),
15162
+ onResize: resizeHandlerFor(pos.id),
15050
15163
  class: "floating-comment",
15051
15164
  parent: __props.parent,
15052
15165
  comment: pos.commentRef,
@@ -15066,7 +15179,7 @@ var FloatingComments_default = /* @__PURE__ */ _plugin_vue_export_helper_default
15066
15179
  }), 128))], 4)], 36);
15067
15180
  };
15068
15181
  }
15069
- }), [["__scopeId", "data-v-aaf2c537"]]);
15182
+ }), [["__scopeId", "data-v-1070f5a1"]]);
15070
15183
  //#endregion
15071
15184
  //#region src/components/CommentsLayer/PdfCommentsLayer.vue?vue&type=script&setup=true&lang.ts
15072
15185
  var _hoisted_1$22 = {
@@ -15199,7 +15312,7 @@ var PdfCommentsLayer_default = /* @__PURE__ */ _plugin_vue_export_helper_default
15199
15312
  }), 128))]);
15200
15313
  };
15201
15314
  }
15202
- }), [["__scopeId", "data-v-86166837"]]);
15315
+ }), [["__scopeId", "data-v-e3ae65fa"]]);
15203
15316
  //#endregion
15204
15317
  //#region src/components/Whiteboard/use-whiteboard.ts
15205
15318
  var useWhiteboard = ({ proxy, layers, documents, modules }) => {
@@ -15791,6 +15904,21 @@ function useCommentSmallScreen({ commentsModuleConfig, superdocRoot, layers }) {
15791
15904
  };
15792
15905
  }
15793
15906
  //#endregion
15907
+ //#region src/composables/use-comment-sidebar.ts
15908
+ function useCommentSidebar({ documentKey, enabled, hasContent }) {
15909
+ const reserved = ref(false);
15910
+ watch$1(documentKey, () => {
15911
+ reserved.value = false;
15912
+ }, { flush: "sync" });
15913
+ watch$1(() => enabled.value && hasContent.value, (visible) => {
15914
+ if (visible) reserved.value = true;
15915
+ }, {
15916
+ immediate: true,
15917
+ flush: "sync"
15918
+ });
15919
+ return computed$1(() => enabled.value && (hasContent.value || reserved.value));
15920
+ }
15921
+ //#endregion
15794
15922
  //#region src/composables/use-compact-comment-popover.ts
15795
15923
  var POPOVER_WIDTH_PX = 320;
15796
15924
  var SAFE_MARGIN_PX = 12;
@@ -16169,6 +16297,116 @@ function resolveV2ReviewTargetCommentId(target, getComment) {
16169
16297
  return commentId != null ? String(commentId) : null;
16170
16298
  }
16171
16299
  //#endregion
16300
+ //#region src/helpers/v2-review-geometry-delivery.ts
16301
+ function createReviewGeometryDelivery(deliver) {
16302
+ const packets = /* @__PURE__ */ new Map();
16303
+ let stageOwners = /* @__PURE__ */ new WeakMap();
16304
+ let active = null;
16305
+ const documentOf = (current) => current.documentId != null ? String(current.documentId) : current.mountContainer?.dataset.superdocV2DocumentId ?? null;
16306
+ const matches = (documentId, current) => current.mountContainer?.dataset.superdocV2DocumentId === documentId && (current.documentId == null || String(current.documentId) === documentId);
16307
+ const matchesPaint = (packet, current) => typeof packet.payload.paintEpoch !== "number" || packet.payload.paintEpoch === current.epoch;
16308
+ const observe = (current) => {
16309
+ const stage = current.mountContainer;
16310
+ const documentId = documentOf(current);
16311
+ if (!stage || !documentId || !matches(documentId, current)) return;
16312
+ const previousOwner = stageOwners.get(stage);
16313
+ if (previousOwner && previousOwner !== documentId) packets.delete(previousOwner);
16314
+ stageOwners.set(stage, documentId);
16315
+ const packet = packets.get(documentId);
16316
+ if (packet?.stage && packet.stage !== stage) packets.delete(documentId);
16317
+ else if (packet && (!active || active.stage !== stage || active.documentId !== documentId || active.epoch !== current.epoch)) packet.delivered = false;
16318
+ active = {
16319
+ documentId,
16320
+ stage,
16321
+ epoch: current.epoch
16322
+ };
16323
+ };
16324
+ const dispatch = (documentId, packet, current, replay = false) => {
16325
+ if (!current.mountContainer || !matches(documentId, current) || !matchesPaint(packet, current)) return false;
16326
+ if (packet.stage && packet.stage !== current.mountContainer || packet.delivering || packet.delivered && !replay) return false;
16327
+ const wasDelivered = packet.delivered;
16328
+ const previousStage = packet.stage;
16329
+ packet.stage = current.mountContainer;
16330
+ packet.delivered = true;
16331
+ packet.delivering = true;
16332
+ try {
16333
+ deliver(current, packet.payload);
16334
+ } catch (error) {
16335
+ packet.delivered = wasDelivered;
16336
+ packet.stage = previousStage;
16337
+ throw error;
16338
+ } finally {
16339
+ packet.delivering = false;
16340
+ }
16341
+ return true;
16342
+ };
16343
+ const notify = (documentId, payload, current, ready = true) => {
16344
+ if (!payload.reviewGeometry || !Array.isArray(payload.pageIndices)) return false;
16345
+ let key;
16346
+ try {
16347
+ key = JSON.stringify([
16348
+ documentId,
16349
+ payload.documentEpoch,
16350
+ payload.paintEpoch,
16351
+ payload.commitSequence,
16352
+ payload.pageIndices,
16353
+ payload.zoomScale,
16354
+ payload.reviewGeometry
16355
+ ]);
16356
+ } catch {
16357
+ return false;
16358
+ }
16359
+ const isCurrent = current != null && matches(documentId, current);
16360
+ if (isCurrent) observe(current);
16361
+ const previous = packets.get(documentId);
16362
+ if (previous && previous.payload.documentEpoch === payload.documentEpoch && typeof previous.payload.paintEpoch === "number" && typeof payload.paintEpoch === "number" && payload.paintEpoch < previous.payload.paintEpoch) return false;
16363
+ if (previous && previous.payload.documentEpoch === payload.documentEpoch && typeof previous.payload.commitSequence === "number" && typeof payload.commitSequence === "number" && payload.commitSequence < previous.payload.commitSequence) return false;
16364
+ if (previous && previous.payload.documentEpoch === payload.documentEpoch && typeof previous.payload.generation === "number" && typeof payload.generation === "number" && payload.generation < previous.payload.generation) return false;
16365
+ if (isCurrent && typeof payload.paintEpoch === "number" && typeof current.epoch === "number" && payload.paintEpoch < current.epoch) return false;
16366
+ if (previous?.key === key) {
16367
+ previous.payload = payload;
16368
+ return isCurrent && ready ? dispatch(documentId, previous, current) : false;
16369
+ }
16370
+ const packet = {
16371
+ payload,
16372
+ key,
16373
+ stage: isCurrent ? current.mountContainer : previous && previous.payload.documentEpoch === payload.documentEpoch ? previous.stage : null,
16374
+ delivered: false,
16375
+ delivering: false
16376
+ };
16377
+ packets.set(documentId, packet);
16378
+ if (!isCurrent || !ready) return false;
16379
+ try {
16380
+ return dispatch(documentId, packet, current);
16381
+ } catch (error) {
16382
+ if (previous) packets.set(documentId, previous);
16383
+ else packets.delete(documentId);
16384
+ throw error;
16385
+ }
16386
+ };
16387
+ return {
16388
+ notify,
16389
+ observe,
16390
+ flush(current) {
16391
+ observe(current);
16392
+ const documentId = documentOf(current);
16393
+ const packet = documentId == null ? null : packets.get(documentId);
16394
+ return documentId && packet ? dispatch(documentId, packet, current) : false;
16395
+ },
16396
+ replay(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, true) : false;
16401
+ },
16402
+ reset() {
16403
+ packets.clear();
16404
+ stageOwners = /* @__PURE__ */ new WeakMap();
16405
+ active = null;
16406
+ }
16407
+ };
16408
+ }
16409
+ //#endregion
16172
16410
  //#region src/helpers/v2-mutation-exception.ts
16173
16411
  var FAILURE_SOURCES = /* @__PURE__ */ new Set(["shell", "receipt"]);
16174
16412
  var DIAGNOSTIC_CODE = /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/;
@@ -16538,7 +16776,7 @@ function usePasswordPrompt({ getSurfaceManager, getPasswordPromptConfig, onUnhan
16538
16776
  })
16539
16777
  });
16540
16778
  } else {
16541
- const mod = await import("./chunks/PasswordPromptSurface-CZqhDQQ1.es.js");
16779
+ const mod = await import("./chunks/PasswordPromptSurface-DjV1JgjD.es.js");
16542
16780
  const surfaceId = `password-prompt-${doc.id}`;
16543
16781
  handle = manager.open({
16544
16782
  id: surfaceId,
@@ -17198,7 +17436,7 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
17198
17436
  } else {
17199
17437
  let FindReplaceSurface;
17200
17438
  try {
17201
- FindReplaceSurface = (await import("./chunks/FindReplaceSurface-B_5ggdWg.es.js")).default;
17439
+ FindReplaceSurface = (await import("./chunks/FindReplaceSurface-BEnRLpD9.es.js")).default;
17202
17440
  } catch {
17203
17441
  opening = false;
17204
17442
  return;
@@ -17572,11 +17810,11 @@ var _hoisted_8$3 = { class: "input-row url-input-row" };
17572
17810
  var _hoisted_9$2 = ["innerHTML"];
17573
17811
  var _hoisted_10$2 = ["readonly", "onKeydown"];
17574
17812
  var _hoisted_11$2 = ["innerHTML"];
17575
- var _hoisted_12$1 = {
17813
+ var _hoisted_12$2 = {
17576
17814
  key: 0,
17577
17815
  class: "input-row link-buttons"
17578
17816
  };
17579
- var _hoisted_13$1 = ["disabled"];
17817
+ var _hoisted_13$2 = ["disabled"];
17580
17818
  var _hoisted_14$1 = ["innerHTML"];
17581
17819
  var _hoisted_15$1 = ["disabled"];
17582
17820
  var _hoisted_16$1 = {
@@ -17835,7 +18073,7 @@ var LinkInput_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
17835
18073
  "data-item": "btn-link-open"
17836
18074
  }, null, 10, _hoisted_11$2)
17837
18075
  ]),
17838
- !isViewingMode.value ? (openBlock(), createElementBlock("div", _hoisted_12$1, [isEditing.value ? (openBlock(), createElementBlock("button", {
18076
+ !isViewingMode.value ? (openBlock(), createElementBlock("div", _hoisted_12$2, [isEditing.value ? (openBlock(), createElementBlock("button", {
17839
18077
  key: 0,
17840
18078
  class: "remove-btn",
17841
18079
  onClick: handleRemove,
@@ -17844,7 +18082,7 @@ var LinkInput_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
17844
18082
  }, [createElementVNode("div", {
17845
18083
  class: "remove-btn__icon",
17846
18084
  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", {
18085
+ }, null, 8, _hoisted_14$1), _cache[6] || (_cache[6] = createTextVNode(" Remove ", -1))], 8, _hoisted_13$2)) : createCommentVNode("", true), createElementVNode("button", {
17848
18086
  class: normalizeClass(["sd-submit-btn", { "disable-btn": isDisabled.value }]),
17849
18087
  onClick: handleSubmit,
17850
18088
  disabled: isSubmitting.value,
@@ -18754,6 +18992,7 @@ function createV2EditorRuntimeAdapter(options) {
18754
18992
  const { id, documentId, root, host, getLegacyEditorProjection, onUnregister } = options;
18755
18993
  let snapshot = host.getSnapshot();
18756
18994
  let didDispose = false;
18995
+ let detached = false;
18757
18996
  let unregistered = false;
18758
18997
  let mountedFocusHandle = null;
18759
18998
  let tokenRevision = 0;
@@ -19050,7 +19289,17 @@ function createV2EditorRuntimeAdapter(options) {
19050
19289
  };
19051
19290
  }
19052
19291
  }
19292
+ function releaseHostSubscriptions() {
19293
+ hostUnsubscribe?.();
19294
+ hostUnsubscribe = null;
19295
+ pageMetricsUnsubscribe?.();
19296
+ pageMetricsUnsubscribe = null;
19297
+ selectionUnsubscribe?.();
19298
+ selectionUnsubscribe = null;
19299
+ activeSelectionController = null;
19300
+ }
19053
19301
  function handleHostSnapshot(next) {
19302
+ if (detached || didDispose) return;
19054
19303
  snapshot = next;
19055
19304
  invalidatePositionTokens();
19056
19305
  syncSelectionSubscription();
@@ -19064,10 +19313,7 @@ function createV2EditorRuntimeAdapter(options) {
19064
19313
  });
19065
19314
  if (next.state === "disposed" && !didDispose) {
19066
19315
  didDispose = true;
19067
- hostUnsubscribe?.();
19068
- pageMetricsUnsubscribe?.();
19069
- selectionUnsubscribe?.();
19070
- selectionUnsubscribe = null;
19316
+ releaseHostSubscriptions();
19071
19317
  emit({ type: "disposed" });
19072
19318
  listeners.clear();
19073
19319
  if (!unregistered) {
@@ -19214,10 +19460,7 @@ function createV2EditorRuntimeAdapter(options) {
19214
19460
  await host.dispose();
19215
19461
  if (!didDispose) {
19216
19462
  didDispose = true;
19217
- hostUnsubscribe?.();
19218
- pageMetricsUnsubscribe?.();
19219
- selectionUnsubscribe?.();
19220
- selectionUnsubscribe = null;
19463
+ releaseHostSubscriptions();
19221
19464
  emit({ type: "disposed" });
19222
19465
  listeners.clear();
19223
19466
  if (!unregistered) {
@@ -19296,8 +19539,17 @@ function createV2EditorRuntimeAdapter(options) {
19296
19539
  }
19297
19540
  },
19298
19541
  attachMountHandle(handle) {
19542
+ if (detached || didDispose) return;
19299
19543
  mountedFocusHandle = handle;
19300
19544
  syncSelectionSubscription();
19545
+ },
19546
+ detach() {
19547
+ if (detached) return;
19548
+ detached = true;
19549
+ unregistered = true;
19550
+ releaseHostSubscriptions();
19551
+ mountedFocusHandle = null;
19552
+ listeners.clear();
19301
19553
  }
19302
19554
  };
19303
19555
  }
@@ -20460,7 +20712,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20460
20712
  __name: "SuperDoc",
20461
20713
  emits: ["selection-update"],
20462
20714
  setup(__props, { emit: __emit }) {
20463
- const PdfViewer = defineAsyncComponent(() => import("./chunks/PdfViewer-CW09MNrB.es.js"));
20715
+ const PdfViewer = defineAsyncComponent(() => import("./chunks/PdfViewer-C2r-K8zC.es.js"));
20464
20716
  const getDocumentLoadPassword = (doc) => doc.password ?? proxy.$superdoc.config.password;
20465
20717
  const resolveDocumentV2Collaboration = (doc) => {
20466
20718
  const rawConfig = doc?.v2Collaboration;
@@ -20545,7 +20797,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20545
20797
  if (commentsConfig.highlightHoverColor) vars["--sd-comments-highlight-hover"] = commentsConfig.highlightHoverColor;
20546
20798
  const activeTrackChangeColors = {
20547
20799
  ...commentsConfig.trackChangeHighlightColors || {},
20548
- ...commentsConfig.trackChangeActiveHighlightColors || {}
20800
+ ...commentsConfig.trackChangeActiveHighlightColors
20549
20801
  };
20550
20802
  if (activeTrackChangeColors.insertBorder) vars["--sd-tracked-changes-insert-border"] = activeTrackChangeColors.insertBorder;
20551
20803
  if (activeTrackChangeColors.insertBackground) vars["--sd-tracked-changes-insert-background"] = activeTrackChangeColors.insertBackground;
@@ -20574,6 +20826,8 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20574
20826
  const v2ReviewSidebarUnlocked = ref(false);
20575
20827
  const v2GeometryEpoch = ref(null);
20576
20828
  let v2GeometryRafHandle = 0;
20829
+ let v2GeometryReplayRafHandle = 0;
20830
+ let v2GeometryReplayGeneration = 0;
20577
20831
  let v2TrackedChangeRestampRetainPaints = 0;
20578
20832
  let v2TrackedChangeRestampRetentionReason = null;
20579
20833
  const activeEditorRef = computed$1(() => proxy.$superdoc.activeEditor);
@@ -20757,6 +21011,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
20757
21011
  if (document && provider?.sendStateless) document.provider = null;
20758
21012
  const entry = v2Runtimes.get(documentId);
20759
21013
  if (!entry) return;
21014
+ entry.adapter.detach();
20760
21015
  proxy.$superdoc["unregisterEditorRuntime"](entry.runtimeId);
20761
21016
  v2Runtimes.delete(documentId);
20762
21017
  const hostRoot = subDocumentRoots.get(documentId);
@@ -21522,10 +21777,11 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21522
21777
  const publishV2PendingPositionEntry = (entry) => {
21523
21778
  if (!entry) return;
21524
21779
  handleEditorLocationsUpdate({
21525
- ...editorCommentPositions.value ?? {},
21780
+ ...editorCommentPositions.value,
21526
21781
  pending: entry
21527
21782
  });
21528
21783
  };
21784
+ let scheduleV2GeometryReplay = () => {};
21529
21785
  const v2GeometryPublisher = resolvedEditorIntegration.createGeometryPublisher({
21530
21786
  getLayersContainer: () => layers.value ?? null,
21531
21787
  isCommentsEnabled: () => Boolean(commentsModuleConfig.value) && shouldRenderCommentsInViewing.value,
@@ -21541,7 +21797,8 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21541
21797
  v2ReviewSidebarUnlocked.value = true;
21542
21798
  v2GeometryEpoch.value = v2GeometryPublisher.getLastEpoch();
21543
21799
  }
21544
- }
21800
+ },
21801
+ onCommittedPagePaint: ({ payload }) => scheduleV2GeometryReplay(payload)
21545
21802
  });
21546
21803
  const armV2TrackedChangeRestampGeometryRetention = (reason = "tracked-change-restamp") => {
21547
21804
  v2TrackedChangeRestampRetainPaints = Math.max(v2TrackedChangeRestampRetainPaints, 3);
@@ -21561,20 +21818,53 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21561
21818
  v2TrackedChangeRestampRetentionReason = null;
21562
21819
  };
21563
21820
  const resolveV2GeometryPublishOptions = (options) => typeof options === "function" ? options() : options;
21821
+ const isV2GeometryReady = (current) => {
21822
+ if (!current?.mountContainer || !commentsModuleConfig.value || !shouldRenderCommentsInViewing.value) return false;
21823
+ const mount = current.mount;
21824
+ let liveEpoch;
21825
+ try {
21826
+ liveEpoch = mount?.getRenderEpoch?.();
21827
+ } catch {
21828
+ return false;
21829
+ }
21830
+ return v2GeometryPublisher.getLastPayload() === current && v2GeometryPublisher.getLastEpoch() != null && v2GeometryPublisher.getLastEpoch() === current.epoch && (liveEpoch == null || liveEpoch === current.epoch);
21831
+ };
21564
21832
  const scheduleV2GeometryPublish = (payload, options = void 0) => {
21565
21833
  v2GeometryRender.value = payload;
21566
21834
  if (v2GeometryRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryRafHandle);
21835
+ const publish = () => {
21836
+ const current = v2GeometryRender.value;
21837
+ Promise.resolve(v2GeometryPublisher.publish(current, resolveV2GeometryPublishOptions(options))).then(() => {
21838
+ if (current === v2GeometryRender.value && isV2GeometryReady(current)) v2ReviewGeometryDelivery.flush(current);
21839
+ });
21840
+ };
21567
21841
  if (typeof requestAnimationFrame !== "function") {
21568
- v2GeometryPublisher.publish(v2GeometryRender.value, resolveV2GeometryPublishOptions(options));
21842
+ publish();
21569
21843
  return;
21570
21844
  }
21571
21845
  v2GeometryRafHandle = requestAnimationFrame(() => {
21572
21846
  v2GeometryRafHandle = 0;
21573
- v2GeometryPublisher.publish(v2GeometryRender.value, resolveV2GeometryPublishOptions(options));
21847
+ publish();
21574
21848
  });
21575
21849
  };
21850
+ const v2ReviewGeometryDelivery = createReviewGeometryDelivery((current, packet) => {
21851
+ v2GeometryPublisher.noteCommittedPageWindow?.(packet);
21852
+ scheduleV2GeometryPublish(current);
21853
+ });
21854
+ scheduleV2GeometryReplay = (payload) => {
21855
+ if (!payload || payload !== v2GeometryRender.value) return;
21856
+ const generation = ++v2GeometryReplayGeneration;
21857
+ if (v2GeometryReplayRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryReplayRafHandle);
21858
+ const replay = () => {
21859
+ v2GeometryReplayRafHandle = 0;
21860
+ if (generation === v2GeometryReplayGeneration && payload === v2GeometryRender.value && isV2GeometryReady(payload)) v2ReviewGeometryDelivery.replay(payload);
21861
+ };
21862
+ if (typeof requestAnimationFrame === "function") v2GeometryReplayRafHandle = requestAnimationFrame(replay);
21863
+ else queueMicrotask(replay);
21864
+ };
21576
21865
  const onV2Render = (payload) => {
21577
21866
  if (!payload) return;
21867
+ v2ReviewGeometryDelivery.observe(payload);
21578
21868
  scheduleV2GeometryPublish(payload, () => takeV2TrackedChangeRestampGeometryRetention());
21579
21869
  if (v2HasRangeSelection.value) scheduleV2SelectionToolbarStateSync();
21580
21870
  };
@@ -21607,7 +21897,11 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21607
21897
  cancelScheduledV2SelectionToolbarSync();
21608
21898
  if (v2GeometryRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryRafHandle);
21609
21899
  v2GeometryRafHandle = 0;
21900
+ if (v2GeometryReplayRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryReplayRafHandle);
21901
+ v2GeometryReplayRafHandle = 0;
21902
+ v2GeometryReplayGeneration++;
21610
21903
  v2GeometryRender.value = null;
21904
+ v2ReviewGeometryDelivery.reset();
21611
21905
  v2GeometryEpoch.value = null;
21612
21906
  v2GeometryAvailable.value = false;
21613
21907
  v2ReviewSidebarUnlocked.value = false;
@@ -21755,14 +22049,15 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21755
22049
  if (!isV2Mode.value) return;
21756
22050
  if (!v2GeometryPublisher.getLastPayload()) return;
21757
22051
  if (v2GeometryRafHandle && typeof cancelAnimationFrame === "function") cancelAnimationFrame(v2GeometryRafHandle);
21758
- if (typeof requestAnimationFrame !== "function") {
21759
- v2GeometryPublisher.recollect(resolveV2GeometryPublishOptions(options));
21760
- return;
21761
- }
21762
- v2GeometryRafHandle = requestAnimationFrame(() => {
22052
+ const recollect = () => {
21763
22053
  v2GeometryRafHandle = 0;
21764
- v2GeometryPublisher.recollect(resolveV2GeometryPublishOptions(options));
21765
- });
22054
+ const current = v2GeometryRender.value;
22055
+ Promise.resolve(v2GeometryPublisher.recollect(resolveV2GeometryPublishOptions(options))).then(() => {
22056
+ if (current === v2GeometryRender.value && isV2GeometryReady(current)) v2ReviewGeometryDelivery.replay(current);
22057
+ });
22058
+ };
22059
+ if (typeof requestAnimationFrame !== "function") recollect();
22060
+ else v2GeometryRafHandle = requestAnimationFrame(recollect);
21766
22061
  };
21767
22062
  const handleV2DocumentModeChange = () => {
21768
22063
  if (!isV2Mode.value) return;
@@ -21990,13 +22285,17 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
21990
22285
  ...payload,
21991
22286
  documentId: doc.id
21992
22287
  });
21993
- v2GeometryPublisher.noteCommittedPageWindow?.(payload);
22288
+ if (payload.reviewGeometry && Array.isArray(payload.pageIndices)) {
22289
+ const current = v2GeometryRender.value;
22290
+ v2ReviewGeometryDelivery.notify(String(doc.id), payload, current, isV2GeometryReady(current));
22291
+ } else v2GeometryPublisher.noteCommittedPageWindow?.(payload);
21994
22292
  },
21995
22293
  fontAssets: proxy.$superdoc.config.fonts,
21996
22294
  workerUrls: proxy.$superdoc.config.workerUrls,
21997
22295
  fieldContext: doc.fieldContext,
21998
22296
  fieldUpdatePolicy: proxy.$superdoc.config.fieldUpdatePolicy,
21999
22297
  workerStartupTimeoutMs: proxy.$superdoc.config.workerStartupTimeoutMs,
22298
+ ...proxy.$superdoc.config.changes ? { changes: proxy.$superdoc.config.changes } : {},
22000
22299
  proofing: resolvedProofingConfig.value,
22001
22300
  isNewFile,
22002
22301
  password: getDocumentLoadPassword(doc),
@@ -22017,7 +22316,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
22017
22316
  documentLoading: proxy.$superdoc.uiConfig.loading.enabled,
22018
22317
  linkPopoverResolver: getHyperlinkActivationHandler(),
22019
22318
  layoutEngineOptions: useLayoutEngine ? {
22020
- ...proxy.$superdoc.config.layoutEngineOptions || {},
22319
+ ...proxy.$superdoc.config.layoutEngineOptions,
22021
22320
  proofing: resolvedProofingConfig.value,
22022
22321
  debugLabel: proxy.$superdoc.config.layoutEngineOptions?.debugLabel ?? doc.name ?? doc.id,
22023
22322
  zoom: (activeZoom.value ?? 100) / 100,
@@ -22280,11 +22579,16 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
22280
22579
  if (layout !== "auto") return true;
22281
22580
  return !isCompactCommentsMode.value;
22282
22581
  });
22283
- const showCommentsSidebar = computed$1(() => {
22582
+ const sidebarEnabled = computed$1(() => {
22284
22583
  if (isV2Mode.value && !v2GeometryAvailable.value && !v2ReviewSidebarUnlocked.value && !hasPdfFloatingComments.value) return false;
22285
22584
  if (!shouldRenderCommentsInViewing.value) return false;
22286
22585
  if (!shouldUseSidebarComments.value) return false;
22287
- return pendingComment.value || (floatingComments.value.length > 0 || hasOpenTrackedChanges.value) && isReady.value && layers.value && isCommentsEnabled.value && !isCommentsListVisible.value;
22586
+ return Boolean(pendingComment.value || isReady.value && layers.value && isCommentsEnabled.value && !isCommentsListVisible.value);
22587
+ });
22588
+ const showCommentsSidebar = useCommentSidebar({
22589
+ documentKey: computed$1(() => JSON.stringify(documents.value.map((document) => document.id))),
22590
+ enabled: sidebarEnabled,
22591
+ hasContent: computed$1(() => Boolean(pendingComment.value || floatingComments.value.length || hasOpenTrackedChanges.value))
22288
22592
  });
22289
22593
  const activeCompactComment = computed$1(() => {
22290
22594
  if (showCommentsSidebar.value) return null;
@@ -22908,7 +23212,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
22908
23212
  ref_key: "superdocRoot",
22909
23213
  ref: superdocRoot,
22910
23214
  class: normalizeClass(["superdoc", {
22911
- "superdoc--with-sidebar": showCommentsSidebar.value,
23215
+ "superdoc--with-sidebar": unref(showCommentsSidebar),
22912
23216
  "superdoc--web-layout": unref(proxy).$superdoc.config.viewOptions?.layout === "web",
22913
23217
  "superdoc--contained": unref(proxy).$superdoc.config.contained,
22914
23218
  "high-contrast": unref(isHighContrastMode)
@@ -23056,7 +23360,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
23056
23360
  onAnchorActivate: handleHighlightClick
23057
23361
  })) : createCommentVNode("", true)
23058
23362
  ])], 512),
23059
- showCommentsSidebar.value ? (openBlock(), createElementBlock("div", {
23363
+ unref(showCommentsSidebar) ? (openBlock(), createElementBlock("div", {
23060
23364
  key: 0,
23061
23365
  ref_key: "rightSidebarRef",
23062
23366
  ref: rightSidebarRef,
@@ -23079,7 +23383,7 @@ var SuperDoc_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__P
23079
23383
  ], 46, _hoisted_1$17);
23080
23384
  };
23081
23385
  }
23082
- }), [["__scopeId", "data-v-d2540ad3"]]);
23386
+ }), [["__scopeId", "data-v-451ab88f"]]);
23083
23387
  //#endregion
23084
23388
  //#region src/core/create-app.ts
23085
23389
  var PINIA_DEVTOOLS_SETUP_EVENT = "devtools-plugin:setup";
@@ -24560,6 +24864,16 @@ var SurfaceManager = class {
24560
24864
  };
24561
24865
  //#endregion
24562
24866
  //#region src/helpers/deprecation.ts
24867
+ /**
24868
+ * Deprecation utilities for steering consumers toward the Document API.
24869
+ *
24870
+ * ProseMirror will be removed in a future major version. These helpers emit
24871
+ * one-time console warnings when deprecated properties are accessed on an
24872
+ * editor instance handed to consumer callbacks (onEditorCreate, etc.).
24873
+ *
24874
+ * Internal code continues to use the raw Editor — only the consumer-facing
24875
+ * boundary wraps the editor with a Proxy.
24876
+ */
24563
24877
  var MIGRATION_URL = "https://docs.superdoc.dev/guides/migration/document-api";
24564
24878
  var _warned = /* @__PURE__ */ new Set();
24565
24879
  /**
@@ -24748,7 +25062,8 @@ function normalizeSuperDocUser(user) {
24748
25062
  if (!user || typeof user !== "object") return { ...DEFAULT_SUPERDOC_USER };
24749
25063
  return {
24750
25064
  ...DEFAULT_SUPERDOC_USER,
24751
- ...user
25065
+ ...user,
25066
+ ...user.commentIdentity !== void 0 ? { commentIdentity: normalizeCommentIdentity(user.commentIdentity) } : {}
24752
25067
  };
24753
25068
  }
24754
25069
  //#endregion
@@ -25267,6 +25582,7 @@ function normalizeUiConfig(config = {}) {
25267
25582
  * Keeping policy separate means an application can disable the built-in
25268
25583
  * comments UI without losing the rules enforced by its custom UI.
25269
25584
  */
25585
+ var isAutocapitalize = (value) => value === "off" || value === "sentences" || value === "words" || value === "characters";
25270
25586
  var isCommentInteractionLevel = (value) => value === "read" || value === "write" || value === "resolve";
25271
25587
  /**
25272
25588
  * Resolve the effective interaction policy.
@@ -25286,14 +25602,26 @@ function normalizeInteractionConfig(config = {}) {
25286
25602
  const canonicalLevel = interactionComments.level;
25287
25603
  const hasCanonicalLevel = isCommentInteractionLevel(canonicalLevel);
25288
25604
  const level = hasCanonicalLevel ? canonicalLevel : legacyReadOnly ? "read" : legacyAllowResolve ? "resolve" : "write";
25605
+ const interactionWritingAssistance = isPlainObject$1(interaction.writingAssistance) ? interaction.writingAssistance : {};
25606
+ const autocorrect = interactionWritingAssistance.autocorrect === true;
25607
+ const autocapitalize = isAutocapitalize(interactionWritingAssistance.autocapitalize) ? interactionWritingAssistance.autocapitalize : "off";
25608
+ const spellcheck = interactionWritingAssistance.spellcheck === true;
25289
25609
  const canonicalAllowDecisions = interactionTrackedChanges.allowDecisions;
25610
+ const interactionTouch = isPlainObject$1(interaction.touch) ? interaction.touch : {};
25290
25611
  return {
25291
25612
  comments: {
25292
25613
  level,
25293
25614
  readOnly: hasCanonicalLevel ? level === "read" : legacyReadOnly,
25294
25615
  allowResolve: hasCanonicalLevel ? level === "resolve" : legacyAllowResolve
25295
25616
  },
25296
- trackedChanges: { allowDecisions: typeof canonicalAllowDecisions === "boolean" ? canonicalAllowDecisions : !legacyReadOnly }
25617
+ trackedChanges: { allowDecisions: typeof canonicalAllowDecisions === "boolean" ? canonicalAllowDecisions : !legacyReadOnly },
25618
+ touch: { gestures: interactionTouch.gestures === true },
25619
+ writingAssistance: {
25620
+ enabled: autocorrect || autocapitalize !== "off" || spellcheck,
25621
+ autocorrect,
25622
+ autocapitalize,
25623
+ spellcheck
25624
+ }
25297
25625
  };
25298
25626
  }
25299
25627
  //#endregion
@@ -25949,26 +26277,39 @@ var ToolbarButtonIcon_default = /* @__PURE__ */ _plugin_vue_export_helper_defaul
25949
26277
  var _hoisted_1$15 = [
25950
26278
  "role",
25951
26279
  "aria-label",
25952
- "aria-disabled"
26280
+ "aria-disabled",
26281
+ "aria-busy"
25953
26282
  ];
25954
26283
  var _hoisted_2$11 = ["data-item"];
25955
26284
  var _hoisted_3$10 = ["data-item"];
25956
26285
  var _hoisted_4$8 = {
25957
- key: 1,
26286
+ key: 0,
26287
+ role: "status",
26288
+ "aria-label": "Applying list",
26289
+ style: { "font-weight": "700" }
26290
+ };
26291
+ var _hoisted_5$6 = {
26292
+ key: 2,
25958
26293
  class: "sd-button-label"
25959
26294
  };
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 = [
26295
+ var _hoisted_6$4 = ["data-item", "aria-label"];
26296
+ var _hoisted_7$2 = ["innerHTML"];
26297
+ var _hoisted_8$2 = {
26298
+ key: 0,
26299
+ role: "status",
26300
+ "aria-label": "Applying list",
26301
+ style: { "font-weight": "700" }
26302
+ };
26303
+ var _hoisted_9$1 = { key: 3 };
26304
+ var _hoisted_10$1 = ["onKeydown", "id"];
26305
+ var _hoisted_11$1 = [
25965
26306
  "placeholder",
25966
26307
  "onKeydown",
25967
26308
  "id",
25968
26309
  "data-item"
25969
26310
  ];
25970
- var _hoisted_10$1 = ["data-item", "innerHTML"];
25971
- var _hoisted_11$1 = {
26311
+ var _hoisted_12$1 = ["data-item", "innerHTML"];
26312
+ var _hoisted_13$1 = {
25972
26313
  "aria-live": "polite",
25973
26314
  class: "sd-visually-hidden"
25974
26315
  };
@@ -26141,6 +26482,7 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26141
26482
  role: __props.isOverflowItem ? "menuitem" : "button",
26142
26483
  "aria-label": unref(attributes).ariaLabel,
26143
26484
  "aria-disabled": unref(disabled) ? "true" : void 0,
26485
+ "aria-busy": unref(attributes).ariaBusy || void 0,
26144
26486
  "data-sd-part": "toolbar-item",
26145
26487
  onClick: handleOuterClick,
26146
26488
  onKeydown: _cache[9] || (_cache[9] = withKeys(($event) => onEnterKeydown($event), ["enter"])),
@@ -26162,17 +26504,21 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26162
26504
  class: "sd-toolbar-button__main",
26163
26505
  "data-item": `btn-${unref(name) || ""}-main`,
26164
26506
  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),
26507
+ }, [
26508
+ unref(attributes).ariaBusy && isSplit.value ? (openBlock(), createElementBlock("span", _hoisted_4$8, "…")) : createCommentVNode("", true),
26509
+ unref(icon) ? (openBlock(), createBlock(ToolbarButtonIcon_default, {
26510
+ key: 1,
26511
+ color: unref(iconColor),
26512
+ class: "sd-toolbar-icon",
26513
+ icon: unref(icon),
26514
+ name: unref(name)
26515
+ }, null, 8, [
26516
+ "color",
26517
+ "icon",
26518
+ "name"
26519
+ ])) : createCommentVNode("", true),
26520
+ unref(label) && !unref(hideLabel) && !unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("div", _hoisted_5$6, toDisplayString(unref(label)), 1)) : createCommentVNode("", true)
26521
+ ], 8, _hoisted_3$10)) : createCommentVNode("", true),
26176
26522
  isSplit.value ? (openBlock(), createElementBlock("div", {
26177
26523
  key: 1,
26178
26524
  class: "sd-toolbar-button__caret",
@@ -26183,9 +26529,10 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26183
26529
  class: "sd-dropdown-caret",
26184
26530
  innerHTML: caretIcon.value,
26185
26531
  style: normalizeStyle({ opacity: unref(disabled) ? .6 : 1 })
26186
- }, null, 12, _hoisted_6$4)], 8, _hoisted_5$6)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
26532
+ }, null, 12, _hoisted_7$2)], 8, _hoisted_6$4)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
26533
+ unref(attributes).ariaBusy ? (openBlock(), createElementBlock("span", _hoisted_8$2, "…")) : createCommentVNode("", true),
26187
26534
  unref(icon) ? (openBlock(), createBlock(ToolbarButtonIcon_default, {
26188
- key: 0,
26535
+ key: 1,
26189
26536
  color: unref(iconColor),
26190
26537
  class: "sd-toolbar-icon",
26191
26538
  icon: unref(icon),
@@ -26196,10 +26543,10 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26196
26543
  "name"
26197
26544
  ])) : createCommentVNode("", true),
26198
26545
  unref(label) && !unref(hideLabel) && (!unref(inlineTextInputVisible) || itemName.value === "fontFamily") ? (openBlock(), createElementBlock("div", {
26199
- key: 1,
26546
+ key: 2,
26200
26547
  class: normalizeClass(["sd-button-label", { "sd-visually-hidden": unref(inlineTextInputVisible) && itemName.value === "fontFamily" }])
26201
26548
  }, toDisplayString(unref(label)), 3)) : createCommentVNode("", true),
26202
- unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("span", _hoisted_7$2, [unref(name) === "fontSize" ? withDirectives((openBlock(), createElementBlock("input", {
26549
+ unref(inlineTextInputVisible) ? (openBlock(), createElementBlock("span", _hoisted_9$1, [unref(name) === "fontSize" ? withDirectives((openBlock(), createElementBlock("input", {
26203
26550
  key: 0,
26204
26551
  "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => inlineTextInput.value = $event),
26205
26552
  onKeydown: [withKeys(withModifiers(handleInlineInputEnter, ["stop", "prevent"]), ["enter"]), _cache[2] || (_cache[2] = withKeys(($event) => handleInlineInputTab($event), ["tab"]))],
@@ -26211,7 +26558,7 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26211
26558
  autocomplete: "off",
26212
26559
  ref_key: "inlineInput",
26213
26560
  ref: inlineInput
26214
- }, null, 42, _hoisted_8$2)), [[vModelText, inlineTextInput.value]]) : withDirectives((openBlock(), createElementBlock("input", {
26561
+ }, null, 42, _hoisted_10$1)), [[vModelText, inlineTextInput.value]]) : withDirectives((openBlock(), createElementBlock("input", {
26215
26562
  key: 1,
26216
26563
  "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => inlineTextInput.value = $event),
26217
26564
  placeholder: unref(label),
@@ -26228,20 +26575,20 @@ var ToolbarButton_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/*
26228
26575
  autocomplete: "off",
26229
26576
  ref_key: "inlineInput",
26230
26577
  ref: inlineInput
26231
- }, null, 42, _hoisted_9$1)), [[vModelText, inlineTextInput.value]])])) : createCommentVNode("", true),
26578
+ }, null, 42, _hoisted_11$1)), [[vModelText, inlineTextInput.value]])])) : createCommentVNode("", true),
26232
26579
  unref(hasCaret) ? (openBlock(), createElementBlock("div", {
26233
- key: 3,
26580
+ key: 4,
26234
26581
  class: "sd-dropdown-caret",
26235
26582
  "data-item": `btn-${unref(name) || ""}-toggle`,
26236
26583
  innerHTML: caretIcon.value,
26237
26584
  style: normalizeStyle({ opacity: unref(disabled) ? .6 : 1 })
26238
- }, null, 12, _hoisted_10$1)) : createCommentVNode("", true)
26585
+ }, null, 12, _hoisted_12$1)) : createCommentVNode("", true)
26239
26586
  ], 64)),
26240
- createElementVNode("div", _hoisted_11$1, toDisplayString(`${unref(attributes).ariaLabel} ${unref(active) ? "selected" : "unset"}`), 1)
26587
+ createElementVNode("div", _hoisted_13$1, toDisplayString(`${unref(attributes).ariaLabel} ${unref(active) ? "selected" : "unset"}`), 1)
26241
26588
  ], 10, _hoisted_2$11)], 46, _hoisted_1$15);
26242
26589
  };
26243
26590
  }
26244
- }), [["__scopeId", "data-v-e599674c"]]);
26591
+ }), [["__scopeId", "data-v-b49ede12"]]);
26245
26592
  //#endregion
26246
26593
  //#region src/internal/toolbar/built-in/ToolbarSeparator.vue?vue&type=script&setup=true&lang.ts
26247
26594
  var _hoisted_1$14 = {
@@ -27533,7 +27880,7 @@ var SdTooltip_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
27533
27880
  ]);
27534
27881
  const contentStyle = computed$1(() => ({
27535
27882
  ...props.contentStyle,
27536
- ...attrs.style || {},
27883
+ ...attrs.style,
27537
27884
  position: "fixed",
27538
27885
  top: position.value.top,
27539
27886
  left: position.value.left,
@@ -27702,7 +28049,7 @@ var SdTooltip_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__
27702
28049
  }, 8, ["css"])]))], 64);
27703
28050
  };
27704
28051
  }
27705
- }), [["__scopeId", "data-v-dfd6f1d1"]]);
28052
+ }), [["__scopeId", "data-v-87967ab1"]]);
27706
28053
  //#endregion
27707
28054
  //#region src/internal/toolbar/built-in/ButtonGroup.vue?vue&type=script&setup=true&lang.ts
27708
28055
  var _hoisted_1$11 = [
@@ -30526,6 +30873,8 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
30526
30873
  attributes: { ariaLabel: "Bold" }
30527
30874
  });
30528
30875
  const fontOptions = (toolbarFonts ?? TOOLBAR_FONTS).map(normalizeFontOption);
30876
+ const isFontOption = (option) => typeof option.label === "string" && typeof option.key === "string";
30877
+ const currentFontOptions = () => fontButton.nestedOptions.value.filter(isFontOption);
30529
30878
  const fontButton = useToolbarItem({
30530
30879
  type: "dropdown",
30531
30880
  name: "fontFamily",
@@ -30552,8 +30901,9 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
30552
30901
  if (typeof fontFamily !== "string" || !fontFamily) return;
30553
30902
  const family = fontFamily.split(",")[0];
30554
30903
  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);
30904
+ const options = currentFontOptions();
30905
+ const defaultFont = options.find((option) => optionValue(option) === fontButton.defaultLabel.value);
30906
+ const foundFont = options.find((option) => optionValue(option) === family);
30557
30907
  if (foundFont) {
30558
30908
  fontButton.label.value = foundFont.label;
30559
30909
  fontButton.selectedValue.value = foundFont.key;
@@ -30566,7 +30916,7 @@ var makeDefaultItems = ({ superToolbar, toolbarIcons: configuredIcons, toolbarTe
30566
30916
  }
30567
30917
  },
30568
30918
  onDeactivate: () => {
30569
- const defaultFont = fontOptions.find((option) => (option.value ?? option.label) === fontButton.defaultLabel.value);
30919
+ const defaultFont = currentFontOptions().find((option) => (option.value ?? option.label) === fontButton.defaultLabel.value);
30570
30920
  fontButton.label.value = defaultFont?.label ?? fontButton.defaultLabel.value;
30571
30921
  fontButton.selectedValue.value = defaultFont?.key ?? "";
30572
30922
  }
@@ -31778,7 +32128,7 @@ var V1_ENABLED_ON_SELECTION_REASON = /* @__PURE__ */ new Set([
31778
32128
  ]);
31779
32129
  var V1_ENABLED_ON_EMPTY_REASON = /* @__PURE__ */ new Set(["list", "numberedlist"]);
31780
32130
  function shouldKeepEnabledForV1ToolbarParity(name, commandState) {
31781
- if (name === "fontFamily" || name === "fontSize") return true;
32131
+ if (name === "fontFamily" || name === "fontSize") return commandState?.reason !== "document-readonly" && commandState?.reason !== "deleted-text-formatting-unsupported";
31782
32132
  if (V1_ENABLED_ON_EMPTY_REASON.has(name) && commandState?.disabled && commandState?.supported !== false && commandState?.reason == null) return true;
31783
32133
  if (!V1_ENABLED_ON_SELECTION_REASON.has(name)) return false;
31784
32134
  const reason = commandState?.reason;
@@ -31812,6 +32162,7 @@ var RESERVED_TOOLBAR_ITEM_NAMES = /* @__PURE__ */ new Set([
31812
32162
  * proof lane, and behavior tests resolve against a real object.
31813
32163
  */
31814
32164
  var BuiltInToolbar = class extends import_eventemitter3.default {
32165
+ #formattingRefusalTooltips;
31815
32166
  #imagePicker;
31816
32167
  #groupItems;
31817
32168
  #highlightOptions;
@@ -31843,6 +32194,7 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
31843
32194
  showPageNumberButton: false
31844
32195
  };
31845
32196
  this.toolbarItems = [];
32197
+ this.#formattingRefusalTooltips = /* @__PURE__ */ new WeakMap();
31846
32198
  this.overflowItems = [];
31847
32199
  this.pendingSelectionCapture = void 0;
31848
32200
  this.#imagePicker = null;
@@ -31969,7 +32321,7 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
31969
32321
  const signature = this.#fontOptionsSignature();
31970
32322
  if (signature !== this._lastFontOptionsSignature) {
31971
32323
  this._lastFontOptionsSignature = signature;
31972
- this.#rebuildToolbarItems();
32324
+ if (!this.#refreshFontFamilyOptions()) this.#rebuildToolbarItems();
31973
32325
  }
31974
32326
  this.updateToolbarState();
31975
32327
  };
@@ -32117,6 +32469,13 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
32117
32469
  this.#makeToolbarItems();
32118
32470
  this.emit("toolbar-items-changed");
32119
32471
  }
32472
+ #refreshFontFamilyOptions() {
32473
+ const items = this.#getAllToolbarItems().filter((item) => item.name.value === "fontFamily");
32474
+ if (!items.length) return false;
32475
+ const options = (this.#resolveToolbarFonts(this.config.fonts) ?? TOOLBAR_FONTS).map(normalizeFontOption);
32476
+ for (const item of items) item.nestedOptions.value = options.map((option) => ({ ...option }));
32477
+ return true;
32478
+ }
32120
32479
  #fontOptionsSignature() {
32121
32480
  if (this.config.fonts) return "custom-fonts";
32122
32481
  const options = this.superdoc?.fonts?.getFontFamilyOptions?.() ?? [];
@@ -32296,6 +32655,17 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
32296
32655
  const commandId = resolveBuiltInItemCommandId(name);
32297
32656
  if (!commandId) return false;
32298
32657
  const commandState = this.snapshot?.commands?.[commandId];
32658
+ if (commandState?.reason === "deleted-text-formatting-unsupported") {
32659
+ if (!this.#formattingRefusalTooltips.has(item)) this.#formattingRefusalTooltips.set(item, item.tooltip.value);
32660
+ item.tooltip.value = "Select kept text, or accept or reject the deletion before formatting.";
32661
+ } else if (this.#formattingRefusalTooltips.has(item)) {
32662
+ item.tooltip.value = this.#formattingRefusalTooltips.get(item);
32663
+ this.#formattingRefusalTooltips.delete(item);
32664
+ }
32665
+ if ((name === "list" || name === "numberedlist") && item.attributes.value.ariaBusy !== Boolean(commandState?.pending)) item.attributes.value = {
32666
+ ...item.attributes.value,
32667
+ ariaBusy: Boolean(commandState?.pending)
32668
+ };
32299
32669
  if (shouldKeepEnabledForV1ToolbarParity(name, commandState)) item.setDisabled(false);
32300
32670
  else item.setDisabled(Boolean(commandState?.disabled));
32301
32671
  const handlers = {
@@ -32812,6 +33182,7 @@ function runtimeUserOf(user) {
32812
33182
  const text = (value) => typeof value === "string" && value ? value : void 0;
32813
33183
  return {
32814
33184
  name: text(user.name) ?? "",
33185
+ ...user.commentIdentity !== void 0 ? { commentIdentity: user.commentIdentity ? { ...user.commentIdentity } : null } : {},
32815
33186
  ...text(user.id) ? { id: text(user.id) } : {},
32816
33187
  ...text(user.email) ? { email: text(user.email) } : {},
32817
33188
  ...text(user.initials) ? { initials: text(user.initials) } : {}
@@ -32840,6 +33211,7 @@ var SuperDoc = class extends import_eventemitter3.default {
32840
33211
  #v2ConnectionUnsub;
32841
33212
  /** Latest `collaboration-connection-change` payload, for late readers. */
32842
33213
  #v2ConnectionState;
33214
+ #v2ConnectionFailureReported;
32843
33215
  /** Whether the current collaboration room has completed a sync at least once. */
32844
33216
  #v2ConnectionHasSynced;
32845
33217
  /** Lock state captured before a local → v2 collaboration promotion remount. */
@@ -32942,6 +33314,7 @@ var SuperDoc = class extends import_eventemitter3.default {
32942
33314
  this.#v2LockUnsub = null;
32943
33315
  this.#v2ConnectionUnsub = null;
32944
33316
  this.#v2ConnectionState = null;
33317
+ this.#v2ConnectionFailureReported = false;
32945
33318
  this.#v2ConnectionHasSynced = false;
32946
33319
  this.#pendingV2LockSeed = null;
32947
33320
  this.#mountWrapper = null;
@@ -33056,6 +33429,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33056
33429
  if (!config.selector) throw new Error("SuperDoc: selector is required");
33057
33430
  const container = typeof config.selector === "string" ? document.querySelector(config.selector) : config.selector;
33058
33431
  if (!(container instanceof HTMLElement)) throw new Error("SuperDoc: selector must be a valid CSS selector string or DOM element");
33432
+ const user = normalizeSuperDocUser(config.user);
33059
33433
  this.#diagnostics = createInteractionHistory(this, config, () => this.version, container);
33060
33434
  this.#surfaceManager = new SurfaceManager({ getModuleConfig: () => this.#surfacesConfig });
33061
33435
  this.#contentControlClickRoot = container;
@@ -33065,7 +33439,10 @@ var SuperDoc = class extends import_eventemitter3.default {
33065
33439
  container.addEventListener("keydown", this.#handleContentControlKeyDown, true);
33066
33440
  container.addEventListener("click", this.#handleContentControlClick, true);
33067
33441
  this.#onConfig("content-control:active-change", config.onContentControlActiveChange);
33068
- this.#init(config, container);
33442
+ this.#init({
33443
+ ...config,
33444
+ user
33445
+ }, container);
33069
33446
  this.#contentControlEventBridgeReady = true;
33070
33447
  if (this.listenerCount("content-control:active-change") > 0) this.ui;
33071
33448
  }
@@ -33118,7 +33495,6 @@ var SuperDoc = class extends import_eventemitter3.default {
33118
33495
  }
33119
33496
  this.editorVersion = 2;
33120
33497
  this.#validateLegacyConfig();
33121
- this.config.user = normalizeSuperDocUser(this.config.user);
33122
33498
  if (!this.config.layoutEngineOptions.virtualization) this.config.layoutEngineOptions.virtualization = {
33123
33499
  enabled: true,
33124
33500
  window: 5,
@@ -33144,7 +33520,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33144
33520
  }
33145
33521
  this.userColorMap = /* @__PURE__ */ new Map();
33146
33522
  this.colorIndex = 0;
33147
- this.version = "2.29.0";
33523
+ this.version = "2.30.0-next.9";
33148
33524
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
33149
33525
  this.superdocId = config.superdocId || v4();
33150
33526
  this.colors = this.config.colors ?? [];
@@ -33563,6 +33939,7 @@ var SuperDoc = class extends import_eventemitter3.default {
33563
33939
  if (this.#v2ConnectionState?.documentId !== documentId) {
33564
33940
  this.#v2ConnectionState = null;
33565
33941
  this.#v2ConnectionHasSynced = false;
33942
+ this.#v2ConnectionFailureReported = false;
33566
33943
  }
33567
33944
  const publish = (snapshot) => {
33568
33945
  if (this.#destroyed) return;
@@ -33577,8 +33954,21 @@ var SuperDoc = class extends import_eventemitter3.default {
33577
33954
  superdoc: this
33578
33955
  };
33579
33956
  this.#v2ConnectionState = payload;
33580
- if (snapshot.state === "synced") this.#v2ConnectionHasSynced = true;
33581
- this.emit("collaboration-connection-change", payload);
33957
+ if (snapshot.state === "synced") {
33958
+ this.#v2ConnectionHasSynced = true;
33959
+ this.#v2ConnectionFailureReported = false;
33960
+ }
33961
+ const firstFailure = snapshot.state === "failed" && !this.#v2ConnectionFailureReported;
33962
+ if (firstFailure) this.#v2ConnectionFailureReported = true;
33963
+ const reportFailure = firstFailure && snapshot.detail !== "worker-transport-failed";
33964
+ try {
33965
+ this.emit("collaboration-connection-change", payload);
33966
+ } finally {
33967
+ if (reportFailure) {
33968
+ const exception = createCollaborationException("collaboration-connection-failed", documentId);
33969
+ if (exception) this.emit("exception", exception);
33970
+ }
33971
+ }
33582
33972
  };
33583
33973
  try {
33584
33974
  const initialSnapshot = connection.getSnapshot?.();
@@ -34003,10 +34393,15 @@ var SuperDoc = class extends import_eventemitter3.default {
34003
34393
  this.#requireReady("setUser");
34004
34394
  const name = typeof user?.name === "string" ? user.name.trim() : "";
34005
34395
  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
- });
34396
+ let next;
34397
+ try {
34398
+ next = normalizeSuperDocUser({
34399
+ ...user,
34400
+ name
34401
+ });
34402
+ } catch (error) {
34403
+ return Promise.reject(error);
34404
+ }
34010
34405
  const applied = this.#userSwitch.then(() => this.#applyUser(next));
34011
34406
  this.#userSwitch = applied.catch(() => {});
34012
34407
  return applied;
@@ -34020,7 +34415,7 @@ var SuperDoc = class extends import_eventemitter3.default {
34020
34415
  "email",
34021
34416
  "image",
34022
34417
  "initials"
34023
- ].every((key) => (current[key] ?? null) === (next[key] ?? null))) return;
34418
+ ].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
34419
  const runtimes = this.#editorRuntimeRegistry.getAll();
34025
34420
  const results = await Promise.allSettled(runtimes.map((runtime) => runtime.setUser?.(runtimeUserOf(next))));
34026
34421
  const failure = results.find((result) => result.status === "rejected");