superdoc 2.12.0-next.9 → 2.12.0

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 (38) hide show
  1. package/dist/chunks/{FindReplaceSurface-40AnrqWk.es.js → FindReplaceSurface-CVKgivXi.es.js} +8 -2
  2. package/dist/chunks/{FindReplaceSurface-0p7EtMZh.cjs → FindReplaceSurface-qi9OhbwN.cjs} +8 -2
  3. package/dist/chunks/{create-super-doc-ui-CBFGggZ2.cjs → create-super-doc-ui-BWWWNPP8.cjs} +180 -84
  4. package/dist/chunks/{create-super-doc-ui-C-AuEM9F.es.js → create-super-doc-ui-FnwgTkUv.es.js} +180 -84
  5. package/dist/collaboration-upgrade-engine.cjs +1 -1
  6. package/dist/collaboration-upgrade-engine.es.js +1 -1
  7. package/dist/layout-engine/contracts/src/column-layout.d.ts +58 -1
  8. package/dist/layout-engine/contracts/src/graphic-placement.d.ts +3 -0
  9. package/dist/layout-engine/contracts/src/index.d.ts +16 -2
  10. package/dist/layout-engine/layout-engine/src/column-balancing.d.ts +11 -7
  11. package/dist/layout-engine/layout-engine/src/layout-paragraph.d.ts +2 -6
  12. package/dist/layout-engine/painters/dom/src/derived-run-text-plane.d.ts +9 -0
  13. package/dist/layout-engine/painters/dom/src/index.d.ts +1 -0
  14. package/dist/layout-engine/painters/dom/src/page-content.d.ts +3 -1
  15. package/dist/layout-engine/painters/dom/src/persistent-page-surface.d.ts +2 -0
  16. package/dist/layout-engine/painters/dom/src/renderer.d.ts +9 -16
  17. package/dist/public/ui-react.cjs +18 -1
  18. package/dist/public/ui-react.es.js +18 -2
  19. package/dist/public/ui-vue.cjs +18 -1
  20. package/dist/public/ui-vue.es.js +18 -2
  21. package/dist/public/ui.cjs +1 -1
  22. package/dist/public/ui.es.js +1 -1
  23. package/dist/style.css +40 -40
  24. package/dist/style.layered.css +40 -40
  25. package/dist/superdoc/src/core/types/index.d.ts +20 -12
  26. package/dist/superdoc/src/public/ui/react.d.ts +3 -1
  27. package/dist/superdoc/src/public/ui/types.d.ts +67 -9
  28. package/dist/superdoc/src/public/ui/vue.d.ts +3 -1
  29. package/dist/superdoc/src/public/ui-react.d.cts +1 -0
  30. package/dist/superdoc/src/public/ui-react.d.ts +1 -1
  31. package/dist/superdoc/src/public/ui-vue.d.cts +1 -0
  32. package/dist/superdoc/src/public/ui-vue.d.ts +1 -1
  33. package/dist/superdoc.cjs +138 -12
  34. package/dist/superdoc.es.js +138 -12
  35. package/dist-cdn/style.layered.css +1 -1
  36. package/dist-cdn/superdoc.min.css +1 -1
  37. package/dist-cdn/superdoc.min.js +37 -37
  38. package/package.json +2 -2
@@ -920,33 +920,49 @@ export interface TrackChangesHandle extends SnapshotSubscribable<TrackChangesSli
920
920
  * already identifies one occurrence, anywhere in the document). A
921
921
  * `{ id, story }` record (e.g. a {@link getAt}/{@link setActive} hit) is
922
922
  * also accepted for convenience, so a hit can be passed straight through.
923
- * Structured failure receipt or `false` if unsupported.
923
+ * Returns the immediate routed result. Use {@link acceptAsync} when later UI
924
+ * depends on the settled document operation.
924
925
  */
925
926
  accept(changeId: string | {
926
927
  id: string;
927
928
  story?: unknown;
928
929
  }): CommandExecutionResult;
930
+ /** Accept a change and resolve after the document operation settles. */
931
+ acceptAsync(changeId: string | {
932
+ id: string;
933
+ story?: unknown;
934
+ }): Promise<CommandExecutionResult>;
929
935
  /**
930
936
  * Reject a change. The id is sufficient on its own (a tracked-change id
931
937
  * already identifies one occurrence, anywhere in the document). A
932
938
  * `{ id, story }` record (e.g. a {@link getAt}/{@link setActive} hit) is
933
939
  * also accepted for convenience, so a hit can be passed straight through.
934
- * Structured failure receipt or `false` if unsupported.
940
+ * Returns the immediate routed result. Use {@link rejectAsync} when later UI
941
+ * depends on the settled document operation.
935
942
  */
936
943
  reject(changeId: string | {
937
944
  id: string;
938
945
  story?: unknown;
939
946
  }): CommandExecutionResult;
947
+ /** Reject a change and resolve after the document operation settles. */
948
+ rejectAsync(changeId: string | {
949
+ id: string;
950
+ story?: unknown;
951
+ }): Promise<CommandExecutionResult>;
940
952
  /**
941
953
  * Accept every active tracked change. Returns the Document API receipt, or
942
954
  * `false` when bulk decisions are unavailable / disabled on the host.
943
955
  */
944
956
  acceptAll(): CommandExecutionResult;
957
+ /** Accept every active tracked change and resolve after the operation settles. */
958
+ acceptAllAsync(): Promise<CommandExecutionResult>;
945
959
  /**
946
960
  * Reject every active tracked change. Returns the Document API receipt, or
947
961
  * `false` when bulk decisions are unavailable / disabled on the host.
948
962
  */
949
963
  rejectAll(): CommandExecutionResult;
964
+ /** Reject every active tracked change and resolve after the operation settles. */
965
+ rejectAllAsync(): Promise<CommandExecutionResult>;
950
966
  /**
951
967
  * Move focus to the next tracked change in document order (relative to the
952
968
  * active change). Returns the id that became active, or `null` when there are
@@ -1005,12 +1021,33 @@ export interface TrackChangesHandle extends SnapshotSubscribable<TrackChangesSli
1005
1021
  /**
1006
1022
  * Scroll the tracked-change anchor into view through the host navigation
1007
1023
  * surface. Resolves with both v1/main `{ success }` and v2 `{ ok, reason? }`
1008
- * fields.
1024
+ * fields. A bare id resolves its story from the loaded rows; a
1025
+ * `{ id, story }` record pins the occurrence when the same id repeats across
1026
+ * the body and a footnote, header, or footer.
1009
1027
  */
1010
- scrollTo(changeId: string): Promise<WorkflowScrollResult>;
1028
+ scrollTo(input: string | {
1029
+ id: string;
1030
+ story?: unknown;
1031
+ }): Promise<WorkflowScrollResult>;
1011
1032
  }
1012
1033
  /** Content-controls handle. */
1013
1034
  export interface ContentControlsHandle extends SnapshotSubscribable<ContentControlsSlice> {
1035
+ /**
1036
+ * Observe the document-wide content-control catalog and active field IDs.
1037
+ * The catalog remains requested until the returned disposer runs.
1038
+ */
1039
+ observe(listener: (snapshot: ContentControlsSlice) => void): () => void;
1040
+ /**
1041
+ * Observe the passive content-controls slice without requesting the
1042
+ * document-wide catalog. Meant for consumers that only need the active
1043
+ * field path, such as the core active-change event bridge; a field panel
1044
+ * should use {@link observe}.
1045
+ */
1046
+ observeActivePath(listener: (snapshot: ContentControlsSlice) => void): () => void;
1047
+ /** Event-shaped alias of {@link observe}. */
1048
+ subscribe(listener: (event: {
1049
+ snapshot: ContentControlsSlice;
1050
+ }) => void): () => void;
1014
1051
  /** Read the current content-controls snapshot. */
1015
1052
  getSnapshot(): ContentControlsSlice;
1016
1053
  /** Read the current content-controls snapshot. */
@@ -1220,11 +1257,21 @@ export interface SearchSlice {
1220
1257
  /** Whether the query is a regular expression (V2 runtime only). */
1221
1258
  regex: boolean;
1222
1259
  /**
1223
- * Whether replace / replaceAll can mutate right now. False in viewing /
1224
- * read-only mode, when replace is host-unavailable, or when the match set is
1225
- * truncated and cannot be fully enumerated.
1260
+ * Whether `replace()` can mutate the active match right now. False in
1261
+ * viewing / read-only mode or when replace is host-unavailable.
1226
1262
  */
1227
1263
  canReplace: boolean;
1264
+ /**
1265
+ * Whether `replaceAll()` can mutate right now. Everything that disables
1266
+ * `canReplace` disables this too. It also requires at least one match, and
1267
+ * it is false for a truncated match set: when more matches exist than the
1268
+ * session enumerates, replacing all of them cannot be applied, while
1269
+ * replacing the active match still can.
1270
+ *
1271
+ * Optional here so existing implementations of the deprecated
1272
+ * {@link SearchHandle} keep compiling; {@link SearchSnapshot} requires it.
1273
+ */
1274
+ canReplaceAll?: boolean;
1228
1275
  /** Stable reason when the surface (or an action) is unavailable. */
1229
1276
  reason?: SuperDocUIReason;
1230
1277
  }
@@ -1232,8 +1279,10 @@ export interface SearchSlice {
1232
1279
  export interface SearchSnapshot extends SearchSlice {
1233
1280
  /** Whether the session includes pending tracked deletions in match discovery. */
1234
1281
  includeTrackedDeletions: boolean;
1282
+ /** Whether `replaceAll()` can mutate right now. See {@link SearchSlice.canReplaceAll}. */
1283
+ canReplaceAll: boolean;
1235
1284
  }
1236
- /** Options for `editor.ui.search.find()`. */
1285
+ /** Options for `superdoc.ui.search.find()`. */
1237
1286
  export interface SearchQueryOptions {
1238
1287
  /** Match uppercase and lowercase letters exactly (default: false). */
1239
1288
  caseSensitive?: boolean;
@@ -1306,12 +1355,21 @@ export interface SearchController extends Omit<SearchHandle, keyof SnapshotSubsc
1306
1355
  */
1307
1356
  search(query: string, options?: SearchQueryOptions): SearchSnapshot;
1308
1357
  }
1309
- /** Runtime control for the built-in context menu. */
1358
+ /** Runtime control and document context for context menus. */
1310
1359
  export interface ContextMenuHandle {
1311
1360
  /** Open the menu at the active selection or caret. */
1312
1361
  open(): WorkflowActionResult;
1313
1362
  /** Close the menu when it is open. */
1314
1363
  close(): void;
1364
+ /**
1365
+ * Resolve the public document context at a viewport point. Coordinates use
1366
+ * `MouseEvent.clientX` and `MouseEvent.clientY`. This remains available when
1367
+ * the built-in context menu is disabled.
1368
+ */
1369
+ contextAt(input: {
1370
+ x: number;
1371
+ y: number;
1372
+ }): ViewportContext;
1315
1373
  }
1316
1374
  /**
1317
1375
  * Structural shape of the active editor the controller reads. Every member is
@@ -1,6 +1,6 @@
1
1
  import { ComputedRef, MaybeRefOrGetter, ShallowRef } from 'vue';
2
2
  import { SliceSource } from './slice-source.js';
3
- import { BorrowedSuperDocUI, CommandExecutionResult, CommandId, CommandState, CommentsSlice, ContentControlsSlice, DocumentSlice, FontFamilyOption, FontSizeOption, SelectionSlice, Subscribable, SuperDocLike, ToolbarSnapshotSlice, TrackChangesSlice, ZoomSlice } from './types.js';
3
+ import { BorrowedSuperDocUI, CommandExecutionResult, CommandId, CommandState, CommentsSlice, ContentControlsSlice, DocumentSlice, FontFamilyOption, FontSizeOption, SearchSnapshot, SelectionSlice, Subscribable, SuperDocLike, ToolbarSnapshotSlice, TrackChangesSlice, ZoomSlice } from './types.js';
4
4
  /** The raw SuperDoc instance (or host stub) handed to the provider. */
5
5
  export type SuperDocHost = SuperDocLike;
6
6
  /**
@@ -84,6 +84,8 @@ export declare function useClearSuperDoc(): (expectedHost: SuperDocHost) => bool
84
84
  export declare function useSuperDocSlice<T>(pick: (ui: BorrowedSuperDocUI) => SliceSource<T> | Subscribable<T>, initial: T): Readonly<ShallowRef<T>>;
85
85
  /** Subscribe to the selection slice. */
86
86
  export declare function useSuperDocSelection(): Readonly<ShallowRef<SelectionSlice>>;
87
+ /** Subscribe to the current Search session. */
88
+ export declare function useSuperDocSearch(): Readonly<ShallowRef<SearchSnapshot>>;
87
89
  /** Subscribe to the comments slice. */
88
90
  export declare function useSuperDocComments(): Readonly<ShallowRef<CommentsSlice>>;
89
91
  /** Subscribe to the content-controls slice. */
@@ -14,6 +14,7 @@ export declare const useSuperDocDocument: typeof import('./ui-react.js', { with:
14
14
  export declare const useSuperDocFontOptions: typeof import('./ui-react.js', { with: { "resolution-mode": "import" } }).useSuperDocFontOptions;
15
15
  export declare const useSuperDocFontSizeOptions: typeof import('./ui-react.js', { with: { "resolution-mode": "import" } }).useSuperDocFontSizeOptions;
16
16
  export declare const useSuperDocHost: typeof import('./ui-react.js', { with: { "resolution-mode": "import" } }).useSuperDocHost;
17
+ export declare const useSuperDocSearch: typeof import('./ui-react.js', { with: { "resolution-mode": "import" } }).useSuperDocSearch;
17
18
  export declare const useSuperDocSelection: typeof import('./ui-react.js', { with: { "resolution-mode": "import" } }).useSuperDocSelection;
18
19
  export declare const useSuperDocSlice: typeof import('./ui-react.js', { with: { "resolution-mode": "import" } }).useSuperDocSlice;
19
20
  export declare const useSuperDocToolbar: typeof import('./ui-react.js', { with: { "resolution-mode": "import" } }).useSuperDocToolbar;
@@ -16,5 +16,5 @@
16
16
  * - AIDEV-NOTE: `verify-public-facade-emit.cjs` parses this file and verifies
17
17
  * the emitted declarations expose exactly these named exports.
18
18
  */
19
- export { SuperDocUIProvider, useSuperDocUI, useSuperDocHost, useSetSuperDoc, useSuperDocSlice, useSuperDocSelection, useSuperDocComments, useSuperDocContentControls, useSuperDocTrackChanges, useSuperDocToolbar, useSuperDocCommand, useSuperDocDocument, useSuperDocFontOptions, useSuperDocFontSizeOptions, useSuperDocZoom, } from './ui/react.js';
19
+ export { SuperDocUIProvider, useSuperDocUI, useSuperDocHost, useSetSuperDoc, useSuperDocSlice, useSuperDocSelection, useSuperDocSearch, useSuperDocComments, useSuperDocContentControls, useSuperDocTrackChanges, useSuperDocToolbar, useSuperDocCommand, useSuperDocDocument, useSuperDocFontOptions, useSuperDocFontSizeOptions, useSuperDocZoom, } from './ui/react.js';
20
20
  export type { SuperDocHost, SuperDocUIProviderProps, UseSuperDocCommandResult } from './ui/react.js';
@@ -15,6 +15,7 @@ export declare const useSuperDocDocument: typeof import('./ui-vue.js', { with: {
15
15
  export declare const useSuperDocFontOptions: typeof import('./ui-vue.js', { with: { "resolution-mode": "import" } }).useSuperDocFontOptions;
16
16
  export declare const useSuperDocFontSizeOptions: typeof import('./ui-vue.js', { with: { "resolution-mode": "import" } }).useSuperDocFontSizeOptions;
17
17
  export declare const useSuperDocHost: typeof import('./ui-vue.js', { with: { "resolution-mode": "import" } }).useSuperDocHost;
18
+ export declare const useSuperDocSearch: typeof import('./ui-vue.js', { with: { "resolution-mode": "import" } }).useSuperDocSearch;
18
19
  export declare const useSuperDocSelection: typeof import('./ui-vue.js', { with: { "resolution-mode": "import" } }).useSuperDocSelection;
19
20
  export declare const useSuperDocSlice: typeof import('./ui-vue.js', { with: { "resolution-mode": "import" } }).useSuperDocSlice;
20
21
  export declare const useSuperDocToolbar: typeof import('./ui-vue.js', { with: { "resolution-mode": "import" } }).useSuperDocToolbar;
@@ -16,5 +16,5 @@
16
16
  * - AIDEV-NOTE: `verify-public-facade-emit.cjs` parses this file and verifies
17
17
  * the emitted declarations expose exactly these named exports.
18
18
  */
19
- export { provideSuperDocUI, useSuperDocUI, useSuperDocHost, useSetSuperDoc, useClearSuperDoc, useSuperDocSlice, useSuperDocSelection, useSuperDocComments, useSuperDocContentControls, useSuperDocTrackChanges, useSuperDocToolbar, useSuperDocCommand, useSuperDocDocument, useSuperDocFontOptions, useSuperDocFontSizeOptions, useSuperDocZoom, } from './ui/vue.js';
19
+ export { provideSuperDocUI, useSuperDocUI, useSuperDocHost, useSetSuperDoc, useClearSuperDoc, useSuperDocSlice, useSuperDocSelection, useSuperDocSearch, useSuperDocComments, useSuperDocContentControls, useSuperDocTrackChanges, useSuperDocToolbar, useSuperDocCommand, useSuperDocDocument, useSuperDocFontOptions, useSuperDocFontSizeOptions, useSuperDocZoom, } from './ui/vue.js';
20
20
  export type { SuperDocHost, SuperDocUIBinding, UseSuperDocCommandResult } from './ui/vue.js';
package/dist/superdoc.cjs CHANGED
@@ -4,7 +4,7 @@ const require_blank_docx = require("./chunks/blank-docx-BuFAbRjs.cjs");
4
4
  const require_eventemitter3 = require("./chunks/eventemitter3-C_TAnXOl.cjs");
5
5
  const require_uuid = require("./chunks/uuid-BhG0ngwk.cjs");
6
6
  const require_jszip = require("./chunks/jszip-D8mAFF-r.cjs");
7
- const require_create_super_doc_ui = require("./chunks/create-super-doc-ui-CBFGggZ2.cjs");
7
+ const require_create_super_doc_ui = require("./chunks/create-super-doc-ui-BWWWNPP8.cjs");
8
8
  const require__plugin_vue_export_helper = require("./chunks/_plugin-vue_export-helper-SDR04tiH.cjs");
9
9
  const require_constants = require("./chunks/constants-DpXuDx_g.cjs");
10
10
  let vue = require("vue");
@@ -316,7 +316,7 @@ var shuffleArray = (array) => {
316
316
  var DEFAULT_ENDPOINT = "https://ingest.superdoc.dev/v1/collect";
317
317
  function getSuperdocVersion() {
318
318
  try {
319
- return "2.12.0-next.9";
319
+ return "2.12.0";
320
320
  } catch {
321
321
  return "unknown";
322
322
  }
@@ -10188,6 +10188,115 @@ var useCommentsStore = defineStore("comments", () => {
10188
10188
  const type = normalizeTrackedChangeDecisionType(comment?.trackedChangeDisplayType) ?? normalizeTrackedChangeDecisionType(comment?.trackedChangeType);
10189
10189
  return normalizedDecision === "accept" && type === "insertion" || normalizedDecision === "reject" && type === "deletion";
10190
10190
  };
10191
+ const getBodyTrackedChangeIdentityIds = (comment) => {
10192
+ const commentId = normalizeCommentId(comment.commentId);
10193
+ const canonicalId = normalizeCommentId(comment.trackedChangeCanonicalId);
10194
+ return [commentId, ...!(canonicalId && canonicalId !== commentId) ? [
10195
+ comment.importedId,
10196
+ canonicalId,
10197
+ comment.trackedChangeAnchorKey,
10198
+ ...normalizeTrackedChangePositionAliases(comment.trackedChangePositionAliases)
10199
+ ] : []].map((id) => normalizeCommentId(id)).filter(Boolean);
10200
+ };
10201
+ const groupBodyTrackedChangeCommentsByIdentity = (comments) => {
10202
+ const parents = comments.map((_, index) => index);
10203
+ const findRoot = (index) => {
10204
+ let root = index;
10205
+ while (parents[root] !== root) root = parents[root];
10206
+ while (parents[index] !== index) {
10207
+ const next = parents[index];
10208
+ parents[index] = root;
10209
+ index = next;
10210
+ }
10211
+ return root;
10212
+ };
10213
+ const join = (left, right) => {
10214
+ const leftRoot = findRoot(left);
10215
+ const rightRoot = findRoot(right);
10216
+ if (leftRoot === rightRoot) return;
10217
+ parents[Math.max(leftRoot, rightRoot)] = Math.min(leftRoot, rightRoot);
10218
+ };
10219
+ const identityOwner = /* @__PURE__ */ new Map();
10220
+ comments.forEach((comment, index) => {
10221
+ getBodyTrackedChangeIdentityIds(comment).forEach((identityId) => {
10222
+ const owner = identityOwner.get(identityId);
10223
+ if (owner === void 0) identityOwner.set(identityId, index);
10224
+ else join(index, owner);
10225
+ });
10226
+ });
10227
+ const groups = /* @__PURE__ */ new Map();
10228
+ comments.forEach((comment, index) => {
10229
+ const root = findRoot(index);
10230
+ const group = groups.get(root) ?? [];
10231
+ group.push(comment);
10232
+ groups.set(root, group);
10233
+ });
10234
+ return groups;
10235
+ };
10236
+ const mergeDuplicateBodyTrackedChangeComments = (groups) => {
10237
+ const duplicates = /* @__PURE__ */ new Map();
10238
+ groups.forEach(([survivor, ...duplicateRows]) => {
10239
+ if (!survivor || duplicateRows.length === 0) return;
10240
+ const resolvedDuplicate = duplicateRows.find((comment) => comment.resolvedTime != null);
10241
+ if (survivor.resolvedTime == null && resolvedDuplicate) {
10242
+ survivor.resolvedTime = resolvedDuplicate.resolvedTime;
10243
+ survivor.resolvedById = resolvedDuplicate.resolvedById;
10244
+ survivor.resolvedByEmail = resolvedDuplicate.resolvedByEmail;
10245
+ survivor.resolvedByName = resolvedDuplicate.resolvedByName;
10246
+ survivor.trackedChangeDecision = resolvedDuplicate.trackedChangeDecision;
10247
+ }
10248
+ survivor.trackedChangePositionAliases = normalizeTrackedChangePositionAliases([...normalizeTrackedChangePositionAliases(survivor.trackedChangePositionAliases), ...duplicateRows.flatMap((comment) => [
10249
+ comment.commentId,
10250
+ comment.importedId,
10251
+ comment.trackedChangeCanonicalId,
10252
+ ...normalizeTrackedChangePositionAliases(comment.trackedChangePositionAliases)
10253
+ ])]);
10254
+ duplicateRows.forEach((comment) => duplicates.set(comment, survivor));
10255
+ });
10256
+ return duplicates;
10257
+ };
10258
+ const createDroppedCommentIdMap = (duplicates) => {
10259
+ const droppedCommentIds = /* @__PURE__ */ new Map();
10260
+ duplicates.forEach((survivor, duplicate) => {
10261
+ const fromId = normalizeCommentId(duplicate.commentId);
10262
+ const toId = normalizeCommentId(survivor.commentId);
10263
+ if (fromId && toId) droppedCommentIds.set(fromId, toId);
10264
+ });
10265
+ return droppedCommentIds;
10266
+ };
10267
+ const remapTrackedChangeReferences = (droppedCommentIds, activeDocumentId) => {
10268
+ const remapSelection = (selection) => {
10269
+ const selectedId = normalizeCommentId(selection.value);
10270
+ if (selectedId && droppedCommentIds.has(selectedId)) selection.value = droppedCommentIds.get(selectedId);
10271
+ };
10272
+ remapSelection(activeComment);
10273
+ remapSelection(activeFloatingCommentInstanceId);
10274
+ commentsList.value.forEach((row) => {
10275
+ if (!belongsToTrackedChangeSyncDocument(row, activeDocumentId)) return;
10276
+ const rowIdentityUpdates = {};
10277
+ for (const field of [
10278
+ "trackedChangeThreadParentId",
10279
+ "trackedChangeParentId",
10280
+ "threadingParentCommentId",
10281
+ "parentCommentId"
10282
+ ]) {
10283
+ const replacementId = droppedCommentIds.get(normalizeCommentId(row?.[field]));
10284
+ if (!replacementId) continue;
10285
+ row[field] = replacementId;
10286
+ rowIdentityUpdates[field] = replacementId;
10287
+ }
10288
+ if (Object.keys(rowIdentityUpdates).length && typeof row.updateIdentityValues === "function") row.updateIdentityValues(rowIdentityUpdates);
10289
+ });
10290
+ };
10291
+ const compactDuplicateBodyTrackedChangeComments = (activeDocumentId) => {
10292
+ const candidates = commentsList.value.filter((comment) => isBodyTrackedChangeComment(comment) && belongsToTrackedChangeSyncDocument(comment, activeDocumentId));
10293
+ const groups = groupBodyTrackedChangeCommentsByIdentity(candidates);
10294
+ const duplicates = mergeDuplicateBodyTrackedChangeComments(groups);
10295
+ if (!duplicates.size) return 0;
10296
+ commentsList.value = commentsList.value.filter((comment) => !duplicates.has(comment));
10297
+ remapTrackedChangeReferences(createDroppedCommentIdMap(duplicates), activeDocumentId);
10298
+ return duplicates.size;
10299
+ };
10191
10300
  /**
10192
10301
  * Remove tracked-change comments that no longer have a corresponding mark in the editor.
10193
10302
  * Also removes any replies linked to those removed tracked-change threads.
@@ -10233,7 +10342,9 @@ var useCommentsStore = defineStore("comments", () => {
10233
10342
  if (importedId) removedIds.add(importedId);
10234
10343
  return false;
10235
10344
  });
10345
+ compactDuplicateBodyTrackedChangeComments(activeDocumentId);
10236
10346
  restoredComments.forEach((comment) => {
10347
+ if (!commentsList.value.includes(comment)) return;
10237
10348
  const payload = getCommentEventPayload(comment);
10238
10349
  const event = {
10239
10350
  type: COMMENT_EVENTS.UPDATE,
@@ -10823,11 +10934,14 @@ var useCommentsStore = defineStore("comments", () => {
10823
10934
  } finally {
10824
10935
  endInteractionSpan(identitySpan, trackedChangeIdentityIndex.work());
10825
10936
  }
10826
- if (!effectiveDocumentId || !pruneStale) return {
10827
- liveIds,
10828
- liveAnchorKeys,
10829
- appliedCount
10830
- };
10937
+ if (!effectiveDocumentId || !pruneStale) {
10938
+ if (effectiveDocumentId) compactDuplicateBodyTrackedChangeComments(String(effectiveDocumentId));
10939
+ return {
10940
+ liveIds,
10941
+ liveAnchorKeys,
10942
+ appliedCount
10943
+ };
10944
+ }
10831
10945
  pruneStaleTrackedChangeComments(liveIds, liveAnchorKeys, String(effectiveDocumentId), superdoc, { broadcastChanges: false });
10832
10946
  return {
10833
10947
  liveIds,
@@ -17263,6 +17377,7 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
17263
17377
  const matchCount = (0, vue.ref)(0);
17264
17378
  const activeMatchIndex = (0, vue.ref)(-1);
17265
17379
  const canReplaceState = (0, vue.ref)(true);
17380
+ const canReplaceAllState = (0, vue.ref)(true);
17266
17381
  const replacePending = (0, vue.ref)(false);
17267
17382
  const ignoreDiacriticsSupported = (0, vue.ref)(true);
17268
17383
  const regexSupported = (0, vue.ref)(false);
@@ -17277,6 +17392,7 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
17277
17392
  });
17278
17393
  const hasMatches = (0, vue.computed)(() => matchCount.value > 0);
17279
17394
  const canReplaceComputed = (0, vue.computed)(() => hasMatches.value && !replacePending.value && canReplaceState.value);
17395
+ const canReplaceAllComputed = (0, vue.computed)(() => canReplaceComputed.value && canReplaceAllState.value);
17280
17396
  /** @type {ReturnType<typeof setTimeout> | null} */
17281
17397
  let debounceTimer = null;
17282
17398
  /** @type {ReturnType<typeof setInterval> | null} */
@@ -17311,6 +17427,7 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
17311
17427
  matchCount.value = typeof slice.total === "number" ? slice.total : 0;
17312
17428
  activeMatchIndex.value = typeof slice.activeIndex === "number" ? slice.activeIndex : -1;
17313
17429
  canReplaceState.value = slice.canReplace === true;
17430
+ canReplaceAllState.value = typeof slice.canReplaceAll === "boolean" ? slice.canReplaceAll : slice.canReplace === true;
17314
17431
  searchError.value = slice.reason === "search-invalid-pattern" ? currentTexts.invalidPatternLabel : null;
17315
17432
  }
17316
17433
  function runSearch() {
@@ -17447,7 +17564,7 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
17447
17564
  if (replacePending.value) return;
17448
17565
  if (!hasMatches.value) return;
17449
17566
  if (currentV2Search) {
17450
- if (!canReplaceState.value) return;
17567
+ if (!canReplaceState.value || !canReplaceAllState.value) return;
17451
17568
  const search = currentV2Search;
17452
17569
  replacePending.value = true;
17453
17570
  const finish = createReplaceContinuation(search, {
@@ -17508,6 +17625,7 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
17508
17625
  matchCount.value = 0;
17509
17626
  activeMatchIndex.value = -1;
17510
17627
  canReplaceState.value = true;
17628
+ canReplaceAllState.value = true;
17511
17629
  replacePending.value = false;
17512
17630
  }
17513
17631
  /**
@@ -17612,6 +17730,11 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
17612
17730
  hasMatches,
17613
17731
  canReplace: canReplaceComputed,
17614
17732
  /**
17733
+ * `canReplace` plus the session enumerating every match. A truncated V2
17734
+ * session keeps Replace enabled and disables Replace all.
17735
+ */
17736
+ canReplaceAll: canReplaceAllComputed,
17737
+ /**
17615
17738
  * Runtime mutability of the active session (viewing/read-only mode sets
17616
17739
  * this false). Distinct from the static `replaceEnabled` config and from
17617
17740
  * `canReplace`, which also requires matches: surfaces hide replace
@@ -17701,6 +17824,9 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
17701
17824
  get canReplace() {
17702
17825
  return handle.canReplace.value;
17703
17826
  },
17827
+ get canReplaceAll() {
17828
+ return handle.canReplaceAll ? handle.canReplaceAll.value : handle.canReplace.value;
17829
+ },
17704
17830
  get replacePending() {
17705
17831
  return handle.replacePending.value;
17706
17832
  },
@@ -17851,7 +17977,7 @@ function useFindReplace({ getSurfaceManager, getActiveEditor, activeEditorRef, g
17851
17977
  /** @type {object | undefined} */
17852
17978
  let FindReplaceSurface;
17853
17979
  try {
17854
- FindReplaceSurface = (await Promise.resolve().then(() => require("./chunks/FindReplaceSurface-0p7EtMZh.cjs"))).default;
17980
+ FindReplaceSurface = (await Promise.resolve().then(() => require("./chunks/FindReplaceSurface-qi9OhbwN.cjs"))).default;
17855
17981
  } catch {
17856
17982
  opening = false;
17857
17983
  return;
@@ -37442,7 +37568,7 @@ var TOOLBAR_ITEM_ALIASES = Object.freeze({
37442
37568
  * - `ui: false` renders no built-in chrome. It is an explicit
37443
37569
  * no-built-in-UI profile, not the inverse of a hypothetical `ui: true`,
37444
37570
  * and it never disables document content, the Document API, or
37445
- * `editor.ui`.
37571
+ * `superdoc.ui`.
37446
37572
  * - `ui: { ... }` opts in per surface. An omitted key keeps that surface's
37447
37573
  * historical default rather than inheriting from its siblings.
37448
37574
  *
@@ -45214,7 +45340,7 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
45214
45340
  this.config.colors = shuffleArray(this.config.colors);
45215
45341
  this.userColorMap = /* @__PURE__ */ new Map();
45216
45342
  this.colorIndex = 0;
45217
- this.version = "2.12.0-next.9";
45343
+ this.version = "2.12.0";
45218
45344
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
45219
45345
  this.superdocId = config.superdocId || require_uuid.v4();
45220
45346
  this.colors = this.config.colors ?? [];
@@ -45383,7 +45509,7 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
45383
45509
  this.#pendingContentControlInputSource = null;
45384
45510
  this.#pendingContentControlInputExpiresAt = null;
45385
45511
  });
45386
- contentControlsUnsub = this.#ui.contentControls.observe((snapshot) => {
45512
+ contentControlsUnsub = this.#ui.contentControls.observeActivePath((snapshot) => {
45387
45513
  const editor = this.activeEditor;
45388
45514
  const activePath = snapshot.activeIds.map((id) => toContentControlRef(snapshot.items.find((item) => item.id === id)));
45389
45515
  if (activePath.some((item) => item === null)) return;