superdoc 2.0.0-next.27 → 2.0.0-next.28

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 (32) hide show
  1. package/dist/chunks/{create-super-doc-ui-Dvl3EZsv.es.js → create-super-doc-ui-BlnUCtrx.es.js} +171 -51
  2. package/dist/chunks/{create-super-doc-ui-pYMthdDn.cjs → create-super-doc-ui-CZBUMdjl.cjs} +171 -51
  3. package/dist/collaboration-upgrade-engine.cjs +1 -1
  4. package/dist/collaboration-upgrade-engine.es.js +1 -1
  5. package/dist/document-api/src/contract/operation-definitions.d.ts +1 -1
  6. package/dist/document-api/src/track-changes/track-changes.d.ts +6 -2
  7. package/dist/document-api/src/types/clipboard.d.ts +5 -1
  8. package/dist/document-api/src/types/table-operations.types.d.ts +6 -4
  9. package/dist/document-api/src/types/track-changes.types.d.ts +15 -0
  10. package/dist/layout-engine/contracts/src/incremental-dependency.d.ts +10 -0
  11. package/dist/layout-engine/contracts/src/index.d.ts +38 -0
  12. package/dist/layout-engine/layout-bridge/src/incrementalLayout.d.ts +23 -1
  13. package/dist/layout-engine/layout-engine/src/index.d.ts +17 -0
  14. package/dist/layout-engine/layout-engine/src/paginator.d.ts +16 -0
  15. package/dist/layout-engine/layout-engine/src/paragraph-layout-eligibility.d.ts +1 -4
  16. package/dist/public/ui-react.cjs +1 -1
  17. package/dist/public/ui-react.es.js +1 -1
  18. package/dist/public/ui.cjs +1 -1
  19. package/dist/public/ui.es.js +1 -1
  20. package/dist/style.css +82 -63
  21. package/dist/style.layered.css +82 -63
  22. package/dist/superdoc/src/core/types/index.d.ts +7 -0
  23. package/dist/superdoc/src/public/ui/reasons.d.ts +8 -0
  24. package/dist/superdoc/src/stores/comments-store.d.ts +9 -9
  25. package/dist/superdoc/src/stores/helpers/floating-comment-instances.d.ts +2 -10
  26. package/dist/superdoc/src/stores/superdoc-store.d.ts +27 -27
  27. package/dist/superdoc.cjs +137 -29
  28. package/dist/superdoc.es.js +137 -29
  29. package/dist-cdn/style.layered.css +1 -1
  30. package/dist-cdn/superdoc.min.css +1 -1
  31. package/dist-cdn/superdoc.min.js +37 -37
  32. package/package.json +1 -1
@@ -311,7 +311,8 @@ const SUPERDOC_UI_REASONS = {
311
311
  historyEmpty: "history-empty",
312
312
  searchUnavailable: "search-unavailable",
313
313
  searchInvalidPattern: "search-invalid-pattern",
314
- replaceUnsupported: "replace-unsupported"
314
+ replaceUnsupported: "replace-unsupported",
315
+ contentControlLocked: "content-control-locked"
315
316
  };
316
317
  const BUILT_IN_COMMAND_IDS = {
317
318
  bold: "bold",
@@ -2451,6 +2452,31 @@ function createSuperDocUI(options) {
2451
2452
  let explicitActiveChange = null;
2452
2453
  let explicitActiveChangeRevision = 0;
2453
2454
  let queuedTrackChangeNavigationInvalidation = 0;
2455
+ const mirrorTrackedChangeFocusToHost = (next) => {
2456
+ const host = getHost();
2457
+ if (typeof host?.getHandles !== "function") return;
2458
+ const handles = safeCall(() => host.getHandles(), null);
2459
+ const review = handles?.review;
2460
+ if (!review) return;
2461
+ if (next) {
2462
+ if (typeof review.setActiveReviewTarget !== "function") return;
2463
+ const layout = handles?.layout;
2464
+ safeCall(() => review.setActiveReviewTarget({
2465
+ entityType: "trackedChange",
2466
+ entityId: next.id,
2467
+ ...next.paintedEntityId ? { paintedEntityId: next.paintedEntityId } : {},
2468
+ origin: "panel",
2469
+ layoutEpoch: typeof layout?.generation === "number" ? layout.generation : 0,
2470
+ story: next.story ?? {
2471
+ kind: "story",
2472
+ storyType: "body"
2473
+ }
2474
+ }), null);
2475
+ return;
2476
+ }
2477
+ if (typeof review.getActiveReviewTarget !== "function" || typeof review.clearActiveReviewTarget !== "function") return;
2478
+ if (safeCall(() => review.getActiveReviewTarget(), null)?.entityType === "trackedChange") safeCall(() => review.clearActiveReviewTarget(), void 0);
2479
+ };
2454
2480
  const setExplicitActiveChange = (next, options$1) => {
2455
2481
  explicitActiveChange = next;
2456
2482
  explicitActiveChangeRevision += 1;
@@ -2464,6 +2490,8 @@ function createSuperDocUI(options) {
2464
2490
  let lastEffectiveInlineReadKey = null;
2465
2491
  let pendingEffectiveInlineRead = null;
2466
2492
  let asyncRefreshScheduled = false;
2493
+ let asyncReadFailureRetryTimer = null;
2494
+ let asyncReadFailureRetryAtMs = 0;
2467
2495
  let foregroundAsyncRetryTimer = null;
2468
2496
  let pendingSelectionSeedValidationToken = null;
2469
2497
  let typingContentInvalidationTimer = null;
@@ -2478,6 +2506,19 @@ function createSuperDocUI(options) {
2478
2506
  if (typeof queueMicrotask === "function") queueMicrotask(run);
2479
2507
  else Promise.resolve().then(run);
2480
2508
  };
2509
+ const ASYNC_READ_FAILURE_RETRY_BASE_MS = 250;
2510
+ const ASYNC_READ_FAILURE_RETRY_MAX_MS = 5e3;
2511
+ const scheduleAsyncReadFailureRetry = (retryAtMs) => {
2512
+ if (disposed) return;
2513
+ if (asyncReadFailureRetryTimer && asyncReadFailureRetryAtMs <= retryAtMs) return;
2514
+ if (asyncReadFailureRetryTimer) clearTimeout(asyncReadFailureRetryTimer);
2515
+ asyncReadFailureRetryAtMs = retryAtMs;
2516
+ asyncReadFailureRetryTimer = setTimeout(() => {
2517
+ asyncReadFailureRetryTimer = null;
2518
+ asyncReadFailureRetryAtMs = 0;
2519
+ if (!disposed) recompute();
2520
+ }, Math.max(0, retryAtMs - Date.now()));
2521
+ };
2481
2522
  const editorIdentityIds = /* @__PURE__ */ new WeakMap();
2482
2523
  let editorIdentityCounter = 0;
2483
2524
  const editorIdentityId = (editor) => {
@@ -2714,7 +2755,7 @@ function createSuperDocUI(options) {
2714
2755
  const read = host?.getDocumentLoadingSnapshot;
2715
2756
  if (typeof read !== "function") return "unknown";
2716
2757
  const stage = safeCall(() => read.call(host), null)?.sourceStage;
2717
- if (stage === "source-loading") return "loading";
2758
+ if (stage === "opening" || stage === "ready-window" || stage === "source-loading") return "loading";
2718
2759
  if (stage === "source-complete" || stage === "source-failed" || stage === "source-cancelled") return "complete";
2719
2760
  return "unknown";
2720
2761
  };
@@ -2880,17 +2921,24 @@ function createSuperDocUI(options) {
2880
2921
  token,
2881
2922
  value: normalize(raw),
2882
2923
  hasSettled: true,
2883
- inflightToken: null
2924
+ inflightToken: null,
2925
+ failedToken: null,
2926
+ failureCount: 0,
2927
+ retryAtMs: 0
2884
2928
  };
2885
2929
  asyncReads.set(key, settled);
2886
2930
  return settled;
2887
2931
  }
2888
2932
  const prev = asyncReads.get(key);
2933
+ const retryingSameToken = prev?.failedToken === token;
2889
2934
  asyncReads.set(key, {
2890
2935
  token: prev?.token ?? null,
2891
2936
  value: prev?.value ?? null,
2892
2937
  hasSettled: prev?.hasSettled ?? false,
2893
- inflightToken: token
2938
+ inflightToken: token,
2939
+ failedToken: retryingSameToken ? token : null,
2940
+ failureCount: retryingSameToken ? prev.failureCount ?? 0 : 0,
2941
+ retryAtMs: 0
2894
2942
  });
2895
2943
  Promise.resolve(raw).then((resolved) => {
2896
2944
  const entry = asyncReads.get(key);
@@ -2899,17 +2947,26 @@ function createSuperDocUI(options) {
2899
2947
  token,
2900
2948
  value: normalize(resolved),
2901
2949
  hasSettled: true,
2902
- inflightToken: null
2950
+ inflightToken: null,
2951
+ failedToken: null,
2952
+ failureCount: 0,
2953
+ retryAtMs: 0
2903
2954
  });
2904
2955
  scheduleAsyncRefresh();
2905
2956
  }, () => {
2906
2957
  const entry = asyncReads.get(key);
2907
2958
  if (!entry || entry.inflightToken !== token) return;
2959
+ const failureCount = entry.failedToken === token ? (entry.failureCount ?? 0) + 1 : 1;
2960
+ const retryDelayMs = Math.min(ASYNC_READ_FAILURE_RETRY_MAX_MS, ASYNC_READ_FAILURE_RETRY_BASE_MS * 2 ** Math.min(failureCount - 1, 8));
2961
+ const retryAtMs = Date.now() + retryDelayMs;
2908
2962
  asyncReads.set(key, {
2909
2963
  ...entry,
2910
- inflightToken: null
2964
+ inflightToken: null,
2965
+ failedToken: token,
2966
+ failureCount,
2967
+ retryAtMs
2911
2968
  });
2912
- scheduleAsyncRefresh();
2969
+ scheduleAsyncReadFailureRetry(retryAtMs);
2913
2970
  });
2914
2971
  return null;
2915
2972
  };
@@ -2970,6 +3027,17 @@ function createSuperDocUI(options) {
2970
3027
  status: "pending"
2971
3028
  };
2972
3029
  }
3030
+ if (entry?.failedToken === token && entry.inflightToken == null && (entry.retryAtMs ?? 0) > Date.now()) {
3031
+ scheduleAsyncReadFailureRetry(entry.retryAtMs);
3032
+ if (entry.hasSettled) return {
3033
+ value: entry.value,
3034
+ status: "stale"
3035
+ };
3036
+ return {
3037
+ value: null,
3038
+ status: "pending"
3039
+ };
3040
+ }
2973
3041
  if (!entry && key.startsWith("effInline:") && shouldDeferEffectiveInlineRead(key)) return {
2974
3042
  value: null,
2975
3043
  status: "pending"
@@ -3021,6 +3089,9 @@ function createSuperDocUI(options) {
3021
3089
  reviewMutationReconciler.reset();
3022
3090
  lastCoordinatorEditor = editor;
3023
3091
  asyncReads.clear();
3092
+ if (asyncReadFailureRetryTimer) clearTimeout(asyncReadFailureRetryTimer);
3093
+ asyncReadFailureRetryTimer = null;
3094
+ asyncReadFailureRetryAtMs = 0;
3024
3095
  if (pendingEffectiveInlineRead?.timer) clearTimeout(pendingEffectiveInlineRead.timer);
3025
3096
  pendingEffectiveInlineRead = null;
3026
3097
  lastEffectiveInlineReadKey = null;
@@ -3284,22 +3355,29 @@ function createSuperDocUI(options) {
3284
3355
  activeIds
3285
3356
  };
3286
3357
  };
3287
- const computeActiveContentControlIds = (ccApi, selection$1) => {
3358
+ const computeActiveContentControlLockModes = (ccApi, selection$1) => {
3288
3359
  if (!ccApi || typeof ccApi.listInRange !== "function") return {
3289
- ids: [],
3360
+ lockModesById: /* @__PURE__ */ new Map(),
3290
3361
  status: "ready"
3291
3362
  };
3292
3363
  const range = selectionBlockRange(selection$1);
3293
3364
  if (!range) return {
3294
- ids: [],
3365
+ lockModesById: /* @__PURE__ */ new Map(),
3295
3366
  status: "ready"
3296
3367
  };
3297
3368
  const { value, status } = readAsync(`contentControls:inRange:${selectionSignature(selection$1)}`, contentToken(), () => ccApi.listInRange(range), (raw) => raw && Array.isArray(raw.items) ? raw.items : []);
3298
3369
  const rows = value ?? [];
3299
- const ids = [];
3300
- for (const row of rows) if (typeof row?.id === "string") ids.push(row.id);
3370
+ const lockModesById = /* @__PURE__ */ new Map();
3371
+ for (const row of rows) if (typeof row?.id === "string") lockModesById.set(row.id, typeof row.lockMode === "string" ? row.lockMode : "unlocked");
3372
+ return {
3373
+ lockModesById,
3374
+ status
3375
+ };
3376
+ };
3377
+ const computeActiveContentControlIds = (ccApi, selection$1) => {
3378
+ const { lockModesById, status } = computeActiveContentControlLockModes(ccApi, selection$1);
3301
3379
  return {
3302
- ids,
3380
+ ids: [...lockModesById.keys()],
3303
3381
  status
3304
3382
  };
3305
3383
  };
@@ -3615,14 +3693,20 @@ function createSuperDocUI(options) {
3615
3693
  frozenProjectedValues = projectedInlineValues;
3616
3694
  frozenProjectedValuesKey = `${selectionKey(selection$1)}:${contentToken()}`;
3617
3695
  }
3696
+ const ccApi = doc?.contentControls;
3697
+ const { lockModesById } = computeActiveContentControlLockModes(ccApi, selection$1);
3618
3698
  const states = {};
3619
- for (const id of allCommandIds()) states[id] = computeCommandState(id, doc, selection$1, projectedInlineValues);
3699
+ for (const id of allCommandIds()) states[id] = computeCommandState(id, doc, selection$1, projectedInlineValues, lockModesById);
3620
3700
  return states;
3621
3701
  };
3622
3702
  const unavailableRouteReason = (doc) => {
3623
3703
  if (doc) return SUPERDOC_UI_REASONS.operationUnavailable;
3624
3704
  return getEditor() ? SUPERDOC_UI_REASONS.documentApiUnavailable : SUPERDOC_UI_REASONS.notReady;
3625
3705
  };
3706
+ const blocksContentStyling = (lockMode) => lockMode === "contentLocked" || lockMode === "sdtContentLocked";
3707
+ const contentControlLockReason = (lockModesById) => {
3708
+ for (const lockMode of lockModesById.values()) if (blocksContentStyling(lockMode)) return SUPERDOC_UI_REASONS.contentControlLocked;
3709
+ };
3626
3710
  const trackDecisionReason = (command, supported, readonly, selection$1, doc) => {
3627
3711
  if (!supported) return command.scope === "all" ? SUPERDOC_UI_REASONS.bulkDecisionsDisabled : unavailableRouteReason(doc);
3628
3712
  if (readonly) return SUPERDOC_UI_REASONS.documentReadonly;
@@ -3645,7 +3729,7 @@ function createSuperDocUI(options) {
3645
3729
  if (support && (support.status === "supported" || support.enabled === true)) return void 0;
3646
3730
  return SUPERDOC_UI_REASONS.bulkDecisionsDisabled;
3647
3731
  };
3648
- const computeCommandState = (id, doc, selection$1, projectedInlineValues) => {
3732
+ const computeCommandState = (id, doc, selection$1, projectedInlineValues, lockModesById = /* @__PURE__ */ new Map()) => {
3649
3733
  if (customCommands.has(id)) return normalizeCommandState({
3650
3734
  supported: true,
3651
3735
  enabled: true,
@@ -3720,6 +3804,13 @@ function createSuperDocUI(options) {
3720
3804
  supported: true,
3721
3805
  reason: SUPERDOC_UI_REASONS.documentReadonly
3722
3806
  }, "builtin");
3807
+ const lockReason = contentControlLockReason(lockModesById);
3808
+ if (lockReason) return normalizeCommandState({
3809
+ enabled: false,
3810
+ active: false,
3811
+ supported: true,
3812
+ reason: lockReason
3813
+ }, "builtin");
3723
3814
  const enabled = selectionBlockIds(selection$1).length > 0 || resolveInlineSelectionTarget(selection$1) != null;
3724
3815
  return normalizeCommandState({
3725
3816
  enabled,
@@ -3770,18 +3861,28 @@ function createSuperDocUI(options) {
3770
3861
  value,
3771
3862
  reason: SUPERDOC_UI_REASONS.documentReadonly
3772
3863
  }, "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");
3864
+ if (descriptor.inline && !resolveInlineSelectionTarget(selection$1)) {
3865
+ const lockReason = contentControlLockReason(lockModesById);
3866
+ if (lockReason) return normalizeCommandState({
3867
+ enabled: false,
3868
+ active,
3869
+ supported: true,
3870
+ value,
3871
+ reason: lockReason
3872
+ }, "builtin");
3873
+ return normalizeCommandState(selectionBlockIds(selection$1).length > 0 && typeof getHost()?.setPendingInlineFormat === "function" ? {
3874
+ enabled: true,
3875
+ active,
3876
+ supported: true,
3877
+ value
3878
+ } : {
3879
+ enabled: false,
3880
+ active,
3881
+ supported: true,
3882
+ value,
3883
+ reason: SUPERDOC_UI_REASONS.rangeSelectionRequired
3884
+ }, "builtin");
3885
+ }
3785
3886
  if ((descriptor.blockParagraph || descriptor.list) && selectionBlockIds(selection$1).length === 0) return normalizeCommandState({
3786
3887
  enabled: false,
3787
3888
  active,
@@ -3795,6 +3896,16 @@ function createSuperDocUI(options) {
3795
3896
  supported: true,
3796
3897
  reason: SUPERDOC_UI_REASONS.rangeSelectionRequired
3797
3898
  }, "builtin");
3899
+ if (descriptor.inline) {
3900
+ const lockReason = contentControlLockReason(lockModesById);
3901
+ if (lockReason) return normalizeCommandState({
3902
+ enabled: false,
3903
+ active,
3904
+ supported: true,
3905
+ value,
3906
+ reason: lockReason
3907
+ }, "builtin");
3908
+ }
3798
3909
  return normalizeCommandState({
3799
3910
  enabled: true,
3800
3911
  active,
@@ -4753,7 +4864,10 @@ function createSuperDocUI(options) {
4753
4864
  }
4754
4865
  function executeTrackDecisionCommand(command, payload) {
4755
4866
  if (command.scope === "id" && payload == null) {
4756
- if (state.selection.activeChangeIds.length > 1) return executeTrackDecisionTargets(command.kind, state.selection.activeChangeIds);
4867
+ if (state.selection.activeChangeIds.length > 1) return executeTrackDecisionTarget(command.kind, {
4868
+ kind: "ids",
4869
+ ids: state.selection.activeChangeIds
4870
+ }, null);
4757
4871
  if (state.selection.activeChangeIds.length === 1 && !state.selection.empty && state.selection.selectionTarget) {
4758
4872
  const route = activeChangePartialDecisionRoute();
4759
4873
  if (route === "unavailable") return false;
@@ -5025,6 +5139,9 @@ function createSuperDocUI(options) {
5025
5139
  return true;
5026
5140
  };
5027
5141
  const executeClearFormattingCommand = () => {
5142
+ const doc = getDoc();
5143
+ const { lockModesById } = computeActiveContentControlLockModes(doc?.contentControls, state.selection);
5144
+ if (contentControlLockReason(lockModesById)) return false;
5028
5145
  const immediateResults = [];
5029
5146
  const settledResults = [];
5030
5147
  const record = (result) => {
@@ -5040,7 +5157,6 @@ function createSuperDocUI(options) {
5040
5157
  settledResults.push(Promise.resolve(false));
5041
5158
  }
5042
5159
  };
5043
- const doc = getDoc();
5044
5160
  const defaultStyleId = getStyleCatalog().cache?.full.defaults.paragraphStyleId ?? DEFAULT_PARAGRAPH_STYLE_ID;
5045
5161
  const rangeTarget = resolveInlineSelectionTarget(state.selection);
5046
5162
  if (rangeTarget) {
@@ -5446,7 +5562,7 @@ function createSuperDocUI(options) {
5446
5562
  const rows = Number(record.rows ?? record.rowCount);
5447
5563
  const columns = Number(record.columns ?? record.cols ?? record.colCount);
5448
5564
  if (!Number.isInteger(rows) || rows < 1 || !Number.isInteger(columns) || columns < 1) return false;
5449
- const caret = readDocumentMode() !== "suggesting" ? collapsedTextAddressFromSelection(state.selection) : null;
5565
+ const caret = collapsedTextAddressFromSelection(state.selection);
5450
5566
  const at = caret && typeof caret.blockId === "string" ? {
5451
5567
  kind: "inParagraph",
5452
5568
  target: paragraphTarget(caret.blockId),
@@ -5595,9 +5711,12 @@ function createSuperDocUI(options) {
5595
5711
  const route = descriptor.docRoute;
5596
5712
  if (!route) return false;
5597
5713
  if (descriptor.mutates && readDocumentMode() === "viewing") return false;
5598
- const op = resolveDocOperation(getDoc(), route);
5714
+ const doc = getDoc();
5715
+ const op = resolveDocOperation(doc, route);
5599
5716
  if (!op) return false;
5600
5717
  if (descriptor.inline) {
5718
+ const { lockModesById } = computeActiveContentControlLockModes(doc?.contentControls, state.selection);
5719
+ if (contentControlLockReason(lockModesById)) return false;
5601
5720
  const target = resolveInlineSelectionTarget(state.selection);
5602
5721
  if (!target) {
5603
5722
  if (state.selection.empty === false) return settleCommandExecution(false);
@@ -5855,6 +5974,12 @@ function createSuperDocUI(options) {
5855
5974
  }
5856
5975
  const doc = getDoc();
5857
5976
  if (!doc) return;
5977
+ const targetSelection = selectionSliceFromInfo(selection$1, "ready");
5978
+ const { lockModesById } = computeActiveContentControlLockModes(doc.contentControls, targetSelection);
5979
+ if (contentControlLockReason(lockModesById)) {
5980
+ recompute();
5981
+ return;
5982
+ }
5858
5983
  const selTarget = selection$1["target"];
5859
5984
  const selectionTarget = selection$1["selectionTarget"];
5860
5985
  if (!(selection$1["empty"] === true) && Object.keys(snap.inline).length > 0) {
@@ -6426,6 +6551,7 @@ function createSuperDocUI(options) {
6426
6551
  setActive: (input) => {
6427
6552
  if (input == null) {
6428
6553
  setExplicitActiveChange(null);
6554
+ mirrorTrackedChangeFocusToHost(null);
6429
6555
  recompute();
6430
6556
  return true;
6431
6557
  }
@@ -6435,6 +6561,7 @@ function createSuperDocUI(options) {
6435
6561
  story: input.story
6436
6562
  };
6437
6563
  if (typeof requested.id !== "string" || requested.id.length === 0) return false;
6564
+ const paintedEntityId = requested.id;
6438
6565
  if (requested.story) {
6439
6566
  const allStoryItems = readAllStoryTrackChanges();
6440
6567
  if (!allStoryItems) return false;
@@ -6447,7 +6574,9 @@ function createSuperDocUI(options) {
6447
6574
  if (!bodyItems.some((item) => readEntityId(item) === publicId)) return false;
6448
6575
  requested.id = publicId;
6449
6576
  }
6577
+ if (paintedEntityId !== requested.id) requested.paintedEntityId = paintedEntityId;
6450
6578
  setExplicitActiveChange(requested);
6579
+ mirrorTrackedChangeFocusToHost(requested);
6451
6580
  recompute();
6452
6581
  return true;
6453
6582
  },
@@ -6466,10 +6595,11 @@ function createSuperDocUI(options) {
6466
6595
  reason: SUPERDOC_UI_REASONS.targetUnresolved
6467
6596
  };
6468
6597
  const previousActiveChange = explicitActiveChange;
6469
- setExplicitActiveChange(story ? {
6598
+ const requestedActiveChange = story ? {
6470
6599
  id: changeId,
6471
6600
  story
6472
- } : { id: changeId });
6601
+ } : { id: changeId };
6602
+ setExplicitActiveChange(requestedActiveChange);
6473
6603
  recompute();
6474
6604
  const optimisticActiveChange = explicitActiveChange;
6475
6605
  const optimisticRevision = explicitActiveChangeRevision;
@@ -6485,6 +6615,7 @@ function createSuperDocUI(options) {
6485
6615
  ok: false
6486
6616
  };
6487
6617
  if (!result.ok && result.reason !== SUPERDOC_UI_REASONS.targetNotVisible) rollback();
6618
+ else mirrorTrackedChangeFocusToHost(requestedActiveChange);
6488
6619
  return result;
6489
6620
  }
6490
6621
  };
@@ -6511,12 +6642,13 @@ function createSuperDocUI(options) {
6511
6642
  const tcApi = getDoc()?.trackChanges;
6512
6643
  const isAllTarget = target.scope === "all";
6513
6644
  const isRangeTarget = target.kind === "range";
6645
+ const isMultiIdTarget = target.kind === "ids";
6514
6646
  if (bulkTrackDecisionBlockedReason({
6515
6647
  kind,
6516
6648
  scope: isAllTarget ? "all" : "id"
6517
6649
  }, tcApi)) return false;
6518
6650
  const legacyName = isAllTarget ? `${kind}All` : kind;
6519
- const op = !isRangeTarget && !story && tcApi && typeof tcApi[legacyName] === "function" ? tcApi[legacyName] : null;
6651
+ const op = !isRangeTarget && !isMultiIdTarget && !story && tcApi && typeof tcApi[legacyName] === "function" ? tcApi[legacyName] : null;
6520
6652
  const decide = tcApi && typeof tcApi.decide === "function" ? tcApi.decide : null;
6521
6653
  if (!op && !decide) return false;
6522
6654
  return op ? op.call(tcApi, changeId ?? {}) : decide.call(tcApi, {
@@ -6524,23 +6656,6 @@ function createSuperDocUI(options) {
6524
6656
  target
6525
6657
  });
6526
6658
  };
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
6659
  const contentControlsSub = sliceHandle((s) => s.contentControls);
6545
6660
  const findContentControl = (id) => {
6546
6661
  return contentControlsSub.get().items.find((item) => item?.id === id) ?? null;
@@ -7468,6 +7583,11 @@ function createSuperDocUI(options) {
7468
7583
  clearTimeout(foregroundAsyncRetryTimer);
7469
7584
  foregroundAsyncRetryTimer = null;
7470
7585
  }
7586
+ if (asyncReadFailureRetryTimer) {
7587
+ clearTimeout(asyncReadFailureRetryTimer);
7588
+ asyncReadFailureRetryTimer = null;
7589
+ asyncReadFailureRetryAtMs = 0;
7590
+ }
7471
7591
  if (pendingEffectiveInlineRead?.timer) {
7472
7592
  clearTimeout(pendingEffectiveInlineRead.timer);
7473
7593
  pendingEffectiveInlineRead.timer = null;
@@ -7,7 +7,7 @@ const COLLABORATION_UPGRADE_ENGINE_MINIMUM_NODE_MAJOR = 20;
7
7
  var PRIVATE_ENGINE_INFO = (0, __superdoc_docx_engine_collaboration_upgrade_engine.getCollaborationUpgradeEngineInfo)();
8
8
  var ENGINE_INFO = Object.freeze({
9
9
  ...PRIVATE_ENGINE_INFO,
10
- superdocVersion: "2.0.0-next.27",
10
+ superdocVersion: "2.0.0-next.28",
11
11
  roomSchemaVersion: Object.freeze({ ...PRIVATE_ENGINE_INFO.roomSchemaVersion }),
12
12
  supportedBundleVersions: SUPPORTED_COLLABORATION_UPGRADE_BUNDLE_VERSIONS,
13
13
  supportedV1ReaderContractVersions: SUPPORTED_V1_READER_CONTRACT_VERSIONS
@@ -6,7 +6,7 @@ const COLLABORATION_UPGRADE_ENGINE_MINIMUM_NODE_MAJOR = 20;
6
6
  var PRIVATE_ENGINE_INFO = getCollaborationUpgradeEngineInfo$1();
7
7
  var ENGINE_INFO = Object.freeze({
8
8
  ...PRIVATE_ENGINE_INFO,
9
- superdocVersion: "2.0.0-next.27",
9
+ superdocVersion: "2.0.0-next.28",
10
10
  roomSchemaVersion: Object.freeze({ ...PRIVATE_ENGINE_INFO.roomSchemaVersion }),
11
11
  supportedBundleVersions: SUPPORTED_COLLABORATION_UPGRADE_BUNDLE_VERSIONS,
12
12
  supportedV1ReaderContractVersions: SUPPORTED_V1_READER_CONTRACT_VERSIONS
@@ -976,7 +976,7 @@ export declare const OPERATION_DEFINITIONS: {
976
976
  };
977
977
  readonly 'trackChanges.decide': {
978
978
  readonly memberPath: "trackChanges.decide";
979
- readonly description: "Accept or reject tracked changes by logical id, by selected text range, by logical anchor range ({ anchor, relativeStart, relativeEnd }), or every active change in the document. Range targets may include overlap / side selectors and may partially resolve a change, splitting surviving content into new fragments with stable ids. Legacy `{ id, range: { kind: \"partial\", ... } }` targets are promoted only when the active adapter supports `decide()`; legacy fallback adapters fail closed with `INVALID_INPUT`. `input.expectedRevision` is accepted as a compatibility alias for mutation options.";
979
+ readonly description: "Accept or reject tracked changes by logical id, by several logical ids atomically, by selected text range, by logical anchor range ({ anchor, relativeStart, relativeEnd }), or every active change in the document. Range targets may include overlap / side selectors and may partially resolve a change, splitting surviving content into new fragments with stable ids. Legacy `{ id, range: { kind: \"partial\", ... } }` targets are promoted only when the active adapter supports `decide()`; legacy fallback adapters fail closed with `INVALID_INPUT`. `input.expectedRevision` is accepted as a compatibility alias for mutation options.";
980
980
  readonly expectedResult: "Returns a Receipt. `removed` lists retired tracked-change ids; `inserted` lists successor fragment ids produced by partial range splits; `invalidatedRefs` lists every retired tracked-change ref plus cascade-deleted comment ids; `remappedRefs` carries surviving comment anchors moved by the decision; `affectedStories` lists touched stories; `textRangeShifts` reports per-story visible-text deltas; `txId` correlates with history. Failures: SPAN_FRAGMENTED when a range crosses an indivisible boundary, CAPABILITY_UNAVAILABLE for formatting / structural / move targets (not yet supported), TARGET_NOT_FOUND for retired ids or zero overlap, STALE_REVISION for stale expectedRevision, REVISION_MISMATCH for stale range coverage, NO_OP when { kind: 'all' } resolves an empty target set.";
981
981
  readonly requiresDocumentContext: true;
982
982
  readonly metadata: CommandStaticMetadata;
@@ -72,6 +72,10 @@ export type ReviewDecideTarget = {
72
72
  story?: StoryLocator;
73
73
  side?: ReviewDecideIdTargetSide;
74
74
  moveRole?: ReviewDecideTargetMoveRole;
75
+ } | {
76
+ /** Resolve several whole logical changes in one atomic history unit. */
77
+ kind: 'ids';
78
+ ids: string[];
75
79
  } | ReviewDecideTextRangeTarget | ReviewDecideLogicalRangeTarget | {
76
80
  kind: 'all';
77
81
  story?: StoryLocator | 'all';
@@ -153,7 +157,7 @@ export declare function executeTrackChangesGet(adapter: TrackChangesAdapter, inp
153
157
  * {@link RevisionGuardOptions} rather than `MutationOptions`.
154
158
  *
155
159
  * Validates the canonical discriminated target shape
156
- * (`{ kind: 'id' | 'range' | 'all' }`) and transparently promotes the
160
+ * (`{ kind: 'id' | 'ids' | 'range' | 'all' }`) and transparently promotes the
157
161
  * legacy `{ id, story? }` / `{ scope: 'all' }` shapes. Adapters that
158
162
  * implement the new {@link TrackChangesAdapter.decide} entrypoint receive
159
163
  * the normalized canonical input; older adapters that only expose
@@ -163,7 +167,7 @@ export declare function executeTrackChangesGet(adapter: TrackChangesAdapter, inp
163
167
  export declare function executeTrackChangesDecide(adapter: TrackChangesAdapter, rawInput: ReviewDecideInput, options?: RevisionGuardOptions): Receipt;
164
168
  /**
165
169
  * Validate and normalize a `trackChanges.decide` input into the canonical
166
- * `{ kind: 'id' | 'range' | 'all' }` target shape. Exposed for adapters /
170
+ * `{ kind: 'id' | 'ids' | 'range' | 'all' }` target shape. Exposed for adapters /
167
171
  * tests that need to share the same validation surface.
168
172
  */
169
173
  export declare function validateReviewDecideInput(rawInput: ReviewDecideInput): ReviewDecideRangeInput;
@@ -75,10 +75,14 @@ export interface SDPasteParagraphBlock {
75
75
  * Portable list membership intent. Without `numbering`, the kernel always
76
76
  * materializes a fresh definition. With `numbering`, the existing identity
77
77
  * is retained when the destination defines the same list kind; otherwise
78
- * this intent is the cross-document fallback. Single-level.
78
+ * this intent is the cross-document fallback. `level` is zero-based and
79
+ * bounded to OOXML's nine list levels; omitted means level 0.
79
80
  */
80
81
  listIntent?: {
81
82
  kind: 'bullet' | 'ordered';
83
+ level?: number;
84
+ /** True on the first block of a distinct adjacent portable list. */
85
+ startsNewList?: boolean;
82
86
  };
83
87
  /**
84
88
  * Pre-validated direct `<w:pPr>` properties. Same-document copy carries the
@@ -109,15 +109,17 @@ export type TableAnchorLocation = {
109
109
  */
110
110
  export type TableCreateLocation = TableAnchorLocation
111
111
  /**
112
- * Split the target paragraph at a visible-text offset and insert the table
113
- * as a standalone block between the head/tail halves (Word semantics: a
114
- * table cannot splice into a paragraph's runs). Body paragraphs only for
115
- * now; `changeMode: 'tracked'` is not supported for this location kind.
112
+ * Split the target paragraph at an offset and insert the table as a
113
+ * standalone block between the head/tail halves (Word semantics: a table
114
+ * cannot splice into a paragraph's runs). Offsets are visible-text by
115
+ * default; use `coordinateSpace: 'tracked'` for a deletion-inclusive caret
116
+ * coordinate. Body paragraphs only for now.
116
117
  */
117
118
  | {
118
119
  kind: 'inParagraph';
119
120
  target: BlockNodeAddress;
120
121
  offset: number;
122
+ coordinateSpace?: 'visible' | 'tracked';
121
123
  };
122
124
  /**
123
125
  * Generic success result for table mutation operations.
@@ -448,6 +448,17 @@ export interface TrackChangeImagePreview {
448
448
  /** Accessible label for clients that render the preview. */
449
449
  alt?: string;
450
450
  }
451
+ /**
452
+ * Authoritative block address for review navigation. Consumers use this
453
+ * instead of interpreting the shape-specific {@link TrackChangeTarget}
454
+ * union. `role` records which semantic carrier supplied the address.
455
+ */
456
+ export interface TrackChangeNavigationTarget {
457
+ kind: 'block';
458
+ story: StoryLocator;
459
+ blockId: string;
460
+ role: 'primary' | 'move-source' | 'move-destination' | 'formatting-carrier' | 'structural-carrier';
461
+ }
451
462
  export interface TrackChangeInfo {
452
463
  address: TrackedChangeAddress;
453
464
  /** Stable SuperDoc logical tracked-change id (spec §3 / §4). */
@@ -459,6 +470,8 @@ export interface TrackChangeInfo {
459
470
  state?: TrackChangeState;
460
471
  /** Logical target description. */
461
472
  target?: TrackChangeTarget;
473
+ /** Stable story/block address for focus and virtualized materialization. */
474
+ navigationTarget?: TrackChangeNavigationTarget;
462
475
  /** Semantic before-state needed to reject the change. */
463
476
  before?: TrackChangeSnapshot;
464
477
  /** Semantic after-state needed to accept the change. */
@@ -598,6 +611,8 @@ export interface TrackChangeDomain {
598
611
  state?: TrackChangeState;
599
612
  /** Logical target summary for list consumers when the compact projection can provide it safely. */
600
613
  target?: TrackChangeTarget;
614
+ /** Stable story/block address for focus and virtualized materialization. */
615
+ navigationTarget?: TrackChangeNavigationTarget;
601
616
  /** Move side metadata for paired tracked moves. */
602
617
  move?: TrackChangeMoveSides;
603
618
  /** Direction of a paired move in document order, when source/destination order is known. */
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Stable dependency classes that an incremental page checkpoint may fence.
3
+ *
4
+ * This list is proof metadata, never a feature switch. Producers must name
5
+ * every retained dependency that required the checkpoint; consumers reject
6
+ * empty, duplicate, or unknown lists instead of trusting a generic boolean.
7
+ */
8
+ export type PageCheckpointDependencyClass = 'multiple-sections' | 'furniture-page-tokens' | 'non-balanceable-multi-column-sections' | 'body-anchored-objects' | 'footnotes' | 'page-references' | 'keep-constraints' | 'tables' | 'furniture-anchored-objects';
9
+ export declare const PAGE_CHECKPOINT_DEPENDENCY_CLASSES: readonly ["multiple-sections", "furniture-page-tokens", "non-balanceable-multi-column-sections", "body-anchored-objects", "footnotes", "page-references", "keep-constraints", "tables", "furniture-anchored-objects"];
10
+ export declare function areValidPageCheckpointDependencyClasses(value: unknown): value is readonly PageCheckpointDependencyClass[];