superdoc 2.0.0-next.26 → 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 (39) hide show
  1. package/dist/chunks/{create-super-doc-ui-CBs8xQUw.es.js → create-super-doc-ui-BlnUCtrx.es.js} +634 -115
  2. package/dist/chunks/{create-super-doc-ui-CHZKlLiH.cjs → create-super-doc-ui-CZBUMdjl.cjs} +634 -115
  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/comments/comments.types.d.ts +9 -0
  6. package/dist/document-api/src/content-controls/content-controls.types.d.ts +13 -1
  7. package/dist/document-api/src/contract/operation-definitions.d.ts +2 -2
  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 +39 -0
  14. package/dist/layout-engine/layout-bridge/src/incrementalLayout.d.ts +23 -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/layout-engine/style-engine/src/normalize/types.d.ts +1 -0
  19. package/dist/public/ui-react.cjs +1 -1
  20. package/dist/public/ui-react.es.js +1 -1
  21. package/dist/public/ui.cjs +1 -1
  22. package/dist/public/ui.es.js +1 -1
  23. package/dist/style.css +119 -100
  24. package/dist/style.layered.css +119 -100
  25. package/dist/superdoc/src/components/CommentsLayer/collect-removed-comment-ids.d.ts +1 -0
  26. package/dist/superdoc/src/components/CommentsLayer/collect-tracked-change-thread.d.ts +1 -1
  27. package/dist/superdoc/src/components/CommentsLayer/tracked-change-threading.d.ts +2 -0
  28. package/dist/superdoc/src/core/types/index.d.ts +7 -0
  29. package/dist/superdoc/src/helpers/v2-remote-review-hydration.d.ts +12 -0
  30. package/dist/superdoc/src/public/ui/reasons.d.ts +8 -0
  31. package/dist/superdoc/src/stores/comments-store.d.ts +9 -9
  32. package/dist/superdoc/src/stores/helpers/floating-comment-instances.d.ts +2 -10
  33. package/dist/superdoc/src/stores/superdoc-store.d.ts +27 -27
  34. package/dist/superdoc.cjs +286 -82
  35. package/dist/superdoc.es.js +286 -82
  36. package/dist-cdn/style.layered.css +1 -1
  37. package/dist-cdn/superdoc.min.css +1 -1
  38. package/dist-cdn/superdoc.min.js +37 -37
  39. package/package.json +3 -2
@@ -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",
@@ -1393,6 +1394,17 @@ function readEntityId(row) {
1393
1394
  const id = record.id ?? record.commentId ?? record.changeId;
1394
1395
  return typeof id === "string" && id.length > 0 ? id : null;
1395
1396
  }
1397
+ function readCollapsedHostTrackChangeId(snapshot) {
1398
+ if (!snapshot || typeof snapshot !== "object") return null;
1399
+ const record = snapshot;
1400
+ const anchor = record.anchor;
1401
+ const focus = record.focus;
1402
+ const anchorVisual = anchor?.visualCaret;
1403
+ const focusVisual = focus?.visualCaret;
1404
+ const anchorId = anchorVisual?.trackChangeId;
1405
+ const focusId = focusVisual?.trackChangeId;
1406
+ return typeof anchorId === "string" && anchorId.length > 0 && anchorId === focusId ? anchorId : null;
1407
+ }
1396
1408
  var trackedChangeIdContextCache = /* @__PURE__ */ new WeakMap();
1397
1409
  function readTrackChangeProvenanceIds(item) {
1398
1410
  const out = [];
@@ -1543,12 +1555,11 @@ function paragraphTarget(blockId, story) {
1543
1555
  ...story && typeof story === "object" ? { story } : {}
1544
1556
  };
1545
1557
  }
1546
- function listsBlockTarget(blockId, story) {
1558
+ function listsBlockTarget(blockId) {
1547
1559
  return {
1548
1560
  kind: "block",
1549
1561
  nodeType: "paragraph",
1550
- nodeId: blockId,
1551
- ...story && typeof story === "object" ? { story } : {}
1562
+ nodeId: blockId
1552
1563
  };
1553
1564
  }
1554
1565
  function listItemTarget(blockId, story) {
@@ -2295,6 +2306,40 @@ function createSuperDocUI(options) {
2295
2306
  const host = getEditor()?.host;
2296
2307
  return host && typeof host === "object" ? host : null;
2297
2308
  };
2309
+ const resolveActiveHeaderFooterSlot = (story) => {
2310
+ if (story.storyType !== "headerFooterPart" || typeof story.refId !== "string") return null;
2311
+ const editor = getEditor();
2312
+ const host = getHost();
2313
+ const pageLayout = editor?.pageLayout ?? host?.pageLayout;
2314
+ if (!pageLayout || typeof pageLayout !== "object") return null;
2315
+ const getActiveRulerContext = pageLayout.getActiveRulerContext;
2316
+ const resolveEditTarget = host?.resolveHeaderFooterEditTarget;
2317
+ if (typeof getActiveRulerContext !== "function" || typeof resolveEditTarget !== "function") return null;
2318
+ const active = safeCall(() => getActiveRulerContext.call(pageLayout), null);
2319
+ if (!active || typeof active.pageIndex !== "number") return null;
2320
+ for (const headerFooterKind of ["header", "footer"]) {
2321
+ const resolved = safeCall(() => resolveEditTarget.call(host, {
2322
+ pageIndex: active.pageIndex,
2323
+ kind: headerFooterKind
2324
+ }), null);
2325
+ if (resolved?.status !== "ready" || resolved.refId !== story.refId || typeof resolved.sectionId !== "string" || ![
2326
+ "default",
2327
+ "first",
2328
+ "even"
2329
+ ].includes(String(resolved.slotVariant))) continue;
2330
+ return {
2331
+ kind: "story",
2332
+ storyType: "headerFooterSlot",
2333
+ section: {
2334
+ kind: "section",
2335
+ sectionId: resolved.sectionId
2336
+ },
2337
+ headerFooterKind,
2338
+ variant: resolved.slotVariant
2339
+ };
2340
+ }
2341
+ return null;
2342
+ };
2298
2343
  const readPendingInlineFormat = () => {
2299
2344
  const host = getHost();
2300
2345
  const fn = host?.getPendingInlineFormat;
@@ -2407,6 +2452,31 @@ function createSuperDocUI(options) {
2407
2452
  let explicitActiveChange = null;
2408
2453
  let explicitActiveChangeRevision = 0;
2409
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
+ };
2410
2480
  const setExplicitActiveChange = (next, options$1) => {
2411
2481
  explicitActiveChange = next;
2412
2482
  explicitActiveChangeRevision += 1;
@@ -2420,6 +2490,8 @@ function createSuperDocUI(options) {
2420
2490
  let lastEffectiveInlineReadKey = null;
2421
2491
  let pendingEffectiveInlineRead = null;
2422
2492
  let asyncRefreshScheduled = false;
2493
+ let asyncReadFailureRetryTimer = null;
2494
+ let asyncReadFailureRetryAtMs = 0;
2423
2495
  let foregroundAsyncRetryTimer = null;
2424
2496
  let pendingSelectionSeedValidationToken = null;
2425
2497
  let typingContentInvalidationTimer = null;
@@ -2434,6 +2506,19 @@ function createSuperDocUI(options) {
2434
2506
  if (typeof queueMicrotask === "function") queueMicrotask(run);
2435
2507
  else Promise.resolve().then(run);
2436
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
+ };
2437
2522
  const editorIdentityIds = /* @__PURE__ */ new WeakMap();
2438
2523
  let editorIdentityCounter = 0;
2439
2524
  const editorIdentityId = (editor) => {
@@ -2653,7 +2738,7 @@ function createSuperDocUI(options) {
2653
2738
  recompute();
2654
2739
  }
2655
2740
  });
2656
- const selectionReadToken = () => `${contentToken()}|s${selectionEpoch}`;
2741
+ const selectionReadToken = () => `${contentToken()}|m${readDocumentMode()}|s${selectionEpoch}`;
2657
2742
  const selectionSignature = (selection$1) => JSON.stringify({
2658
2743
  t: selection$1.target ?? null,
2659
2744
  s: selection$1.selectionTarget ?? null
@@ -2670,7 +2755,7 @@ function createSuperDocUI(options) {
2670
2755
  const read = host?.getDocumentLoadingSnapshot;
2671
2756
  if (typeof read !== "function") return "unknown";
2672
2757
  const stage = safeCall(() => read.call(host), null)?.sourceStage;
2673
- if (stage === "source-loading") return "loading";
2758
+ if (stage === "opening" || stage === "ready-window" || stage === "source-loading") return "loading";
2674
2759
  if (stage === "source-complete" || stage === "source-failed" || stage === "source-cancelled") return "complete";
2675
2760
  return "unknown";
2676
2761
  };
@@ -2836,17 +2921,24 @@ function createSuperDocUI(options) {
2836
2921
  token,
2837
2922
  value: normalize(raw),
2838
2923
  hasSettled: true,
2839
- inflightToken: null
2924
+ inflightToken: null,
2925
+ failedToken: null,
2926
+ failureCount: 0,
2927
+ retryAtMs: 0
2840
2928
  };
2841
2929
  asyncReads.set(key, settled);
2842
2930
  return settled;
2843
2931
  }
2844
2932
  const prev = asyncReads.get(key);
2933
+ const retryingSameToken = prev?.failedToken === token;
2845
2934
  asyncReads.set(key, {
2846
2935
  token: prev?.token ?? null,
2847
2936
  value: prev?.value ?? null,
2848
2937
  hasSettled: prev?.hasSettled ?? false,
2849
- inflightToken: token
2938
+ inflightToken: token,
2939
+ failedToken: retryingSameToken ? token : null,
2940
+ failureCount: retryingSameToken ? prev.failureCount ?? 0 : 0,
2941
+ retryAtMs: 0
2850
2942
  });
2851
2943
  Promise.resolve(raw).then((resolved) => {
2852
2944
  const entry = asyncReads.get(key);
@@ -2855,17 +2947,26 @@ function createSuperDocUI(options) {
2855
2947
  token,
2856
2948
  value: normalize(resolved),
2857
2949
  hasSettled: true,
2858
- inflightToken: null
2950
+ inflightToken: null,
2951
+ failedToken: null,
2952
+ failureCount: 0,
2953
+ retryAtMs: 0
2859
2954
  });
2860
2955
  scheduleAsyncRefresh();
2861
2956
  }, () => {
2862
2957
  const entry = asyncReads.get(key);
2863
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;
2864
2962
  asyncReads.set(key, {
2865
2963
  ...entry,
2866
- inflightToken: null
2964
+ inflightToken: null,
2965
+ failedToken: token,
2966
+ failureCount,
2967
+ retryAtMs
2867
2968
  });
2868
- scheduleAsyncRefresh();
2969
+ scheduleAsyncReadFailureRetry(retryAtMs);
2869
2970
  });
2870
2971
  return null;
2871
2972
  };
@@ -2926,6 +3027,17 @@ function createSuperDocUI(options) {
2926
3027
  status: "pending"
2927
3028
  };
2928
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
+ }
2929
3041
  if (!entry && key.startsWith("effInline:") && shouldDeferEffectiveInlineRead(key)) return {
2930
3042
  value: null,
2931
3043
  status: "pending"
@@ -2977,6 +3089,9 @@ function createSuperDocUI(options) {
2977
3089
  reviewMutationReconciler.reset();
2978
3090
  lastCoordinatorEditor = editor;
2979
3091
  asyncReads.clear();
3092
+ if (asyncReadFailureRetryTimer) clearTimeout(asyncReadFailureRetryTimer);
3093
+ asyncReadFailureRetryTimer = null;
3094
+ asyncReadFailureRetryAtMs = 0;
2980
3095
  if (pendingEffectiveInlineRead?.timer) clearTimeout(pendingEffectiveInlineRead.timer);
2981
3096
  pendingEffectiveInlineRead = null;
2982
3097
  lastEffectiveInlineReadKey = null;
@@ -3094,14 +3209,15 @@ function createSuperDocUI(options) {
3094
3209
  const end = target?.end;
3095
3210
  return start?.kind === "text" && end?.kind === "text" && typeof start.blockId === "string" && start.blockId === end.blockId && typeof start.offset === "number" && start.offset === end.offset;
3096
3211
  };
3097
- const seedCaretSelectionFromHost = () => {
3212
+ const seedCaretSelectionFromHost = (hostSelectionSnapshot) => {
3098
3213
  const host = getHost();
3099
3214
  const read = host?.readLiveSelectionSyncSnapshot;
3100
3215
  if (typeof read !== "function") return;
3101
3216
  const seed = normalizeSelectionInfo(safeCall(() => read.call(host), null));
3102
3217
  if (!seed || !isCollapsedCaretSnapshot(seed)) return;
3103
3218
  const token = selectionReadToken();
3104
- const deferAuthoritativeRead = foregroundMutationActive();
3219
+ const foreground = foregroundMutationState();
3220
+ const deferAuthoritativeRead = Boolean(foreground && (foreground.active > 0 || foreground.pending > 0));
3105
3221
  if (!deferAuthoritativeRead) {
3106
3222
  pendingSelectionSeedValidationToken = null;
3107
3223
  if (issueAsyncRead("selection", token, selectionCurrentRun, normalizeSelectionInfo)) return;
@@ -3111,11 +3227,22 @@ function createSuperDocUI(options) {
3111
3227
  }
3112
3228
  const prev = asyncReads.get("selection");
3113
3229
  const seedMarks = Array.isArray(seed.activeMarks) && seed.activeMarks.length > 0 ? seed.activeMarks : Array.isArray(state.selection.activeMarks) ? state.selection.activeMarks : [];
3230
+ const seedCommentIds = Array.isArray(seed.activeCommentIds) ? seed.activeCommentIds : [];
3231
+ const paintedTrackChangeId = readCollapsedHostTrackChangeId(hostSelectionSnapshot);
3232
+ const previousCaret = collapsedTextAddressFromSelection(state.selection);
3233
+ const nextCaret = collapsedTextAddressFromTarget(seed.selectionTarget) ?? collapsedTextAddressFromTarget(seed.target);
3234
+ const previousRange = previousCaret?.range;
3235
+ const nextRange = nextCaret?.range;
3236
+ const sameActiveTypingCaret = Boolean(foreground && foreground.active > 0 && previousCaret && nextCaret && previousCaret.blockId === nextCaret.blockId && storyLocatorSignature(previousCaret.story) === storyLocatorSignature(nextCaret.story) && typeof previousRange?.start === "number" && typeof nextRange?.start === "number" && Math.abs(nextRange.start - previousRange.start) <= 2);
3237
+ const carriedChangeIds = paintedTrackChangeId != null || sameActiveTypingCaret ? state.selection.activeChangeIds.filter((id) => paintedTrackChangeId == null || buildTrackedChangeIdContext(state.trackChanges.items).aliasesFor(id).includes(paintedTrackChangeId)) : [];
3238
+ const seedChangeIds = Array.isArray(seed.activeChangeIds) && seed.activeChangeIds.length > 0 ? seed.activeChangeIds : carriedChangeIds;
3114
3239
  asyncReads.set("selection", {
3115
3240
  token,
3116
3241
  value: {
3117
3242
  ...seed,
3118
- activeMarks: seedMarks
3243
+ activeMarks: seedMarks,
3244
+ activeCommentIds: seedCommentIds,
3245
+ activeChangeIds: seedChangeIds
3119
3246
  },
3120
3247
  hasSettled: true,
3121
3248
  inflightToken: deferAuthoritativeRead ? null : prev?.inflightToken ?? null
@@ -3228,22 +3355,29 @@ function createSuperDocUI(options) {
3228
3355
  activeIds
3229
3356
  };
3230
3357
  };
3231
- const computeActiveContentControlIds = (ccApi, selection$1) => {
3358
+ const computeActiveContentControlLockModes = (ccApi, selection$1) => {
3232
3359
  if (!ccApi || typeof ccApi.listInRange !== "function") return {
3233
- ids: [],
3360
+ lockModesById: /* @__PURE__ */ new Map(),
3234
3361
  status: "ready"
3235
3362
  };
3236
3363
  const range = selectionBlockRange(selection$1);
3237
3364
  if (!range) return {
3238
- ids: [],
3365
+ lockModesById: /* @__PURE__ */ new Map(),
3239
3366
  status: "ready"
3240
3367
  };
3241
3368
  const { value, status } = readAsync(`contentControls:inRange:${selectionSignature(selection$1)}`, contentToken(), () => ccApi.listInRange(range), (raw) => raw && Array.isArray(raw.items) ? raw.items : []);
3242
3369
  const rows = value ?? [];
3243
- const ids = [];
3244
- 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);
3245
3379
  return {
3246
- ids,
3380
+ ids: [...lockModesById.keys()],
3247
3381
  status
3248
3382
  };
3249
3383
  };
@@ -3559,14 +3693,20 @@ function createSuperDocUI(options) {
3559
3693
  frozenProjectedValues = projectedInlineValues;
3560
3694
  frozenProjectedValuesKey = `${selectionKey(selection$1)}:${contentToken()}`;
3561
3695
  }
3696
+ const ccApi = doc?.contentControls;
3697
+ const { lockModesById } = computeActiveContentControlLockModes(ccApi, selection$1);
3562
3698
  const states = {};
3563
- 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);
3564
3700
  return states;
3565
3701
  };
3566
3702
  const unavailableRouteReason = (doc) => {
3567
3703
  if (doc) return SUPERDOC_UI_REASONS.operationUnavailable;
3568
3704
  return getEditor() ? SUPERDOC_UI_REASONS.documentApiUnavailable : SUPERDOC_UI_REASONS.notReady;
3569
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
+ };
3570
3710
  const trackDecisionReason = (command, supported, readonly, selection$1, doc) => {
3571
3711
  if (!supported) return command.scope === "all" ? SUPERDOC_UI_REASONS.bulkDecisionsDisabled : unavailableRouteReason(doc);
3572
3712
  if (readonly) return SUPERDOC_UI_REASONS.documentReadonly;
@@ -3589,7 +3729,7 @@ function createSuperDocUI(options) {
3589
3729
  if (support && (support.status === "supported" || support.enabled === true)) return void 0;
3590
3730
  return SUPERDOC_UI_REASONS.bulkDecisionsDisabled;
3591
3731
  };
3592
- const computeCommandState = (id, doc, selection$1, projectedInlineValues) => {
3732
+ const computeCommandState = (id, doc, selection$1, projectedInlineValues, lockModesById = /* @__PURE__ */ new Map()) => {
3593
3733
  if (customCommands.has(id)) return normalizeCommandState({
3594
3734
  supported: true,
3595
3735
  enabled: true,
@@ -3664,6 +3804,13 @@ function createSuperDocUI(options) {
3664
3804
  supported: true,
3665
3805
  reason: SUPERDOC_UI_REASONS.documentReadonly
3666
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");
3667
3814
  const enabled = selectionBlockIds(selection$1).length > 0 || resolveInlineSelectionTarget(selection$1) != null;
3668
3815
  return normalizeCommandState({
3669
3816
  enabled,
@@ -3706,6 +3853,7 @@ function createSuperDocUI(options) {
3706
3853
  const active = commandActiveState(descriptor, doc, selection$1);
3707
3854
  const value = routedCommandValue(descriptor, doc, selection$1, projectedInlineValues);
3708
3855
  if (descriptor.list?.mode === "indent" || descriptor.list?.mode === "outdent") return computeHybridIndentCommandState(descriptor, doc, readonly, selection$1, active, value);
3856
+ if (descriptor.list?.mode === "toggle-seed") return computeListToggleCommandState(descriptor, doc, readonly, selection$1, active, value);
3709
3857
  if (descriptor.mutates && readonly) return normalizeCommandState({
3710
3858
  enabled: false,
3711
3859
  active,
@@ -3713,18 +3861,28 @@ function createSuperDocUI(options) {
3713
3861
  value,
3714
3862
  reason: SUPERDOC_UI_REASONS.documentReadonly
3715
3863
  }, "builtin");
3716
- if (descriptor.inline && !resolveInlineSelectionTarget(selection$1)) return normalizeCommandState(selectionBlockIds(selection$1).length > 0 && typeof getHost()?.setPendingInlineFormat === "function" ? {
3717
- enabled: true,
3718
- active,
3719
- supported: true,
3720
- value
3721
- } : {
3722
- enabled: false,
3723
- active,
3724
- supported: true,
3725
- value,
3726
- reason: SUPERDOC_UI_REASONS.rangeSelectionRequired
3727
- }, "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
+ }
3728
3886
  if ((descriptor.blockParagraph || descriptor.list) && selectionBlockIds(selection$1).length === 0) return normalizeCommandState({
3729
3887
  enabled: false,
3730
3888
  active,
@@ -3738,6 +3896,16 @@ function createSuperDocUI(options) {
3738
3896
  supported: true,
3739
3897
  reason: SUPERDOC_UI_REASONS.rangeSelectionRequired
3740
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
+ }
3741
3909
  return normalizeCommandState({
3742
3910
  enabled: true,
3743
3911
  active,
@@ -3822,47 +3990,81 @@ function createSuperDocUI(options) {
3822
3990
  }
3823
3991
  if (descriptor.id === "link") return readActiveLinkHref(doc, selection$1) ?? void 0;
3824
3992
  };
3825
- const readListStateForBlock = (doc, blockId) => {
3993
+ const readListStateSnapshotForBlock = (doc, blockId, story) => {
3826
3994
  const listsApi = doc?.lists;
3827
- if (!listsApi || typeof listsApi.getState !== "function") return null;
3828
- const { value: result } = readAsync(`lists:${blockId}`, contentToken(), () => listsApi.getState({ target: listsBlockTarget(blockId) }), (raw) => raw && typeof raw === "object" ? raw : null);
3829
- return result?.success === true ? result : null;
3995
+ if (!listsApi) return {
3996
+ value: null,
3997
+ status: "ready"
3998
+ };
3999
+ const isBodyStory = story.storyType === "body";
4000
+ const readState = typeof listsApi.getStateInStory === "function" ? () => listsApi.getStateInStory({
4001
+ target: listsBlockTarget(blockId),
4002
+ story
4003
+ }) : isBodyStory && typeof listsApi.getState === "function" ? () => listsApi.getState({ target: listsBlockTarget(blockId) }) : null;
4004
+ if (!readState) return {
4005
+ value: null,
4006
+ status: "ready"
4007
+ };
4008
+ const { value: result, status } = readAsync(`lists:${storyLocatorSignature(story)}:${blockId}`, contentToken(), readState, (raw) => raw && typeof raw === "object" ? raw : null);
4009
+ return {
4010
+ value: result?.success === true ? result : null,
4011
+ status
4012
+ };
3830
4013
  };
3831
- const readListSeedForBlock = (doc, blockId) => {
3832
- const result = readListStateForBlock(doc, blockId);
4014
+ const readListStateForBlock = (doc, blockId, story) => {
4015
+ return readListStateSnapshotForBlock(doc, blockId, story).value;
4016
+ };
4017
+ const readListSeedForBlock = (doc, blockId, story) => {
4018
+ const result = readListStateForBlock(doc, blockId, story);
3833
4019
  if (!result || result.isListItem !== true) return null;
3834
4020
  return result.seed === "bullet" || result.seed === "ordered" ? result.seed : null;
3835
4021
  };
3836
- const readListMembershipForBlock = (doc, blockId) => {
3837
- const result = readListStateForBlock(doc, blockId);
3838
- return result && typeof result.isListItem === "boolean" ? result.isListItem : null;
4022
+ const readListMembershipSnapshotForBlock = (doc, blockId, story) => {
4023
+ const snapshot = readListStateSnapshotForBlock(doc, blockId, story);
4024
+ const result = snapshot.value;
4025
+ return {
4026
+ value: result && typeof result.isListItem === "boolean" ? result.isListItem : null,
4027
+ status: snapshot.status
4028
+ };
3839
4029
  };
3840
4030
  const readListSeed = (doc, selection$1) => {
3841
4031
  const blockIds = selectionBlockIds(selection$1);
3842
4032
  if (blockIds.length === 0 || !canProbeEverySelectedBlock(blockIds)) return null;
3843
- const firstSeed = readListSeedForBlock(doc, blockIds[0]);
4033
+ const story = selectionStory(selection$1);
4034
+ const firstSeed = readListSeedForBlock(doc, blockIds[0], story);
3844
4035
  if (firstSeed !== "bullet" && firstSeed !== "ordered") return null;
3845
- for (const blockId of blockIds.slice(1)) if (readListSeedForBlock(doc, blockId) !== firstSeed) return null;
4036
+ for (const blockId of blockIds.slice(1)) if (readListSeedForBlock(doc, blockId, story) !== firstSeed) return null;
3846
4037
  return firstSeed;
3847
4038
  };
3848
- const readNodeById = (doc, blockId) => {
3849
- if (!doc || typeof doc.getNodeById !== "function") return {
4039
+ const readNodeById = (doc, blockId, story) => {
4040
+ if (!doc) return {
4041
+ value: null,
4042
+ status: "ready"
4043
+ };
4044
+ const storySignature = storyLocatorSignature(story);
4045
+ const isBodyStory = storySignature === "story:body";
4046
+ if (isBodyStory && typeof doc.getNodeById !== "function") return {
4047
+ value: null,
4048
+ status: "ready"
4049
+ };
4050
+ if (!isBodyStory && typeof doc.getNode !== "function") return {
3850
4051
  value: null,
3851
4052
  status: "ready"
3852
4053
  };
3853
- return readAsync(`node:${blockId}`, contentToken(), () => doc.getNodeById({
4054
+ return readAsync(`node:${storySignature}:${blockId}`, contentToken(), () => isBodyStory ? doc.getNodeById({
3854
4055
  nodeId: blockId,
3855
4056
  nodeType: "paragraph"
3856
- }), (raw) => raw && typeof raw === "object" ? raw : null);
4057
+ }) : doc.getNode(paragraphTarget(blockId, story)), (raw) => raw && typeof raw === "object" ? raw : null);
3857
4058
  };
3858
4059
  const resolveListMembershipForBlockAsync = async (doc, blockId, story) => {
3859
4060
  const listsApi = doc?.lists;
3860
4061
  if (!listsApi) return null;
3861
4062
  try {
4063
+ const isBodyStory = story.storyType === "body";
3862
4064
  const raw = typeof listsApi.getStateInStory === "function" ? await listsApi.getStateInStory({
3863
4065
  target: listsBlockTarget(blockId),
3864
4066
  story
3865
- }) : typeof listsApi.getState === "function" ? await listsApi.getState({ target: listsBlockTarget(blockId) }) : null;
4067
+ }) : isBodyStory && typeof listsApi.getState === "function" ? await listsApi.getState({ target: listsBlockTarget(blockId) }) : null;
3866
4068
  const result = raw && typeof raw === "object" ? raw : null;
3867
4069
  if (!result || result.success !== true || typeof result.isListItem !== "boolean") return null;
3868
4070
  return result.isListItem;
@@ -3870,13 +4072,42 @@ function createSuperDocUI(options) {
3870
4072
  return null;
3871
4073
  }
3872
4074
  };
3873
- const resolveParagraphIndentationForBlockAsync = async (doc, blockId) => {
3874
- if (!doc || typeof doc.getNodeById !== "function") return null;
4075
+ const resolveListSeedForBlock = (doc, blockId, story) => {
4076
+ const unavailable = {
4077
+ resolved: false,
4078
+ seed: null
4079
+ };
4080
+ const listsApi = doc?.lists;
4081
+ if (!listsApi) return unavailable;
4082
+ const isBodyStory = story.storyType === "body";
4083
+ const readState = typeof listsApi.getStateInStory === "function" ? () => listsApi.getStateInStory({
4084
+ target: listsBlockTarget(blockId),
4085
+ story
4086
+ }) : isBodyStory && typeof listsApi.getState === "function" ? () => listsApi.getState({ target: listsBlockTarget(blockId) }) : null;
4087
+ if (!readState) return unavailable;
4088
+ const normalize = (raw) => {
4089
+ const result = raw && typeof raw === "object" ? raw : null;
4090
+ if (!result || result.success !== true || typeof result.isListItem !== "boolean") return unavailable;
4091
+ return {
4092
+ resolved: true,
4093
+ seed: result.isListItem === true && (result.seed === "bullet" || result.seed === "ordered") ? result.seed : null
4094
+ };
4095
+ };
4096
+ try {
4097
+ const raw = readState();
4098
+ return isPromiseLike(raw) ? Promise.resolve(raw).then(normalize, () => unavailable) : normalize(raw);
4099
+ } catch {
4100
+ return unavailable;
4101
+ }
4102
+ };
4103
+ const resolveParagraphIndentationForBlockAsync = async (doc, blockId, story) => {
4104
+ if (!doc) return null;
3875
4105
  try {
3876
- const raw = await doc.getNodeById({
4106
+ const isBodyStory = !story || story.storyType === "body";
4107
+ const raw = story && typeof doc.getNode === "function" ? await doc.getNode(paragraphTarget(blockId, story)) : isBodyStory && typeof doc.getNodeById === "function" ? await doc.getNodeById({
3877
4108
  nodeId: blockId,
3878
4109
  nodeType: "paragraph"
3879
- });
4110
+ }) : null;
3880
4111
  return readParagraphIndentationFromResult(raw && typeof raw === "object" ? raw : null);
3881
4112
  } catch {
3882
4113
  return null;
@@ -3901,9 +4132,50 @@ function createSuperDocUI(options) {
3901
4132
  const listOp = descriptor.docRoute ? resolveDocOperation(doc, descriptor.docRoute) : null;
3902
4133
  const paragraphSetIndent = resolveDocOperation(doc, "format.paragraph.setIndentation");
3903
4134
  const paragraphClearIndent = resolveDocOperation(doc, "format.paragraph.clearIndentation");
3904
- const paragraphOpAvailable = descriptor.list?.mode === "indent" ? paragraphSetIndent != null : paragraphSetIndent != null || paragraphClearIndent != null;
3905
- for (const blockId of blockIds) {
3906
- const isListItem = readListMembershipForBlock(doc, blockId);
4135
+ const mode = descriptor.list?.mode;
4136
+ const paragraphOpAvailable = mode === "indent" ? paragraphSetIndent != null : paragraphSetIndent != null || paragraphClearIndent != null;
4137
+ const story = selectionStory(selection$1);
4138
+ let listMembership = null;
4139
+ if (story.storyType !== "body") {
4140
+ listMembership = [];
4141
+ for (const blockId of blockIds) {
4142
+ const { value: isListItem, status } = readListMembershipSnapshotForBlock(doc, blockId, story);
4143
+ if (isListItem === null) {
4144
+ if (status !== "ready" && (listOp != null || paragraphOpAvailable)) return normalizeCommandState({
4145
+ enabled: true,
4146
+ active,
4147
+ supported: true,
4148
+ value
4149
+ }, "builtin");
4150
+ return normalizeCommandState({
4151
+ enabled: false,
4152
+ active,
4153
+ supported: false,
4154
+ value,
4155
+ reason: unavailableRouteReason(doc)
4156
+ }, "builtin");
4157
+ }
4158
+ listMembership.push(isListItem);
4159
+ }
4160
+ }
4161
+ if (listMembership?.some((isListItem) => isListItem === true)) {
4162
+ const rangeRoute = mode === "indent" ? "lists.indentRange" : "lists.outdentRange";
4163
+ const canUseRange = readDocumentMode() !== "suggesting" && listMembership.every((isListItem) => isListItem === true) && resolveDocOperation(doc, rangeRoute) != null;
4164
+ const canUseStoryOutdent = mode === "outdent" && resolveDocOperation(doc, "lists.outdentInStory") != null;
4165
+ if (!canUseRange && !canUseStoryOutdent) return normalizeCommandState({
4166
+ enabled: false,
4167
+ active,
4168
+ supported: false,
4169
+ value,
4170
+ reason: unavailableRouteReason(doc)
4171
+ }, "builtin");
4172
+ }
4173
+ for (const [index, blockId] of blockIds.entries()) {
4174
+ const membership = listMembership ? {
4175
+ value: listMembership[index] ?? null,
4176
+ status: "ready"
4177
+ } : readListMembershipSnapshotForBlock(doc, blockId, story);
4178
+ const isListItem = membership.value;
3907
4179
  if (isListItem === true) {
3908
4180
  if (listOp != null) return normalizeCommandState({
3909
4181
  enabled: true,
@@ -3913,18 +4185,30 @@ function createSuperDocUI(options) {
3913
4185
  }, "builtin");
3914
4186
  continue;
3915
4187
  }
3916
- if (isListItem === false && paragraphOpAvailable) return normalizeCommandState({
3917
- enabled: true,
3918
- active,
3919
- supported: true,
3920
- value
3921
- }, "builtin");
3922
- if (isListItem === null && (listOp != null || paragraphOpAvailable)) return normalizeCommandState({
3923
- enabled: true,
3924
- active,
3925
- supported: true,
3926
- value
3927
- }, "builtin");
4188
+ if (isListItem === false && paragraphOpAvailable) {
4189
+ if (story.storyType !== "body" && typeof doc?.getNode !== "function") continue;
4190
+ return normalizeCommandState({
4191
+ enabled: true,
4192
+ active,
4193
+ supported: true,
4194
+ value
4195
+ }, "builtin");
4196
+ }
4197
+ if (isListItem === null) {
4198
+ if (membership.status !== "ready" && (listOp != null || paragraphOpAvailable)) return normalizeCommandState({
4199
+ enabled: true,
4200
+ active,
4201
+ supported: true,
4202
+ value
4203
+ }, "builtin");
4204
+ return normalizeCommandState({
4205
+ enabled: false,
4206
+ active,
4207
+ supported: false,
4208
+ value,
4209
+ reason: unavailableRouteReason(doc)
4210
+ }, "builtin");
4211
+ }
3928
4212
  }
3929
4213
  return normalizeCommandState({
3930
4214
  enabled: false,
@@ -3934,6 +4218,86 @@ function createSuperDocUI(options) {
3934
4218
  reason: unavailableRouteReason(doc)
3935
4219
  }, "builtin");
3936
4220
  };
4221
+ const computeListToggleCommandState = (descriptor, doc, readonly, selection$1, active, value) => {
4222
+ if (descriptor.mutates && readonly) return normalizeCommandState({
4223
+ enabled: false,
4224
+ active,
4225
+ supported: true,
4226
+ value,
4227
+ reason: SUPERDOC_UI_REASONS.documentReadonly
4228
+ }, "builtin");
4229
+ const blockIds = selectionBlockIds(selection$1);
4230
+ if (blockIds.length === 0) return normalizeCommandState({
4231
+ enabled: false,
4232
+ active,
4233
+ supported: true,
4234
+ value,
4235
+ reason: SUPERDOC_UI_REASONS.selectionRequired
4236
+ }, "builtin");
4237
+ const story = selectionStory(selection$1);
4238
+ const listsApi = doc?.lists;
4239
+ if (!(typeof listsApi?.getStateInStory === "function" || story.storyType === "body" && typeof listsApi?.getState === "function")) return normalizeCommandState({
4240
+ enabled: false,
4241
+ active,
4242
+ supported: false,
4243
+ value,
4244
+ reason: SUPERDOC_UI_REASONS.operationUnavailable
4245
+ }, "builtin");
4246
+ if (!canProbeEverySelectedBlock(blockIds)) return normalizeCommandState(story.storyType === "body" && resolveDocOperation(doc, "lists.remove") != null ? {
4247
+ enabled: true,
4248
+ active,
4249
+ supported: true,
4250
+ value
4251
+ } : {
4252
+ enabled: false,
4253
+ active,
4254
+ supported: false,
4255
+ value,
4256
+ reason: SUPERDOC_UI_REASONS.operationUnavailable
4257
+ }, "builtin");
4258
+ const snapshots = blockIds.map((blockId) => readListStateSnapshotForBlock(doc, blockId, story));
4259
+ if (snapshots.some(({ status }) => status !== "ready")) return normalizeCommandState({
4260
+ enabled: true,
4261
+ active,
4262
+ supported: true,
4263
+ value
4264
+ }, "builtin");
4265
+ if (snapshots.some(({ value: state$1 }) => state$1 === null)) return normalizeCommandState({
4266
+ enabled: false,
4267
+ active,
4268
+ supported: false,
4269
+ value,
4270
+ reason: SUPERDOC_UI_REASONS.operationUnavailable
4271
+ }, "builtin");
4272
+ const seed = descriptor.list?.seed;
4273
+ const shouldRemove = snapshots.every(({ value: state$1 }) => state$1?.isListItem === true && state$1.seed === seed);
4274
+ if (story.storyType === "body") return normalizeCommandState(!shouldRemove || resolveDocOperation(doc, "lists.remove") != null ? {
4275
+ enabled: true,
4276
+ active,
4277
+ supported: true,
4278
+ value
4279
+ } : {
4280
+ enabled: false,
4281
+ active,
4282
+ supported: false,
4283
+ value,
4284
+ reason: SUPERDOC_UI_REASONS.operationUnavailable
4285
+ }, "builtin");
4286
+ const removeInStory = resolveDocOperation(doc, "lists.removeInStory");
4287
+ if (shouldRemove && removeInStory) return normalizeCommandState({
4288
+ enabled: true,
4289
+ active,
4290
+ supported: true,
4291
+ value
4292
+ }, "builtin");
4293
+ return normalizeCommandState({
4294
+ enabled: false,
4295
+ active,
4296
+ supported: false,
4297
+ value,
4298
+ reason: SUPERDOC_UI_REASONS.operationUnavailable
4299
+ }, "builtin");
4300
+ };
3937
4301
  const readActiveLinkHref = (doc, selection$1) => {
3938
4302
  const href = (resolveCurrentHyperlink(doc, selection$1)?.properties)?.href;
3939
4303
  return typeof href === "string" ? href : null;
@@ -4048,8 +4412,8 @@ function createSuperDocUI(options) {
4048
4412
  status: combineStatus(full.status, quickResult.status)
4049
4413
  };
4050
4414
  };
4051
- const readBlockStyleRef = (doc, blockId) => {
4052
- const { value: result, status } = readNodeById(doc, blockId);
4415
+ const readBlockStyleRef = (doc, blockId, story) => {
4416
+ const { value: result, status } = readNodeById(doc, blockId, story);
4053
4417
  if (!result) return {
4054
4418
  ok: false,
4055
4419
  styleRef: null,
@@ -4128,8 +4492,9 @@ function createSuperDocUI(options) {
4128
4492
  let blockReadFailures = 0;
4129
4493
  let defaultReadFailures = 0;
4130
4494
  let readStatus = "ready";
4495
+ const story = selectionStory(selection$1);
4131
4496
  for (const blockId of blockIds) {
4132
- const read = readBlockStyleRef(doc, blockId);
4497
+ const read = readBlockStyleRef(doc, blockId, story);
4133
4498
  readStatus = combineStatus(readStatus, read.status);
4134
4499
  if (!read.ok) {
4135
4500
  blockReadFailures += 1;
@@ -4273,9 +4638,9 @@ function createSuperDocUI(options) {
4273
4638
  currentHostSelectionSource = next;
4274
4639
  if (!next) return;
4275
4640
  try {
4276
- const unsubscribe = next.subscribe(() => {
4641
+ const unsubscribe = next.subscribe((snapshot) => {
4277
4642
  selectionEpoch += 1;
4278
- seedCaretSelectionFromHost();
4643
+ seedCaretSelectionFromHost(snapshot);
4279
4644
  recompute();
4280
4645
  });
4281
4646
  if (typeof unsubscribe === "function") detachHostSelection = unsubscribe;
@@ -4428,6 +4793,39 @@ function createSuperDocUI(options) {
4428
4793
  function readTrackDecisionTargetId(target) {
4429
4794
  return target && typeof target === "object" && typeof target.id === "string" ? String(target.id) : null;
4430
4795
  }
4796
+ function trackDecisionRangeFromSelection(selection$1) {
4797
+ if (selection$1.empty) return null;
4798
+ const explicit = selection$1.selectionTarget;
4799
+ const start = explicit?.start;
4800
+ const end = explicit?.end;
4801
+ if (explicit?.kind !== "selection" || start?.kind !== "text" || end?.kind !== "text" || typeof start.blockId !== "string" || start.blockId !== end.blockId || !Number.isInteger(start.offset) || !Number.isInteger(end.offset) || start.offset < 0 || end.offset < 0 || start.offset === end.offset) return null;
4802
+ const rangeStart = Math.min(start.offset, end.offset);
4803
+ const rangeEnd = Math.max(start.offset, end.offset);
4804
+ const story = selectionStoryLocator(selection$1);
4805
+ return {
4806
+ kind: "text",
4807
+ ...story ? { story } : {},
4808
+ segments: [{
4809
+ blockId: start.blockId,
4810
+ range: {
4811
+ start: rangeStart,
4812
+ end: rangeEnd
4813
+ }
4814
+ }]
4815
+ };
4816
+ }
4817
+ function activeChangePartialDecisionRoute() {
4818
+ const activeId = state.selection.activeChangeIds[0];
4819
+ if (!activeId) return "unavailable";
4820
+ const items = readAllStoryTrackChanges();
4821
+ if (!items) return "unavailable";
4822
+ const story = selectionStoryLocator(state.selection);
4823
+ const item = items.find((candidate) => entityRowMatchesRequest(candidate, activeId, story));
4824
+ if (!item) return "unavailable";
4825
+ const type = trackChangesItemPayload(item).type;
4826
+ const grouping = trackChangesItemPayload(item).grouping;
4827
+ return (type === "insertion" || type === "deletion" || type === "insert" || type === "delete") && grouping === "standalone" ? "range" : "id";
4828
+ }
4431
4829
  function resolveTrackDecisionTarget(command, payload) {
4432
4830
  if (command.scope === "all") return {
4433
4831
  target: { scope: "all" },
@@ -4465,6 +4863,23 @@ function createSuperDocUI(options) {
4465
4863
  } : null;
4466
4864
  }
4467
4865
  function executeTrackDecisionCommand(command, payload) {
4866
+ if (command.scope === "id" && payload == null) {
4867
+ if (state.selection.activeChangeIds.length > 1) return executeTrackDecisionTarget(command.kind, {
4868
+ kind: "ids",
4869
+ ids: state.selection.activeChangeIds
4870
+ }, null);
4871
+ if (state.selection.activeChangeIds.length === 1 && !state.selection.empty && state.selection.selectionTarget) {
4872
+ const route = activeChangePartialDecisionRoute();
4873
+ if (route === "unavailable") return false;
4874
+ if (route === "range") {
4875
+ const range = trackDecisionRangeFromSelection(state.selection);
4876
+ return range ? executeTrackDecisionTarget(command.kind, {
4877
+ kind: "range",
4878
+ range
4879
+ }, null) : false;
4880
+ }
4881
+ }
4882
+ }
4468
4883
  const resolved = resolveTrackDecisionTarget(command, payload);
4469
4884
  if (!resolved) return false;
4470
4885
  return executeTrackDecisionTarget(command.kind, resolved.target, resolved.changeId);
@@ -4653,8 +5068,8 @@ function createSuperDocUI(options) {
4653
5068
  offsetSpace: "selection"
4654
5069
  });
4655
5070
  };
4656
- const buildBlockParagraphInput = (spec, blockId, payload) => {
4657
- const target = paragraphTarget(blockId);
5071
+ const buildBlockParagraphInput = (spec, blockId, payload, story) => {
5072
+ const target = paragraphTarget(blockId, story);
4658
5073
  switch (spec.kind) {
4659
5074
  case "alignment": {
4660
5075
  const alignment = payload;
@@ -4724,6 +5139,9 @@ function createSuperDocUI(options) {
4724
5139
  return true;
4725
5140
  };
4726
5141
  const executeClearFormattingCommand = () => {
5142
+ const doc = getDoc();
5143
+ const { lockModesById } = computeActiveContentControlLockModes(doc?.contentControls, state.selection);
5144
+ if (contentControlLockReason(lockModesById)) return false;
4727
5145
  const immediateResults = [];
4728
5146
  const settledResults = [];
4729
5147
  const record = (result) => {
@@ -4739,7 +5157,6 @@ function createSuperDocUI(options) {
4739
5157
  settledResults.push(Promise.resolve(false));
4740
5158
  }
4741
5159
  };
4742
- const doc = getDoc();
4743
5160
  const defaultStyleId = getStyleCatalog().cache?.full.defaults.paragraphStyleId ?? DEFAULT_PARAGRAPH_STYLE_ID;
4744
5161
  const rangeTarget = resolveInlineSelectionTarget(state.selection);
4745
5162
  if (rangeTarget) {
@@ -4755,13 +5172,19 @@ function createSuperDocUI(options) {
4755
5172
  }
4756
5173
  }
4757
5174
  const resetOp = resolveDocOperation(doc, "format.paragraph.resetDirectFormatting");
4758
- const listsRemoveOp = resolveDocOperation(doc, "lists.remove");
4759
5175
  const setStyleRefOp = resolveDocOperation(doc, "styles.paragraph.setStyleRef");
5176
+ const story = selectionStoryLocator(state.selection);
5177
+ const isBodyStory = !story || story.storyType === "body";
5178
+ const listsRemoveOp = resolveDocOperation(doc, isBodyStory ? "lists.remove" : "lists.removeInStory");
4760
5179
  for (const blockId of selectionBlockIds(state.selection)) {
4761
- const target = paragraphTarget(blockId);
5180
+ const target = paragraphTarget(blockId, story);
4762
5181
  if (resetOp) run(resetOp, { target });
4763
5182
  if (listsRemoveOp) try {
4764
- const r = settleOperationResult(listsRemoveOp({ target }));
5183
+ const listTarget = listsBlockTarget(blockId);
5184
+ const r = settleOperationResult(listsRemoveOp(isBodyStory ? { target: listTarget } : {
5185
+ target: listTarget,
5186
+ story
5187
+ }));
4765
5188
  settledResults.push(r.settled.then((s) => commandResultSucceeded(s) ? s : true));
4766
5189
  } catch {}
4767
5190
  if (setStyleRefOp) run(setStyleRefOp, {
@@ -4772,7 +5195,7 @@ function createSuperDocUI(options) {
4772
5195
  if (!rangeTarget && readDocumentMode() !== "suggesting") {
4773
5196
  const setMarkRunPropsOp = resolveDocOperation(doc, "format.paragraph.setMarkRunProps");
4774
5197
  if (setMarkRunPropsOp) for (const blockId of selectionBlockIds(state.selection)) run(setMarkRunPropsOp, {
4775
- target: paragraphTarget(blockId),
5198
+ target: paragraphTarget(blockId, story),
4776
5199
  markRunProps: {
4777
5200
  bold: false,
4778
5201
  italic: false,
@@ -4790,7 +5213,8 @@ function createSuperDocUI(options) {
4790
5213
  const executeBlockParagraph = (descriptor, op, payload) => {
4791
5214
  const blockIds = selectionBlockIds(state.selection);
4792
5215
  if (blockIds.length === 0) return false;
4793
- return applyPerBlock(descriptor.docRoute, op, blockIds, (blockId) => buildBlockParagraphInput(descriptor.blockParagraph, blockId, payload));
5216
+ const story = selectionStoryLocator(state.selection);
5217
+ return applyPerBlock(descriptor.docRoute, op, blockIds, (blockId) => buildBlockParagraphInput(descriptor.blockParagraph, blockId, payload, story));
4794
5218
  };
4795
5219
  const executeHybridIndentCommand = (descriptor, listOp, blockIds) => {
4796
5220
  const doc = getDoc();
@@ -4798,6 +5222,7 @@ function createSuperDocUI(options) {
4798
5222
  const paragraphClearIndent = resolveDocOperation(doc, "format.paragraph.clearIndentation");
4799
5223
  const mode = descriptor.list?.mode;
4800
5224
  const story = selectionStory(state.selection);
5225
+ const targetStory = selectionStoryLocator(state.selection);
4801
5226
  const planBlock = async (blockId) => {
4802
5227
  const isListItem = await resolveListMembershipForBlockAsync(doc, blockId, story);
4803
5228
  if (isListItem === true) return {
@@ -4807,7 +5232,8 @@ function createSuperDocUI(options) {
4807
5232
  input: { target: listItemTarget(blockId) }
4808
5233
  };
4809
5234
  if (isListItem === null) return null;
4810
- const current = await resolveParagraphIndentationForBlockAsync(doc, blockId);
5235
+ if (targetStory && targetStory.storyType !== "body" && typeof doc?.getNode !== "function") return null;
5236
+ const current = await resolveParagraphIndentationForBlockAsync(doc, blockId, targetStory);
4811
5237
  const preserved = {};
4812
5238
  if (current?.right != null) preserved.right = current.right;
4813
5239
  if (current?.firstLine != null) preserved.firstLine = current.firstLine;
@@ -4819,7 +5245,7 @@ function createSuperDocUI(options) {
4819
5245
  routeToCall: "format.paragraph.setIndentation",
4820
5246
  opToCall: paragraphSetIndent,
4821
5247
  input: {
4822
- target: paragraphTarget(blockId),
5248
+ target: paragraphTarget(blockId, targetStory),
4823
5249
  left: Math.max(0, current?.left ?? 0) + PARAGRAPH_INDENT_STEP_TWIPS,
4824
5250
  ...preserved
4825
5251
  }
@@ -4832,14 +5258,14 @@ function createSuperDocUI(options) {
4832
5258
  kind: "paragraph",
4833
5259
  routeToCall: "format.paragraph.clearIndentation",
4834
5260
  opToCall: paragraphClearIndent,
4835
- input: { target: paragraphTarget(blockId) }
5261
+ input: { target: paragraphTarget(blockId, targetStory) }
4836
5262
  };
4837
5263
  if (paragraphSetIndent) return {
4838
5264
  kind: "paragraph",
4839
5265
  routeToCall: "format.paragraph.setIndentation",
4840
5266
  opToCall: paragraphSetIndent,
4841
5267
  input: {
4842
- target: paragraphTarget(blockId),
5268
+ target: paragraphTarget(blockId, targetStory),
4843
5269
  left: 0
4844
5270
  }
4845
5271
  };
@@ -4850,7 +5276,7 @@ function createSuperDocUI(options) {
4850
5276
  routeToCall: "format.paragraph.setIndentation",
4851
5277
  opToCall: paragraphSetIndent,
4852
5278
  input: {
4853
- target: paragraphTarget(blockId),
5279
+ target: paragraphTarget(blockId, targetStory),
4854
5280
  ...preserved
4855
5281
  }
4856
5282
  };
@@ -4870,9 +5296,16 @@ function createSuperDocUI(options) {
4870
5296
  return false;
4871
5297
  }
4872
5298
  }
5299
+ const nonBodyListPlans = story.storyType !== "body" && plans.some((plan) => plan?.kind === "list");
5300
+ const outdentInStory = nonBodyListPlans && mode === "outdent" ? resolveDocOperation(doc, "lists.outdentInStory") : null;
5301
+ if (nonBodyListPlans && !outdentInStory) return false;
4873
5302
  const settledResults = plans.map((plan) => {
4874
5303
  if (!plan) return Promise.resolve(false);
4875
5304
  try {
5305
+ if (plan.kind === "list" && outdentInStory) return settleOperationResult(callEditorMutation("lists.outdentInStory", outdentInStory, {
5306
+ target: plan.input.target,
5307
+ story
5308
+ })).settled;
4876
5309
  return settleOperationResult(callEditorMutation(plan.routeToCall, plan.opToCall, plan.input)).settled;
4877
5310
  } catch {
4878
5311
  return Promise.resolve(false);
@@ -4887,17 +5320,62 @@ function createSuperDocUI(options) {
4887
5320
  const doc = getDoc();
4888
5321
  const blockIds = selectionBlockIds(state.selection);
4889
5322
  if (blockIds.length === 0) return false;
5323
+ const story = selectionStory(state.selection);
5324
+ const isBodyStory = story.storyType === "body";
4890
5325
  if (spec.mode === "indent" || spec.mode === "outdent") return executeHybridIndentCommand(descriptor, op, blockIds);
4891
5326
  const seed = spec.seed;
4892
- if (readListSeed(doc, state.selection) === seed) {
4893
- const remove = resolveDocOperation(doc, "lists.remove");
4894
- if (!remove) return false;
4895
- return applyPerBlock("lists.remove", remove, blockIds, (blockId) => ({ target: listsBlockTarget(blockId) }));
5327
+ const executeResolved = (states) => {
5328
+ if (states.some((listState) => !listState.resolved)) return {
5329
+ immediate: false,
5330
+ settled: null
5331
+ };
5332
+ const shouldRemove = states.every((listState) => listState.seed === seed);
5333
+ if (!shouldRemove && !isBodyStory) return {
5334
+ immediate: false,
5335
+ settled: null
5336
+ };
5337
+ const route = shouldRemove ? isBodyStory ? "lists.remove" : "lists.removeInStory" : descriptor.docRoute;
5338
+ const operation = shouldRemove ? resolveDocOperation(doc, route) : op;
5339
+ if (!operation) return {
5340
+ immediate: false,
5341
+ settled: null
5342
+ };
5343
+ const immediateResults = [];
5344
+ const settledResults = [];
5345
+ blockIds.forEach((blockId, index) => {
5346
+ if (!shouldRemove && states[index]?.seed === seed) return;
5347
+ const input = shouldRemove ? {
5348
+ target: listsBlockTarget(blockId),
5349
+ ...!isBodyStory ? { story } : {}
5350
+ } : {
5351
+ target: listsBlockTarget(blockId),
5352
+ seed
5353
+ };
5354
+ try {
5355
+ const result$1 = settleOperationResult(callEditorMutation(route, operation, input));
5356
+ immediateResults.push(result$1.immediate);
5357
+ settledResults.push(result$1.settled);
5358
+ } catch {
5359
+ immediateResults.push(false);
5360
+ settledResults.push(Promise.resolve(false));
5361
+ }
5362
+ });
5363
+ return {
5364
+ immediate: combineCommandResults(immediateResults),
5365
+ settled: settledResults.length ? Promise.all(settledResults).then((results) => combineCommandResults(results)) : null
5366
+ };
5367
+ };
5368
+ const stateReads = blockIds.map((blockId) => resolveListSeedForBlock(doc, blockId, story));
5369
+ if (stateReads.some(isPromiseLike)) {
5370
+ pendingCommandSettlement = Promise.all(stateReads).then(async (states) => {
5371
+ const result$1 = executeResolved(states);
5372
+ return result$1.settled ? await result$1.settled : result$1.immediate;
5373
+ });
5374
+ return true;
4896
5375
  }
4897
- return applyPerBlock(descriptor.docRoute, op, blockIds, (blockId) => readListSeedForBlock(doc, blockId) === seed ? null : {
4898
- target: listsBlockTarget(blockId),
4899
- seed
4900
- });
5376
+ const result = executeResolved(stateReads);
5377
+ pendingCommandSettlement = result.settled;
5378
+ return result.immediate;
4901
5379
  };
4902
5380
  function readLinkPayloadRecord(payload) {
4903
5381
  return payload && typeof payload === "object" ? payload : {};
@@ -5084,7 +5562,7 @@ function createSuperDocUI(options) {
5084
5562
  const rows = Number(record.rows ?? record.rowCount);
5085
5563
  const columns = Number(record.columns ?? record.cols ?? record.colCount);
5086
5564
  if (!Number.isInteger(rows) || rows < 1 || !Number.isInteger(columns) || columns < 1) return false;
5087
- const caret = readDocumentMode() !== "suggesting" ? collapsedTextAddressFromSelection(state.selection) : null;
5565
+ const caret = collapsedTextAddressFromSelection(state.selection);
5088
5566
  const at = caret && typeof caret.blockId === "string" ? {
5089
5567
  kind: "inParagraph",
5090
5568
  target: paragraphTarget(caret.blockId),
@@ -5104,14 +5582,29 @@ function createSuperDocUI(options) {
5104
5582
  const src = record.src ?? record.value;
5105
5583
  if (typeof src !== "string" || src.trim() === "") return false;
5106
5584
  const caret = collapsedTextAddressFromSelection(state.selection);
5585
+ const story = selectionStory(state.selection);
5586
+ const nonBodyStory = story.storyType === "body" ? null : story;
5587
+ let at;
5588
+ if (caret && typeof caret.blockId === "string") at = {
5589
+ kind: "inParagraph",
5590
+ target: paragraphTarget(caret.blockId, nonBodyStory),
5591
+ offset: caret.range?.start ?? 0
5592
+ };
5593
+ else {
5594
+ at = createLocationAt(state.selection, "target");
5595
+ if (nonBodyStory && at.kind === "after") at = {
5596
+ ...at,
5597
+ target: {
5598
+ ...at.target,
5599
+ story: nonBodyStory
5600
+ }
5601
+ };
5602
+ }
5107
5603
  const input$1 = {
5108
5604
  src,
5109
- at: caret && typeof caret.blockId === "string" ? {
5110
- kind: "inParagraph",
5111
- target: paragraphTarget(caret.blockId),
5112
- offset: caret.range?.start ?? 0
5113
- } : createLocationAt(state.selection, "target")
5605
+ at
5114
5606
  };
5607
+ if (nonBodyStory) input$1.in = at.kind === "inParagraph" ? resolveActiveHeaderFooterSlot(nonBodyStory) ?? nonBodyStory : nonBodyStory;
5115
5608
  if (typeof record.alt === "string") input$1.alt = record.alt;
5116
5609
  if (typeof record.title === "string") input$1.title = record.title;
5117
5610
  if (record.size && typeof record.size === "object") input$1.size = record.size;
@@ -5218,9 +5711,12 @@ function createSuperDocUI(options) {
5218
5711
  const route = descriptor.docRoute;
5219
5712
  if (!route) return false;
5220
5713
  if (descriptor.mutates && readDocumentMode() === "viewing") return false;
5221
- const op = resolveDocOperation(getDoc(), route);
5714
+ const doc = getDoc();
5715
+ const op = resolveDocOperation(doc, route);
5222
5716
  if (!op) return false;
5223
5717
  if (descriptor.inline) {
5718
+ const { lockModesById } = computeActiveContentControlLockModes(doc?.contentControls, state.selection);
5719
+ if (contentControlLockReason(lockModesById)) return false;
5224
5720
  const target = resolveInlineSelectionTarget(state.selection);
5225
5721
  if (!target) {
5226
5722
  if (state.selection.empty === false) return settleCommandExecution(false);
@@ -5478,6 +5974,12 @@ function createSuperDocUI(options) {
5478
5974
  }
5479
5975
  const doc = getDoc();
5480
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
+ }
5481
5983
  const selTarget = selection$1["target"];
5482
5984
  const selectionTarget = selection$1["selectionTarget"];
5483
5985
  if (!(selection$1["empty"] === true) && Object.keys(snap.inline).length > 0) {
@@ -6049,6 +6551,7 @@ function createSuperDocUI(options) {
6049
6551
  setActive: (input) => {
6050
6552
  if (input == null) {
6051
6553
  setExplicitActiveChange(null);
6554
+ mirrorTrackedChangeFocusToHost(null);
6052
6555
  recompute();
6053
6556
  return true;
6054
6557
  }
@@ -6058,6 +6561,7 @@ function createSuperDocUI(options) {
6058
6561
  story: input.story
6059
6562
  };
6060
6563
  if (typeof requested.id !== "string" || requested.id.length === 0) return false;
6564
+ const paintedEntityId = requested.id;
6061
6565
  if (requested.story) {
6062
6566
  const allStoryItems = readAllStoryTrackChanges();
6063
6567
  if (!allStoryItems) return false;
@@ -6070,7 +6574,9 @@ function createSuperDocUI(options) {
6070
6574
  if (!bodyItems.some((item) => readEntityId(item) === publicId)) return false;
6071
6575
  requested.id = publicId;
6072
6576
  }
6577
+ if (paintedEntityId !== requested.id) requested.paintedEntityId = paintedEntityId;
6073
6578
  setExplicitActiveChange(requested);
6579
+ mirrorTrackedChangeFocusToHost(requested);
6074
6580
  recompute();
6075
6581
  return true;
6076
6582
  },
@@ -6089,10 +6595,11 @@ function createSuperDocUI(options) {
6089
6595
  reason: SUPERDOC_UI_REASONS.targetUnresolved
6090
6596
  };
6091
6597
  const previousActiveChange = explicitActiveChange;
6092
- setExplicitActiveChange(story ? {
6598
+ const requestedActiveChange = story ? {
6093
6599
  id: changeId,
6094
6600
  story
6095
- } : { id: changeId });
6601
+ } : { id: changeId };
6602
+ setExplicitActiveChange(requestedActiveChange);
6096
6603
  recompute();
6097
6604
  const optimisticActiveChange = explicitActiveChange;
6098
6605
  const optimisticRevision = explicitActiveChangeRevision;
@@ -6108,6 +6615,7 @@ function createSuperDocUI(options) {
6108
6615
  ok: false
6109
6616
  };
6110
6617
  if (!result.ok && result.reason !== SUPERDOC_UI_REASONS.targetNotVisible) rollback();
6618
+ else mirrorTrackedChangeFocusToHost(requestedActiveChange);
6111
6619
  return result;
6112
6620
  }
6113
6621
  };
@@ -6123,24 +6631,30 @@ function createSuperDocUI(options) {
6123
6631
  }, id, story);
6124
6632
  };
6125
6633
  const executeTrackDecisionTarget = (kind, target, changeId, story) => {
6634
+ try {
6635
+ return settleCommandExecution(callTrackDecisionTarget(kind, target, changeId, story));
6636
+ } catch {
6637
+ return false;
6638
+ }
6639
+ };
6640
+ const callTrackDecisionTarget = (kind, target, changeId, story) => {
6126
6641
  if (reviewMutationsAreReadOnly()) return false;
6127
6642
  const tcApi = getDoc()?.trackChanges;
6643
+ const isAllTarget = target.scope === "all";
6644
+ const isRangeTarget = target.kind === "range";
6645
+ const isMultiIdTarget = target.kind === "ids";
6128
6646
  if (bulkTrackDecisionBlockedReason({
6129
6647
  kind,
6130
- scope: changeId ? "id" : "all"
6648
+ scope: isAllTarget ? "all" : "id"
6131
6649
  }, tcApi)) return false;
6132
- const legacyName = changeId ? kind : `${kind}All`;
6133
- const op = !story && tcApi && typeof tcApi[legacyName] === "function" ? tcApi[legacyName] : null;
6650
+ const legacyName = isAllTarget ? `${kind}All` : kind;
6651
+ const op = !isRangeTarget && !isMultiIdTarget && !story && tcApi && typeof tcApi[legacyName] === "function" ? tcApi[legacyName] : null;
6134
6652
  const decide = tcApi && typeof tcApi.decide === "function" ? tcApi.decide : null;
6135
6653
  if (!op && !decide) return false;
6136
- try {
6137
- return settleCommandExecution(op ? op.call(tcApi, changeId ?? {}) : decide.call(tcApi, {
6138
- decision: kind,
6139
- target
6140
- }));
6141
- } catch {
6142
- return false;
6143
- }
6654
+ return op ? op.call(tcApi, changeId ?? {}) : decide.call(tcApi, {
6655
+ decision: kind,
6656
+ target
6657
+ });
6144
6658
  };
6145
6659
  const contentControlsSub = sliceHandle((s) => s.contentControls);
6146
6660
  const findContentControl = (id) => {
@@ -7069,6 +7583,11 @@ function createSuperDocUI(options) {
7069
7583
  clearTimeout(foregroundAsyncRetryTimer);
7070
7584
  foregroundAsyncRetryTimer = null;
7071
7585
  }
7586
+ if (asyncReadFailureRetryTimer) {
7587
+ clearTimeout(asyncReadFailureRetryTimer);
7588
+ asyncReadFailureRetryTimer = null;
7589
+ asyncReadFailureRetryAtMs = 0;
7590
+ }
7072
7591
  if (pendingEffectiveInlineRead?.timer) {
7073
7592
  clearTimeout(pendingEffectiveInlineRead.timer);
7074
7593
  pendingEffectiveInlineRead.timer = null;