superdoc 2.0.0-next.49 → 2.0.0-next.50

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 (29) hide show
  1. package/AGENTS.md +31 -10
  2. package/dist/chunks/{create-super-doc-ui-BmtUtDDR.es.js → create-super-doc-ui-D4UDePxr.es.js} +126 -28
  3. package/dist/chunks/{create-super-doc-ui-BNo2DvD1.cjs → create-super-doc-ui-DlSN0ENQ.cjs} +126 -28
  4. package/dist/collaboration-upgrade-engine.cjs +1 -1
  5. package/dist/collaboration-upgrade-engine.es.js +1 -1
  6. package/dist/document-api/src/contract/operation-definitions.d.ts +1 -1
  7. package/dist/public/ui-react.cjs +1 -1
  8. package/dist/public/ui-react.es.js +1 -1
  9. package/dist/public/ui.cjs +1 -1
  10. package/dist/public/ui.es.js +1 -1
  11. package/dist/shared/font-system/src/font-offerings.d.ts +1 -1
  12. package/dist/style.css +28 -28
  13. package/dist/style.layered.css +28 -28
  14. package/dist/superdoc/src/core/SuperDoc.d.ts +43 -1
  15. package/dist/superdoc/src/core/config/merge-defined.d.ts +35 -0
  16. package/dist/superdoc/src/core/config/normalize-interaction-config.d.ts +15 -0
  17. package/dist/superdoc/src/core/config/normalize-surfaces-config.d.ts +23 -0
  18. package/dist/superdoc/src/core/config/normalize-ui-config.d.ts +62 -0
  19. package/dist/superdoc/src/core/types/index.d.ts +209 -2
  20. package/dist/superdoc/src/public/index.d.cts +6 -0
  21. package/dist/superdoc/src/public/index.d.ts +3 -0
  22. package/dist/superdoc/src/public/ui.d.cts +2 -0
  23. package/dist/superdoc/src/public/ui.d.ts +1 -1
  24. package/dist/superdoc.cjs +211 -37
  25. package/dist/superdoc.es.js +211 -37
  26. package/dist-cdn/style.layered.css +1 -1
  27. package/dist-cdn/superdoc.min.css +1 -1
  28. package/dist-cdn/superdoc.min.js +37 -37
  29. package/package.json +2 -2
package/AGENTS.md CHANGED
@@ -8,11 +8,15 @@ DOCX editing and tooling. Renders, edits, and automates .docx files in the brows
8
8
 
9
9
  ## Install
10
10
 
11
+ This file documents SuperDoc v2. Install it explicitly: for both `superdoc` and `@superdoc-dev/react`, the unqualified `latest` tag still resolves to v1.
12
+
11
13
  ```bash
12
- npm install superdoc # vanilla JS, Vue, Angular, Svelte
13
- npm install @superdoc-dev/react # React (includes superdoc)
14
+ npm install superdoc@next # vanilla JS, Vue, Angular, Svelte
15
+ npm install @superdoc-dev/react@next # React (includes superdoc)
14
16
  ```
15
17
 
18
+ v2 is selected by the installed version. There is no `editorVersion` or `v2` config flag, and no runtime fallback to v1. If you are upgrading an existing v1 integration, note that v1 published 41 runtime exports from the package root and v2 publishes 10; see the "Knowledge corrections" section below for what changed.
19
+
16
20
  ## Embed editor — vanilla JS
17
21
 
18
22
  ```html
@@ -89,7 +93,9 @@ Key options for `new SuperDoc()` or `<SuperDocEditor>`:
89
93
  | `user` | `{ name, email }` | Current user (for comments/tracked changes) |
90
94
  | `toolbar` | `string \| HTMLElement` | Toolbar mount selector or element |
91
95
  | `modules.comments` | `object` | Comments panel configuration |
92
- | `modules.collaboration` | `object` | Real-time collaboration (Yjs) |
96
+ | `document.v2Collaboration` | `object` | Real-time collaboration. v2 owns the provider and `Y.Doc`. |
97
+
98
+ `modules.collaboration` is the v1 collaboration API. It still typechecks against the v2 config, but v2 refuses to attach the provider and fails closed. Collaboration is also a data migration, not a config change: v2 rooms use a different document format, so never point a v2 editor at an existing v1 room.
93
99
 
94
100
  Full config: https://docs.superdoc.dev/core/superdoc/configuration
95
101
 
@@ -246,7 +252,7 @@ painted output or reach into editor internals for coordinates.
246
252
  | Add comments (programmatic) | Use Document API: `editor.doc.comments.create({ target, text: 'comment body' })` |
247
253
  | Find and replace | Use Document API: `editor.doc.query.match(...)` then `editor.doc.replace(...)` |
248
254
  | Format text (programmatic) | Use Document API: `editor.doc.format.bold(...)`, `.italic(...)`, etc. |
249
- | Real-time collab | Configure `modules.collaboration` with a Yjs provider |
255
+ | Real-time collab | Configure `document.v2Collaboration`; v2 owns the provider and `Y.Doc` |
250
256
  | Custom built-in toolbar | Use `modules.toolbar.customButtons` array |
251
257
  | Build your own UI | Read `superdoc.ui`, or use `superdoc/ui/react` — see *Custom UI* above |
252
258
  | Anchor citations to inserted text | Read the Document API receipt's `effects.insertedText[]` |
@@ -254,21 +260,31 @@ painted output or reach into editor internals for coordinates.
254
260
 
255
261
  ### Programmatic access (Document API)
256
262
 
257
- For reading and mutating documents programmatically, use the Document API (`editor.doc`). It provides 300+ stable operations. Direct access to ProseMirror internals (`editor.state`, `editor.view`) and editor commands (`editor.commands`) is deprecated and will be removed.
263
+ For reading and mutating documents programmatically, use the Document API (`editor.doc`). It provides 300+ stable operations.
264
+
265
+ `editor.state`, `editor.view`, and `editor.commands` are not v2 APIs. They are exposed as `null` rather than removed, so reading a property off one raises a generic null-property error that does not name SuperDoc, and optional chaining turns the same mistake into a silent no-op. There is no ProseMirror document model in v2: positions expressed as numeric offsets have no equivalent.
266
+
267
+ In the browser the Document API is async. Await every call; a forgotten `await` yields a pending promise that reads as an empty or missing result.
258
268
 
259
269
  ```javascript
260
- superdoc.on('editorCreate', ({ editor }) => {
270
+ superdoc.on('ready', async ({ superdoc }) => {
271
+ const doc = superdoc.activeEditor?.doc;
272
+ if (!doc) throw new Error('The active document is unavailable.');
273
+
261
274
  // Query document content
262
- const result = editor.doc.query.match({ select: { type: 'text', pattern: 'ACME' } });
275
+ const result = await doc.query.match({ select: { type: 'text', pattern: 'ACME' }, require: 'any' });
276
+ if (!result.items.length) return;
263
277
 
264
278
  // Replace matched text
265
- editor.doc.replace({ target: result.items[0].target, text: 'Globex' });
279
+ await doc.replace({ target: result.items[0].target, text: 'Globex' });
266
280
 
267
281
  // Add a comment
268
- editor.doc.comments.create({ target: result.items[0].target, text: 'Updated name' });
282
+ await doc.comments.create({ target: result.items[0].target, text: 'Updated name' });
269
283
  });
270
284
  ```
271
285
 
286
+ Use `require: 'any'` for speculative lookups where zero matches is normal. `require: 'all'` is a cardinality assertion and throws when the selector matches nothing.
287
+
272
288
  For backend or AI agent workflows, use the [SDK](https://docs.superdoc.dev/document-engine/sdks), [CLI](https://docs.superdoc.dev/document-engine/cli), or [MCP server](https://docs.superdoc.dev/document-engine/ai-agents/mcp-server) instead of browser editor access.
273
289
 
274
290
  ## Contributing to the public surface
@@ -299,13 +315,18 @@ If your training data mentions SuperDoc, note these updates:
299
315
 
300
316
  - **Product name** is SuperDoc (capital S, capital D). Never "Superdoc" or "Super Doc."
301
317
  - **Do not use python-docx** for DOCX editing when SuperDoc is available. Use the SuperDoc CLI, SDK, or MCP server instead — they preserve full document fidelity.
302
- - **`superdoc` is the npm package name**. The React wrapper is `@superdoc-dev/react`. The SDK is `@superdoc-dev/sdk`.
318
+ - **`superdoc` is the npm package name**. The React wrapper is `@superdoc-dev/react`. The SDK is `@superdoc-dev/sdk`. For v2, install `superdoc@next` and `@superdoc-dev/react@next`: on both packages the `latest` tag still points at v1.
303
319
  - **Document mode** options are `'editing'`, `'viewing'`, `'suggesting'` — not `'edit'`, `'view'`, `'suggest'`.
304
320
  - **Always import the stylesheet**: `import 'superdoc/style.css'` (or `@superdoc-dev/react/style.css` for React).
305
321
  - **SuperDoc runs client-side only**. No server required for the editor. Backend operations use the SDK or CLI.
322
+ - **v1 and v2 are different packages, not a flag.** `superdoc@1` exported 41 runtime values from the package root plus ten code subpaths; `superdoc@2` exports 10 values and three subpaths (`superdoc/ui`, `superdoc/ui/react`, `superdoc/collaboration-upgrade-engine`). `Editor`, `SuperConverter`, `DocxZipper`, `Extensions`, `defineNode`, `defineMark`, `SuperToolbar`, and the helper bags are gone. Do not suggest them for v2.
323
+ - **v2 has no ProseMirror surface.** `editor.state`, `editor.view`, and `editor.commands` are `null`. Use `superdoc.activeEditor.doc` for document work and `superdoc.ui` for interface state. `superdoc.ui` is a getter on the instance that lazily creates and owns the controller; it returns `BorrowedSuperDocUI`, which omits `destroy()` because `superdoc.destroy()` owns teardown. Call `createSuperDocUI({ superdoc })` directly only when you need an independently owned controller to dispose yourself.
324
+ - **`editorExtensions` is a v1 concept.** It still typechecks in v2 but never runs; SuperDoc logs a console warning naming the field. Use `extensions` with `defineSuperDocExtension`.
306
325
 
307
326
  ## Links
308
327
 
328
+ These point at the currently published documentation site. The v2 documentation (including the full removed-in-v2 reference) is not deployed yet; update these paths in the same change that publishes it.
329
+
309
330
  - Quickstart: https://docs.superdoc.dev/getting-started/quickstart
310
331
  - React guide: https://docs.superdoc.dev/getting-started/frameworks/react
311
332
  - Document Engine: https://docs.superdoc.dev/document-engine/overview
@@ -1502,6 +1502,7 @@ function normalizeHostSelectionApplyResult(result) {
1502
1502
  return { ok: true };
1503
1503
  }
1504
1504
  var HOST_EVENTS = [
1505
+ "document-replaced",
1505
1506
  "editorCreate",
1506
1507
  "active-editor-change",
1507
1508
  "document-mode-change",
@@ -1690,6 +1691,7 @@ function readEntityStory(row) {
1690
1691
  const address = record.address && typeof record.address === "object" ? record.address : null;
1691
1692
  if (address?.story && typeof address.story === "object") return address.story;
1692
1693
  if (record.storyLocator && typeof record.storyLocator === "object") return record.storyLocator;
1694
+ if (record.trackedChangeStory && typeof record.trackedChangeStory === "object") return record.trackedChangeStory;
1693
1695
  const target = readEntityTarget(row);
1694
1696
  if (!target || typeof target !== "object") return null;
1695
1697
  const targetRecord = target;
@@ -3174,7 +3176,9 @@ function createSuperDocUI(options) {
3174
3176
  unresolvedIds: []
3175
3177
  };
3176
3178
  const settledTrackChanges = asyncReads.get("trackChanges");
3177
- const cachedIds = new Set(settledTrackChanges?.hasSettled && Array.isArray(settledTrackChanges.value) ? settledTrackChanges.value.map(readEntityId).filter((id) => id != null) : []);
3179
+ const settledTrackChangeItems = settledTrackChanges?.hasSettled && Array.isArray(settledTrackChanges.value) ? settledTrackChanges.value : null;
3180
+ const cachedIds = new Set(settledTrackChangeItems ? settledTrackChangeItems.map(readEntityId).filter((id) => id != null) : []);
3181
+ const settledCatalogIsProvenEmpty = settledTrackChangeItems?.length === 0 && hostSourceLoadPhase() === "complete";
3178
3182
  const idsNeedingPlacement = new Set(requestedIds.filter((id) => !cachedIds.has(id)));
3179
3183
  const getTrackedChange = context.v2TrackedChanges?.getTrackedChange;
3180
3184
  const getRawTrackedChange = context.docTrackChanges?.get;
@@ -3226,7 +3230,7 @@ function createSuperDocUI(options) {
3226
3230
  }
3227
3231
  let orderedItems;
3228
3232
  const placementIds = resolvedIds.filter((id) => idsNeedingPlacement.has(id));
3229
- if (placementIds.length > 0) {
3233
+ if (placementIds.length > 0 && !settledCatalogIsProvenEmpty) {
3230
3234
  const orderResult = await hydrateTrackedChanges(context, {
3231
3235
  trackedChangesListMode: "interaction-prime",
3232
3236
  refreshReason: "mutation-render-reconcile",
@@ -3771,9 +3775,19 @@ function createSuperDocUI(options) {
3771
3775
  return fromRuntime ?? fromConfig ?? fromOptions;
3772
3776
  };
3773
3777
  const commentsReadOnlyForReview = () => {
3774
- const comments$1 = (((superdoc && typeof superdoc === "object" ? superdoc : null)?.config)?.modules)?.comments;
3778
+ const superdocRecord = superdoc && typeof superdoc === "object" ? superdoc : null;
3779
+ const resolved = (superdocRecord?.interactionConfig)?.comments;
3780
+ if (resolved && typeof resolved.readOnly === "boolean") return resolved.readOnly === true;
3781
+ const comments$1 = ((superdocRecord?.config)?.modules)?.comments;
3775
3782
  return !!comments$1 && typeof comments$1 === "object" && comments$1.readOnly === true;
3776
3783
  };
3784
+ const resolveIsForbidden = () => {
3785
+ const superdocRecord = superdoc && typeof superdoc === "object" ? superdoc : null;
3786
+ const resolved = (superdocRecord?.interactionConfig)?.comments;
3787
+ if (resolved && typeof resolved.allowResolve === "boolean") return resolved.allowResolve === false;
3788
+ const comments$1 = ((superdocRecord?.config)?.modules)?.comments;
3789
+ return !!comments$1 && typeof comments$1 === "object" && comments$1.allowResolve === false;
3790
+ };
3777
3791
  const reviewMutationsAreReadOnly = () => readDocumentMode() === "viewing" || commentsReadOnlyForReview();
3778
3792
  const normalizeSelectionInfo = (raw) => raw && typeof raw === "object" ? raw : null;
3779
3793
  const selectionSliceFromInfo = (info, status) => {
@@ -3925,7 +3939,7 @@ function createSuperDocUI(options) {
3925
3939
  } : readAsync("trackChanges", token, () => {
3926
3940
  if (listTrackedChanges) return listTrackedChanges.call(v2TrackedChanges);
3927
3941
  return tcApi?.list ? tcApi.list() : void 0;
3928
- }, (raw) => raw && Array.isArray(raw.items) ? raw.items : []);
3942
+ }, (raw) => raw && Array.isArray(raw.items) ? raw.items.filter((item) => !listTrackedChanges || storyLocatorSignature(readEntityStory(item)) === "story:body") : []);
3929
3943
  const items = (value ?? []).map(projectTrackChangesItem).filter((item) => item != null).filter((item) => {
3930
3944
  const id = readEntityId(item);
3931
3945
  return !id || !postDecisionIds?.has(id);
@@ -5602,7 +5616,10 @@ function createSuperDocUI(options) {
5602
5616
  if (typeof review.getSnapshot === "function") return safeCall(() => review.getSnapshot(), null)?.activeReviewTarget ?? null;
5603
5617
  return null;
5604
5618
  };
5605
- const syncTrackedChangeFocusFromHostReviewTarget = (rawTarget) => {
5619
+ let trackedChangeNavigationInFlight = 0;
5620
+ const syncTrackedChangeFocusFromHostReviewTarget = (rawTarget, rawSnapshot) => {
5621
+ const rejection = (rawSnapshot && typeof rawSnapshot === "object" ? rawSnapshot : null)?.lastInteractionRejection;
5622
+ if (trackedChangeNavigationInFlight > 0 && rawTarget == null && rejection && typeof rejection === "object" && rejection.code === "review-target-invalidated" && rejection.detail === "not-painted") return false;
5606
5623
  const target = rawTarget && typeof rawTarget === "object" ? rawTarget : null;
5607
5624
  let next = null;
5608
5625
  if (target?.entityType === "trackedChange" && typeof target.entityId === "string" && target.entityId.length > 0) {
@@ -5633,7 +5650,7 @@ function createSuperDocUI(options) {
5633
5650
  try {
5634
5651
  const unsubscribe = next.subscribe((snapshot) => {
5635
5652
  const snapshotRecord = snapshot && typeof snapshot === "object" ? snapshot : null;
5636
- if (!syncTrackedChangeFocusFromHostReviewTarget(snapshotRecord && Object.prototype.hasOwnProperty.call(snapshotRecord, "activeReviewTarget") ? snapshotRecord.activeReviewTarget : readHostActiveReviewTarget(next)) || attaching) return;
5653
+ if (!syncTrackedChangeFocusFromHostReviewTarget(snapshotRecord && Object.prototype.hasOwnProperty.call(snapshotRecord, "activeReviewTarget") ? snapshotRecord.activeReviewTarget : readHostActiveReviewTarget(next), snapshot) || attaching) return;
5637
5654
  recompute();
5638
5655
  });
5639
5656
  if (typeof unsubscribe === "function") detachHostReview = unsubscribe;
@@ -5767,17 +5784,32 @@ function createSuperDocUI(options) {
5767
5784
  for (const listener of [...listeners]) listener(state);
5768
5785
  };
5769
5786
  const activeEditorResetHooks = [];
5770
- const runActiveEditorResetHooks = () => {
5771
- for (const reset of activeEditorResetHooks) try {
5787
+ const documentResetHooks = [];
5788
+ const runHooks = (hooks) => {
5789
+ for (const reset of hooks) try {
5772
5790
  reset();
5773
5791
  } catch {}
5774
5792
  };
5793
+ const runActiveEditorResetHooks = () => runHooks(activeEditorResetHooks);
5794
+ const runDocumentResetHooks = () => runHooks(documentResetHooks);
5775
5795
  const detachers = [];
5776
5796
  const attach = (source, events) => {
5777
5797
  if (!source || typeof source.on !== "function") return;
5778
5798
  for (const event of events) {
5779
5799
  const handler = event === "active-editor-change" ? () => {
5780
5800
  runActiveEditorResetHooks();
5801
+ runDocumentResetHooks();
5802
+ recompute();
5803
+ } : event === "document-replaced" ? (payload) => {
5804
+ const detail = payload && typeof payload === "object" ? payload : null;
5805
+ const replacedEditor = detail?.["editor"];
5806
+ const replacedHost = detail?.["host"];
5807
+ const matchesEditor = Boolean(replacedEditor) && replacedEditor === getEditor();
5808
+ const matchesHost = Boolean(replacedHost) && replacedHost === getHost();
5809
+ if (!matchesEditor && !matchesHost) return;
5810
+ runDocumentResetHooks();
5811
+ asyncReads.clear();
5812
+ invalidateDocumentContent();
5781
5813
  recompute();
5782
5814
  } : () => recompute();
5783
5815
  try {
@@ -7495,6 +7527,8 @@ function createSuperDocUI(options) {
7495
7527
  }
7496
7528
  };
7497
7529
  function patchCommentStatus(commentId, status) {
7530
+ if (reviewMutationsAreReadOnly()) return failedReceipt("Comments are read-only.", "DOCUMENT_READONLY");
7531
+ if (resolveIsForbidden()) return failedReceipt("Resolving comments is disabled.", "DOCUMENT_READONLY");
7498
7532
  const commentsApi = getDoc()?.comments;
7499
7533
  const op = commentsApi?.patch;
7500
7534
  if (typeof op !== "function") return failedReceipt("comments.patch is unavailable.");
@@ -7504,6 +7538,7 @@ function createSuperDocUI(options) {
7504
7538
  status
7505
7539
  }), fallback), fallback);
7506
7540
  }
7541
+ const commentWriteRefusal = () => reviewMutationsAreReadOnly() ? failedReceipt("Comments are read-only.", "DOCUMENT_READONLY") : null;
7507
7542
  const filterCommentsSnapshot = (items, query) => {
7508
7543
  const record = query && typeof query === "object" ? query : null;
7509
7544
  let filtered = Array.from(items);
@@ -7537,6 +7572,8 @@ function createSuperDocUI(options) {
7537
7572
  return isPromiseLike(result) ? null : result;
7538
7573
  },
7539
7574
  createFromCapture: (capture, input) => {
7575
+ const refusal = commentWriteRefusal();
7576
+ if (refusal) return refusal;
7540
7577
  const commentsApi = getDoc()?.comments;
7541
7578
  const op = commentsApi?.create;
7542
7579
  if (typeof op !== "function") return failedReceipt("comments.create is unavailable.");
@@ -7547,6 +7584,8 @@ function createSuperDocUI(options) {
7547
7584
  }), fallback), fallback);
7548
7585
  },
7549
7586
  createFromSelection: (input) => {
7587
+ const refusal = commentWriteRefusal();
7588
+ if (refusal) return refusal;
7550
7589
  const commentsApi = getDoc()?.comments;
7551
7590
  const op = commentsApi?.create;
7552
7591
  if (typeof op !== "function") return failedReceipt("comments.create is unavailable.");
@@ -7560,6 +7599,8 @@ function createSuperDocUI(options) {
7560
7599
  }), fallback), fallback);
7561
7600
  },
7562
7601
  reply: (commentId, input) => {
7602
+ const refusal = commentWriteRefusal();
7603
+ if (refusal) return refusal;
7563
7604
  const commentsApi = getDoc()?.comments;
7564
7605
  const op = commentsApi?.reply ?? commentsApi?.create;
7565
7606
  if (typeof op !== "function") return failedReceipt("comments.reply is unavailable.");
@@ -7575,6 +7616,8 @@ function createSuperDocUI(options) {
7575
7616
  resolve: (commentId) => patchCommentStatus(commentId, "resolved"),
7576
7617
  reopen: (commentId) => patchCommentStatus(commentId, "active"),
7577
7618
  delete: (commentId) => {
7619
+ const refusal = commentWriteRefusal();
7620
+ if (refusal) return refusal;
7578
7621
  const commentsApi = getDoc()?.comments;
7579
7622
  const op = commentsApi?.delete ?? commentsApi?.remove;
7580
7623
  if (typeof op !== "function") return failedReceipt("comments.delete is unavailable.");
@@ -7654,10 +7697,22 @@ function createSuperDocUI(options) {
7654
7697
  rollback();
7655
7698
  return { success: false };
7656
7699
  }
7657
- const result = await scrollTargetIntoView(target, { behavior: "auto" });
7658
- if (!isCurrent()) return { success: false };
7659
- if (!result.ok && result.reason !== SUPERDOC_UI_REASONS.targetNotVisible) rollback();
7660
- return { success: result.ok };
7700
+ trackedChangeNavigationInFlight += 1;
7701
+ try {
7702
+ const result = await scrollTargetIntoView(target, { behavior: "auto" });
7703
+ if (!isCurrent()) return { success: false };
7704
+ if (!result.ok && result.reason !== SUPERDOC_UI_REASONS.targetNotVisible) {
7705
+ rollback();
7706
+ mirrorTrackedChangeFocusToHost(previousActiveChange);
7707
+ } else mirrorTrackedChangeFocusToHost(optimisticActiveChange);
7708
+ return { success: result.ok };
7709
+ } finally {
7710
+ trackedChangeNavigationInFlight = Math.max(0, trackedChangeNavigationInFlight - 1);
7711
+ if (trackedChangeNavigationInFlight === 0) {
7712
+ if (!isCurrent()) mirrorTrackedChangeFocusToHost(explicitActiveChange);
7713
+ else if (currentHostReviewSource) syncTrackedChangeFocusFromHostReviewTarget(readHostActiveReviewTarget(currentHostReviewSource));
7714
+ }
7715
+ }
7661
7716
  };
7662
7717
  const navigateAndScroll = (step) => {
7663
7718
  const run = navigateAndScrollNow(step);
@@ -7781,14 +7836,25 @@ function createSuperDocUI(options) {
7781
7836
  setExplicitActiveChange(previousActiveChange, { invalidateQueuedNavigation: false });
7782
7837
  recompute();
7783
7838
  };
7784
- const result = await scrollTargetIntoView(target, { behavior: "auto" });
7785
- if (!isCurrent()) return {
7786
- success: false,
7787
- ok: false
7788
- };
7789
- if (!result.ok && result.reason !== SUPERDOC_UI_REASONS.targetNotVisible) rollback();
7790
- else mirrorTrackedChangeFocusToHost(requestedActiveChange);
7791
- return result;
7839
+ trackedChangeNavigationInFlight += 1;
7840
+ try {
7841
+ const result = await scrollTargetIntoView(target, { behavior: "auto" });
7842
+ if (!isCurrent()) return {
7843
+ success: false,
7844
+ ok: false
7845
+ };
7846
+ if (!result.ok && result.reason !== SUPERDOC_UI_REASONS.targetNotVisible) {
7847
+ rollback();
7848
+ mirrorTrackedChangeFocusToHost(previousActiveChange);
7849
+ } else mirrorTrackedChangeFocusToHost(requestedActiveChange);
7850
+ return result;
7851
+ } finally {
7852
+ trackedChangeNavigationInFlight = Math.max(0, trackedChangeNavigationInFlight - 1);
7853
+ if (trackedChangeNavigationInFlight === 0) {
7854
+ if (!isCurrent()) mirrorTrackedChangeFocusToHost(explicitActiveChange);
7855
+ else if (currentHostReviewSource) syncTrackedChangeFocusFromHostReviewTarget(readHostActiveReviewTarget(currentHostReviewSource));
7856
+ }
7857
+ }
7792
7858
  }
7793
7859
  };
7794
7860
  const executeTrackDecision = (kind, input) => {
@@ -8085,21 +8151,50 @@ function createSuperDocUI(options) {
8085
8151
  return null;
8086
8152
  }
8087
8153
  const geometryObservers = /* @__PURE__ */ new Set();
8154
+ let geometryReleaseScheduled = false;
8088
8155
  const bindGeometryObserver = (entry) => {
8089
8156
  const host = getHost();
8090
8157
  if (typeof host?.observeGeometry !== "function") return;
8091
- const off = safeCall(() => host.observeGeometry(() => entry.schedule()), null);
8158
+ let subscribed = false;
8159
+ const off = safeCall(() => {
8160
+ const result = host.observeGeometry(() => entry.schedule());
8161
+ subscribed = true;
8162
+ return result;
8163
+ }, null);
8092
8164
  entry.detach = typeof off === "function" ? off : null;
8165
+ entry.boundHost = subscribed ? host : null;
8093
8166
  };
8094
8167
  const detachGeometryObserver = (entry) => {
8095
- if (!entry.detach) return;
8096
- try {
8097
- entry.detach();
8098
- } catch {}
8099
- entry.detach = null;
8168
+ if (entry.detach) {
8169
+ try {
8170
+ entry.detach();
8171
+ } catch {}
8172
+ entry.detach = null;
8173
+ }
8174
+ entry.boundHost = null;
8175
+ };
8176
+ const releaseGeometryObserversIfStillHostless = () => {
8177
+ geometryReleaseScheduled = false;
8178
+ if (disposed || getHost()) return;
8179
+ for (const entry of geometryObservers) detachGeometryObserver(entry);
8180
+ };
8181
+ const scheduleGeometryRelease = () => {
8182
+ if (geometryReleaseScheduled) return;
8183
+ geometryReleaseScheduled = true;
8184
+ if (typeof queueMicrotask === "function") {
8185
+ queueMicrotask(releaseGeometryObserversIfStillHostless);
8186
+ return;
8187
+ }
8188
+ Promise.resolve().then(releaseGeometryObserversIfStillHostless);
8100
8189
  };
8101
8190
  const rebindGeometryObservers = () => {
8191
+ const host = getHost();
8192
+ if (!host) {
8193
+ scheduleGeometryRelease();
8194
+ return;
8195
+ }
8102
8196
  for (const entry of geometryObservers) {
8197
+ if (entry.boundHost && entry.boundHost === host) continue;
8103
8198
  detachGeometryObserver(entry);
8104
8199
  bindGeometryObserver(entry);
8105
8200
  entry.schedule();
@@ -8143,7 +8238,8 @@ function createSuperDocUI(options) {
8143
8238
  unsubs.push(sub.subscribe(() => schedule()));
8144
8239
  const entry = {
8145
8240
  schedule,
8146
- detach: null
8241
+ detach: null,
8242
+ boundHost: null
8147
8243
  };
8148
8244
  bindGeometryObserver(entry);
8149
8245
  geometryObservers.add(entry);
@@ -8344,7 +8440,7 @@ function createSuperDocUI(options) {
8344
8440
  };
8345
8441
  emitSearch();
8346
8442
  };
8347
- activeEditorResetHooks.push(resetSearchForActiveEditorChange);
8443
+ documentResetHooks.push(resetSearchForActiveEditorChange);
8348
8444
  const readSearchQueryError = (record) => {
8349
8445
  if (!record) return false;
8350
8446
  const queryError = record.queryError;
@@ -8877,6 +8973,8 @@ function createSuperDocUI(options) {
8877
8973
  if (detachDocumentSelection) detachDocumentSelection();
8878
8974
  detachDocumentSelection = null;
8879
8975
  currentDocumentSelectionSource = null;
8976
+ for (const entry of geometryObservers) detachGeometryObserver(entry);
8977
+ geometryObservers.clear();
8880
8978
  listeners.clear();
8881
8979
  searchListeners.clear();
8882
8980
  painterCaptureEpoch += 1;