superdoc 2.0.0-next.27 → 2.0.0-next.29

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/{PdfViewer-XZg0nr8y.es.js → PdfViewer-CNOtyS-I.es.js} +1 -1
  2. package/dist/chunks/{PdfViewer-BtuYf2yM.cjs → PdfViewer-CoFiGAhm.cjs} +1 -1
  3. package/dist/chunks/{create-super-doc-ui-Dvl3EZsv.es.js → create-super-doc-ui-BKVf2uCM.es.js} +660 -159
  4. package/dist/chunks/{create-super-doc-ui-pYMthdDn.cjs → create-super-doc-ui-BXehf3i3.cjs} +660 -159
  5. package/dist/collaboration-upgrade-engine.cjs +1 -1
  6. package/dist/collaboration-upgrade-engine.es.js +1 -1
  7. package/dist/document-api/src/contract/operation-definitions.d.ts +1 -1
  8. package/dist/document-api/src/track-changes/track-changes.d.ts +6 -2
  9. package/dist/document-api/src/types/clipboard.d.ts +5 -1
  10. package/dist/document-api/src/types/table-operations.types.d.ts +6 -4
  11. package/dist/document-api/src/types/track-changes.types.d.ts +15 -0
  12. package/dist/layout-engine/contracts/src/incremental-dependency.d.ts +10 -0
  13. package/dist/layout-engine/contracts/src/index.d.ts +38 -0
  14. package/dist/layout-engine/layout-bridge/src/incrementalLayout.d.ts +40 -1
  15. package/dist/layout-engine/layout-engine/src/index.d.ts +17 -0
  16. package/dist/layout-engine/layout-engine/src/paginator.d.ts +16 -0
  17. package/dist/layout-engine/layout-engine/src/paragraph-layout-eligibility.d.ts +1 -4
  18. package/dist/public/ui-react.cjs +1 -1
  19. package/dist/public/ui-react.es.js +1 -1
  20. package/dist/public/ui.cjs +1 -1
  21. package/dist/public/ui.es.js +1 -1
  22. package/dist/style.css +130 -86
  23. package/dist/style.layered.css +130 -86
  24. package/dist/superdoc/src/components/CommentsLayer/floating-comment-positioning.d.ts +1 -2
  25. package/dist/superdoc/src/core/types/index.d.ts +7 -0
  26. package/dist/superdoc/src/helpers/v2-review-mutation-impact.d.ts +4 -1
  27. package/dist/superdoc/src/helpers/v2-review-mutation-reconciler.d.ts +6 -5
  28. package/dist/superdoc/src/public/ui/reasons.d.ts +8 -0
  29. package/dist/superdoc/src/stores/comments-store.d.ts +17 -11
  30. package/dist/superdoc/src/stores/helpers/floating-comment-instances.d.ts +2 -10
  31. package/dist/superdoc/src/stores/helpers/tracked-change-thread-index.d.ts +14 -0
  32. package/dist/superdoc/src/stores/superdoc-store.d.ts +51 -33
  33. package/dist/superdoc.cjs +531 -135
  34. package/dist/superdoc.es.js +531 -135
  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
@@ -21,7 +21,8 @@ function getV2TrackedChangeMutationImpact(event) {
21
21
  if (upsertIds.size === 0 && removedIds.size === 0) return null;
22
22
  return {
23
23
  upsertIds,
24
- removedIds
24
+ removedIds,
25
+ reconcileMode: event.origin === "history" ? "authoritative" : "targeted"
25
26
  };
26
27
  }
27
28
  var normalizeIds = (values) => new Set(Array.from(values ?? []).filter((value) => typeof value === "string" && value.length > 0));
@@ -31,7 +32,7 @@ var itemId = (item) => {
31
32
  if (typeof item?.address?.entityId === "string" && item.address.entityId.length > 0) return item.address.entityId;
32
33
  return null;
33
34
  };
34
- var contextMatches = (left, right) => left?.adapter === right?.adapter && left?.documentId === right?.documentId && left?.editor === right?.editor;
35
+ var contextMatches = (left, right) => left?.adapter === right?.adapter && left?.documentId === right?.documentId && left?.editor === right?.editor && left?.reconcileToken === right?.reconcileToken;
35
36
  function createV2ReviewMutationReconciler({ getContext, reconcile, hydrate, onReconciled }) {
36
37
  const pendingUpserts = /* @__PURE__ */ new Map();
37
38
  let sequence = 0;
@@ -48,43 +49,78 @@ function createV2ReviewMutationReconciler({ getContext, reconcile, hydrate, onRe
48
49
  }
49
50
  return clearedIds;
50
51
  };
51
- const readySnapshot = () => new Map([...pendingUpserts].filter(([, entry]) => entry.ready === true));
52
+ const readySnapshot = (reconcileMode) => new Map([...pendingUpserts].filter(([, entry]) => entry.ready === true && entry.reconcileMode === reconcileMode));
52
53
  const markRenderGatedReady = () => {
53
- for (const entry of pendingUpserts.values()) if (entry.gate === "render") entry.ready = true;
54
+ let changed = false;
55
+ for (const entry of pendingUpserts.values()) {
56
+ if (entry.gate !== "render" || entry.ready) continue;
57
+ entry.ready = true;
58
+ changed = true;
59
+ }
60
+ return changed;
61
+ };
62
+ const markEntriesReadyThrough = (maxVersion) => {
63
+ let changed = false;
64
+ for (const entry of pendingUpserts.values()) {
65
+ if (entry.version > maxVersion || entry.ready) continue;
66
+ entry.ready = true;
67
+ changed = true;
68
+ }
69
+ return changed;
54
70
  };
55
- const notifyReconciled = async (context, ids, result) => {
56
- if (ids.size === 0 || typeof onReconciled !== "function") return;
71
+ const notifyReconciled = async (context, ids, result, allowEmpty = false) => {
72
+ if (!allowEmpty && ids.size === 0 || typeof onReconciled !== "function") return;
57
73
  try {
58
74
  await onReconciled(context, ids, result);
59
75
  } catch {}
60
76
  };
61
77
  const idsFromResult = (result) => normalizeIds(result?.resolvedIds ?? (result?.items ?? []).map(itemId).filter(Boolean));
78
+ const reconcileTargetedSnapshot = async (context, snapshot, capturedGeneration) => {
79
+ const result = await reconcile(context, {
80
+ upsertIds: new Set(snapshot.keys()),
81
+ removedIds: /* @__PURE__ */ new Set(),
82
+ reconcileMode: "targeted"
83
+ });
84
+ if (!isCurrent(capturedGeneration, context)) return;
85
+ await notifyReconciled(context, clearResolved(idsFromResult(result), snapshot), result);
86
+ if (new Set([...snapshot].filter(([id, version]) => pendingUpserts.get(id) === version).map(([id]) => id)).size === 0) return;
87
+ const fallback = await hydrate(context, {
88
+ trackedChangesListMode: "interaction-prime",
89
+ refreshReason: "mutation-render-reconcile",
90
+ blocking: false
91
+ });
92
+ if (!isCurrent(capturedGeneration, context)) return;
93
+ await notifyReconciled(context, clearResolved(idsFromResult(fallback), snapshot), fallback);
94
+ };
95
+ const reconcileAuthoritativeSnapshot = async (context, snapshot, capturedGeneration) => {
96
+ const result = await hydrate(context, {
97
+ trackedChangesListMode: "all",
98
+ refreshReason: "mutation-history-render-reconcile",
99
+ blocking: true
100
+ });
101
+ if (!isCurrent(capturedGeneration, context)) return;
102
+ if (![...snapshot].some(([id, entry]) => pendingUpserts.get(id) === entry)) return;
103
+ const transportSucceeded = result?.ok === true;
104
+ const catalogIsAuthoritative = transportSucceeded && result?.complete === true && result?.sourceCoverageComplete === true;
105
+ const returnedIds = transportSucceeded ? idsFromResult(result) : /* @__PURE__ */ new Set();
106
+ const clearedIds = catalogIsAuthoritative ? clearResolved(new Set(snapshot.keys()), snapshot) : /* @__PURE__ */ new Set();
107
+ await notifyReconciled(context, catalogIsAuthoritative ? clearedIds : returnedIds, {
108
+ ...result,
109
+ reconcileMode: "authoritative"
110
+ }, true);
111
+ };
62
112
  const flush = () => {
63
- if (flushInFlight) {
64
- flushRequested = true;
65
- return flushInFlight;
66
- }
67
- const snapshot = readySnapshot();
68
- if (snapshot.size === 0) return null;
113
+ if (flushInFlight) return flushInFlight;
114
+ const targetedSnapshot = readySnapshot("targeted");
115
+ const authoritativeSnapshot = readySnapshot("authoritative");
116
+ if (targetedSnapshot.size === 0 && authoritativeSnapshot.size === 0) return null;
69
117
  const context = getContext();
70
118
  if (!context?.adapter) return null;
71
119
  flushRequested = false;
72
120
  const capturedGeneration = generation;
73
- const upsertIds = new Set(snapshot.keys());
74
- flushInFlight = Promise.resolve().then(() => reconcile(context, {
75
- upsertIds,
76
- removedIds: /* @__PURE__ */ new Set()
77
- })).then(async (result) => {
78
- if (!isCurrent(capturedGeneration, context)) return;
79
- await notifyReconciled(context, clearResolved(idsFromResult(result), snapshot), result);
80
- if (new Set([...snapshot].filter(([id, version]) => pendingUpserts.get(id) === version).map(([id]) => id)).size === 0) return;
81
- const fallback = await hydrate(context, {
82
- trackedChangesListMode: "interaction-prime",
83
- refreshReason: "mutation-render-reconcile",
84
- blocking: false
85
- });
86
- if (!isCurrent(capturedGeneration, context)) return;
87
- await notifyReconciled(context, clearResolved(idsFromResult(fallback), snapshot), fallback);
121
+ flushInFlight = Promise.resolve().then(async () => {
122
+ if (authoritativeSnapshot.size > 0) await reconcileAuthoritativeSnapshot(context, authoritativeSnapshot, capturedGeneration).catch(() => void 0);
123
+ if (targetedSnapshot.size > 0) await reconcileTargetedSnapshot(context, targetedSnapshot, capturedGeneration).catch(() => void 0);
88
124
  }).catch(() => {}).finally(() => {
89
125
  flushInFlight = null;
90
126
  if (flushRequested) flush();
@@ -93,8 +129,18 @@ function createV2ReviewMutationReconciler({ getContext, reconcile, hydrate, onRe
93
129
  };
94
130
  const enqueueWithGate = (impact, gate) => {
95
131
  if (!impact) return;
132
+ const reconcileMode = impact.reconcileMode === "authoritative" ? "authoritative" : "targeted";
96
133
  const removedIds = normalizeIds(impact.removedIds);
97
- for (const id of removedIds) pendingUpserts.delete(id);
134
+ for (const id of removedIds) {
135
+ const existing = pendingUpserts.get(id);
136
+ if (existing?.reconcileMode === "authoritative") pendingUpserts.set(id, {
137
+ ...existing,
138
+ version: ++sequence,
139
+ gate,
140
+ ready: false
141
+ });
142
+ else pendingUpserts.delete(id);
143
+ }
98
144
  const context = getContext();
99
145
  if (removedIds.size > 0 && context?.adapter) try {
100
146
  Promise.resolve(reconcile(context, {
@@ -102,16 +148,20 @@ function createV2ReviewMutationReconciler({ getContext, reconcile, hydrate, onRe
102
148
  removedIds
103
149
  })).catch(() => void 0);
104
150
  } catch {}
105
- for (const id of normalizeIds(impact.upsertIds)) if (!removedIds.has(id)) pendingUpserts.set(id, {
106
- version: ++sequence,
107
- gate,
108
- ready: false
109
- });
151
+ for (const id of normalizeIds(impact.upsertIds)) if (!removedIds.has(id)) {
152
+ const existing = pendingUpserts.get(id);
153
+ pendingUpserts.set(id, {
154
+ version: ++sequence,
155
+ gate,
156
+ ready: false,
157
+ reconcileMode: reconcileMode === "authoritative" || existing?.reconcileMode === "authoritative" ? "authoritative" : "targeted"
158
+ });
159
+ }
110
160
  };
111
161
  const enqueue = (impact) => enqueueWithGate(impact, "render");
112
- const onRender = () => {
113
- markRenderGatedReady();
114
- flushRequested = true;
162
+ const onRender = (supersedesInFlight = false) => {
163
+ const madeReady = markRenderGatedReady();
164
+ if (flushInFlight && (madeReady || supersedesInFlight)) flushRequested = true;
115
165
  flush();
116
166
  };
117
167
  return {
@@ -125,18 +175,22 @@ function createV2ReviewMutationReconciler({ getContext, reconcile, hydrate, onRe
125
175
  const context = getContext();
126
176
  if (!context?.adapter) return;
127
177
  const capturedGeneration = generation;
128
- const capturedEntries = new Map([...normalizeIds(impact.upsertIds)].flatMap((id) => {
178
+ const capturedSequence = sequence;
179
+ const impactedIds = new Set([...normalizeIds(impact.upsertIds), ...normalizeIds(impact.removedIds)]);
180
+ const capturedEntries = new Map([...impactedIds].flatMap((id) => {
129
181
  const entry = pendingUpserts.get(id);
130
182
  return entry ? [[id, entry]] : [];
131
183
  }));
132
184
  Promise.resolve().then(() => waitForPaint()).then(() => {
133
185
  if (!isCurrent(capturedGeneration, context)) return;
134
- markRenderGatedReady();
186
+ let madeReady = markRenderGatedReady();
187
+ if (markEntriesReadyThrough(capturedSequence)) madeReady = true;
135
188
  for (const [id, entry] of capturedEntries) {
136
189
  if (pendingUpserts.get(id) !== entry) continue;
190
+ if (!entry.ready) madeReady = true;
137
191
  entry.ready = true;
138
192
  }
139
- flushRequested = true;
193
+ if (flushInFlight && (madeReady || capturedSequence > 0)) flushRequested = true;
140
194
  flush();
141
195
  }).catch(() => {
142
196
  if (!isCurrent(capturedGeneration, context)) return;
@@ -311,7 +365,8 @@ const SUPERDOC_UI_REASONS = {
311
365
  historyEmpty: "history-empty",
312
366
  searchUnavailable: "search-unavailable",
313
367
  searchInvalidPattern: "search-invalid-pattern",
314
- replaceUnsupported: "replace-unsupported"
368
+ replaceUnsupported: "replace-unsupported",
369
+ contentControlLocked: "content-control-locked"
315
370
  };
316
371
  const BUILT_IN_COMMAND_IDS = {
317
372
  bold: "bold",
@@ -2275,7 +2330,13 @@ function buildTableCommandInput(action, context) {
2275
2330
  function createSuperDocUI(options) {
2276
2331
  const superdoc = options.superdoc;
2277
2332
  const optimisticInlineValues = /* @__PURE__ */ new Map();
2333
+ const optimisticInlineToggles = /* @__PURE__ */ new Map();
2334
+ const pendingInlineToggleMutations = [];
2278
2335
  let lastOptimisticInlineSelectionSignature = null;
2336
+ let optimisticInlineToggleGeneration = 0;
2337
+ let inlineToggleMutationActive = false;
2338
+ let inlineToggleMutationIdle = Promise.resolve();
2339
+ let resolveInlineToggleMutationIdle = null;
2279
2340
  let painter = {
2280
2341
  mode: "idle",
2281
2342
  snapshot: null,
@@ -2451,6 +2512,31 @@ function createSuperDocUI(options) {
2451
2512
  let explicitActiveChange = null;
2452
2513
  let explicitActiveChangeRevision = 0;
2453
2514
  let queuedTrackChangeNavigationInvalidation = 0;
2515
+ const mirrorTrackedChangeFocusToHost = (next) => {
2516
+ const host = getHost();
2517
+ if (typeof host?.getHandles !== "function") return;
2518
+ const handles = safeCall(() => host.getHandles(), null);
2519
+ const review = handles?.review;
2520
+ if (!review) return;
2521
+ if (next) {
2522
+ if (typeof review.setActiveReviewTarget !== "function") return;
2523
+ const layout = handles?.layout;
2524
+ safeCall(() => review.setActiveReviewTarget({
2525
+ entityType: "trackedChange",
2526
+ entityId: next.id,
2527
+ ...next.paintedEntityId ? { paintedEntityId: next.paintedEntityId } : {},
2528
+ origin: "panel",
2529
+ layoutEpoch: typeof layout?.generation === "number" ? layout.generation : 0,
2530
+ story: next.story ?? {
2531
+ kind: "story",
2532
+ storyType: "body"
2533
+ }
2534
+ }), null);
2535
+ return;
2536
+ }
2537
+ if (typeof review.getActiveReviewTarget !== "function" || typeof review.clearActiveReviewTarget !== "function") return;
2538
+ if (safeCall(() => review.getActiveReviewTarget(), null)?.entityType === "trackedChange") safeCall(() => review.clearActiveReviewTarget(), void 0);
2539
+ };
2454
2540
  const setExplicitActiveChange = (next, options$1) => {
2455
2541
  explicitActiveChange = next;
2456
2542
  explicitActiveChangeRevision += 1;
@@ -2464,6 +2550,8 @@ function createSuperDocUI(options) {
2464
2550
  let lastEffectiveInlineReadKey = null;
2465
2551
  let pendingEffectiveInlineRead = null;
2466
2552
  let asyncRefreshScheduled = false;
2553
+ let asyncReadFailureRetryTimer = null;
2554
+ let asyncReadFailureRetryAtMs = 0;
2467
2555
  let foregroundAsyncRetryTimer = null;
2468
2556
  let pendingSelectionSeedValidationToken = null;
2469
2557
  let typingContentInvalidationTimer = null;
@@ -2478,6 +2566,19 @@ function createSuperDocUI(options) {
2478
2566
  if (typeof queueMicrotask === "function") queueMicrotask(run);
2479
2567
  else Promise.resolve().then(run);
2480
2568
  };
2569
+ const ASYNC_READ_FAILURE_RETRY_BASE_MS = 250;
2570
+ const ASYNC_READ_FAILURE_RETRY_MAX_MS = 5e3;
2571
+ const scheduleAsyncReadFailureRetry = (retryAtMs) => {
2572
+ if (disposed) return;
2573
+ if (asyncReadFailureRetryTimer && asyncReadFailureRetryAtMs <= retryAtMs) return;
2574
+ if (asyncReadFailureRetryTimer) clearTimeout(asyncReadFailureRetryTimer);
2575
+ asyncReadFailureRetryAtMs = retryAtMs;
2576
+ asyncReadFailureRetryTimer = setTimeout(() => {
2577
+ asyncReadFailureRetryTimer = null;
2578
+ asyncReadFailureRetryAtMs = 0;
2579
+ if (!disposed) recompute();
2580
+ }, Math.max(0, retryAtMs - Date.now()));
2581
+ };
2481
2582
  const editorIdentityIds = /* @__PURE__ */ new WeakMap();
2482
2583
  let editorIdentityCounter = 0;
2483
2584
  const editorIdentityId = (editor) => {
@@ -2490,6 +2591,16 @@ function createSuperDocUI(options) {
2490
2591
  return `e${id}`;
2491
2592
  };
2492
2593
  let documentMutationRevision = 0;
2594
+ let reviewMutationToken = {};
2595
+ let authoritativeTrackChangesPendingToken = null;
2596
+ let authoritativeTrackChangesRetryTimer = null;
2597
+ let authoritativeTrackChangesFailureCount = 0;
2598
+ const clearAuthoritativeTrackChangesRetry = () => {
2599
+ if (authoritativeTrackChangesRetryTimer) clearTimeout(authoritativeTrackChangesRetryTimer);
2600
+ authoritativeTrackChangesRetryTimer = null;
2601
+ authoritativeTrackChangesFailureCount = 0;
2602
+ };
2603
+ let scheduleAuthoritativeTrackChangesRetry = (_reconcileToken) => void 0;
2493
2604
  let postDecisionTrackChangesToken = null;
2494
2605
  let postDecisionTrackChangesReconcileToken = null;
2495
2606
  let postDecisionTrackChangeIds = /* @__PURE__ */ new Set();
@@ -2535,28 +2646,92 @@ function createSuperDocUI(options) {
2535
2646
  run();
2536
2647
  }, POST_DECISION_TRACKED_CHANGES_RECONCILE_DELAY_MS);
2537
2648
  };
2538
- const markPostDecisionTrackChanges = (ids) => {
2649
+ const notifyPostDecisionTrackChanges = (receipt) => {
2650
+ const notify = () => {
2651
+ if (disposed) return;
2652
+ for (const listener of [...listeners]) listener(state);
2653
+ };
2654
+ const record = isLooseObject(receipt) ? receipt : null;
2655
+ const readiness = getEditor()?.documentMutationReadiness;
2656
+ const waitForPostPaintTask = () => {
2657
+ const requestFrame = globalThis.requestAnimationFrame;
2658
+ if (typeof requestFrame !== "function") return Promise.resolve();
2659
+ return new Promise((resolve) => {
2660
+ let settled = false;
2661
+ const finish = () => {
2662
+ if (settled) return;
2663
+ settled = true;
2664
+ clearTimeout(fallback);
2665
+ resolve();
2666
+ };
2667
+ const fallback = setTimeout(finish, 250);
2668
+ requestFrame(() => requestFrame(() => setTimeout(finish, 0)));
2669
+ });
2670
+ };
2671
+ if (record?.success === true && typeof record.txId === "string" && record.txId.length > 0 && typeof readiness?.whenPainted === "function") try {
2672
+ Promise.resolve(readiness.whenPainted.call(readiness, record)).then(waitForPostPaintTask, () => void 0).then(notify, notify);
2673
+ return;
2674
+ } catch {}
2675
+ notify();
2676
+ };
2677
+ const publishPostDecisionTrackChanges = (ids, receipt) => {
2678
+ const previous = state.trackChanges;
2679
+ const items = previous.items.filter((item) => {
2680
+ const id = readEntityId(item);
2681
+ return !id || !ids.has(id);
2682
+ });
2683
+ if (items.length === previous.items.length) return;
2684
+ if (explicitActiveChange && ids.has(explicitActiveChange.id)) setExplicitActiveChange(null);
2685
+ const authors = /* @__PURE__ */ new Set();
2686
+ for (const item of items) {
2687
+ const change = trackChangesItemPayload(item);
2688
+ const row = item;
2689
+ const author = change.author ?? change.authorEmail ?? row.author ?? row.authorEmail;
2690
+ if (typeof author === "string") authors.add(author);
2691
+ }
2692
+ const removedCount = previous.items.length - items.length;
2693
+ state = {
2694
+ ...state,
2695
+ trackChanges: {
2696
+ ...previous,
2697
+ items,
2698
+ total: Math.max(items.length, previous.total - removedCount),
2699
+ activeId: previous.activeId && ids.has(previous.activeId) ? null : previous.activeId,
2700
+ authors: [...authors]
2701
+ }
2702
+ };
2703
+ notifyPostDecisionTrackChanges(receipt);
2704
+ };
2705
+ const markPostDecisionTrackChanges = (ids, receipt) => {
2539
2706
  if (ids.size === 0) return;
2540
- postDecisionTrackChangesToken = contentToken();
2541
- postDecisionTrackChangeIds = new Set(ids);
2707
+ const token = contentToken();
2708
+ const alreadyRemoved = postDecisionTrackChangesToken === token ? postDecisionTrackChangeIds : /* @__PURE__ */ new Set();
2709
+ const newlyRemoved = new Set([...ids].filter((id) => !alreadyRemoved.has(id)));
2710
+ if (newlyRemoved.size === 0) return;
2711
+ postDecisionTrackChangesToken = token;
2712
+ postDecisionTrackChangeIds = new Set([...alreadyRemoved, ...newlyRemoved]);
2542
2713
  applyPostDecisionTrackChangesToCache(postDecisionTrackChangeIds);
2543
2714
  schedulePostDecisionTrackChangesReconcile();
2544
- recompute();
2715
+ publishPostDecisionTrackChanges(newlyRemoved, receipt);
2545
2716
  };
2546
2717
  const postDecisionTrackChangeIdsForToken = (token) => postDecisionTrackChangesToken === token && postDecisionTrackChangeIds.size > 0 ? postDecisionTrackChangeIds : null;
2547
- const mergeTrackedChangeItemsIntoCache = (items) => {
2718
+ const mergeTrackedChangeItemsIntoCache = (items, orderedItems = []) => {
2548
2719
  const projected = items.map(projectTrackChangesItem).filter((item) => item != null && readEntityId(item) != null);
2549
2720
  if (projected.length === 0) return;
2721
+ const orderedIds = orderedItems.map(projectTrackChangesItem).map((item) => item == null ? null : readEntityId(item)).filter((id) => id != null);
2550
2722
  for (const key of ["trackChanges", "trackChanges:all"]) {
2551
2723
  const entry = asyncReads.get(key);
2552
2724
  const nextValue = entry?.hasSettled && Array.isArray(entry.value) ? [...entry.value] : [];
2725
+ const insertedIds = /* @__PURE__ */ new Set();
2553
2726
  let changed = false;
2554
2727
  for (const fresh of projected) {
2555
2728
  const id = readEntityId(fresh);
2556
2729
  if (!id) continue;
2557
2730
  const index = nextValue.findIndex((candidate) => readEntityId(candidate) === id);
2558
- if (index < 0) nextValue.push(fresh);
2559
- else {
2731
+ if (index < 0) {
2732
+ nextValue.push(fresh);
2733
+ insertedIds.add(id);
2734
+ } else {
2560
2735
  const previous = nextValue[index];
2561
2736
  nextValue[index] = isLooseObject(previous) ? {
2562
2737
  ...previous,
@@ -2565,6 +2740,24 @@ function createSuperDocUI(options) {
2565
2740
  }
2566
2741
  changed = true;
2567
2742
  }
2743
+ if (key === "trackChanges" && insertedIds.size > 0 && orderedIds.length > 0) {
2744
+ const placeableIds = new Set(orderedIds.filter((id) => insertedIds.has(id)));
2745
+ const insertedRows = new Map(nextValue.filter((item) => placeableIds.has(readEntityId(item) ?? "")).map((item) => [readEntityId(item), item]));
2746
+ const placedValue = nextValue.filter((item) => !placeableIds.has(readEntityId(item) ?? ""));
2747
+ for (const [orderIndex, id] of orderedIds.entries()) {
2748
+ if (!placeableIds.has(id)) continue;
2749
+ const item = insertedRows.get(id);
2750
+ if (!item) continue;
2751
+ const followingAnchor = orderedIds.slice(orderIndex + 1).map((candidateId) => placedValue.findIndex((candidate) => readEntityId(candidate) === candidateId)).find((index) => index >= 0);
2752
+ if (followingAnchor != null) {
2753
+ placedValue.splice(followingAnchor, 0, item);
2754
+ continue;
2755
+ }
2756
+ const precedingAnchor = orderedIds.slice(0, orderIndex).reverse().map((candidateId) => placedValue.findIndex((candidate) => readEntityId(candidate) === candidateId)).find((index) => index >= 0);
2757
+ placedValue.splice(precedingAnchor == null ? placedValue.length : precedingAnchor + 1, 0, item);
2758
+ }
2759
+ nextValue.splice(0, nextValue.length, ...placedValue);
2760
+ }
2568
2761
  if (!changed) continue;
2569
2762
  asyncReads.set(key, {
2570
2763
  token: entry?.token ?? null,
@@ -2574,6 +2767,57 @@ function createSuperDocUI(options) {
2574
2767
  });
2575
2768
  }
2576
2769
  };
2770
+ const replaceTrackedChangeItemsInCache = (items) => {
2771
+ const token = contentToken();
2772
+ const suppressedIds = postDecisionTrackChangeIdsForToken(token);
2773
+ const projected = items.map(projectTrackChangesItem).filter((item) => {
2774
+ if (item == null) return false;
2775
+ const id = readEntityId(item);
2776
+ return id != null && !suppressedIds?.has(id);
2777
+ });
2778
+ for (const key of ["trackChanges", "trackChanges:all"]) asyncReads.set(key, {
2779
+ token,
2780
+ value: [...projected],
2781
+ hasSettled: true,
2782
+ inflightToken: null
2783
+ });
2784
+ };
2785
+ const hydrateTrackedChanges = async (context, options$1) => {
2786
+ try {
2787
+ const listTrackedChanges = context.v2TrackedChanges?.listTrackedChanges;
2788
+ if (typeof listTrackedChanges === "function") {
2789
+ const result$1 = await Promise.resolve(listTrackedChanges.call(context.v2TrackedChanges, {
2790
+ mode: options$1.trackedChangesListMode,
2791
+ refreshReason: options$1.refreshReason,
2792
+ blocking: options$1.blocking,
2793
+ ...options$1.targetIds?.length ? { targetIds: options$1.targetIds } : {}
2794
+ }));
2795
+ const ok = options$1.trackedChangesListMode === "all" && options$1.refreshReason === "mutation-history-render-reconcile" ? result$1?.ok === true : result$1?.ok !== false;
2796
+ return {
2797
+ ok,
2798
+ items: ok && Array.isArray(result$1?.items) ? result$1.items : [],
2799
+ ...typeof result$1?.complete === "boolean" ? { complete: result$1.complete } : {},
2800
+ ...typeof result$1?.sourceCoverageComplete === "boolean" ? { sourceCoverageComplete: result$1.sourceCoverageComplete } : {}
2801
+ };
2802
+ }
2803
+ const list = context.docTrackChanges?.list;
2804
+ if (typeof list !== "function") return {
2805
+ ok: false,
2806
+ items: []
2807
+ };
2808
+ const result = await Promise.resolve(list.call(context.docTrackChanges));
2809
+ return {
2810
+ ok: true,
2811
+ items: Array.isArray(result?.items) ? result.items : []
2812
+ };
2813
+ } catch (error) {
2814
+ return {
2815
+ ok: false,
2816
+ items: [],
2817
+ failures: [error]
2818
+ };
2819
+ }
2820
+ };
2577
2821
  const reviewMutationReconciler = createV2ReviewMutationReconciler({
2578
2822
  getContext: () => {
2579
2823
  const editor = getEditor();
@@ -2587,6 +2831,7 @@ function createSuperDocUI(options) {
2587
2831
  editor,
2588
2832
  adapter,
2589
2833
  documentId: rawDocumentId == null ? null : String(rawDocumentId),
2834
+ reconcileToken: reviewMutationToken,
2590
2835
  v2TrackedChanges,
2591
2836
  docTrackChanges
2592
2837
  };
@@ -2600,6 +2845,9 @@ function createSuperDocUI(options) {
2600
2845
  resolvedIds: [],
2601
2846
  unresolvedIds: []
2602
2847
  };
2848
+ const settledTrackChanges = asyncReads.get("trackChanges");
2849
+ const cachedIds = new Set(settledTrackChanges?.hasSettled && Array.isArray(settledTrackChanges.value) ? settledTrackChanges.value.map(readEntityId).filter((id) => id != null) : []);
2850
+ const idsNeedingPlacement = new Set(requestedIds.filter((id) => !cachedIds.has(id)));
2603
2851
  const getTrackedChange = context.v2TrackedChanges?.getTrackedChange;
2604
2852
  const getRawTrackedChange = context.docTrackChanges?.get;
2605
2853
  if (typeof getTrackedChange !== "function" && typeof getRawTrackedChange !== "function") return {
@@ -2648,55 +2896,59 @@ function createSuperDocUI(options) {
2648
2896
  resolvedIds.push(read.id);
2649
2897
  } else unresolvedIds.push(read.id);
2650
2898
  }
2899
+ let orderedItems;
2900
+ const placementIds = resolvedIds.filter((id) => idsNeedingPlacement.has(id));
2901
+ if (placementIds.length > 0) {
2902
+ const orderResult = await hydrateTrackedChanges(context, {
2903
+ trackedChangesListMode: "interaction-prime",
2904
+ refreshReason: "mutation-render-reconcile",
2905
+ blocking: false,
2906
+ targetIds: placementIds
2907
+ });
2908
+ if (orderResult.ok) orderedItems = orderResult.items;
2909
+ }
2651
2910
  return {
2652
2911
  ok: unresolvedIds.length === 0,
2653
2912
  items,
2913
+ orderedItems,
2654
2914
  resolvedIds,
2655
2915
  unresolvedIds,
2656
2916
  failures: reads.filter((read) => !read.ok).map((read) => read.result)
2657
2917
  };
2658
2918
  },
2659
- hydrate: async (context, options$1) => {
2660
- try {
2661
- const listTrackedChanges = context.v2TrackedChanges?.listTrackedChanges;
2662
- if (typeof listTrackedChanges === "function") {
2663
- const result$1 = await Promise.resolve(listTrackedChanges.call(context.v2TrackedChanges, {
2664
- mode: options$1.trackedChangesListMode,
2665
- refreshReason: options$1.refreshReason,
2666
- blocking: options$1.blocking
2667
- }));
2668
- return {
2669
- ok: result$1?.ok !== false,
2670
- items: result$1?.ok !== false && Array.isArray(result$1?.items) ? result$1.items : []
2671
- };
2672
- }
2673
- const list = context.docTrackChanges?.list;
2674
- if (typeof list !== "function") return {
2675
- ok: false,
2676
- items: []
2677
- };
2678
- const result = await Promise.resolve(list.call(context.docTrackChanges));
2679
- return {
2680
- ok: true,
2681
- items: Array.isArray(result?.items) ? result.items : []
2682
- };
2683
- } catch (error) {
2684
- return {
2685
- ok: false,
2686
- items: [],
2687
- failures: [error]
2688
- };
2919
+ hydrate: hydrateTrackedChanges,
2920
+ onReconciled: (context, ids, result) => {
2921
+ if (result?.reconcileMode === "authoritative") {
2922
+ if (context.reconcileToken !== reviewMutationToken) return;
2923
+ if (result.ok === true && result.complete === true && result.sourceCoverageComplete === true) {
2924
+ clearAuthoritativeTrackChangesRetry();
2925
+ replaceTrackedChangeItemsInCache(result.items ?? []);
2926
+ authoritativeTrackChangesPendingToken = null;
2927
+ } else if (result.ok === true) {
2928
+ clearAuthoritativeTrackChangesRetry();
2929
+ mergeTrackedChangeItemsIntoCache(result.items ?? []);
2930
+ } else scheduleAuthoritativeTrackChangesRetry(context.reconcileToken);
2931
+ recompute();
2932
+ return;
2689
2933
  }
2690
- },
2691
- onReconciled: (_context, ids, result) => {
2692
2934
  mergeTrackedChangeItemsIntoCache((result?.items ?? []).filter((item) => {
2693
2935
  const projected = projectTrackChangesItem(item);
2694
2936
  const id = projected == null ? null : readEntityId(projected);
2695
2937
  return id != null && ids.has(id);
2696
- }));
2938
+ }), result?.orderedItems ?? result?.items ?? []);
2697
2939
  recompute();
2698
2940
  }
2699
2941
  });
2942
+ scheduleAuthoritativeTrackChangesRetry = (reconcileToken) => {
2943
+ if (disposed || authoritativeTrackChangesRetryTimer) return;
2944
+ const delayMs = Math.min(ASYNC_READ_FAILURE_RETRY_BASE_MS * 2 ** authoritativeTrackChangesFailureCount, ASYNC_READ_FAILURE_RETRY_MAX_MS);
2945
+ authoritativeTrackChangesFailureCount += 1;
2946
+ authoritativeTrackChangesRetryTimer = setTimeout(() => {
2947
+ authoritativeTrackChangesRetryTimer = null;
2948
+ if (disposed || authoritativeTrackChangesPendingToken === null || reconcileToken !== reviewMutationToken) return;
2949
+ reviewMutationReconciler.onRender(true);
2950
+ }, delayMs);
2951
+ };
2700
2952
  const selectionReadToken = () => `${contentToken()}|m${readDocumentMode()}|s${selectionEpoch}`;
2701
2953
  const selectionSignature = (selection$1) => JSON.stringify({
2702
2954
  t: selection$1.target ?? null,
@@ -2714,7 +2966,7 @@ function createSuperDocUI(options) {
2714
2966
  const read = host?.getDocumentLoadingSnapshot;
2715
2967
  if (typeof read !== "function") return "unknown";
2716
2968
  const stage = safeCall(() => read.call(host), null)?.sourceStage;
2717
- if (stage === "source-loading") return "loading";
2969
+ if (stage === "opening" || stage === "ready-window" || stage === "source-loading") return "loading";
2718
2970
  if (stage === "source-complete" || stage === "source-failed" || stage === "source-cancelled") return "complete";
2719
2971
  return "unknown";
2720
2972
  };
@@ -2880,17 +3132,24 @@ function createSuperDocUI(options) {
2880
3132
  token,
2881
3133
  value: normalize(raw),
2882
3134
  hasSettled: true,
2883
- inflightToken: null
3135
+ inflightToken: null,
3136
+ failedToken: null,
3137
+ failureCount: 0,
3138
+ retryAtMs: 0
2884
3139
  };
2885
3140
  asyncReads.set(key, settled);
2886
3141
  return settled;
2887
3142
  }
2888
3143
  const prev = asyncReads.get(key);
3144
+ const retryingSameToken = prev?.failedToken === token;
2889
3145
  asyncReads.set(key, {
2890
3146
  token: prev?.token ?? null,
2891
3147
  value: prev?.value ?? null,
2892
3148
  hasSettled: prev?.hasSettled ?? false,
2893
- inflightToken: token
3149
+ inflightToken: token,
3150
+ failedToken: retryingSameToken ? token : null,
3151
+ failureCount: retryingSameToken ? prev.failureCount ?? 0 : 0,
3152
+ retryAtMs: 0
2894
3153
  });
2895
3154
  Promise.resolve(raw).then((resolved) => {
2896
3155
  const entry = asyncReads.get(key);
@@ -2899,17 +3158,26 @@ function createSuperDocUI(options) {
2899
3158
  token,
2900
3159
  value: normalize(resolved),
2901
3160
  hasSettled: true,
2902
- inflightToken: null
3161
+ inflightToken: null,
3162
+ failedToken: null,
3163
+ failureCount: 0,
3164
+ retryAtMs: 0
2903
3165
  });
2904
3166
  scheduleAsyncRefresh();
2905
3167
  }, () => {
2906
3168
  const entry = asyncReads.get(key);
2907
3169
  if (!entry || entry.inflightToken !== token) return;
3170
+ const failureCount = entry.failedToken === token ? (entry.failureCount ?? 0) + 1 : 1;
3171
+ const retryDelayMs = Math.min(ASYNC_READ_FAILURE_RETRY_MAX_MS, ASYNC_READ_FAILURE_RETRY_BASE_MS * 2 ** Math.min(failureCount - 1, 8));
3172
+ const retryAtMs = Date.now() + retryDelayMs;
2908
3173
  asyncReads.set(key, {
2909
3174
  ...entry,
2910
- inflightToken: null
3175
+ inflightToken: null,
3176
+ failedToken: token,
3177
+ failureCount,
3178
+ retryAtMs
2911
3179
  });
2912
- scheduleAsyncRefresh();
3180
+ scheduleAsyncReadFailureRetry(retryAtMs);
2913
3181
  });
2914
3182
  return null;
2915
3183
  };
@@ -2970,6 +3238,17 @@ function createSuperDocUI(options) {
2970
3238
  status: "pending"
2971
3239
  };
2972
3240
  }
3241
+ if (entry?.failedToken === token && entry.inflightToken == null && (entry.retryAtMs ?? 0) > Date.now()) {
3242
+ scheduleAsyncReadFailureRetry(entry.retryAtMs);
3243
+ if (entry.hasSettled) return {
3244
+ value: entry.value,
3245
+ status: "stale"
3246
+ };
3247
+ return {
3248
+ value: null,
3249
+ status: "pending"
3250
+ };
3251
+ }
2973
3252
  if (!entry && key.startsWith("effInline:") && shouldDeferEffectiveInlineRead(key)) return {
2974
3253
  value: null,
2975
3254
  status: "pending"
@@ -3019,8 +3298,14 @@ function createSuperDocUI(options) {
3019
3298
  const editor = getEditor();
3020
3299
  if (editor === lastCoordinatorEditor) return;
3021
3300
  reviewMutationReconciler.reset();
3301
+ reviewMutationToken = {};
3022
3302
  lastCoordinatorEditor = editor;
3023
3303
  asyncReads.clear();
3304
+ authoritativeTrackChangesPendingToken = null;
3305
+ clearAuthoritativeTrackChangesRetry();
3306
+ if (asyncReadFailureRetryTimer) clearTimeout(asyncReadFailureRetryTimer);
3307
+ asyncReadFailureRetryTimer = null;
3308
+ asyncReadFailureRetryAtMs = 0;
3024
3309
  if (pendingEffectiveInlineRead?.timer) clearTimeout(pendingEffectiveInlineRead.timer);
3025
3310
  pendingEffectiveInlineRead = null;
3026
3311
  lastEffectiveInlineReadKey = null;
@@ -3036,7 +3321,9 @@ function createSuperDocUI(options) {
3036
3321
  clearPostDecisionTrackChanges();
3037
3322
  };
3038
3323
  const invalidateDocumentContent = (options$1) => {
3324
+ const carryAuthoritativeTrackChangesHold = authoritativeTrackChangesPendingToken !== null;
3039
3325
  documentMutationRevision += 1;
3326
+ if (carryAuthoritativeTrackChangesHold) authoritativeTrackChangesPendingToken = contentToken();
3040
3327
  if (!options$1?.preservePostDecisionTrackChanges) clearPostDecisionTrackChanges();
3041
3328
  };
3042
3329
  const insertText = (text) => {
@@ -3227,7 +3514,12 @@ function createSuperDocUI(options) {
3227
3514
  };
3228
3515
  const readAllStoryTrackChanges = () => {
3229
3516
  const tcApi = getDoc()?.trackChanges;
3230
- const { value, status } = readAsync("trackChanges:all", contentToken(), () => tcApi?.list ? tcApi.list({ in: "all" }) : void 0, (raw) => {
3517
+ const token = contentToken();
3518
+ const heldEntry = asyncReads.get("trackChanges:all");
3519
+ const { value, status } = authoritativeTrackChangesPendingToken === token ? {
3520
+ value: heldEntry?.hasSettled ? heldEntry.value : null,
3521
+ status: heldEntry?.hasSettled ? "stale" : "pending"
3522
+ } : readAsync("trackChanges:all", token, () => tcApi?.list ? tcApi.list({ in: "all" }) : void 0, (raw) => {
3231
3523
  return (raw && Array.isArray(raw.items) ? raw.items : []).map(projectTrackChangesItem).filter((item) => item != null);
3232
3524
  });
3233
3525
  return status === "ready" ? value : null;
@@ -3238,7 +3530,11 @@ function createSuperDocUI(options) {
3238
3530
  const listTrackedChanges = typeof v2TrackedChanges?.listTrackedChanges === "function" ? v2TrackedChanges.listTrackedChanges : null;
3239
3531
  const token = contentToken();
3240
3532
  const postDecisionIds = postDecisionTrackChangeIdsForToken(token);
3241
- const { value, status: listStatus } = readAsync("trackChanges", token, () => {
3533
+ const heldEntry = asyncReads.get("trackChanges");
3534
+ const { value, status: listStatus } = authoritativeTrackChangesPendingToken === token ? {
3535
+ value: heldEntry?.hasSettled ? heldEntry.value : null,
3536
+ status: heldEntry?.hasSettled ? "stale" : "pending"
3537
+ } : readAsync("trackChanges", token, () => {
3242
3538
  if (listTrackedChanges) return postDecisionTrackChangesToken === token ? listTrackedChanges.call(v2TrackedChanges, POST_DECISION_TRACKED_CHANGES_LIST_OPTIONS) : listTrackedChanges.call(v2TrackedChanges);
3243
3539
  return tcApi?.list ? tcApi.list() : void 0;
3244
3540
  }, (raw) => raw && Array.isArray(raw.items) ? raw.items : []);
@@ -3284,22 +3580,29 @@ function createSuperDocUI(options) {
3284
3580
  activeIds
3285
3581
  };
3286
3582
  };
3287
- const computeActiveContentControlIds = (ccApi, selection$1) => {
3583
+ const computeActiveContentControlLockModes = (ccApi, selection$1) => {
3288
3584
  if (!ccApi || typeof ccApi.listInRange !== "function") return {
3289
- ids: [],
3585
+ lockModesById: /* @__PURE__ */ new Map(),
3290
3586
  status: "ready"
3291
3587
  };
3292
3588
  const range = selectionBlockRange(selection$1);
3293
3589
  if (!range) return {
3294
- ids: [],
3590
+ lockModesById: /* @__PURE__ */ new Map(),
3295
3591
  status: "ready"
3296
3592
  };
3297
3593
  const { value, status } = readAsync(`contentControls:inRange:${selectionSignature(selection$1)}`, contentToken(), () => ccApi.listInRange(range), (raw) => raw && Array.isArray(raw.items) ? raw.items : []);
3298
3594
  const rows = value ?? [];
3299
- const ids = [];
3300
- for (const row of rows) if (typeof row?.id === "string") ids.push(row.id);
3595
+ const lockModesById = /* @__PURE__ */ new Map();
3596
+ for (const row of rows) if (typeof row?.id === "string") lockModesById.set(row.id, typeof row.lockMode === "string" ? row.lockMode : "unlocked");
3597
+ return {
3598
+ lockModesById,
3599
+ status
3600
+ };
3601
+ };
3602
+ const computeActiveContentControlIds = (ccApi, selection$1) => {
3603
+ const { lockModesById, status } = computeActiveContentControlLockModes(ccApi, selection$1);
3301
3604
  return {
3302
- ids,
3605
+ ids: [...lockModesById.keys()],
3303
3606
  status
3304
3607
  };
3305
3608
  };
@@ -3615,14 +3918,20 @@ function createSuperDocUI(options) {
3615
3918
  frozenProjectedValues = projectedInlineValues;
3616
3919
  frozenProjectedValuesKey = `${selectionKey(selection$1)}:${contentToken()}`;
3617
3920
  }
3921
+ const ccApi = doc?.contentControls;
3922
+ const { lockModesById } = computeActiveContentControlLockModes(ccApi, selection$1);
3618
3923
  const states = {};
3619
- for (const id of allCommandIds()) states[id] = computeCommandState(id, doc, selection$1, projectedInlineValues);
3924
+ for (const id of allCommandIds()) states[id] = computeCommandState(id, doc, selection$1, projectedInlineValues, lockModesById);
3620
3925
  return states;
3621
3926
  };
3622
3927
  const unavailableRouteReason = (doc) => {
3623
3928
  if (doc) return SUPERDOC_UI_REASONS.operationUnavailable;
3624
3929
  return getEditor() ? SUPERDOC_UI_REASONS.documentApiUnavailable : SUPERDOC_UI_REASONS.notReady;
3625
3930
  };
3931
+ const blocksContentStyling = (lockMode) => lockMode === "contentLocked" || lockMode === "sdtContentLocked";
3932
+ const contentControlLockReason = (lockModesById) => {
3933
+ for (const lockMode of lockModesById.values()) if (blocksContentStyling(lockMode)) return SUPERDOC_UI_REASONS.contentControlLocked;
3934
+ };
3626
3935
  const trackDecisionReason = (command, supported, readonly, selection$1, doc) => {
3627
3936
  if (!supported) return command.scope === "all" ? SUPERDOC_UI_REASONS.bulkDecisionsDisabled : unavailableRouteReason(doc);
3628
3937
  if (readonly) return SUPERDOC_UI_REASONS.documentReadonly;
@@ -3645,7 +3954,7 @@ function createSuperDocUI(options) {
3645
3954
  if (support && (support.status === "supported" || support.enabled === true)) return void 0;
3646
3955
  return SUPERDOC_UI_REASONS.bulkDecisionsDisabled;
3647
3956
  };
3648
- const computeCommandState = (id, doc, selection$1, projectedInlineValues) => {
3957
+ const computeCommandState = (id, doc, selection$1, projectedInlineValues, lockModesById = /* @__PURE__ */ new Map()) => {
3649
3958
  if (customCommands.has(id)) return normalizeCommandState({
3650
3959
  supported: true,
3651
3960
  enabled: true,
@@ -3720,6 +4029,13 @@ function createSuperDocUI(options) {
3720
4029
  supported: true,
3721
4030
  reason: SUPERDOC_UI_REASONS.documentReadonly
3722
4031
  }, "builtin");
4032
+ const lockReason = contentControlLockReason(lockModesById);
4033
+ if (lockReason) return normalizeCommandState({
4034
+ enabled: false,
4035
+ active: false,
4036
+ supported: true,
4037
+ reason: lockReason
4038
+ }, "builtin");
3723
4039
  const enabled = selectionBlockIds(selection$1).length > 0 || resolveInlineSelectionTarget(selection$1) != null;
3724
4040
  return normalizeCommandState({
3725
4041
  enabled,
@@ -3770,18 +4086,28 @@ function createSuperDocUI(options) {
3770
4086
  value,
3771
4087
  reason: SUPERDOC_UI_REASONS.documentReadonly
3772
4088
  }, "builtin");
3773
- if (descriptor.inline && !resolveInlineSelectionTarget(selection$1)) return normalizeCommandState(selectionBlockIds(selection$1).length > 0 && typeof getHost()?.setPendingInlineFormat === "function" ? {
3774
- enabled: true,
3775
- active,
3776
- supported: true,
3777
- value
3778
- } : {
3779
- enabled: false,
3780
- active,
3781
- supported: true,
3782
- value,
3783
- reason: SUPERDOC_UI_REASONS.rangeSelectionRequired
3784
- }, "builtin");
4089
+ if (descriptor.inline && !resolveInlineSelectionTarget(selection$1)) {
4090
+ const lockReason = contentControlLockReason(lockModesById);
4091
+ if (lockReason) return normalizeCommandState({
4092
+ enabled: false,
4093
+ active,
4094
+ supported: true,
4095
+ value,
4096
+ reason: lockReason
4097
+ }, "builtin");
4098
+ return normalizeCommandState(selectionBlockIds(selection$1).length > 0 && typeof getHost()?.setPendingInlineFormat === "function" ? {
4099
+ enabled: true,
4100
+ active,
4101
+ supported: true,
4102
+ value
4103
+ } : {
4104
+ enabled: false,
4105
+ active,
4106
+ supported: true,
4107
+ value,
4108
+ reason: SUPERDOC_UI_REASONS.rangeSelectionRequired
4109
+ }, "builtin");
4110
+ }
3785
4111
  if ((descriptor.blockParagraph || descriptor.list) && selectionBlockIds(selection$1).length === 0) return normalizeCommandState({
3786
4112
  enabled: false,
3787
4113
  active,
@@ -3795,6 +4121,16 @@ function createSuperDocUI(options) {
3795
4121
  supported: true,
3796
4122
  reason: SUPERDOC_UI_REASONS.rangeSelectionRequired
3797
4123
  }, "builtin");
4124
+ if (descriptor.inline) {
4125
+ const lockReason = contentControlLockReason(lockModesById);
4126
+ if (lockReason) return normalizeCommandState({
4127
+ enabled: false,
4128
+ active,
4129
+ supported: true,
4130
+ value,
4131
+ reason: lockReason
4132
+ }, "builtin");
4133
+ }
3798
4134
  return normalizeCommandState({
3799
4135
  enabled: true,
3800
4136
  active,
@@ -3857,6 +4193,9 @@ function createSuperDocUI(options) {
3857
4193
  if (descriptor.list?.mode === "toggle-seed" && descriptor.list.seed) return readListSeed(doc, selection$1) === descriptor.list.seed;
3858
4194
  const pending = pendingInlineActive(descriptor);
3859
4195
  if (pending !== null) return pending;
4196
+ const optimistic = optimisticInlineToggles.get(descriptor.id);
4197
+ const selectionSignature$1 = selectionInlineValueSignature(selection$1);
4198
+ if (selectionSignature$1 && optimistic?.selectionSignature === selectionSignature$1) return optimistic.active;
3860
4199
  return commandIsActive(descriptor, selection$1);
3861
4200
  };
3862
4201
  const routedCommandValue = (descriptor, doc, selection$1, projectedInlineValues) => {
@@ -4483,6 +4822,9 @@ function createSuperDocUI(options) {
4483
4822
  const selection$1 = computeSelection();
4484
4823
  if (selection$1.status === "ready") pruneSelectionScopedAsyncReads(selection$1);
4485
4824
  reconcilePendingInlineFormat(selection$1);
4825
+ const selectionSignature$1 = selectionInlineValueSignature(selection$1);
4826
+ for (const [commandId, optimistic] of optimisticInlineToggles) if (!selectionSignature$1 || optimistic.selectionSignature !== selectionSignature$1) optimisticInlineToggles.delete(commandId);
4827
+ else if (optimistic.settled && selection$1.status === "ready") optimisticInlineToggles.delete(commandId);
4486
4828
  return {
4487
4829
  ready: getEditor() != null,
4488
4830
  documentMode,
@@ -4503,7 +4845,10 @@ function createSuperDocUI(options) {
4503
4845
  };
4504
4846
  const syncOptimisticInlineSelection = (selection$1) => {
4505
4847
  const nextSignature = selectionInlineValueSignature(selection$1);
4506
- if (nextSignature && lastOptimisticInlineSelectionSignature && nextSignature !== lastOptimisticInlineSelectionSignature) optimisticInlineValues.clear();
4848
+ if (nextSignature && lastOptimisticInlineSelectionSignature && nextSignature !== lastOptimisticInlineSelectionSignature) {
4849
+ optimisticInlineValues.clear();
4850
+ optimisticInlineToggles.clear();
4851
+ }
4507
4852
  if (nextSignature) lastOptimisticInlineSelectionSignature = nextSignature;
4508
4853
  };
4509
4854
  let currentHostSelectionSource = null;
@@ -4581,24 +4926,48 @@ function createSuperDocUI(options) {
4581
4926
  try {
4582
4927
  const off = next.subscribe((event) => {
4583
4928
  const type = event?.type;
4584
- if (type === "mutation:committed") lastEditableMutationAtMs = Date.now();
4929
+ if (type === "source:complete" || type === "source:signals-complete") {
4930
+ if (authoritativeTrackChangesPendingToken !== null) {
4931
+ clearAuthoritativeTrackChangesRetry();
4932
+ reviewMutationReconciler.onRender(true);
4933
+ }
4934
+ return;
4935
+ }
4936
+ if (type === "mutation:committed") {
4937
+ lastEditableMutationAtMs = Date.now();
4938
+ clearAuthoritativeTrackChangesRetry();
4939
+ reviewMutationToken = {};
4940
+ }
4585
4941
  if (type === "mutation:committed" || type === "save:completed") {
4586
4942
  const impact = type === "mutation:committed" ? getV2TrackedChangeMutationImpact(event) : null;
4587
- if (impact?.removedIds.size) markPostDecisionTrackChanges(new Set(impact.removedIds));
4588
- if (impact?.upsertIds.size) {
4943
+ const supersedesPendingReviewReconcile = type === "mutation:committed" && reviewMutationReconciler.getPendingIds().size > 0;
4944
+ if (impact?.removedIds.size) markPostDecisionTrackChanges(new Set(impact.removedIds), event.receipt);
4945
+ const authoritativeHistoryImpact = impact?.reconcileMode === "authoritative" && impact.upsertIds.size > 0;
4946
+ if (authoritativeHistoryImpact) {
4947
+ invalidateDocumentContent();
4948
+ clearAuthoritativeTrackChangesRetry();
4949
+ authoritativeTrackChangesPendingToken = contentToken();
4950
+ recompute();
4951
+ }
4952
+ if (impact?.upsertIds.size || supersedesPendingReviewReconcile) {
4589
4953
  const receipt = event.origin === "history" ? null : event.receipt;
4590
4954
  const readiness = getEditor()?.documentMutationReadiness;
4591
- const canWaitForExactPaint = typeof receipt?.txId === "string" && receipt.txId.length > 0 && typeof readiness?.whenPainted === "function";
4955
+ const canWaitForReceiptPaint = typeof receipt?.txId === "string" && receipt.txId.length > 0 && typeof readiness?.whenPainted === "function";
4956
+ const historyRenderEpoch = event.origin === "history" && typeof readiness?.getRenderEpoch === "function" ? safeCall(() => readiness.getRenderEpoch.call(readiness), null) : null;
4957
+ const canWaitForHistoryPaint = event.origin === "history" && typeof readiness?.whenPainted === "function";
4592
4958
  const upsertImpact = {
4593
- upsertIds: impact.upsertIds,
4594
- removedIds: /* @__PURE__ */ new Set()
4959
+ upsertIds: impact?.upsertIds ?? /* @__PURE__ */ new Set(),
4960
+ removedIds: /* @__PURE__ */ new Set(),
4961
+ reconcileMode: impact?.reconcileMode === "authoritative" ? "authoritative" : "targeted"
4595
4962
  };
4596
- if (canWaitForExactPaint) reviewMutationReconciler.enqueueAfterPaint(upsertImpact, () => readiness.whenPainted.call(readiness, receipt));
4963
+ if (canWaitForReceiptPaint) reviewMutationReconciler.enqueueAfterPaint(upsertImpact, () => readiness.whenPainted.call(readiness, receipt));
4964
+ else if (canWaitForHistoryPaint) reviewMutationReconciler.enqueueAfterPaint(upsertImpact, () => readiness.whenPainted.call(readiness, typeof historyRenderEpoch === "number" ? { afterEpoch: historyRenderEpoch } : void 0));
4597
4965
  else {
4598
4966
  reviewMutationReconciler.enqueue(upsertImpact);
4599
- reviewMutationReconciler.onRender();
4967
+ reviewMutationReconciler.onRender(supersedesPendingReviewReconcile);
4600
4968
  }
4601
4969
  }
4970
+ if (authoritativeHistoryImpact) return;
4602
4971
  if (type === "mutation:committed" && isEditableTextMutationEvent(event)) {
4603
4972
  lastTypingMutationAtMs = Date.now();
4604
4973
  scheduleTypingDocumentContentInvalidation();
@@ -4753,7 +5122,10 @@ function createSuperDocUI(options) {
4753
5122
  }
4754
5123
  function executeTrackDecisionCommand(command, payload) {
4755
5124
  if (command.scope === "id" && payload == null) {
4756
- if (state.selection.activeChangeIds.length > 1) return executeTrackDecisionTargets(command.kind, state.selection.activeChangeIds);
5125
+ if (state.selection.activeChangeIds.length > 1) return executeTrackDecisionTarget(command.kind, {
5126
+ kind: "ids",
5127
+ ids: state.selection.activeChangeIds
5128
+ }, null);
4757
5129
  if (state.selection.activeChangeIds.length === 1 && !state.selection.empty && state.selection.selectionTarget) {
4758
5130
  const route = activeChangePartialDecisionRoute();
4759
5131
  if (route === "unavailable") return false;
@@ -4789,6 +5161,7 @@ function createSuperDocUI(options) {
4789
5161
  }
4790
5162
  let lastCommandSettlement = Promise.resolve(false);
4791
5163
  let pendingCommandSettlement = null;
5164
+ let lastInlineToggleCommandSettlement = Promise.resolve(false);
4792
5165
  const awaitMutationReadiness = async (result) => {
4793
5166
  if (!isSuccessfulReceipt(result)) return result;
4794
5167
  const readiness = getEditor()?.documentMutationReadiness;
@@ -4816,6 +5189,69 @@ function createSuperDocUI(options) {
4816
5189
  settled: awaitMutationReadiness(immediate)
4817
5190
  };
4818
5191
  };
5192
+ const releaseInlineToggleMutation = () => {
5193
+ const next = pendingInlineToggleMutations.shift();
5194
+ if (!next) {
5195
+ inlineToggleMutationActive = false;
5196
+ resolveInlineToggleMutationIdle?.();
5197
+ resolveInlineToggleMutationIdle = null;
5198
+ return;
5199
+ }
5200
+ try {
5201
+ next.resolve(disposed ? false : next.mutate());
5202
+ } catch {
5203
+ next.resolve(false);
5204
+ }
5205
+ };
5206
+ const releaseInlineToggleMutationOnce = () => {
5207
+ let released = false;
5208
+ return () => {
5209
+ if (released) return;
5210
+ released = true;
5211
+ releaseInlineToggleMutation();
5212
+ };
5213
+ };
5214
+ const scheduleInlineToggleMutation = (key, mutate) => {
5215
+ if (!inlineToggleMutationActive) {
5216
+ inlineToggleMutationActive = true;
5217
+ inlineToggleMutationIdle = new Promise((resolve$1) => {
5218
+ resolveInlineToggleMutationIdle = resolve$1;
5219
+ });
5220
+ try {
5221
+ return {
5222
+ result: mutate(),
5223
+ release: releaseInlineToggleMutationOnce()
5224
+ };
5225
+ } catch (error) {
5226
+ inlineToggleMutationActive = false;
5227
+ resolveInlineToggleMutationIdle?.();
5228
+ resolveInlineToggleMutationIdle = null;
5229
+ throw error;
5230
+ }
5231
+ }
5232
+ const pending = pendingInlineToggleMutations.find((mutation) => mutation.key === key);
5233
+ if (pending) {
5234
+ pending.mutate = mutate;
5235
+ return {
5236
+ result: pending.result,
5237
+ release: () => void 0
5238
+ };
5239
+ }
5240
+ let resolve;
5241
+ const result = new Promise((settle) => {
5242
+ resolve = settle;
5243
+ });
5244
+ pendingInlineToggleMutations.push({
5245
+ key,
5246
+ mutate,
5247
+ result,
5248
+ resolve
5249
+ });
5250
+ return {
5251
+ result,
5252
+ release: releaseInlineToggleMutationOnce()
5253
+ };
5254
+ };
4819
5255
  const UNSUPPORTED_TRACKED_UI_MUTATION_ROUTES = new Set(["create.tableOfContents"]);
4820
5256
  const unsupportedTrackedMutationReceipt = (route) => failedReceipt(`Tracked authoring is not supported for ${route}.`);
4821
5257
  const editorMutationOptionsForRoute = (route) => {
@@ -4859,33 +5295,62 @@ function createSuperDocUI(options) {
4859
5295
  });
4860
5296
  return result;
4861
5297
  };
4862
- const finalizeCommandSettlement = (promise) => promise.then((settled) => {
5298
+ const captureOptimisticInlineToggle = (descriptor, selection$1, input, result) => {
5299
+ if (descriptor.inline?.kind !== "toggle") return null;
5300
+ if (!commandResultSucceeded(commandResultFromOperationResult(result))) return null;
5301
+ if (typeof input.value !== "boolean" && input.value !== null) return null;
5302
+ const selectionSignature$1 = selectionInlineValueSignature(selection$1);
5303
+ if (!selectionSignature$1) return null;
5304
+ const generation = ++optimisticInlineToggleGeneration;
5305
+ optimisticInlineToggles.set(descriptor.id, {
5306
+ selectionSignature: selectionSignature$1,
5307
+ active: input.value === true,
5308
+ generation,
5309
+ settled: false
5310
+ });
5311
+ recompute();
5312
+ return generation;
5313
+ };
5314
+ const settleOptimisticInlineToggle = (descriptor, generation, result) => {
5315
+ if (generation == null) return;
5316
+ const optimistic = optimisticInlineToggles.get(descriptor.id);
5317
+ if (!optimistic || optimistic.generation !== generation) return;
5318
+ if (!commandResultSucceeded(result)) {
5319
+ optimisticInlineToggles.delete(descriptor.id);
5320
+ return;
5321
+ }
5322
+ optimistic.settled = true;
5323
+ };
5324
+ const finalizeCommandSettlement = (promise, onSettled) => promise.then((settled) => {
5325
+ onSettled?.(settled);
4863
5326
  invalidateDocumentContent();
4864
5327
  recompute();
4865
5328
  return settled;
4866
5329
  }, () => {
5330
+ onSettled?.(false);
4867
5331
  invalidateDocumentContent();
4868
5332
  recompute();
4869
5333
  return false;
4870
5334
  });
4871
- const settleCommandExecution = (result) => {
5335
+ const settleCommandExecution = (result, onSettled) => {
4872
5336
  const pending = pendingCommandSettlement;
4873
5337
  pendingCommandSettlement = null;
4874
5338
  if (pending) {
4875
- lastCommandSettlement = finalizeCommandSettlement(pending);
5339
+ lastCommandSettlement = finalizeCommandSettlement(pending, onSettled);
4876
5340
  return commandResultFromOperationResult(result);
4877
5341
  }
4878
5342
  if (isPromiseLike(result)) {
4879
- lastCommandSettlement = finalizeCommandSettlement(settleOperationResult(result).settled);
5343
+ lastCommandSettlement = finalizeCommandSettlement(settleOperationResult(result).settled, onSettled);
4880
5344
  return true;
4881
5345
  }
4882
5346
  const settled = commandResultFromOperationResult(result);
4883
5347
  if (isSuccessfulReceipt(settled)) {
4884
5348
  if (typeof (getEditor()?.documentMutationReadiness)?.whenPainted === "function") {
4885
- lastCommandSettlement = finalizeCommandSettlement(awaitMutationReadiness(settled));
5349
+ lastCommandSettlement = finalizeCommandSettlement(awaitMutationReadiness(settled), onSettled);
4886
5350
  return settled;
4887
5351
  }
4888
5352
  }
5353
+ onSettled?.(settled);
4889
5354
  lastCommandSettlement = Promise.resolve(settled);
4890
5355
  invalidateDocumentContent();
4891
5356
  recompute();
@@ -5025,6 +5490,9 @@ function createSuperDocUI(options) {
5025
5490
  return true;
5026
5491
  };
5027
5492
  const executeClearFormattingCommand = () => {
5493
+ const doc = getDoc();
5494
+ const { lockModesById } = computeActiveContentControlLockModes(doc?.contentControls, state.selection);
5495
+ if (contentControlLockReason(lockModesById)) return false;
5028
5496
  const immediateResults = [];
5029
5497
  const settledResults = [];
5030
5498
  const record = (result) => {
@@ -5040,7 +5508,6 @@ function createSuperDocUI(options) {
5040
5508
  settledResults.push(Promise.resolve(false));
5041
5509
  }
5042
5510
  };
5043
- const doc = getDoc();
5044
5511
  const defaultStyleId = getStyleCatalog().cache?.full.defaults.paragraphStyleId ?? DEFAULT_PARAGRAPH_STYLE_ID;
5045
5512
  const rangeTarget = resolveInlineSelectionTarget(state.selection);
5046
5513
  if (rangeTarget) {
@@ -5446,7 +5913,7 @@ function createSuperDocUI(options) {
5446
5913
  const rows = Number(record.rows ?? record.rowCount);
5447
5914
  const columns = Number(record.columns ?? record.cols ?? record.colCount);
5448
5915
  if (!Number.isInteger(rows) || rows < 1 || !Number.isInteger(columns) || columns < 1) return false;
5449
- const caret = readDocumentMode() !== "suggesting" ? collapsedTextAddressFromSelection(state.selection) : null;
5916
+ const caret = collapsedTextAddressFromSelection(state.selection);
5450
5917
  const at = caret && typeof caret.blockId === "string" ? {
5451
5918
  kind: "inParagraph",
5452
5919
  target: paragraphTarget(caret.blockId),
@@ -5595,20 +6062,42 @@ function createSuperDocUI(options) {
5595
6062
  const route = descriptor.docRoute;
5596
6063
  if (!route) return false;
5597
6064
  if (descriptor.mutates && readDocumentMode() === "viewing") return false;
5598
- const op = resolveDocOperation(getDoc(), route);
6065
+ const doc = getDoc();
6066
+ const op = resolveDocOperation(doc, route);
5599
6067
  if (!op) return false;
5600
6068
  if (descriptor.inline) {
6069
+ const { lockModesById } = computeActiveContentControlLockModes(doc?.contentControls, state.selection);
6070
+ if (contentControlLockReason(lockModesById)) return false;
5601
6071
  const target = resolveInlineSelectionTarget(state.selection);
5602
6072
  if (!target) {
5603
6073
  if (state.selection.empty === false) return settleCommandExecution(false);
5604
6074
  return settleCommandExecution(storePendingInlineFormat(descriptor, normalized));
5605
6075
  }
5606
- const active = commandIsActive(descriptor, state.selection);
6076
+ const active = commandActiveState(descriptor, doc, state.selection);
5607
6077
  const input = buildInlineFormatInput(descriptor.inline, target, normalized, active);
5608
6078
  if (!input) return false;
6079
+ let releaseScheduledMutation = null;
5609
6080
  try {
5610
- return settleCommandExecution(decorateInlineCommandResult(descriptor, state.selection, input, callInlineFormatMutation(route, op, input)));
6081
+ const commandEditor = getEditor();
6082
+ const commandMode = readDocumentMode();
6083
+ const mutate = () => {
6084
+ if (getEditor() !== commandEditor || getDoc() !== doc || readDocumentMode() !== commandMode) return false;
6085
+ return callInlineFormatMutation(route, op, input);
6086
+ };
6087
+ const selectionSignature$1 = selectionInlineValueSignature(state.selection) ?? selectionKey(state.selection);
6088
+ const scheduledMutation = descriptor.inline.kind === "toggle" ? scheduleInlineToggleMutation(`${descriptor.id}:${selectionSignature$1}`, mutate) : null;
6089
+ releaseScheduledMutation = scheduledMutation?.release ?? null;
6090
+ const mutationResult = scheduledMutation ? scheduledMutation.result : mutate();
6091
+ const optimisticGeneration = captureOptimisticInlineToggle(descriptor, state.selection, input, mutationResult);
6092
+ const immediate = settleCommandExecution(decorateInlineCommandResult(descriptor, state.selection, input, mutationResult), (settled) => {
6093
+ settleOptimisticInlineToggle(descriptor, optimisticGeneration, settled);
6094
+ releaseScheduledMutation?.();
6095
+ releaseScheduledMutation = null;
6096
+ });
6097
+ if (descriptor.inline.kind === "toggle") lastInlineToggleCommandSettlement = lastCommandSettlement;
6098
+ return immediate;
5611
6099
  } catch {
6100
+ releaseScheduledMutation?.();
5612
6101
  return false;
5613
6102
  }
5614
6103
  }
@@ -5637,6 +6126,15 @@ function createSuperDocUI(options) {
5637
6126
  recompute();
5638
6127
  };
5639
6128
  const executeCopyFormat = async () => {
6129
+ const visibleActiveMarksAtClick = [
6130
+ "bold",
6131
+ "italic",
6132
+ "underline",
6133
+ "strikethrough"
6134
+ ].filter((id) => state.toolbar.commands[id]?.active === true);
6135
+ const inlineToggleSettlement = await lastInlineToggleCommandSettlement;
6136
+ await inlineToggleMutationIdle;
6137
+ const visibleActiveMarks = commandResultSucceeded(inlineToggleSettlement) ? visibleActiveMarksAtClick : [];
5640
6138
  const DOUBLE_CLICK_MS = 500;
5641
6139
  const now = Date.now();
5642
6140
  if (painter.mode !== "idle") {
@@ -5659,11 +6157,9 @@ function createSuperDocUI(options) {
5659
6157
  ...painter,
5660
6158
  lastClickAt: now
5661
6159
  };
5662
- const captureSlice = state.selection;
6160
+ const captureSlice = await readSelectionInfoFresh();
5663
6161
  const sourceKey = `${selectionKey(captureSlice)}:${contentToken()}`;
5664
- const captureProjected = sourceKey && sourceKey === frozenProjectedValuesKey ? frozenProjectedValues : void 0;
5665
- const sel = readSelectionInfoLive().value;
5666
- const painterSnapshot = await captureFormatPainter(sel, captureSlice, captureProjected);
6162
+ const painterSnapshot = await captureFormatPainter(null, captureSlice, sourceKey && sourceKey === frozenProjectedValuesKey ? frozenProjectedValues : void 0, visibleActiveMarks);
5667
6163
  if (captureEpoch !== painterCaptureEpoch) return true;
5668
6164
  painter = {
5669
6165
  ...painter,
@@ -5676,7 +6172,7 @@ function createSuperDocUI(options) {
5676
6172
  recompute();
5677
6173
  return true;
5678
6174
  };
5679
- const captureFormatPainter = async (selection$1, captureSlice, captureProjected) => {
6175
+ const captureFormatPainter = async (selection$1, captureSlice, captureProjected, visibleActiveMarks = []) => {
5680
6176
  const selTarget = captureSlice?.target ?? selection$1?.target;
5681
6177
  if (!selTarget || selTarget["kind"] !== "text") return null;
5682
6178
  const segments = Array.isArray(selTarget["segments"]) ? selTarget["segments"] : [];
@@ -5700,8 +6196,8 @@ function createSuperDocUI(options) {
5700
6196
  underline: "underline",
5701
6197
  strikethrough: "strike"
5702
6198
  };
5703
- const activeMarks = captureSlice?.activeMarks ?? (Array.isArray(selection$1?.["activeMarks"]) ? selection$1["activeMarks"] : []);
5704
- for (const [markName, patchKey] of Object.entries(MARK_TO_PATCH)) if (activeMarks.includes(markName)) inlinePatch[patchKey] = true;
6199
+ const activeMarks = new Set([...captureSlice?.activeMarks ?? (Array.isArray(selection$1?.["activeMarks"]) ? selection$1["activeMarks"] : []), ...visibleActiveMarks]);
6200
+ for (const [markName, patchKey] of Object.entries(MARK_TO_PATCH)) if (activeMarks.has(markName)) inlinePatch[patchKey] = true;
5705
6201
  const projectedInlineValues = captureProjected ?? await readFormatPainterInlineValues(captureSlice ?? selectionSliceFromInfo(selection$1, "ready"));
5706
6202
  if (projectedInlineValues.fontFamily) inlinePatch["fontFamily"] = projectedInlineValues.fontFamily;
5707
6203
  if (projectedInlineValues.color) inlinePatch["color"] = projectedInlineValues.color;
@@ -5855,6 +6351,12 @@ function createSuperDocUI(options) {
5855
6351
  }
5856
6352
  const doc = getDoc();
5857
6353
  if (!doc) return;
6354
+ const targetSelection = selectionSliceFromInfo(selection$1, "ready");
6355
+ const { lockModesById } = computeActiveContentControlLockModes(doc.contentControls, targetSelection);
6356
+ if (contentControlLockReason(lockModesById)) {
6357
+ recompute();
6358
+ return;
6359
+ }
5858
6360
  const selTarget = selection$1["target"];
5859
6361
  const selectionTarget = selection$1["selectionTarget"];
5860
6362
  if (!(selection$1["empty"] === true) && Object.keys(snap.inline).length > 0) {
@@ -5954,12 +6456,15 @@ function createSuperDocUI(options) {
5954
6456
  };
5955
6457
  const executeCommandAsync = (id, payload, context) => {
5956
6458
  const prepared = prepareCommandSelectionAsync(id, payload);
5957
- if (prepared) return prepared.then(() => {
6459
+ const settlement = prepared ? prepared.then(() => {
5958
6460
  executeCommand(id, payload, context);
5959
6461
  return lastCommandSettlement;
5960
- });
5961
- executeCommand(id, payload, context);
5962
- return lastCommandSettlement;
6462
+ }) : (() => {
6463
+ executeCommand(id, payload, context);
6464
+ return lastCommandSettlement;
6465
+ })();
6466
+ if (getCommandDescriptor(id)?.inline?.kind === "toggle") lastInlineToggleCommandSettlement = settlement;
6467
+ return settlement;
5963
6468
  };
5964
6469
  const makeCommandHandle = (id) => ({
5965
6470
  id,
@@ -6426,6 +6931,7 @@ function createSuperDocUI(options) {
6426
6931
  setActive: (input) => {
6427
6932
  if (input == null) {
6428
6933
  setExplicitActiveChange(null);
6934
+ mirrorTrackedChangeFocusToHost(null);
6429
6935
  recompute();
6430
6936
  return true;
6431
6937
  }
@@ -6435,6 +6941,7 @@ function createSuperDocUI(options) {
6435
6941
  story: input.story
6436
6942
  };
6437
6943
  if (typeof requested.id !== "string" || requested.id.length === 0) return false;
6944
+ const paintedEntityId = requested.id;
6438
6945
  if (requested.story) {
6439
6946
  const allStoryItems = readAllStoryTrackChanges();
6440
6947
  if (!allStoryItems) return false;
@@ -6447,7 +6954,9 @@ function createSuperDocUI(options) {
6447
6954
  if (!bodyItems.some((item) => readEntityId(item) === publicId)) return false;
6448
6955
  requested.id = publicId;
6449
6956
  }
6957
+ if (paintedEntityId !== requested.id) requested.paintedEntityId = paintedEntityId;
6450
6958
  setExplicitActiveChange(requested);
6959
+ mirrorTrackedChangeFocusToHost(requested);
6451
6960
  recompute();
6452
6961
  return true;
6453
6962
  },
@@ -6466,10 +6975,11 @@ function createSuperDocUI(options) {
6466
6975
  reason: SUPERDOC_UI_REASONS.targetUnresolved
6467
6976
  };
6468
6977
  const previousActiveChange = explicitActiveChange;
6469
- setExplicitActiveChange(story ? {
6978
+ const requestedActiveChange = story ? {
6470
6979
  id: changeId,
6471
6980
  story
6472
- } : { id: changeId });
6981
+ } : { id: changeId };
6982
+ setExplicitActiveChange(requestedActiveChange);
6473
6983
  recompute();
6474
6984
  const optimisticActiveChange = explicitActiveChange;
6475
6985
  const optimisticRevision = explicitActiveChangeRevision;
@@ -6485,6 +6995,7 @@ function createSuperDocUI(options) {
6485
6995
  ok: false
6486
6996
  };
6487
6997
  if (!result.ok && result.reason !== SUPERDOC_UI_REASONS.targetNotVisible) rollback();
6998
+ else mirrorTrackedChangeFocusToHost(requestedActiveChange);
6488
6999
  return result;
6489
7000
  }
6490
7001
  };
@@ -6511,12 +7022,13 @@ function createSuperDocUI(options) {
6511
7022
  const tcApi = getDoc()?.trackChanges;
6512
7023
  const isAllTarget = target.scope === "all";
6513
7024
  const isRangeTarget = target.kind === "range";
7025
+ const isMultiIdTarget = target.kind === "ids";
6514
7026
  if (bulkTrackDecisionBlockedReason({
6515
7027
  kind,
6516
7028
  scope: isAllTarget ? "all" : "id"
6517
7029
  }, tcApi)) return false;
6518
7030
  const legacyName = isAllTarget ? `${kind}All` : kind;
6519
- const op = !isRangeTarget && !story && tcApi && typeof tcApi[legacyName] === "function" ? tcApi[legacyName] : null;
7031
+ const op = !isRangeTarget && !isMultiIdTarget && !story && tcApi && typeof tcApi[legacyName] === "function" ? tcApi[legacyName] : null;
6520
7032
  const decide = tcApi && typeof tcApi.decide === "function" ? tcApi.decide : null;
6521
7033
  if (!op && !decide) return false;
6522
7034
  return op ? op.call(tcApi, changeId ?? {}) : decide.call(tcApi, {
@@ -6524,23 +7036,6 @@ function createSuperDocUI(options) {
6524
7036
  target
6525
7037
  });
6526
7038
  };
6527
- const executeTrackDecisionTargets = (kind, changeIds) => {
6528
- const immediateResults = [];
6529
- const settledResults = [];
6530
- for (const changeId of changeIds) try {
6531
- const result = settleOperationResult(callTrackDecisionTarget(kind, {
6532
- kind: "id",
6533
- id: changeId
6534
- }, changeId));
6535
- immediateResults.push(result.immediate);
6536
- settledResults.push(result.settled);
6537
- } catch {
6538
- immediateResults.push(false);
6539
- settledResults.push(Promise.resolve(false));
6540
- }
6541
- pendingCommandSettlement = Promise.all(settledResults).then((results) => combineCommandResults(results));
6542
- return settleCommandExecution(combineCommandResults(immediateResults));
6543
- };
6544
7039
  const contentControlsSub = sliceHandle((s) => s.contentControls);
6545
7040
  const findContentControl = (id) => {
6546
7041
  return contentControlsSub.get().items.find((item) => item?.id === id) ?? null;
@@ -7464,10 +7959,16 @@ function createSuperDocUI(options) {
7464
7959
  if (disposed) return;
7465
7960
  disposed = true;
7466
7961
  reviewMutationReconciler.reset();
7962
+ clearAuthoritativeTrackChangesRetry();
7467
7963
  if (foregroundAsyncRetryTimer) {
7468
7964
  clearTimeout(foregroundAsyncRetryTimer);
7469
7965
  foregroundAsyncRetryTimer = null;
7470
7966
  }
7967
+ if (asyncReadFailureRetryTimer) {
7968
+ clearTimeout(asyncReadFailureRetryTimer);
7969
+ asyncReadFailureRetryTimer = null;
7970
+ asyncReadFailureRetryAtMs = 0;
7971
+ }
7471
7972
  if (pendingEffectiveInlineRead?.timer) {
7472
7973
  clearTimeout(pendingEffectiveInlineRead.timer);
7473
7974
  pendingEffectiveInlineRead.timer = null;