@beyondwork/docx-react-component 1.0.133 → 1.0.135

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 (83) hide show
  1. package/dist/api/public-types.cjs +23 -3
  2. package/dist/api/public-types.d.cts +1 -1
  3. package/dist/api/public-types.d.ts +1 -1
  4. package/dist/api/public-types.js +2 -2
  5. package/dist/api/v3.cjs +708 -47
  6. package/dist/api/v3.d.cts +2 -2
  7. package/dist/api/v3.d.ts +2 -2
  8. package/dist/api/v3.js +4 -4
  9. package/dist/{chunk-REFHJ2FN.js → chunk-2BNXARVO.js} +3 -3
  10. package/dist/{chunk-INLRCC4N.js → chunk-4CIHTMCH.js} +2 -2
  11. package/dist/{chunk-224TSMEB.js → chunk-5CCYF333.js} +138 -42
  12. package/dist/{chunk-MQ5GAJ54.js → chunk-BJXSMPHD.js} +1 -1
  13. package/dist/{chunk-OTRVGNZQ.js → chunk-EPFVMUKF.js} +548 -3
  14. package/dist/{chunk-XBQFDBXE.js → chunk-EZFF6GKF.js} +9 -2
  15. package/dist/{chunk-S3PEKX6H.js → chunk-FGJTOFZY.js} +72 -554
  16. package/dist/{chunk-3JEE5RJU.js → chunk-GIFXKIM5.js} +612 -34
  17. package/dist/{chunk-57HTKX3P.js → chunk-H4HI6RUE.js} +1 -1
  18. package/dist/{chunk-KL4TZSZV.js → chunk-HWMPNLEF.js} +1 -1
  19. package/dist/{chunk-ZFCZ7XXH.js → chunk-NEMOQ4QR.js} +1 -1
  20. package/dist/{chunk-CVSD3UNK.js → chunk-P7XDEVS6.js} +15 -2
  21. package/dist/{chunk-QTRJLKR2.js → chunk-TSNK4ECL.js} +1 -1
  22. package/dist/{chunk-WDDFU2N2.js → chunk-UR2LW63N.js} +1 -1
  23. package/dist/core/commands/formatting-commands.d.cts +1 -1
  24. package/dist/core/commands/formatting-commands.d.ts +1 -1
  25. package/dist/core/commands/image-commands.cjs +9 -2
  26. package/dist/core/commands/image-commands.d.cts +1 -1
  27. package/dist/core/commands/image-commands.d.ts +1 -1
  28. package/dist/core/commands/image-commands.js +4 -4
  29. package/dist/core/commands/section-layout-commands.d.cts +1 -1
  30. package/dist/core/commands/section-layout-commands.d.ts +1 -1
  31. package/dist/core/commands/style-commands.d.cts +1 -1
  32. package/dist/core/commands/style-commands.d.ts +1 -1
  33. package/dist/core/commands/table-structure-commands.cjs +9 -2
  34. package/dist/core/commands/table-structure-commands.d.cts +1 -1
  35. package/dist/core/commands/table-structure-commands.d.ts +1 -1
  36. package/dist/core/commands/table-structure-commands.js +3 -3
  37. package/dist/core/commands/text-commands.cjs +9 -2
  38. package/dist/core/commands/text-commands.d.cts +1 -1
  39. package/dist/core/commands/text-commands.d.ts +1 -1
  40. package/dist/core/commands/text-commands.js +4 -4
  41. package/dist/core/selection/mapping.d.cts +1 -1
  42. package/dist/core/selection/mapping.d.ts +1 -1
  43. package/dist/core/state/editor-state.d.cts +1 -1
  44. package/dist/core/state/editor-state.d.ts +1 -1
  45. package/dist/index.cjs +1478 -636
  46. package/dist/index.d.cts +4 -4
  47. package/dist/index.d.ts +4 -4
  48. package/dist/index.js +113 -26
  49. package/dist/io/docx-session.d.cts +3 -3
  50. package/dist/io/docx-session.d.ts +3 -3
  51. package/dist/{loader-B2H99237.d.cts → loader-BQ7AB-0v.d.cts} +2 -2
  52. package/dist/{loader-DfTjqVwn.d.ts → loader-Cy6OYBfn.d.ts} +2 -2
  53. package/dist/{public-types-S8gTYwKo.d.cts → public-types-D31xKNGc.d.cts} +146 -3
  54. package/dist/{public-types-B5lOUIrP.d.ts → public-types-DqYt8GdP.d.ts} +146 -3
  55. package/dist/public-types.cjs +23 -3
  56. package/dist/public-types.d.cts +1 -1
  57. package/dist/public-types.d.ts +1 -1
  58. package/dist/public-types.js +2 -2
  59. package/dist/runtime/collab.d.cts +2 -2
  60. package/dist/runtime/collab.d.ts +2 -2
  61. package/dist/runtime/document-runtime.cjs +760 -68
  62. package/dist/runtime/document-runtime.d.cts +1 -1
  63. package/dist/runtime/document-runtime.d.ts +1 -1
  64. package/dist/runtime/document-runtime.js +10 -10
  65. package/dist/{session-CR2A1hGZ.d.cts → session-DA-F2fCw.d.cts} +2 -2
  66. package/dist/{session-CBDIOYXA.d.ts → session-DqL8H0oZ.d.ts} +2 -2
  67. package/dist/session.d.cts +4 -4
  68. package/dist/session.d.ts +4 -4
  69. package/dist/tailwind.cjs +81 -554
  70. package/dist/tailwind.d.cts +1 -1
  71. package/dist/tailwind.d.ts +1 -1
  72. package/dist/tailwind.js +5 -5
  73. package/dist/{types-yty2K-hk.d.cts → types-B2y94n5t.d.cts} +1 -1
  74. package/dist/{types-B-90ywjU.d.ts → types-SllbCtGs.d.ts} +1 -1
  75. package/dist/ui-tailwind/editor-surface/search-plugin.cjs +11 -0
  76. package/dist/ui-tailwind/editor-surface/search-plugin.d.cts +2 -2
  77. package/dist/ui-tailwind/editor-surface/search-plugin.d.ts +2 -2
  78. package/dist/ui-tailwind/editor-surface/search-plugin.js +3 -3
  79. package/dist/ui-tailwind.cjs +81 -554
  80. package/dist/ui-tailwind.d.cts +2 -2
  81. package/dist/ui-tailwind.d.ts +2 -2
  82. package/dist/ui-tailwind.js +5 -5
  83. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -12983,10 +12983,16 @@ var FormattingContextImpl = class {
12983
12983
  // surface-projection calls `resolveParagraphCascade` on the same
12984
12984
  // paragraph via `resolveParagraph` + numbering resolve + marker-rPr;
12985
12985
  // each of those re-walked the style catalog before.
12986
- paragraphCascadeCache = /* @__PURE__ */ new WeakMap();
12986
+ //
12987
+ // Defaults to a per-context instance, but the runtime can supply a
12988
+ // persistent `WeakMap` via `FormattingContextOptions.paragraphCascadeCache`
12989
+ // so unchanged paragraphs don't re-cascade on every keystroke. See the
12990
+ // contract on the option for invalidation semantics.
12991
+ paragraphCascadeCache;
12987
12992
  constructor(doc, opts) {
12988
12993
  this.doc = doc;
12989
12994
  this.opts = opts;
12995
+ this.paragraphCascadeCache = opts.paragraphCascadeCache ?? /* @__PURE__ */ new WeakMap();
12990
12996
  const canonicalTheme = doc.subParts?.canonicalTheme;
12991
12997
  this.theme = opts.themeResolver ?? (canonicalTheme ? new ThemeColorResolver(canonicalTheme) : void 0);
12992
12998
  this.numbering = opts.numberingPrefixResolver ?? createNumberingPrefixResolver(doc.numbering);
@@ -13552,7 +13558,8 @@ function createEditorSurfaceSnapshot(document2, _selection, activeStory = { kind
13552
13558
  const formattingContext = createFormattingContext(document2, {
13553
13559
  ...options.revisionMarkupMode ? { revisionMarkupMode: options.revisionMarkupMode } : {},
13554
13560
  ...options.getEffectiveMarkupMode ? { getEffectiveMarkupMode: options.getEffectiveMarkupMode } : {},
13555
- ...options.authorColorPalette ? { authorColorPalette: options.authorColorPalette } : {}
13561
+ ...options.authorColorPalette ? { authorColorPalette: options.authorColorPalette } : {},
13562
+ ...options.paragraphCascadeCache ? { paragraphCascadeCache: options.paragraphCascadeCache } : {}
13556
13563
  });
13557
13564
  const editableTargetsByBlockPath = options.editableTargetsByBlockPath ?? indexEditableTargetsByBlockPath(document2);
13558
13565
  const layoutIdentitiesByBlockPath = options.layoutIdentitiesByBlockPath;
@@ -15747,7 +15754,8 @@ function buildResolvedSections(document2) {
15747
15754
  const mainSurface = createEditorSurfaceSnapshot(
15748
15755
  document2,
15749
15756
  createSelectionSnapshot(0, 0),
15750
- MAIN_STORY_TARGET
15757
+ MAIN_STORY_TARGET,
15758
+ { editableTargetsByBlockPath: NO_EDITABLE_TARGETS_INDEX }
15751
15759
  );
15752
15760
  const sections = [];
15753
15761
  let sectionStart = 0;
@@ -22574,6 +22582,8 @@ function buildSearchPattern(query, options = {}) {
22574
22582
  function findSearchMatches(text, query, options = {}) {
22575
22583
  const pattern = buildSearchPattern(query, options);
22576
22584
  if (!pattern) return [];
22585
+ const limit = normalizeSearchLimit(options.limit);
22586
+ if (limit === 0) return [];
22577
22587
  const results = [];
22578
22588
  let match;
22579
22589
  pattern.lastIndex = 0;
@@ -22584,12 +22594,21 @@ function findSearchMatches(text, query, options = {}) {
22584
22594
  text: match[0],
22585
22595
  index: results.length
22586
22596
  });
22597
+ if (results.length >= limit) {
22598
+ break;
22599
+ }
22587
22600
  if (match[0].length === 0) {
22588
22601
  pattern.lastIndex += 1;
22589
22602
  }
22590
22603
  }
22591
22604
  return results;
22592
22605
  }
22606
+ function normalizeSearchLimit(limit) {
22607
+ if (limit === void 0 || !Number.isFinite(limit)) {
22608
+ return Number.POSITIVE_INFINITY;
22609
+ }
22610
+ return Math.max(0, Math.floor(limit));
22611
+ }
22593
22612
  function createSearchExcerpt(text, from, to, radius = 24) {
22594
22613
  const safeFrom = Math.max(0, Math.min(from, text.length));
22595
22614
  const safeTo = Math.max(safeFrom, Math.min(to, text.length));
@@ -35768,9 +35787,11 @@ function applyRevisionAction(options) {
35768
35787
  );
35769
35788
  }
35770
35789
  const slice = story.units.slice(range.from, range.to);
35771
- if (slice.some(
35772
- (unit) => unit.kind === "paragraph_break" || unit.kind === "opaque_block" || unit.kind === "structural_block"
35773
- )) {
35790
+ const touchesStructuralContent = slice.some(
35791
+ (unit) => unit.kind === "opaque_block" || unit.kind === "structural_block"
35792
+ );
35793
+ const touchesParagraphBoundary = slice.some((unit) => unit.kind === "paragraph_break");
35794
+ if (touchesStructuralContent || touchesParagraphBoundary && !canApplyRuntimeTextBlockDeletion(revision, options.intent)) {
35774
35795
  return skippedResult(
35775
35796
  options,
35776
35797
  "structural-range",
@@ -35778,7 +35799,7 @@ function applyRevisionAction(options) {
35778
35799
  );
35779
35800
  }
35780
35801
  if (slice.some(
35781
- (unit) => unit.kind === "image" || unit.kind === "opaque_inline"
35802
+ (unit) => unit.kind === "image" || unit.kind === "opaque_inline" || unit.kind === "protected_inline"
35782
35803
  )) {
35783
35804
  return skippedResult(
35784
35805
  options,
@@ -35833,6 +35854,9 @@ function applyRevisionAction(options) {
35833
35854
  detachedRevisionIds: findNewDetachedRevisionIds(options.store, nextStore)
35834
35855
  };
35835
35856
  }
35857
+ function canApplyRuntimeTextBlockDeletion(revision, intent) {
35858
+ return revision.metadata.source === "runtime" && (intent === "accept" && revision.kind === "deletion" && (revision.metadata.semanticKind === "replacement" || revision.metadata.semanticKind === "deletion") || intent === "reject" && revision.kind === "insertion" && (revision.metadata.semanticKind === "replacement" || revision.metadata.semanticKind === "structural-change"));
35859
+ }
35836
35860
  function applyPairedMoveAction(options, revision) {
35837
35861
  const resultingStatus = toResultingStatus(options.intent);
35838
35862
  return {
@@ -36936,6 +36960,11 @@ function executeEditorCommand(state, command, context) {
36936
36960
  );
36937
36961
  return buildDocumentReplaceTransaction(state, context, result);
36938
36962
  }
36963
+ case "fragment.insert-tracked": {
36964
+ const result = applySuggestingFragmentInsert(state, command.fragment, context);
36965
+ if (result) return result;
36966
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
36967
+ }
36939
36968
  case "runtime.set-read-only":
36940
36969
  return createTransaction(
36941
36970
  {
@@ -38181,7 +38210,7 @@ function applyTextCommand(state, timestamp, apply) {
38181
38210
  const reviewState = remapReviewStateAfterContentChange(
38182
38211
  state,
38183
38212
  result.document,
38184
- result.mapping
38213
+ result.mapping ?? createEmptyMapping()
38185
38214
  );
38186
38215
  const scopeTagTouches = collectScopeTagTouches(
38187
38216
  state.document.review.comments,
@@ -38521,6 +38550,38 @@ function isSingleParagraphSuggestingRange(document2, from, to) {
38521
38550
  );
38522
38551
  return ranges.some((range) => from >= range.start && to <= range.end);
38523
38552
  }
38553
+ function isTextOnlySuggestingRange(document2, from, to) {
38554
+ const story = parseTextStory(document2.content);
38555
+ const range = normalizeTextStoryRange(story, from, to);
38556
+ if (!range) {
38557
+ return isSingleParagraphSuggestingRange(document2, from, to);
38558
+ }
38559
+ const rootStoryTextOnly = story.units.slice(range.from, range.to).every(isSuggestingTextReplacementUnit);
38560
+ if (rootStoryTextOnly) return true;
38561
+ return isSingleParagraphSuggestingRange(document2, from, to);
38562
+ }
38563
+ function normalizeTextStoryRange(story, from, to) {
38564
+ const start = Math.min(from, to);
38565
+ const end = Math.max(from, to);
38566
+ if (start < 0 || end > story.size) return null;
38567
+ return { from: start, to: end };
38568
+ }
38569
+ function isSuggestingTextReplacementUnit(unit) {
38570
+ switch (unit.kind) {
38571
+ case "text":
38572
+ case "tab":
38573
+ case "hard_break":
38574
+ case "paragraph_break":
38575
+ case "scope_marker":
38576
+ return true;
38577
+ case "protected_inline":
38578
+ case "image":
38579
+ case "opaque_inline":
38580
+ case "opaque_block":
38581
+ case "structural_block":
38582
+ return false;
38583
+ }
38584
+ }
38524
38585
  function collectSuggestingParagraphRanges(blocks, startCursor, output, addRootParagraphBoundaries) {
38525
38586
  let cursor = startCursor;
38526
38587
  for (let index = 0; index < blocks.length; index += 1) {
@@ -38593,10 +38654,10 @@ function applySuggestingInsert(state, text, context, formatting) {
38593
38654
  const from = Math.min(selection.anchor, selection.head);
38594
38655
  const to = Math.max(selection.anchor, selection.head);
38595
38656
  const isCollapsed = from === to;
38596
- if (!isCollapsed && !isSingleParagraphSuggestingRange(state.document, from, to)) {
38657
+ if (!isCollapsed && !isTextOnlySuggestingRange(state.document, from, to)) {
38597
38658
  return createSuggestingUnsupportedTransaction(
38598
38659
  state,
38599
- "Suggesting mode does not yet support multi-paragraph replacement ranges."
38660
+ "Suggesting mode text replacement ranges must contain only editable text and paragraph breaks."
38600
38661
  );
38601
38662
  }
38602
38663
  if (isCollapsed) {
@@ -38705,7 +38766,7 @@ function applySuggestingInsert(state, text, context, formatting) {
38705
38766
  const reviewState = remapReviewStateAfterContentChange(
38706
38767
  state,
38707
38768
  result.document,
38708
- result.mapping
38769
+ result.mapping ?? createEmptyMapping()
38709
38770
  );
38710
38771
  const replacementSuggestionId = createSuggestingRevisionId(
38711
38772
  reviewState.document.review.revisions,
@@ -38770,6 +38831,131 @@ function applySuggestingInsert(state, text, context, formatting) {
38770
38831
  activeCommentId: reviewState.activeCommentId
38771
38832
  }
38772
38833
  },
38834
+ {
38835
+ historyBoundary: "push",
38836
+ markDirty: true,
38837
+ mapping: result.mapping ?? createEmptyMapping(),
38838
+ effects: {
38839
+ ...reviewState.effects,
38840
+ revisionAuthored: { changeId: insertionRevision.changeId, kind: "insertion" }
38841
+ }
38842
+ }
38843
+ );
38844
+ }
38845
+ function applySuggestingFragmentInsert(state, fragment, context) {
38846
+ if (state.readOnly) {
38847
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
38848
+ }
38849
+ if (fragment.blocks.length === 0) {
38850
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
38851
+ }
38852
+ if (!isTrackableSuggestingFragment(fragment)) {
38853
+ return createSuggestingUnsupportedTransaction(
38854
+ state,
38855
+ "Suggesting mode structured fragment replacement supports paragraph/text fragments only."
38856
+ );
38857
+ }
38858
+ const authorId = context.defaultAuthorId ?? "unknown";
38859
+ const selection = state.selection;
38860
+ const from = Math.min(selection.anchor, selection.head);
38861
+ const to = Math.max(selection.anchor, selection.head);
38862
+ const isCollapsed = from === to;
38863
+ if (!isCollapsed && !isTextOnlySuggestingRange(state.document, from, to)) {
38864
+ return createSuggestingUnsupportedTransaction(
38865
+ state,
38866
+ "Suggesting mode structured fragment replacement ranges must contain only editable text and paragraph breaks."
38867
+ );
38868
+ }
38869
+ const storyBefore = parseTextStory(state.document.content);
38870
+ const insertSelection = createSelectionSnapshot(to, to);
38871
+ const result = structureLayer.applyFragmentInsert(
38872
+ state.document,
38873
+ insertSelection,
38874
+ fragment,
38875
+ context
38876
+ );
38877
+ if (!result.changed) {
38878
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
38879
+ }
38880
+ const storyAfter = parseTextStory(result.document.content);
38881
+ const insertedFrom = to;
38882
+ const insertedTo = to + Math.max(0, storyAfter.size - storyBefore.size);
38883
+ if (insertedTo <= insertedFrom) {
38884
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
38885
+ }
38886
+ const reviewState = remapReviewStateAfterContentChange(
38887
+ state,
38888
+ result.document,
38889
+ result.mapping ?? createEmptyMapping()
38890
+ );
38891
+ const replacementSuggestionId = !isCollapsed ? createSuggestingRevisionId(
38892
+ reviewState.document.review.revisions,
38893
+ context.timestamp,
38894
+ authorId
38895
+ ) : void 0;
38896
+ let deletionRevision = !isCollapsed ? createAuthoredRevision(
38897
+ reviewState.document.review.revisions,
38898
+ "deletion",
38899
+ from,
38900
+ to,
38901
+ authorId,
38902
+ context.timestamp,
38903
+ createSuggestionMetadata({
38904
+ suggestionId: replacementSuggestionId,
38905
+ semanticKind: "replacement"
38906
+ })
38907
+ ) : void 0;
38908
+ const insertionRevision = createAuthoredRevision(
38909
+ {
38910
+ ...reviewState.document.review.revisions,
38911
+ ...deletionRevision ? { [deletionRevision.changeId]: deletionRevision } : {}
38912
+ },
38913
+ "insertion",
38914
+ insertedFrom,
38915
+ insertedTo,
38916
+ authorId,
38917
+ context.timestamp,
38918
+ createSuggestionMetadata(
38919
+ deletionRevision ? {
38920
+ suggestionId: replacementSuggestionId,
38921
+ semanticKind: "replacement",
38922
+ linkedRevisionIds: [deletionRevision.changeId]
38923
+ } : {
38924
+ semanticKind: "structural-change"
38925
+ }
38926
+ )
38927
+ );
38928
+ if (deletionRevision) {
38929
+ deletionRevision = {
38930
+ ...deletionRevision,
38931
+ metadata: {
38932
+ ...deletionRevision.metadata,
38933
+ linkedRevisionIds: [insertionRevision.changeId]
38934
+ }
38935
+ };
38936
+ }
38937
+ const finalDocument = {
38938
+ ...reviewState.document,
38939
+ review: {
38940
+ ...reviewState.document.review,
38941
+ revisions: {
38942
+ ...reviewState.document.review.revisions,
38943
+ ...deletionRevision ? { [deletionRevision.changeId]: deletionRevision } : {},
38944
+ [insertionRevision.changeId]: insertionRevision
38945
+ }
38946
+ }
38947
+ };
38948
+ return createTransaction(
38949
+ {
38950
+ ...state,
38951
+ document: finalDocument,
38952
+ selection: createSelectionSnapshot(insertedTo, insertedTo),
38953
+ warnings: reviewState.warnings,
38954
+ runtime: {
38955
+ ...state.runtime,
38956
+ activeCommentId: reviewState.activeCommentId
38957
+ }
38958
+ },
38773
38959
  {
38774
38960
  historyBoundary: "push",
38775
38961
  markDirty: true,
@@ -38781,6 +38967,10 @@ function applySuggestingInsert(state, text, context, formatting) {
38781
38967
  }
38782
38968
  );
38783
38969
  }
38970
+ function isTrackableSuggestingFragment(fragment) {
38971
+ const story = parseTextStory({ type: "doc", children: fragment.blocks });
38972
+ return story.units.every(isSuggestingTextReplacementUnit);
38973
+ }
38784
38974
  function applySuggestingDelete(state, direction, context) {
38785
38975
  if (state.readOnly) {
38786
38976
  return createTransaction(state, { historyBoundary: "skip", markDirty: false });
@@ -38790,10 +38980,10 @@ function applySuggestingDelete(state, direction, context) {
38790
38980
  const from = Math.min(selection.anchor, selection.head);
38791
38981
  const to = Math.max(selection.anchor, selection.head);
38792
38982
  const isCollapsed = from === to;
38793
- if (!isCollapsed && !isSingleParagraphSuggestingRange(state.document, from, to)) {
38983
+ if (!isCollapsed && !isTextOnlySuggestingRange(state.document, from, to)) {
38794
38984
  return createSuggestingUnsupportedTransaction(
38795
38985
  state,
38796
- "Suggesting mode does not yet support multi-paragraph deletion ranges."
38986
+ "Suggesting mode text deletion ranges must contain only editable text and paragraph breaks."
38797
38987
  );
38798
38988
  }
38799
38989
  let deleteFrom;
@@ -49918,11 +50108,9 @@ function compileParagraphReplacement(entry, proposed, options) {
49918
50108
  };
49919
50109
  }
49920
50110
  if (proposed.proposedContent.kind === "structured") {
49921
- if (options.posture === "suggest-mode") {
49922
- return null;
49923
- }
49924
50111
  const fragment = proposed.proposedContent.structured;
49925
50112
  if (!isStructuredReplacementContent(fragment)) return null;
50113
+ const stepKind = options.posture === "suggest-mode" ? "fragment-replace-tracked" : "fragment-replace";
49926
50114
  const blockCount = fragment.blocks.length;
49927
50115
  const summaryScope = rangeKind === "inline-marker" ? `paragraph #${entry.blockIndex} inline-marker range [${effectiveRange.from}..${effectiveRange.to}]` : rangeKind === "opaque-preserving-text" ? `paragraph #${entry.blockIndex} opaque-preserving text range [${effectiveRange.from}..${effectiveRange.to}]` : `paragraph #${entry.blockIndex}`;
49928
50116
  const actionVerb = proposed.operation === "insert-before" ? "insert before" : proposed.operation === "insert-after" ? "insert after" : "replace";
@@ -49933,8 +50121,8 @@ function compileParagraphReplacement(entry, proposed, options) {
49933
50121
  operation: proposed.operation,
49934
50122
  steps: Object.freeze([
49935
50123
  {
49936
- kind: "fragment-replace",
49937
- summary: actionSummary,
50124
+ kind: stepKind,
50125
+ summary: stepKind === "fragment-replace" ? actionSummary : `suggest-mode ${actionSummary}`,
49938
50126
  ...textLeafEditableTargetHint(entry, blockRange) ? { editableTargetHint: textLeafEditableTargetHint(entry, blockRange) } : {},
49939
50127
  range: { from: operationRange.from, to: operationRange.to },
49940
50128
  fragment
@@ -50021,7 +50209,7 @@ function compileScopeKind(entry, options) {
50021
50209
  };
50022
50210
  }
50023
50211
  function compileScopeReplacement(entry, proposed, options) {
50024
- if (entry.handle.provenance !== "marker-backed" || proposed.operation !== "replace" || options.posture !== "direct-edit") {
50212
+ if (entry.handle.provenance !== "marker-backed" || proposed.operation !== "replace") {
50025
50213
  return null;
50026
50214
  }
50027
50215
  const markerRange = buildScopePositionMap(options.document).markerScopes.get(
@@ -50030,14 +50218,15 @@ function compileScopeReplacement(entry, proposed, options) {
50030
50218
  if (!markerRange) return null;
50031
50219
  if (proposed.proposedContent.kind === "text") {
50032
50220
  const text = proposed.proposedContent.text ?? "";
50221
+ const stepKind = options.posture === "suggest-mode" ? "text-insert-tracked" : "text-replace";
50033
50222
  return {
50034
50223
  scopeId: entry.handle.scopeId,
50035
50224
  targetKind: "scope",
50036
50225
  operation: proposed.operation,
50037
50226
  steps: Object.freeze([
50038
50227
  {
50039
- kind: "text-replace",
50040
- summary: `replace multi-paragraph scope ${entry.handle.scopeId} text [${markerRange.from}..${markerRange.to}] (len ${text.length})`,
50228
+ kind: stepKind,
50229
+ summary: stepKind === "text-replace" ? `replace multi-paragraph scope ${entry.handle.scopeId} text [${markerRange.from}..${markerRange.to}] (len ${text.length})` : `suggest-mode replace multi-paragraph scope ${entry.handle.scopeId} text [${markerRange.from}..${markerRange.to}] (len ${text.length})`,
50041
50230
  range: { from: markerRange.from, to: markerRange.to },
50042
50231
  text,
50043
50232
  ...proposed.formatting ? { formatting: proposed.formatting } : {}
@@ -50050,14 +50239,15 @@ function compileScopeReplacement(entry, proposed, options) {
50050
50239
  if (proposed.proposedContent.kind === "structured") {
50051
50240
  const fragment = proposed.proposedContent.structured;
50052
50241
  if (!isStructuredReplacementContent(fragment)) return null;
50242
+ const stepKind = options.posture === "suggest-mode" ? "fragment-replace-tracked" : "fragment-replace";
50053
50243
  return {
50054
50244
  scopeId: entry.handle.scopeId,
50055
50245
  targetKind: "scope",
50056
50246
  operation: proposed.operation,
50057
50247
  steps: Object.freeze([
50058
50248
  {
50059
- kind: "fragment-replace",
50060
- summary: `replace multi-paragraph scope ${entry.handle.scopeId} with structured fragment [${markerRange.from}..${markerRange.to}] (${fragment.blocks.length} block(s))`,
50249
+ kind: stepKind,
50250
+ summary: stepKind === "fragment-replace" ? `replace multi-paragraph scope ${entry.handle.scopeId} with structured fragment [${markerRange.from}..${markerRange.to}] (${fragment.blocks.length} block(s))` : `suggest-mode replace multi-paragraph scope ${entry.handle.scopeId} with structured fragment [${markerRange.from}..${markerRange.to}] (${fragment.blocks.length} block(s))`,
50061
50251
  range: { from: markerRange.from, to: markerRange.to },
50062
50252
  fragment
50063
50253
  }
@@ -50439,6 +50629,99 @@ function paragraphFirstMarkerStart(paragraph, knownScopeIds) {
50439
50629
  }
50440
50630
  return null;
50441
50631
  }
50632
+ function paragraphHasMarkerEnd(paragraph, scopeId) {
50633
+ return paragraph.children.some(
50634
+ (child) => child.type === "scope_marker_end" && child.scopeId === scopeId
50635
+ );
50636
+ }
50637
+ function paragraphSameParagraphMarkerScopeId(paragraph, knownScopeIds) {
50638
+ const markerScopeId = paragraphFirstMarkerStart(paragraph, knownScopeIds);
50639
+ if (!markerScopeId) return null;
50640
+ return paragraphHasMarkerEnd(paragraph, markerScopeId) ? markerScopeId : null;
50641
+ }
50642
+ function markerStableRefOverride(markerScopeId, semanticPath, overlay) {
50643
+ const hint = stableRefHintForScopeId(markerScopeId, overlay);
50644
+ if (hint === "semantic-path") {
50645
+ return {
50646
+ kind: "semantic-path",
50647
+ value: semanticPath.join("/")
50648
+ };
50649
+ }
50650
+ return { kind: "scope-id", value: markerScopeId };
50651
+ }
50652
+ function enumerateNestedMarkerBackedParagraphs(blocks, input) {
50653
+ const out = [];
50654
+ for (let childIndex = 0; childIndex < blocks.length; childIndex += 1) {
50655
+ const block = blocks[childIndex];
50656
+ if (!block) continue;
50657
+ if (block.type === "paragraph") {
50658
+ const markerScopeId = paragraphSameParagraphMarkerScopeId(
50659
+ block,
50660
+ input.knownOverlayScopeIds
50661
+ );
50662
+ if (!markerScopeId) continue;
50663
+ const kind = detectParagraphKind(block);
50664
+ const semanticPath = [
50665
+ ...input.semanticPrefix,
50666
+ kind,
50667
+ String(childIndex)
50668
+ ];
50669
+ const handle = buildHandle(
50670
+ markerScopeId,
50671
+ input.documentId,
50672
+ semanticPath,
50673
+ "marker-backed",
50674
+ "marker-backed",
50675
+ input.parentScopeId,
50676
+ markerStableRefOverride(markerScopeId, semanticPath, input.overlay)
50677
+ );
50678
+ out.push({
50679
+ kind,
50680
+ handle,
50681
+ paragraph: block,
50682
+ blockIndex: input.rootBlockIndex,
50683
+ classifications: input.classificationIndex.get(markerScopeId) ?? Object.freeze([])
50684
+ });
50685
+ continue;
50686
+ }
50687
+ if (block.type === "sdt") {
50688
+ out.push(
50689
+ ...enumerateNestedMarkerBackedParagraphs(block.children, {
50690
+ ...input,
50691
+ semanticPrefix: [
50692
+ ...input.semanticPrefix,
50693
+ "sdt",
50694
+ String(childIndex)
50695
+ ]
50696
+ })
50697
+ );
50698
+ continue;
50699
+ }
50700
+ if (block.type === "table") {
50701
+ for (let rowIdx = 0; rowIdx < block.rows.length; rowIdx += 1) {
50702
+ const row2 = block.rows[rowIdx];
50703
+ for (let cellIdx = 0; cellIdx < row2.cells.length; cellIdx += 1) {
50704
+ const cell = row2.cells[cellIdx];
50705
+ out.push(
50706
+ ...enumerateNestedMarkerBackedParagraphs(cell.children, {
50707
+ ...input,
50708
+ semanticPrefix: [
50709
+ ...input.semanticPrefix,
50710
+ "table",
50711
+ String(childIndex),
50712
+ "row",
50713
+ String(rowIdx),
50714
+ "cell",
50715
+ String(cellIdx)
50716
+ ]
50717
+ })
50718
+ );
50719
+ }
50720
+ }
50721
+ }
50722
+ }
50723
+ return out;
50724
+ }
50442
50725
  function enumerateFieldsInParagraph(paragraph, blockIndex, documentId, parentScopeId) {
50443
50726
  const out = [];
50444
50727
  for (let i = 0; i < paragraph.children.length; i += 1) {
@@ -50721,6 +51004,17 @@ function enumerateScopes(document2, inputs = {}) {
50721
51004
  cellIndex: cellIdx,
50722
51005
  classifications: Object.freeze([])
50723
51006
  });
51007
+ for (const nested of enumerateNestedMarkerBackedParagraphs(cell.children, {
51008
+ documentId,
51009
+ rootBlockIndex: index,
51010
+ semanticPrefix: cellSemanticPath,
51011
+ parentScopeId: cellScopeId,
51012
+ knownOverlayScopeIds,
51013
+ classificationIndex,
51014
+ overlay: inputs.overlay
51015
+ })) {
51016
+ results.push(nested);
51017
+ }
50724
51018
  }
50725
51019
  }
50726
51020
  }
@@ -51206,16 +51500,12 @@ function replaceTextCapability(scope, context) {
51206
51500
  );
51207
51501
  }
51208
51502
  if (scope.kind === "scope") {
51209
- if (scope.workflow.effectiveMode === "suggest") {
51210
- return unsupported(
51211
- "compile-refused:scope:multi-paragraph-suggesting-not-implemented",
51212
- ["compile-refused:scope:multi-paragraph-suggesting-not-implemented"],
51213
- ["guard:suggest-mode", ...evidenceWarnings(context)]
51214
- );
51215
- }
51216
51503
  return supported(
51217
51504
  "compile-supported:scope:multi-paragraph-text-replace",
51218
- evidenceWarnings(context)
51505
+ [
51506
+ ...scope.workflow.effectiveMode === "suggest" ? ["guard:suggest-mode"] : [],
51507
+ ...evidenceWarnings(context)
51508
+ ]
51219
51509
  );
51220
51510
  }
51221
51511
  if (!PARAGRAPH_LIKE.has(scope.kind)) {
@@ -51269,34 +51559,24 @@ function replaceFragmentCapability(scope, context) {
51269
51559
  );
51270
51560
  }
51271
51561
  if (scope.kind === "scope") {
51272
- if (scope.workflow.effectiveMode === "suggest") {
51273
- return unsupported(
51274
- "compile-refused:scope:multi-paragraph-structured-suggesting-not-implemented",
51275
- [
51276
- "compile-refused:scope:multi-paragraph-structured-suggesting-not-implemented"
51277
- ],
51278
- ["guard:suggest-mode", ...evidenceWarnings(context)]
51279
- );
51280
- }
51281
51562
  return supported(
51282
51563
  "compile-supported:scope:multi-paragraph-fragment-replace",
51283
- evidenceWarnings(context)
51564
+ [
51565
+ ...scope.workflow.effectiveMode === "suggest" ? ["guard:suggest-mode"] : [],
51566
+ ...evidenceWarnings(context)
51567
+ ]
51284
51568
  );
51285
51569
  }
51286
51570
  if (!PARAGRAPH_LIKE.has(scope.kind)) {
51287
51571
  const reason = `compile-refused:${scope.kind}`;
51288
51572
  return unsupported(reason, [reason], evidenceWarnings(context));
51289
51573
  }
51290
- if (scope.workflow.effectiveMode === "suggest") {
51291
- return unsupported(
51292
- `compile-refused:${scope.kind}:structured-suggesting-not-implemented`,
51293
- [`compile-refused:${scope.kind}:structured-suggesting-not-implemented`],
51294
- ["guard:suggest-mode"]
51295
- );
51296
- }
51297
51574
  return supported(
51298
- "compile-supported:paragraph-like:fragment-replace",
51299
- evidenceWarnings(context)
51575
+ scope.workflow.effectiveMode === "suggest" ? "compile-supported:paragraph-like:fragment-replace-tracked" : "compile-supported:paragraph-like:fragment-replace",
51576
+ [
51577
+ ...scope.workflow.effectiveMode === "suggest" ? ["guard:suggest-mode"] : [],
51578
+ ...evidenceWarnings(context)
51579
+ ]
51300
51580
  );
51301
51581
  }
51302
51582
  function formattingCapability(scope, context) {
@@ -52350,6 +52630,14 @@ function supportedNonTextCommandEvidence(target) {
52350
52630
  if (target.commandFamily === "link-bookmark") {
52351
52631
  return linkBookmarkCommandEvidence(target, []);
52352
52632
  }
52633
+ if (target.commandFamily === "object" && isImageObjectTarget(target) && typeof target.object?.mediaId === "string" && target.object.mediaId.length > 0 && onlyBlockers(target.posture.blockers, ["unmodeled-target"])) {
52634
+ return {
52635
+ status: "supported",
52636
+ commandFamily: target.commandFamily,
52637
+ intents: commandIntentsForTarget(target),
52638
+ reason: "l07:image-layout-target-supported"
52639
+ };
52640
+ }
52353
52641
  return null;
52354
52642
  }
52355
52643
  function onlyBlockers(blockers, allowed) {
@@ -54850,9 +55138,7 @@ function applyScopeReplacement(inputs) {
54850
55138
  });
54851
55139
  if (!plan) {
54852
55140
  const paragraphLike = resolvedScope.kind === "paragraph" || resolvedScope.kind === "heading" || resolvedScope.kind === "list-item";
54853
- const blockers = resolvedScope.kind === "scope" && posture === "suggest-mode" ? [
54854
- proposed.proposedContent.kind === "structured" ? "compile-refused:scope:multi-paragraph-structured-suggesting-not-implemented" : "compile-refused:scope:multi-paragraph-suggesting-not-implemented"
54855
- ] : resolvedScope.kind === "scope" && proposed.operation !== "replace" ? [
55141
+ const blockers = resolvedScope.kind === "scope" && proposed.operation !== "replace" ? [
54856
55142
  `compile-refused:scope:operation-not-implemented:${proposed.operation}`
54857
55143
  ] : resolvedScope.kind === "scope" ? multiParagraphReplacementBlockers(
54858
55144
  proposed.proposedContent.kind === "structured" ? "fragment" : "text"
@@ -70287,6 +70573,36 @@ function createDocumentRuntime(options) {
70287
70573
  let viewportBlockRanges = null;
70288
70574
  let viewportRangesKey = serializeViewportRanges(viewportBlockRanges);
70289
70575
  let viewportBlocksPerPageEstimate = null;
70576
+ let fullSurfaceWarmupScheduled = false;
70577
+ let fullSurfaceWarmupCompleted = false;
70578
+ function scheduleFullSurfaceWarmupAfterCull() {
70579
+ if (fullSurfaceWarmupScheduled || fullSurfaceWarmupCompleted) return;
70580
+ fullSurfaceWarmupScheduled = true;
70581
+ perfCounters.increment("surface.fullSurfaceWarmup.scheduled");
70582
+ runOnIdle(() => {
70583
+ fullSurfaceWarmupScheduled = false;
70584
+ if (cachedFullSurface) {
70585
+ perfCounters.increment("surface.fullSurfaceWarmup.alreadyCached");
70586
+ fullSurfaceWarmupCompleted = true;
70587
+ return;
70588
+ }
70589
+ const t0 = performance.now();
70590
+ try {
70591
+ getCachedFullSurface(state.document, activeStory);
70592
+ fullSurfaceWarmupCompleted = true;
70593
+ perfCounters.increment("surface.fullSurfaceWarmup.committed");
70594
+ } catch (error) {
70595
+ perfCounters.increment("surface.fullSurfaceWarmup.failed");
70596
+ fullSurfaceWarmupCompleted = false;
70597
+ void error;
70598
+ } finally {
70599
+ perfCounters.increment(
70600
+ "surface.fullSurfaceWarmup.us",
70601
+ Math.round((performance.now() - t0) * 1e3)
70602
+ );
70603
+ }
70604
+ });
70605
+ }
70290
70606
  const getRuntimeForLayoutFacet = () => {
70291
70607
  if (!runtimeRef) {
70292
70608
  throw new Error("Document runtime viewport methods are not initialized");
@@ -70479,6 +70795,15 @@ function createDocumentRuntime(options) {
70479
70795
  let cachedSurface;
70480
70796
  let cachedFullSurface;
70481
70797
  const editableTargetBlockCache = createEditableTargetBlockCache();
70798
+ let paragraphCascadeCache = /* @__PURE__ */ new WeakMap();
70799
+ let paragraphCascadeCacheStylesRef = null;
70800
+ function ensureParagraphCascadeCacheForStyles(nextStyles) {
70801
+ if (paragraphCascadeCacheStylesRef !== nextStyles) {
70802
+ paragraphCascadeCache = /* @__PURE__ */ new WeakMap();
70803
+ paragraphCascadeCacheStylesRef = nextStyles;
70804
+ }
70805
+ return paragraphCascadeCache;
70806
+ }
70482
70807
  let cachedEditableTargetMap = null;
70483
70808
  function getEditableTargetsByBlockPath(document2) {
70484
70809
  if (cachedEditableTargetMap !== null && cachedEditableTargetMap.document === document2) {
@@ -70532,6 +70857,7 @@ function createDocumentRuntime(options) {
70532
70857
  const snapshot = createEditorSurfaceSnapshot(document2, state.selection, nextActiveStory, {
70533
70858
  viewportBlockRanges: null,
70534
70859
  editableTargetsByBlockPath: getEditableTargetsByBlockPath(document2),
70860
+ paragraphCascadeCache: ensureParagraphCascadeCacheForStyles(document2.styles),
70535
70861
  ...effectiveMarkupModeProvider ? { getEffectiveMarkupMode: effectiveMarkupModeProvider } : {}
70536
70862
  });
70537
70863
  recordPerfSample("snapshot.surface");
@@ -70572,6 +70898,7 @@ function createDocumentRuntime(options) {
70572
70898
  activeStoryKey,
70573
70899
  surfaceViewportRanges
70574
70900
  ),
70901
+ paragraphCascadeCache: ensureParagraphCascadeCacheForStyles(document2.styles),
70575
70902
  ...effectiveMarkupModeProvider ? { getEffectiveMarkupMode: effectiveMarkupModeProvider } : {}
70576
70903
  });
70577
70904
  recordPerfSample("snapshot.surface");
@@ -70783,7 +71110,17 @@ function createDocumentRuntime(options) {
70783
71110
  }
70784
71111
  }
70785
71112
  function tryPatchNestedLocalTextSurface(previousSurface, editFrom, editTo, insertedText) {
70786
- const shiftBudget = estimateLocalTextPatchShiftBudget(previousSurface.blocks, 0);
71113
+ const containerIndex = previousSurface.blocks.findIndex(
71114
+ (block) => editFrom >= block.from && editTo <= block.to
71115
+ );
71116
+ if (containerIndex < 0) {
71117
+ perfCounters.increment("surface.localText.patchMiss");
71118
+ return null;
71119
+ }
71120
+ const shiftBudget = estimateLocalTextPatchShiftBudget(
71121
+ previousSurface.blocks,
71122
+ containerIndex + 1
71123
+ );
70787
71124
  perfCounters.increment("surface.localText.shiftedBlocks", shiftBudget.shiftedBlocks);
70788
71125
  perfCounters.increment("surface.localText.shiftedNodes", shiftBudget.shiftedNodes);
70789
71126
  if (!shiftBudget.withinBudget) {
@@ -70863,19 +71200,9 @@ function createDocumentRuntime(options) {
70863
71200
  for (let index = startIndex; index < blocks.length; index += 1) {
70864
71201
  const shiftedBlockNodes = countSurfaceShiftNodesUpTo(
70865
71202
  blocks[index],
70866
- Math.min(
70867
- policy.maxShiftedNodesPerBlock,
70868
- policy.maxShiftedSurfaceNodes - shiftedNodes
70869
- ) + 1
71203
+ policy.maxShiftedSurfaceNodes - shiftedNodes + 1
70870
71204
  );
70871
71205
  shiftedNodes += shiftedBlockNodes;
70872
- if (shiftedBlockNodes > policy.maxShiftedNodesPerBlock) {
70873
- return {
70874
- shiftedBlocks,
70875
- shiftedNodes,
70876
- withinBudget: false
70877
- };
70878
- }
70879
71206
  if (shiftedNodes > policy.maxShiftedSurfaceNodes) {
70880
71207
  return {
70881
71208
  shiftedBlocks,
@@ -71547,6 +71874,9 @@ function createDocumentRuntime(options) {
71547
71874
  viewportBlockRangesOverride: firstSurfaceViewportBlockRanges
71548
71875
  }) : getCachedSurface(state.document, activeStory)
71549
71876
  );
71877
+ if (firstSurfaceViewportBlockRanges) {
71878
+ scheduleFullSurfaceWarmupAfterCull();
71879
+ }
71550
71880
  const snapshot = {
71551
71881
  documentId: state.documentId,
71552
71882
  sessionId: state.sessionId,
@@ -71660,6 +71990,9 @@ function createDocumentRuntime(options) {
71660
71990
  activeStoryKey,
71661
71991
  viewportBlockRanges
71662
71992
  ),
71993
+ paragraphCascadeCache: ensureParagraphCascadeCacheForStyles(
71994
+ state.document.styles
71995
+ ),
71663
71996
  ...effectiveMarkupModeProvider ? { getEffectiveMarkupMode: effectiveMarkupModeProvider } : {}
71664
71997
  }
71665
71998
  );
@@ -72310,7 +72643,20 @@ function createDocumentRuntime(options) {
72310
72643
  selection: prepared.selection
72311
72644
  };
72312
72645
  resolvedReplayTextTarget = prepared.textTarget;
72313
- } else if (command.type === "field.refresh" || command.type === "toc.refresh" || command.type === "bookmark.edit-content" || command.type === "hyperlink.update-destination") {
72646
+ } else {
72647
+ const selectedListItemDeleteCommand = createSelectedListItemDeleteReplayCommand({
72648
+ command,
72649
+ document: replayState.document,
72650
+ selection: replayState.selection,
72651
+ surface: replaySnapshot.surface?.blocks ?? [],
72652
+ storyTarget: replayStory,
72653
+ timestamp: context.timestamp
72654
+ });
72655
+ if (selectedListItemDeleteCommand) {
72656
+ executableCommand = selectedListItemDeleteCommand;
72657
+ }
72658
+ }
72659
+ if (command.type === "field.refresh" || command.type === "toc.refresh" || command.type === "bookmark.edit-content" || command.type === "hyperlink.update-destination") {
72314
72660
  const prepared = prepareModeledTargetCommandForExecution(
72315
72661
  command,
72316
72662
  replayState.document,
@@ -72454,7 +72800,20 @@ function createDocumentRuntime(options) {
72454
72800
  selection: prepared.selection
72455
72801
  };
72456
72802
  resolvedReplayTextTarget = prepared.textTarget;
72457
- } else if (command.type === "field.refresh" || command.type === "toc.refresh" || command.type === "bookmark.edit-content" || command.type === "hyperlink.update-destination") {
72803
+ } else {
72804
+ const selectedListItemDeleteCommand = createSelectedListItemDeleteReplayCommand({
72805
+ command,
72806
+ document: stateForCommand.document,
72807
+ selection: stateForCommand.selection,
72808
+ surface: snapshotForCommand.surface?.blocks ?? [],
72809
+ storyTarget: replayStory,
72810
+ timestamp: context.timestamp
72811
+ });
72812
+ if (selectedListItemDeleteCommand) {
72813
+ executableCommand = selectedListItemDeleteCommand;
72814
+ }
72815
+ }
72816
+ if (command.type === "field.refresh" || command.type === "toc.refresh" || command.type === "bookmark.edit-content" || command.type === "hyperlink.update-destination") {
72458
72817
  const prepared = prepareModeledTargetCommandForExecution(
72459
72818
  command,
72460
72819
  stateForCommand.document,
@@ -72875,6 +73234,50 @@ function createDocumentRuntime(options) {
72875
73234
  } catch (error) {
72876
73235
  emitError(toRuntimeError(error));
72877
73236
  }
73237
+ } else if (step.kind === "fragment-replace-tracked" && step.range && step.fragment && Array.isArray(step.fragment.blocks)) {
73238
+ const editableTarget = resolveEditableTargetHint(step.editableTargetHint);
73239
+ if (editableTarget === null) {
73240
+ emit2({
73241
+ type: "command_blocked",
73242
+ documentId: state.documentId,
73243
+ command: "applyScopeReplacement",
73244
+ reasons: [{
73245
+ code: "unsupported_surface",
73246
+ message: "Scope replacement editable target no longer resolves."
73247
+ }]
73248
+ });
73249
+ continue;
73250
+ }
73251
+ const dispatchRange = mapSemanticStepRangeToEditableTarget(
73252
+ step.range,
73253
+ step.editableTargetHint,
73254
+ editableTarget?.range
73255
+ );
73256
+ const anchor = {
73257
+ kind: "range",
73258
+ from: dispatchRange.from,
73259
+ to: dispatchRange.to,
73260
+ assoc: { start: -1, end: 1 }
73261
+ };
73262
+ const timestamp = clock();
73263
+ try {
73264
+ applyTextCommandInActiveStory(
73265
+ {
73266
+ type: "fragment.insert-tracked",
73267
+ fragment: step.fragment,
73268
+ ...editableTarget?.target ? { editableTarget: editableTarget.target } : {},
73269
+ origin: createOrigin("api", timestamp)
73270
+ },
73271
+ {
73272
+ selection: createSelectionFromPublicAnchor(anchor),
73273
+ blockedCommandName: "applyScopeReplacement",
73274
+ documentModeOverride: "suggesting",
73275
+ skipWorkflowGuard: true
73276
+ }
73277
+ );
73278
+ } catch (error) {
73279
+ emitError(toRuntimeError(error));
73280
+ }
72878
73281
  } else if (step.kind === "text-insert-tracked" && step.range && typeof step.text === "string") {
72879
73282
  const editableTarget = resolveEditableTargetHint(step.editableTargetHint);
72880
73283
  if (editableTarget === null) {
@@ -74857,6 +75260,39 @@ function createDocumentRuntime(options) {
74857
75260
  const preSelection = selection;
74858
75261
  const preActiveStory = activeStory;
74859
75262
  const priorDocument = state.document;
75263
+ const selectedListItemDelete = createSelectedListItemDeleteReplacement({
75264
+ command: commandForDispatch,
75265
+ document: state.document,
75266
+ editableTarget,
75267
+ selection,
75268
+ storyTarget: activeStory,
75269
+ targetResolution,
75270
+ timestamp
75271
+ });
75272
+ if (selectedListItemDelete && context.documentMode !== "suggesting") {
75273
+ const replacementCommand = {
75274
+ type: "document.replace",
75275
+ document: selectedListItemDelete.document,
75276
+ selection: selectedListItemDelete.selection,
75277
+ mapping: selectedListItemDelete.mapping,
75278
+ protectionSelection: selection,
75279
+ origin: commandForDispatch.origin
75280
+ };
75281
+ const transaction = executeEditorCommand(baseState, replacementCommand, context);
75282
+ commit(transaction);
75283
+ options.onCommandApplied?.(commandForDispatch, transaction, context, {
75284
+ preSelection,
75285
+ activeStory: preActiveStory,
75286
+ priorDocument
75287
+ });
75288
+ return completeDispatch(classifyAck({
75289
+ command: commandForDispatch,
75290
+ opId,
75291
+ priorState: baseState,
75292
+ transaction,
75293
+ newRevisionToken: state.revisionToken
75294
+ }));
75295
+ }
74860
75296
  if (activeStory.kind === "main") {
74861
75297
  const mainTransaction = executeEditorCommand(baseState, commandForDispatch, context);
74862
75298
  commit(mainTransaction);
@@ -75098,6 +75534,9 @@ function createDocumentRuntime(options) {
75098
75534
  });
75099
75535
  }
75100
75536
  function scheduleIdleContextAnalytics(callback) {
75537
+ runOnIdle(callback);
75538
+ }
75539
+ function runOnIdle(callback) {
75101
75540
  const requestIdle = globalThis.requestIdleCallback;
75102
75541
  if (typeof requestIdle === "function") {
75103
75542
  requestIdle(callback, { timeout: 250 });
@@ -76051,7 +76490,8 @@ function getStoryPlainText(document2, storyTarget, cache) {
76051
76490
  const plainText = createEditorSurfaceSnapshot(
76052
76491
  document2,
76053
76492
  createSelectionSnapshot(0, 0),
76054
- storyTarget
76493
+ storyTarget,
76494
+ { editableTargetsByBlockPath: NO_EDITABLE_TARGETS_INDEX }
76055
76495
  ).plainText;
76056
76496
  cache.set(key, plainText);
76057
76497
  return plainText;
@@ -77727,6 +78167,258 @@ function stripStoryTarget2(selection) {
77727
78167
  function isTopLevelMainStoryBlockPath(blockPath) {
77728
78168
  return typeof blockPath === "string" && /^main\/block\[\d+\]$/u.test(blockPath);
77729
78169
  }
78170
+ function createSelectedListItemDeleteReplacement(input) {
78171
+ const { command, document: document2, editableTarget, selection, storyTarget, targetResolution, timestamp } = input;
78172
+ if (command.type !== "text.delete-backward" && command.type !== "text.delete-forward") {
78173
+ return null;
78174
+ }
78175
+ if (selection.isCollapsed || editableTarget?.listAddress?.operationScope !== "list-text" || targetResolution?.kind !== "accepted") {
78176
+ return null;
78177
+ }
78178
+ const selectionFrom = Math.min(selection.anchor, selection.head);
78179
+ const selectionTo = Math.max(selection.anchor, selection.head);
78180
+ if (selectionFrom !== targetResolution.range.from || selectionTo !== targetResolution.range.to) {
78181
+ return null;
78182
+ }
78183
+ const storyBlocks = getStoryBlocks(document2, storyTarget);
78184
+ const replacement = removeNumberedParagraphAtStoryPath(storyBlocks, editableTarget.blockPath, storyTarget);
78185
+ if (!replacement) {
78186
+ return null;
78187
+ }
78188
+ const nextDocument = replaceStoryBlocks({
78189
+ ...document2,
78190
+ updatedAt: timestamp
78191
+ }, storyTarget, replacement.blocks);
78192
+ const nextStorySize = parseTextStory({
78193
+ type: "doc",
78194
+ children: [...getStoryBlocks(nextDocument, storyTarget)]
78195
+ }).size;
78196
+ const nextAnchor = Math.min(selectionFrom, nextStorySize);
78197
+ return {
78198
+ document: nextDocument,
78199
+ selection: createSelectionSnapshot(nextAnchor, nextAnchor),
78200
+ mapping: {
78201
+ steps: [{
78202
+ from: selectionFrom,
78203
+ to: selectionTo,
78204
+ insertSize: 0
78205
+ }],
78206
+ metadata: {
78207
+ invalidatesStructures: true
78208
+ }
78209
+ }
78210
+ };
78211
+ }
78212
+ function removeNumberedParagraphAtStoryPath(blocks, blockPath, storyTarget) {
78213
+ const tokens = parseStoryBlockPathTokens(blockPath, storyTarget);
78214
+ if (!tokens) return null;
78215
+ return removeNumberedParagraphFromBlocks(blocks, tokens);
78216
+ }
78217
+ function removeNumberedParagraphFromBlocks(blocks, tokens) {
78218
+ const [token, ...rest] = tokens;
78219
+ if (!token || token.kind !== "block") return null;
78220
+ const block = blocks[token.index];
78221
+ if (!block) return null;
78222
+ if (rest.length === 0) {
78223
+ if (block.type !== "paragraph" || !block.numbering) {
78224
+ return null;
78225
+ }
78226
+ if (blocks.length === 1) {
78227
+ return { blocks: [{ type: "paragraph", children: [] }] };
78228
+ }
78229
+ return {
78230
+ blocks: [
78231
+ ...blocks.slice(0, token.index),
78232
+ ...blocks.slice(token.index + 1)
78233
+ ]
78234
+ };
78235
+ }
78236
+ const next = rest[0];
78237
+ if (block.type === "table" && next?.kind === "row") {
78238
+ const updatedTable = removeNumberedParagraphFromTable(block, rest);
78239
+ if (!updatedTable) return null;
78240
+ return {
78241
+ blocks: [
78242
+ ...blocks.slice(0, token.index),
78243
+ updatedTable,
78244
+ ...blocks.slice(token.index + 1)
78245
+ ]
78246
+ };
78247
+ }
78248
+ if ((block.type === "sdt" || block.type === "custom_xml") && next?.kind === "block") {
78249
+ const updatedChildren = removeNumberedParagraphFromBlocks(block.children, rest);
78250
+ if (!updatedChildren) return null;
78251
+ return {
78252
+ blocks: [
78253
+ ...blocks.slice(0, token.index),
78254
+ { ...block, children: updatedChildren.blocks },
78255
+ ...blocks.slice(token.index + 1)
78256
+ ]
78257
+ };
78258
+ }
78259
+ if (block.type === "paragraph" && next?.kind === "inline") {
78260
+ const updatedParagraph = removeNumberedParagraphFromTextBoxInline(block, rest);
78261
+ if (!updatedParagraph) return null;
78262
+ return {
78263
+ blocks: [
78264
+ ...blocks.slice(0, token.index),
78265
+ updatedParagraph,
78266
+ ...blocks.slice(token.index + 1)
78267
+ ]
78268
+ };
78269
+ }
78270
+ return null;
78271
+ }
78272
+ function removeNumberedParagraphFromTextBoxInline(paragraph, tokens) {
78273
+ const [inlineToken, textBoxToken, ...childTokens] = tokens;
78274
+ if (inlineToken?.kind !== "inline" || textBoxToken?.kind !== "txbx" || childTokens[0]?.kind !== "block") {
78275
+ return null;
78276
+ }
78277
+ const inline = paragraph.children[inlineToken.index];
78278
+ if (!inline) return null;
78279
+ const updatedInline = removeNumberedParagraphFromInlineTextBox(inline, childTokens);
78280
+ if (!updatedInline) return null;
78281
+ return {
78282
+ ...paragraph,
78283
+ children: [
78284
+ ...paragraph.children.slice(0, inlineToken.index),
78285
+ updatedInline,
78286
+ ...paragraph.children.slice(inlineToken.index + 1)
78287
+ ]
78288
+ };
78289
+ }
78290
+ function removeNumberedParagraphFromInlineTextBox(inline, tokens) {
78291
+ if (inline.type === "shape" && inline.txbxBlocks) {
78292
+ const updatedBlocks = removeNumberedParagraphFromBlocks(inline.txbxBlocks, tokens);
78293
+ return updatedBlocks ? { ...inline, txbxBlocks: updatedBlocks.blocks } : null;
78294
+ }
78295
+ if (inline.type === "drawing_frame" && inline.content.type === "shape" && inline.content.txbxBlocks) {
78296
+ const updatedBlocks = removeNumberedParagraphFromBlocks(inline.content.txbxBlocks, tokens);
78297
+ return updatedBlocks ? {
78298
+ ...inline,
78299
+ content: {
78300
+ ...inline.content,
78301
+ txbxBlocks: updatedBlocks.blocks
78302
+ }
78303
+ } : null;
78304
+ }
78305
+ return null;
78306
+ }
78307
+ function removeNumberedParagraphFromTable(table, tokens) {
78308
+ const [rowToken, cellToken, ...childTokens] = tokens;
78309
+ if (rowToken?.kind !== "row" || cellToken?.kind !== "cell" || childTokens[0]?.kind !== "block") {
78310
+ return null;
78311
+ }
78312
+ const row2 = table.rows[rowToken.index];
78313
+ const cell = row2?.cells[cellToken.index];
78314
+ if (!row2 || !cell) return null;
78315
+ const updatedChildren = removeNumberedParagraphFromBlocks(cell.children, childTokens);
78316
+ if (!updatedChildren) return null;
78317
+ const nextCells = [
78318
+ ...row2.cells.slice(0, cellToken.index),
78319
+ { ...cell, children: updatedChildren.blocks },
78320
+ ...row2.cells.slice(cellToken.index + 1)
78321
+ ];
78322
+ const nextRows = [
78323
+ ...table.rows.slice(0, rowToken.index),
78324
+ { ...row2, cells: nextCells },
78325
+ ...table.rows.slice(rowToken.index + 1)
78326
+ ];
78327
+ return { ...table, rows: nextRows };
78328
+ }
78329
+ function createSelectedListItemDeleteReplayCommand(input) {
78330
+ const { command, document: document2, selection, surface, storyTarget, timestamp } = input;
78331
+ if (command.type !== "text.delete-backward" && command.type !== "text.delete-forward") {
78332
+ return null;
78333
+ }
78334
+ const editableTarget = command.editableTarget;
78335
+ if (!editableTarget) {
78336
+ return null;
78337
+ }
78338
+ const targetResolution = resolveEditableTextTarget({
78339
+ document: document2,
78340
+ selection,
78341
+ surface,
78342
+ target: editableTarget,
78343
+ activeStoryKey: canonicalEditableTargetStoryKey(storyTarget)
78344
+ });
78345
+ const resolvedEditableTarget = targetResolution.kind === "accepted" ? editableTarget : storyTarget.kind !== "main" && blockPathBelongsToStoryTarget(editableTarget.blockPath, storyTarget) ? resolveEditableCommandTarget({
78346
+ document: document2,
78347
+ target: editableTarget,
78348
+ activeStoryKey: canonicalEditableTargetStoryKey(storyTarget),
78349
+ commandFamilies: ["text-leaf"]
78350
+ }) : null;
78351
+ const selectedRange = {
78352
+ from: Math.min(selection.anchor, selection.head),
78353
+ to: Math.max(selection.anchor, selection.head)
78354
+ };
78355
+ const replacement = createSelectedListItemDeleteReplacement({
78356
+ command,
78357
+ document: document2,
78358
+ editableTarget: resolvedEditableTarget && resolvedEditableTarget.kind === "accepted" ? resolvedEditableTarget.target : editableTarget,
78359
+ selection,
78360
+ storyTarget,
78361
+ targetResolution: resolvedEditableTarget && resolvedEditableTarget.kind === "accepted" ? {
78362
+ kind: "accepted",
78363
+ range: selectedRange
78364
+ } : targetResolution,
78365
+ timestamp
78366
+ });
78367
+ if (!replacement) {
78368
+ return null;
78369
+ }
78370
+ return {
78371
+ type: "document.replace",
78372
+ document: replacement.document,
78373
+ selection: replacement.selection,
78374
+ mapping: replacement.mapping,
78375
+ protectionSelection: selection,
78376
+ origin: command.origin
78377
+ };
78378
+ }
78379
+ function parseStoryBlockPathTokens(blockPath, storyTarget) {
78380
+ if (!blockPath) {
78381
+ return null;
78382
+ }
78383
+ const firstTokenMatch = /\/(?:block|row|cell)\[\d+\]/u.exec(blockPath);
78384
+ if (!firstTokenMatch?.index) {
78385
+ return null;
78386
+ }
78387
+ const storyPrefix = blockPath.slice(0, firstTokenMatch.index);
78388
+ if (!blockPathBelongsToStoryTarget(storyPrefix, storyTarget)) {
78389
+ return null;
78390
+ }
78391
+ const parts = blockPath.slice(firstTokenMatch.index + 1).split("/");
78392
+ const tokens = [];
78393
+ for (const part of parts) {
78394
+ if (part === "txbx") {
78395
+ tokens.push({ kind: "txbx" });
78396
+ continue;
78397
+ }
78398
+ const match = /^(block|row|cell|inline)\[(\d+)\]$/u.exec(part);
78399
+ if (!match) return null;
78400
+ const kind = match[1];
78401
+ tokens.push({
78402
+ kind,
78403
+ index: Number.parseInt(match[2], 10)
78404
+ });
78405
+ }
78406
+ return tokens;
78407
+ }
78408
+ function blockPathBelongsToStoryTarget(storyPrefix, storyTarget) {
78409
+ switch (storyTarget.kind) {
78410
+ case "main":
78411
+ return storyPrefix === "main" || storyPrefix.startsWith("main/");
78412
+ case "header":
78413
+ return storyPrefix.startsWith("header:");
78414
+ case "footer":
78415
+ return storyPrefix.startsWith("footer:");
78416
+ case "footnote":
78417
+ return storyPrefix === `footnote:${storyTarget.noteId}` || storyPrefix.startsWith(`footnote:${storyTarget.noteId}/`);
78418
+ case "endnote":
78419
+ return storyPrefix === `endnote:${storyTarget.noteId}` || storyPrefix.startsWith(`endnote:${storyTarget.noteId}/`);
78420
+ }
78421
+ }
77730
78422
  function toInternalSelectionSnapshot2(selection) {
77731
78423
  return {
77732
78424
  anchor: selection.anchor,
@@ -94200,7 +94892,8 @@ async function persistAndExport(input) {
94200
94892
  });
94201
94893
  result = withExportDelivery(result, {
94202
94894
  mode: "persisted-by-host",
94203
- savedAt: saveResult.savedAt
94895
+ savedAt: saveResult.savedAt,
94896
+ ...input.options?.download === false ? { downloadSuppressed: true } : {}
94204
94897
  });
94205
94898
  } catch (error) {
94206
94899
  const normalized = normalizeStorageError(error, {
@@ -94621,18 +95314,7 @@ function findScrollAnchor(root, options) {
94621
95314
  offsetWithinBlock: viewportTopFramePx - blockTop
94622
95315
  };
94623
95316
  }
94624
- }
94625
- const rootRect = root.getBoundingClientRect();
94626
- const rootTop = rootRect.top;
94627
- for (const block of blocks) {
94628
- const rect3 = block.getBoundingClientRect();
94629
- if (rect3.bottom < rootTop) continue;
94630
- const blockId = block.getAttribute("data-block-id");
94631
- if (!blockId) continue;
94632
- return {
94633
- blockId,
94634
- offsetWithinBlock: rootTop - rect3.top
94635
- };
95317
+ return null;
94636
95318
  }
94637
95319
  return null;
94638
95320
  }
@@ -94650,17 +95332,9 @@ function resolveScrollTopForAnchor(root, anchor, options) {
94650
95332
  const rect3 = geometry.rects[0];
94651
95333
  return rect3.topPx + anchor.offsetWithinBlock;
94652
95334
  }
95335
+ return null;
94653
95336
  }
94654
- const selector = `[data-block-id="${cssEscape(anchor.blockId)}"]`;
94655
- const block = root.querySelector(selector);
94656
- if (!block) return null;
94657
- const rootRect = root.getBoundingClientRect();
94658
- const blockRect = block.getBoundingClientRect();
94659
- const delta = blockRect.top - rootRect.top + anchor.offsetWithinBlock;
94660
- return root.scrollTop + delta;
94661
- }
94662
- function cssEscape(value) {
94663
- return value.replace(/[^a-zA-Z0-9_-]/g, (ch) => `\\${ch}`);
95337
+ return null;
94664
95338
  }
94665
95339
 
94666
95340
  // src/ui-tailwind/chrome/tw-alert-banner.tsx
@@ -103245,6 +103919,14 @@ var EDITOR_ACTION_REGISTRY = [
103245
103919
  targetKinds: ["generated-field"],
103246
103920
  callback: "onUpdateFields"
103247
103921
  }),
103922
+ mk({
103923
+ id: "toc-refresh",
103924
+ label: "Refresh table of contents",
103925
+ description: "Refresh TOC entries through the runtime table-of-contents updater.",
103926
+ group: "misc",
103927
+ targetKinds: ["toc-field"],
103928
+ callback: "onUpdateTableOfContents"
103929
+ }),
103248
103930
  // -------- Workflow scope --------
103249
103931
  mk({
103250
103932
  id: "scope-open-card",
@@ -104138,6 +104820,18 @@ function hasAncestorAttributeValue(el, attribute, expected, root) {
104138
104820
  }
104139
104821
  return false;
104140
104822
  }
104823
+ function hasAncestorAttributeValueInsensitive(el, attribute, expected, root) {
104824
+ const normalizedExpected = expected.toLowerCase();
104825
+ let cursor = el;
104826
+ while (cursor) {
104827
+ if (cursor.getAttribute?.(attribute)?.toLowerCase() === normalizedExpected) {
104828
+ return true;
104829
+ }
104830
+ if (cursor === root) return false;
104831
+ cursor = cursor.parentElement;
104832
+ }
104833
+ return false;
104834
+ }
104141
104835
  function resolveTargetKind(target, options = {}) {
104142
104836
  const kinds = [];
104143
104837
  const el = toElement(target);
@@ -104152,7 +104846,12 @@ function resolveTargetKind(target, options = {}) {
104152
104846
  const insideListItem = insideNumberingMarker || hasAncestorAttributeValue(el, "data-numbered", "true", root);
104153
104847
  const insideGeneratedField = hasAncestorAttributeValue(el, "data-generated-field", "true", root) || hasAncestorAttributeValue(el, "data-node-type", "field_ref_atom", root) || hasAncestorAttributeValue(el, "data-node-type", "field_ref", root);
104154
104848
  if (insideListItem) kinds.push("list-item");
104155
- if (insideGeneratedField) kinds.push("generated-field");
104849
+ if (insideGeneratedField) {
104850
+ kinds.push("generated-field");
104851
+ if (hasAncestorAttributeValueInsensitive(el, "data-field-family", "TOC", root)) {
104852
+ kinds.push("toc-field");
104853
+ }
104854
+ }
104156
104855
  if (hasAncestorTag(el, "a", root)) kinds.push("hyperlink");
104157
104856
  if (!insideNumberingMarker && hasAncestorTag(el, "img", root)) {
104158
104857
  kinds.push("image");
@@ -105225,294 +105924,6 @@ function resolveSkeletalPageOverlayRectsFromLayout(facet) {
105225
105924
  }
105226
105925
  return rects;
105227
105926
  }
105228
- function pageOverlayLastBottom(rects) {
105229
- let bottom = 0;
105230
- for (const rect3 of rects) {
105231
- if (rect3.bottomPx > bottom) bottom = rect3.bottomPx;
105232
- }
105233
- return bottom;
105234
- }
105235
- function extendFinalPageOverlayRectToFlowHeight(rects, flowHeightPx) {
105236
- if (rects.length === 0 || !Number.isFinite(flowHeightPx)) return rects;
105237
- const last = rects[rects.length - 1];
105238
- if (flowHeightPx <= last.bottomPx + 1) return rects;
105239
- return [
105240
- ...rects.slice(0, -1),
105241
- {
105242
- ...last,
105243
- bottomPx: flowHeightPx,
105244
- heightPx: Math.max(0, flowHeightPx - last.topPx)
105245
- }
105246
- ];
105247
- }
105248
- function extendPageOverlayRectsAcrossTableBoundaryGaps(rects, tableBoundaryIndices) {
105249
- if (rects.length === 0 || tableBoundaryIndices.length === 0) return rects;
105250
- const tableBoundaries = new Set(tableBoundaryIndices);
105251
- const byPageIndex = /* @__PURE__ */ new Map();
105252
- for (const rect3 of rects) {
105253
- byPageIndex.set(rect3.pageIndex, rect3);
105254
- }
105255
- let changed = false;
105256
- const bridged = rects.map((rect3) => {
105257
- if (!tableBoundaries.has(rect3.pageIndex)) return rect3;
105258
- const next = byPageIndex.get(rect3.pageIndex + 1);
105259
- if (!next || next.topPx <= rect3.bottomPx + 1) return rect3;
105260
- changed = true;
105261
- return {
105262
- ...rect3,
105263
- bottomPx: next.topPx,
105264
- heightPx: Math.max(0, next.topPx - rect3.topPx)
105265
- };
105266
- });
105267
- return changed ? bridged : rects;
105268
- }
105269
- function mergePageOverlayRectsByPageIndex(baseRects, flowRects) {
105270
- if (baseRects.length === 0 || flowRects.length === 0) return baseRects;
105271
- const flowByIndex = /* @__PURE__ */ new Map();
105272
- for (const rect3 of flowRects) {
105273
- flowByIndex.set(rect3.pageIndex, rect3);
105274
- }
105275
- return baseRects.map((rect3) => flowByIndex.get(rect3.pageIndex) ?? rect3);
105276
- }
105277
- function normalizeVisiblePageIndexRange(range, pageCount) {
105278
- if (!range || pageCount <= 0) return null;
105279
- const start = Math.max(0, Math.min(range.start, pageCount));
105280
- const end = Math.max(start, Math.min(range.end, pageCount));
105281
- if (start >= end) return null;
105282
- return { start, end };
105283
- }
105284
- function collectBoundaryIndicesForVisibleRange(range, pageCount) {
105285
- if (pageCount <= 1) return [];
105286
- const startBoundaryIndex = Math.max(0, range.start - 1);
105287
- const endBoundaryIndex = Math.min(pageCount - 2, range.end - 1);
105288
- if (startBoundaryIndex > endBoundaryIndex) return [];
105289
- const indices = [];
105290
- for (let index = startBoundaryIndex; index <= endBoundaryIndex; index += 1) {
105291
- indices.push(index);
105292
- }
105293
- return indices;
105294
- }
105295
- function parsePageBoundaryIndex(prevPageId) {
105296
- const match = /^page-(\d+)$/.exec(prevPageId);
105297
- if (!match) return void 0;
105298
- return Number.parseInt(match[1] ?? "", 10);
105299
- }
105300
- function collectTableEmbeddedBoundaryIndices(queryRoot) {
105301
- if (!queryRoot) return [];
105302
- const indices = [];
105303
- const widgets = Array.from(
105304
- queryRoot.querySelectorAll("[data-page-frame-end]")
105305
- );
105306
- for (const widget of widgets) {
105307
- const prevPageId = widget.getAttribute("data-page-frame-end");
105308
- if (!prevPageId) continue;
105309
- const boundaryIndex = parsePageBoundaryIndex(prevPageId);
105310
- if (boundaryIndex === void 0) continue;
105311
- if (widget.closest("[data-pm-table-root='true'], table")) {
105312
- indices.push(boundaryIndex);
105313
- }
105314
- }
105315
- return indices;
105316
- }
105317
- function containsTableBoundaryRisk(queryRoot) {
105318
- if (!queryRoot) return false;
105319
- if (queryRoot.getElementsByTagName("table").length > 0) return true;
105320
- const descendants = queryRoot.getElementsByTagName("*");
105321
- for (let i = 0; i < descendants.length; i += 1) {
105322
- const element = descendants[i];
105323
- if (element.getAttribute("data-pm-table-root") === "true") {
105324
- return true;
105325
- }
105326
- }
105327
- return false;
105328
- }
105329
- function resolvePageOverlayRects(input, legacyPageCount) {
105330
- let widgets;
105331
- let pageCount;
105332
- let scrollHeight;
105333
- if (Array.isArray(input)) {
105334
- const [scrollRoot, count] = input;
105335
- if (!scrollRoot || count <= 0) return [];
105336
- widgets = measureWidgetsViaOffsetChain(scrollRoot);
105337
- pageCount = count;
105338
- scrollHeight = scrollRoot.clientHeight;
105339
- } else if (input !== null && typeof input === "object" && "widgets" in input) {
105340
- widgets = input.widgets;
105341
- pageCount = input.pageCount;
105342
- scrollHeight = input.scrollHeight;
105343
- } else if (input && legacyPageCount !== void 0) {
105344
- const scrollRoot = input;
105345
- if (legacyPageCount <= 0) return [];
105346
- widgets = measureWidgetsViaOffsetChain(scrollRoot);
105347
- pageCount = legacyPageCount;
105348
- scrollHeight = scrollRoot.clientHeight;
105349
- } else {
105350
- return [];
105351
- }
105352
- if (pageCount <= 0) return [];
105353
- const boundaries = [...widgets].sort((a, b) => a.topPx - b.topPx);
105354
- const normalizedVisiblePageIndexRange = Array.isArray(input) ? null : normalizeVisiblePageIndexRange(input.visiblePageIndexRange, pageCount);
105355
- const boundaryByIndex = /* @__PURE__ */ new Map();
105356
- boundaries.forEach((boundary, index) => {
105357
- const boundaryIndex = boundary.boundaryIndex ?? parsePageBoundaryIndex(boundary.prevPageId) ?? index;
105358
- boundaryByIndex.set(boundaryIndex, boundary);
105359
- });
105360
- const pageStart = normalizedVisiblePageIndexRange?.start ?? 0;
105361
- const pageEnd = normalizedVisiblePageIndexRange?.end ?? pageCount;
105362
- const rects = [];
105363
- for (let pageIndex = pageStart; pageIndex < pageEnd; pageIndex += 1) {
105364
- const boundaryBefore = pageIndex === 0 ? null : boundaryByIndex.get(pageIndex - 1) ?? null;
105365
- const boundaryAfter = pageIndex === pageCount - 1 ? null : boundaryByIndex.get(pageIndex) ?? null;
105366
- let pageId = null;
105367
- if (boundaryBefore) pageId = boundaryBefore.nextPageId;
105368
- else if (boundaryAfter) pageId = boundaryAfter.prevPageId;
105369
- if (!pageId) pageId = `page-${pageIndex}`;
105370
- const topPx = boundaryBefore ? boundaryBefore.bottomPx : 0;
105371
- const bottomPx = boundaryAfter ? boundaryAfter.topPx : scrollHeight;
105372
- if (bottomPx <= topPx) continue;
105373
- rects.push({
105374
- pageId,
105375
- pageIndex,
105376
- topPx,
105377
- bottomPx,
105378
- heightPx: bottomPx - topPx
105379
- });
105380
- }
105381
- return rects;
105382
- }
105383
- function measureWidgetsViaBoundingRect(queryRoot, originElement, options) {
105384
- if (!queryRoot || !originElement) return [];
105385
- const originRect = originElement.getBoundingClientRect();
105386
- const normalizedVisiblePageIndexRange = normalizeVisiblePageIndexRange(
105387
- options?.visiblePageIndexRange,
105388
- options?.pageCount ?? 0
105389
- );
105390
- const queryOne = typeof queryRoot.querySelector === "function" ? queryRoot.querySelector.bind(queryRoot) : null;
105391
- const widgets = normalizedVisiblePageIndexRange && queryOne && options?.pageCount ? collectBoundaryIndicesForVisibleRange(
105392
- normalizedVisiblePageIndexRange,
105393
- options.pageCount
105394
- ).map(
105395
- (boundaryIndex) => queryOne(`[data-page-frame-end="page-${boundaryIndex}"]`)
105396
- ).filter((widget) => widget !== null) : Array.from(
105397
- queryRoot.querySelectorAll("[data-page-frame-end]")
105398
- );
105399
- const out = [];
105400
- for (const widget of widgets) {
105401
- const prevPageId = widget.getAttribute("data-page-frame-end");
105402
- const nextPageId = widget.getAttribute("data-page-frame-start");
105403
- if (!prevPageId || !nextPageId) continue;
105404
- const rect3 = widget.getBoundingClientRect();
105405
- out.push({
105406
- prevPageId,
105407
- nextPageId,
105408
- boundaryIndex: parsePageBoundaryIndex(prevPageId),
105409
- topPx: rect3.top - originRect.top,
105410
- bottomPx: rect3.bottom - originRect.top
105411
- });
105412
- }
105413
- return out;
105414
- }
105415
- function measureWidgetsViaOffsetChain(scrollRoot, options) {
105416
- const normalizedVisiblePageIndexRange = normalizeVisiblePageIndexRange(
105417
- options?.visiblePageIndexRange,
105418
- options?.pageCount ?? 0
105419
- );
105420
- const queryOne = typeof scrollRoot.querySelector === "function" ? scrollRoot.querySelector.bind(scrollRoot) : null;
105421
- const widgets = normalizedVisiblePageIndexRange && queryOne && options?.pageCount ? collectBoundaryIndicesForVisibleRange(
105422
- normalizedVisiblePageIndexRange,
105423
- options.pageCount
105424
- ).map(
105425
- (boundaryIndex) => queryOne(`[data-page-frame-end="page-${boundaryIndex}"]`)
105426
- ).filter((widget) => widget !== null) : Array.from(
105427
- scrollRoot.querySelectorAll("[data-page-frame-end]")
105428
- );
105429
- const out = [];
105430
- for (const widget of widgets) {
105431
- const prevPageId = widget.getAttribute("data-page-frame-end");
105432
- const nextPageId = widget.getAttribute("data-page-frame-start");
105433
- if (!prevPageId || !nextPageId) continue;
105434
- const topPx = resolveOffsetTop(widget, scrollRoot);
105435
- const bottomPx = topPx + resolveOffsetHeight(widget);
105436
- out.push({
105437
- prevPageId,
105438
- nextPageId,
105439
- boundaryIndex: parsePageBoundaryIndex(prevPageId),
105440
- topPx,
105441
- bottomPx
105442
- });
105443
- }
105444
- return out;
105445
- }
105446
- function resolveOffsetTop(widget, scrollRoot) {
105447
- let node = widget;
105448
- let top = 0;
105449
- while (node) {
105450
- top += node.offsetTop ?? 0;
105451
- const parent = node.offsetParent;
105452
- if (parent === scrollRoot || parent === null) break;
105453
- node = parent;
105454
- }
105455
- return top;
105456
- }
105457
- function resolveOffsetHeight(widget) {
105458
- return widget.offsetHeight ?? 0;
105459
- }
105460
- function readElementFlowHeight(element, options = {}) {
105461
- if (!element) return 0;
105462
- let height = 0;
105463
- height = Math.max(height, element.clientHeight || 0);
105464
- if (options.includeScrollHeight !== false) {
105465
- height = Math.max(height, element.scrollHeight || 0);
105466
- }
105467
- if (height <= 0) {
105468
- const rect3 = element.getBoundingClientRect();
105469
- height = Math.max(height, rect3.height || 0);
105470
- }
105471
- return height;
105472
- }
105473
- function readOverlayFlowHeight(origin) {
105474
- if (!origin) return 0;
105475
- const parent = origin.parentElement instanceof HTMLElement ? origin.parentElement : null;
105476
- const parentHeight = readElementFlowHeight(parent);
105477
- if (parentHeight > 0) return parentHeight;
105478
- return readElementFlowHeight(origin, { includeScrollHeight: false });
105479
- }
105480
- function reconcilePageStackRectsWithFlow(input) {
105481
- const { baseRects, pageCount, scrollRoot, originElement } = input;
105482
- if (baseRects.length === 0 || pageCount <= 0) return baseRects;
105483
- const flowHeight = readOverlayFlowHeight(originElement);
105484
- if (flowHeight <= 0) return baseRects;
105485
- const geometryBottom = pageOverlayLastBottom(baseRects);
105486
- const tableBoundaryRisk = containsTableBoundaryRisk(scrollRoot);
105487
- if (!tableBoundaryRisk && flowHeight <= geometryBottom + 1) {
105488
- return extendFinalPageOverlayRectToFlowHeight(baseRects, flowHeight);
105489
- }
105490
- const bridgedBase = extendPageOverlayRectsAcrossTableBoundaryGaps(
105491
- baseRects,
105492
- tableBoundaryRisk ? collectTableEmbeddedBoundaryIndices(scrollRoot) : []
105493
- );
105494
- const extendedBase = extendFinalPageOverlayRectToFlowHeight(
105495
- bridgedBase,
105496
- flowHeight
105497
- );
105498
- if (!originElement || !scrollRoot) return extendedBase;
105499
- if (flowHeight <= pageOverlayLastBottom(bridgedBase) + 1) {
105500
- return extendedBase;
105501
- }
105502
- const widgets = measureWidgetsViaBoundingRect(scrollRoot, originElement, {
105503
- pageCount,
105504
- visiblePageIndexRange: null
105505
- });
105506
- if (widgets.length === 0) return extendedBase;
105507
- const flowRects = resolvePageOverlayRects({
105508
- widgets,
105509
- pageCount,
105510
- scrollHeight: flowHeight,
105511
- visiblePageIndexRange: null
105512
- });
105513
- const merged = mergePageOverlayRectsByPageIndex(extendedBase, flowRects);
105514
- return extendFinalPageOverlayRectToFlowHeight(merged, flowHeight);
105515
- }
105516
105927
  var resolvePageOverlayRectsFromGeometry2 = resolvePageOverlayRectsFromGeometry;
105517
105928
  function resolvePageOverlayRectsFromUiApi(ui, pageCount, visiblePageIndexRange, pageIds) {
105518
105929
  if (pageCount <= 0) return [];
@@ -105570,17 +105981,6 @@ var TwPageStackOverlayLayer = ({
105570
105981
  },
105571
105982
  []
105572
105983
  );
105573
- const reconcilePaperRectsWithFlow = React23.useCallback(
105574
- (baseRects, pageCount) => {
105575
- return reconcilePageStackRectsWithFlow({
105576
- baseRects,
105577
- pageCount,
105578
- scrollRoot,
105579
- originElement: overlayRootRef.current
105580
- });
105581
- },
105582
- [scrollRoot]
105583
- );
105584
105984
  const refreshRectsNow = React23.useCallback(() => {
105585
105985
  const pageCount = facet.getPageCount();
105586
105986
  const skeletalRects = resolveSkeletalPageOverlayRectsFromLayout(facet);
@@ -105621,51 +106021,11 @@ var TwPageStackOverlayLayer = ({
105621
106021
  setRectsIfChanged(skeletalRects);
105622
106022
  return;
105623
106023
  }
105624
- if (!scrollRoot) {
105625
- setRectsIfChanged(skeletalRects);
105626
- return;
105627
- }
105628
- const origin = overlayRootRef.current;
105629
- incrementInvalidationCounter("overlay.page.dom_fallback");
105630
- if (origin) {
105631
- incrementInvalidationCounter("overlay.page.dom.degraded");
105632
- const widgets = measureWidgetsViaBoundingRect(scrollRoot, origin, {
105633
- pageCount,
105634
- visiblePageIndexRange: null
105635
- });
105636
- const originRect = origin.getBoundingClientRect();
105637
- const domRects = resolvePageOverlayRects({
105638
- widgets,
105639
- pageCount,
105640
- scrollHeight: (
105641
- // geometry:allow-dom-fallback
105642
- origin.clientHeight > 0 ? origin.clientHeight : originRect.height
105643
- ),
105644
- visiblePageIndexRange: null
105645
- });
105646
- const reconciled = reconcilePaperRectsWithFlow(domRects, pageCount);
105647
- setRectsIfChanged(reconciled.length > 0 ? reconciled : skeletalRects);
105648
- } else {
105649
- incrementInvalidationCounter("overlay.page.dom.degraded");
105650
- const widgets = measureWidgetsViaOffsetChain(scrollRoot, {
105651
- pageCount,
105652
- visiblePageIndexRange: null
105653
- });
105654
- const domRects = resolvePageOverlayRects({
105655
- widgets,
105656
- pageCount,
105657
- // geometry:allow-dom-fallback
105658
- scrollHeight: scrollRoot.clientHeight,
105659
- visiblePageIndexRange: null
105660
- });
105661
- const reconciled = reconcilePaperRectsWithFlow(domRects, pageCount);
105662
- setRectsIfChanged(reconciled.length > 0 ? reconciled : skeletalRects);
105663
- }
106024
+ incrementInvalidationCounter("overlay.page.skeletal_fallback");
106025
+ setRectsIfChanged(skeletalRects);
105664
106026
  }, [
105665
106027
  facet,
105666
106028
  geometryFacet,
105667
- reconcilePaperRectsWithFlow,
105668
- scrollRoot,
105669
106029
  setRectsIfChanged,
105670
106030
  ui
105671
106031
  ]);
@@ -105696,33 +106056,6 @@ var TwPageStackOverlayLayer = ({
105696
106056
  }
105697
106057
  };
105698
106058
  }, [refreshRects, renderFrameRevision, scrollRoot]);
105699
- React23.useEffect(() => {
105700
- if (geometryFacet) return;
105701
- if (!scrollRoot) return;
105702
- const runtime = scrollRoot.ownerDocument?.defaultView;
105703
- if (!runtime?.ResizeObserver) return;
105704
- const observer = new runtime.ResizeObserver(() => refreshRects());
105705
- observer.observe(scrollRoot);
105706
- return () => observer.disconnect();
105707
- }, [geometryFacet, scrollRoot, refreshRects]);
105708
- React23.useEffect(() => {
105709
- if (geometryFacet) return;
105710
- if (!scrollRoot) return;
105711
- const runtime = scrollRoot.ownerDocument?.defaultView;
105712
- if (!runtime?.MutationObserver) return;
105713
- const observer = new runtime.MutationObserver((records) => {
105714
- const overlay = overlayRootRef.current;
105715
- if (overlay) {
105716
- const allSelf = records.every(
105717
- (r) => r.target instanceof Node && overlay.contains(r.target)
105718
- );
105719
- if (allSelf) return;
105720
- }
105721
- refreshRects();
105722
- });
105723
- observer.observe(scrollRoot, { childList: true, subtree: false });
105724
- return () => observer.disconnect();
105725
- }, [geometryFacet, scrollRoot, refreshRects]);
105726
106059
  if (rects.length === 0) {
105727
106060
  return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
105728
106061
  "div",
@@ -107814,25 +108147,23 @@ var TwPageStackChromeLayerInner = ({
107814
108147
  const pageCount = facet.getPageCount();
107815
108148
  const uiRects = resolveUiPageRects(pageCount);
107816
108149
  if (uiRects !== null) return uiRects;
107817
- if (!geometryFacet) return [];
107818
- const warm = resolvePageOverlayRectsFromGeometry2(
107819
- geometryFacet,
107820
- pageCount,
107821
- visiblePageIndexRange
108150
+ if (geometryFacet) {
108151
+ const warm = resolvePageOverlayRectsFromGeometry2(
108152
+ geometryFacet,
108153
+ pageCount,
108154
+ visiblePageIndexRange
108155
+ );
108156
+ if (warm !== null) return warm;
108157
+ }
108158
+ return resolveSkeletalPageOverlayRectsFromLayout(facet).filter(
108159
+ (rect3) => !visiblePageIndexRange || rect3.pageIndex >= visiblePageIndexRange.start && rect3.pageIndex < visiblePageIndexRange.end
107822
108160
  );
107823
- return warm ?? [];
107824
108161
  });
107825
108162
  const overlayRootRef = import_react33.default.useRef(null);
107826
108163
  const rafHandleRef = import_react33.default.useRef(null);
107827
108164
  const [activeStoryPageIndex, setActiveStoryPageIndex] = import_react33.default.useState(null);
107828
108165
  const refreshRectsNow = import_react33.default.useCallback(() => {
107829
108166
  const pageCount = facet.getPageCount();
107830
- const reconcileDomRects = (baseRects) => reconcilePageStackRectsWithFlow({
107831
- baseRects,
107832
- pageCount,
107833
- scrollRoot,
107834
- originElement: overlayRootRef.current
107835
- });
107836
108167
  const uiRects = resolveUiPageRects(pageCount);
107837
108168
  if (uiRects !== null) {
107838
108169
  setRects(uiRects);
@@ -107851,47 +108182,11 @@ var TwPageStackChromeLayerInner = ({
107851
108182
  setRects([]);
107852
108183
  return;
107853
108184
  }
107854
- if (!scrollRoot) {
107855
- setRects([]);
107856
- return;
107857
- }
107858
- const origin = overlayRootRef.current;
107859
- if (origin) {
107860
- const widgets = measureWidgetsViaBoundingRect(scrollRoot, origin, {
107861
- pageCount,
107862
- visiblePageIndexRange
107863
- });
107864
- const originRect = origin.getBoundingClientRect();
107865
- const scrollHeight = (
107866
- // geometry:allow-dom-fallback
107867
- origin.clientHeight > 0 ? origin.clientHeight : originRect.height > 0 ? originRect.height : scrollRoot.clientHeight
107868
- );
107869
- const domRects = resolvePageOverlayRects({
107870
- widgets,
107871
- pageCount,
107872
- scrollHeight,
107873
- visiblePageIndexRange
107874
- });
107875
- setRects(
107876
- reconcileDomRects(domRects)
107877
- );
107878
- } else {
107879
- const widgets = measureWidgetsViaOffsetChain(scrollRoot, {
107880
- pageCount,
107881
- visiblePageIndexRange
107882
- });
107883
- const domRects = resolvePageOverlayRects({
107884
- widgets,
107885
- pageCount,
107886
- // geometry:allow-dom-fallback
107887
- scrollHeight: scrollRoot.clientHeight,
107888
- visiblePageIndexRange
107889
- });
107890
- setRects(
107891
- reconcileDomRects(domRects)
107892
- );
107893
- }
107894
- }, [facet, geometryFacet, resolveUiPageRects, scrollRoot, visiblePageIndexRange]);
108185
+ const skeletalRects = resolveSkeletalPageOverlayRectsFromLayout(facet).filter(
108186
+ (rect3) => !visiblePageIndexRange || rect3.pageIndex >= visiblePageIndexRange.start && rect3.pageIndex < visiblePageIndexRange.end
108187
+ );
108188
+ setRects(skeletalRects);
108189
+ }, [facet, geometryFacet, resolveUiPageRects, visiblePageIndexRange]);
107895
108190
  const refreshRects = import_react33.default.useCallback(() => {
107896
108191
  if (!scrollRoot) {
107897
108192
  refreshRectsNow();
@@ -107931,33 +108226,6 @@ var TwPageStackChromeLayerInner = ({
107931
108226
  },
107932
108227
  [onOpenStory]
107933
108228
  );
107934
- import_react33.default.useEffect(() => {
107935
- if (geometryFacet) return;
107936
- if (!scrollRoot) return;
107937
- const runtime = scrollRoot.ownerDocument?.defaultView;
107938
- if (!runtime?.ResizeObserver) return;
107939
- const observer = new runtime.ResizeObserver(() => refreshRects());
107940
- observer.observe(scrollRoot);
107941
- return () => observer.disconnect();
107942
- }, [geometryFacet, scrollRoot, refreshRects]);
107943
- import_react33.default.useEffect(() => {
107944
- if (geometryFacet) return;
107945
- if (!scrollRoot) return;
107946
- const runtime = scrollRoot.ownerDocument?.defaultView;
107947
- if (!runtime?.MutationObserver) return;
107948
- const observer = new runtime.MutationObserver((records) => {
107949
- const overlay = overlayRootRef.current;
107950
- if (overlay) {
107951
- const allSelf = records.every(
107952
- (r) => r.target instanceof Node && overlay.contains(r.target)
107953
- );
107954
- if (allSelf) return;
107955
- }
107956
- refreshRects();
107957
- });
107958
- observer.observe(scrollRoot, { childList: true, subtree: false });
107959
- return () => observer.disconnect();
107960
- }, [geometryFacet, scrollRoot, refreshRects]);
107961
108229
  import_react33.default.useLayoutEffect(() => {
107962
108230
  if (!pmSurfaceElement) return;
107963
108231
  const overlay = overlayRootRef.current;
@@ -108135,6 +108403,10 @@ function TwTableGripLayer({
108135
108403
  tableGeometry,
108136
108404
  plan.columnsTwips
108137
108405
  );
108406
+ const columnWidthCapability = tableContext.operations.setColumnWidth;
108407
+ const rowHeightCapability = tableContext.operations.setRowHeight;
108408
+ const columnResizeDisabled = !!disabled || !onSetColumnWidth || columnWidthCapability?.enabled === false;
108409
+ const rowResizeDisabled = !!disabled || !onSetRowHeight || rowHeightCapability?.enabled === false;
108138
108410
  return /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(import_jsx_runtime61.Fragment, { children: [
108139
108411
  plan.columnResizeHandles.map((handle) => {
108140
108412
  const rect3 = resolveColumnEdgeRect(
@@ -108149,7 +108421,8 @@ function TwTableGripLayer({
108149
108421
  colIndex: handle.columnIndex,
108150
108422
  originalTwips: plan.columnsTwips[handle.columnIndex] ?? 720,
108151
108423
  pxPerTwip,
108152
- disabled: !!disabled || !onSetColumnWidth,
108424
+ disabled: columnResizeDisabled,
108425
+ disabledReason: columnWidthCapability?.reason,
108153
108426
  onCommit: onSetColumnWidth
108154
108427
  },
108155
108428
  `col-${blockId}-${handle.columnIndex}`
@@ -108170,7 +108443,8 @@ function TwTableGripLayer({
108170
108443
  rowIndex,
108171
108444
  originalTwips,
108172
108445
  pxPerTwip,
108173
- disabled: !!disabled || !onSetRowHeight,
108446
+ disabled: rowResizeDisabled,
108447
+ disabledReason: rowHeightCapability?.reason,
108174
108448
  onCommit: onSetRowHeight
108175
108449
  },
108176
108450
  `row-${blockId}-${rowIndex}`
@@ -108260,6 +108534,7 @@ function ColResizeGrip({
108260
108534
  originalTwips,
108261
108535
  pxPerTwip,
108262
108536
  disabled,
108537
+ disabledReason,
108263
108538
  onCommit
108264
108539
  }) {
108265
108540
  const dragRef = (0, import_react34.useRef)(null);
@@ -108318,11 +108593,13 @@ function ColResizeGrip({
108318
108593
  role: "separator",
108319
108594
  "aria-orientation": "vertical",
108320
108595
  "aria-label": `Resize column ${colIndex + 1}`,
108596
+ "aria-disabled": disabled ? "true" : void 0,
108321
108597
  "data-testid": `col-resize-grip-${colIndex}`,
108322
108598
  "data-active": isActive ? "true" : "false",
108599
+ title: disabled ? disabledReason : void 0,
108323
108600
  className: [
108324
- "wre-table-grip-col pointer-events-auto absolute",
108325
- disabled ? "opacity-0 cursor-default" : ""
108601
+ "wre-table-grip-col absolute",
108602
+ disabled ? "pointer-events-none opacity-0 cursor-default" : "pointer-events-auto"
108326
108603
  ].filter(Boolean).join(" "),
108327
108604
  style: {
108328
108605
  left: `calc(${pos.left} - ${GRIP_HIT_PX / 2}px)`,
@@ -108340,6 +108617,7 @@ function RowResizeGrip({
108340
108617
  originalTwips,
108341
108618
  pxPerTwip,
108342
108619
  disabled,
108620
+ disabledReason,
108343
108621
  onCommit
108344
108622
  }) {
108345
108623
  const dragRef = (0, import_react34.useRef)(null);
@@ -108399,11 +108677,13 @@ function RowResizeGrip({
108399
108677
  role: "separator",
108400
108678
  "aria-orientation": "horizontal",
108401
108679
  "aria-label": `Resize row ${rowIndex + 1}`,
108680
+ "aria-disabled": disabled ? "true" : void 0,
108402
108681
  "data-testid": `row-resize-grip-${rowIndex}`,
108403
108682
  "data-active": isActive ? "true" : "false",
108683
+ title: disabled ? disabledReason : void 0,
108404
108684
  className: [
108405
- "wre-table-grip-row pointer-events-auto absolute",
108406
- disabled ? "opacity-0 cursor-default" : ""
108685
+ "wre-table-grip-row absolute",
108686
+ disabled ? "pointer-events-none opacity-0 cursor-default" : "pointer-events-auto"
108407
108687
  ].filter(Boolean).join(" "),
108408
108688
  style: {
108409
108689
  left: pos.left,
@@ -109439,45 +109719,17 @@ var TwFloatingImageLayer = ({
109439
109719
  const [pageRects, setPageRects] = React39.useState([]);
109440
109720
  const refreshPageRectsNow = React39.useCallback(() => {
109441
109721
  const pageCount = facet.getPageCount();
109442
- if (geometryFacet) {
109443
- const geometryRects = resolvePageOverlayRectsFromGeometry2(
109444
- geometryFacet,
109445
- pageCount,
109446
- visiblePageIndexRange
109447
- );
109448
- if (geometryRects !== null) {
109449
- setPageRects(geometryRects);
109450
- return;
109451
- }
109452
- setPageRects([]);
109453
- return;
109454
- }
109455
- if (!scrollRoot) {
109722
+ if (!geometryFacet) {
109456
109723
  setPageRects([]);
109457
109724
  return;
109458
109725
  }
109459
- const origin = overlayRootRef.current;
109460
- if (!origin) {
109461
- setPageRects([]);
109462
- return;
109463
- }
109464
- const widgets = measureWidgetsViaBoundingRect(scrollRoot, origin, {
109726
+ const geometryRects = resolvePageOverlayRectsFromGeometry2(
109727
+ geometryFacet,
109465
109728
  pageCount,
109466
109729
  visiblePageIndexRange
109467
- });
109468
- const originRect = origin.getBoundingClientRect();
109469
- setPageRects(
109470
- resolvePageOverlayRects({
109471
- widgets,
109472
- pageCount,
109473
- scrollHeight: (
109474
- // geometry:allow-dom-fallback
109475
- origin.clientHeight > 0 ? origin.clientHeight : originRect.height
109476
- ),
109477
- visiblePageIndexRange
109478
- })
109479
109730
  );
109480
- }, [facet, geometryFacet, scrollRoot, visiblePageIndexRange]);
109731
+ setPageRects(geometryRects ?? []);
109732
+ }, [facet, geometryFacet, visiblePageIndexRange]);
109481
109733
  const refreshPageRects = React39.useCallback(() => {
109482
109734
  if (!scrollRoot) {
109483
109735
  refreshPageRectsNow();
@@ -109507,47 +109759,6 @@ var TwFloatingImageLayer = ({
109507
109759
  }
109508
109760
  };
109509
109761
  }, [refreshPageRects, renderFrameRevision, scrollRoot]);
109510
- React39.useEffect(() => {
109511
- if (geometryFacet) {
109512
- return;
109513
- }
109514
- if (!scrollRoot) {
109515
- return;
109516
- }
109517
- const runtime = scrollRoot.ownerDocument?.defaultView;
109518
- if (!runtime?.ResizeObserver) {
109519
- return;
109520
- }
109521
- const observer = new runtime.ResizeObserver(() => refreshPageRects());
109522
- observer.observe(scrollRoot);
109523
- return () => observer.disconnect();
109524
- }, [geometryFacet, refreshPageRects, scrollRoot]);
109525
- React39.useEffect(() => {
109526
- if (geometryFacet) {
109527
- return;
109528
- }
109529
- if (!scrollRoot) {
109530
- return;
109531
- }
109532
- const runtime = scrollRoot.ownerDocument?.defaultView;
109533
- if (!runtime?.MutationObserver) {
109534
- return;
109535
- }
109536
- const observer = new runtime.MutationObserver((records) => {
109537
- const overlay = overlayRootRef.current;
109538
- if (overlay) {
109539
- const allSelf = records.every(
109540
- (record) => record.target instanceof Node && overlay.contains(record.target)
109541
- );
109542
- if (allSelf) {
109543
- return;
109544
- }
109545
- }
109546
- refreshPageRects();
109547
- });
109548
- observer.observe(scrollRoot, { childList: true, subtree: false });
109549
- return () => observer.disconnect();
109550
- }, [geometryFacet, refreshPageRects, scrollRoot]);
109551
109762
  const items = React39.useMemo(() => {
109552
109763
  const viewportScale = geometryFacet?.getViewport().pxPerTwip;
109553
109764
  const pxPerTwip = typeof viewportScale === "number" && viewportScale > 0 ? viewportScale : void 0;
@@ -112526,7 +112737,7 @@ function createOverlaysFamily(ctx) {
112526
112737
  return {
112527
112738
  handle: {
112528
112739
  kind: "table-action",
112529
- id: tableActionHandle2(documentSeed2(), targetKey),
112740
+ id: tableActionHandle2(documentSeed3(), targetKey),
112530
112741
  targetKind,
112531
112742
  commandFamily,
112532
112743
  scope: tableActionScope(entry)
@@ -112550,9 +112761,9 @@ function createOverlaysFamily(ctx) {
112550
112761
  };
112551
112762
  }
112552
112763
  function tableActionHandle2(seed, targetKey) {
112553
- return `table-action:${hashOpaque3(`${seed}::${targetKey}`)}`;
112764
+ return `table-action:${hashOpaque4(`${seed}::${targetKey}`)}`;
112554
112765
  }
112555
- function hashOpaque3(input) {
112766
+ function hashOpaque4(input) {
112556
112767
  let h = 2166136261;
112557
112768
  for (let i = 0; i < input.length; i += 1) {
112558
112769
  h ^= input.charCodeAt(i);
@@ -112560,7 +112771,7 @@ function createOverlaysFamily(ctx) {
112560
112771
  }
112561
112772
  return (h >>> 0).toString(36).padStart(7, "0");
112562
112773
  }
112563
- function documentSeed2() {
112774
+ function documentSeed3() {
112564
112775
  return ctx.handle.getSessionState().documentId ?? "document";
112565
112776
  }
112566
112777
  function modeledTargetSourceIdentity(target) {
@@ -117036,8 +117247,39 @@ function buildPositionMap(surface) {
117036
117247
  }
117037
117248
  return lastStoryRestorableEntry(entries)?.pmEnd ?? 1;
117038
117249
  };
117250
+ const runtimeToPmWithBias = (runtimePos, bias) => {
117251
+ if (bias > 0) return runtimeToPm(runtimePos);
117252
+ const firstEditable = entries.find(isStoryRuntimeRestorableEntry);
117253
+ if (runtimePos <= 0) {
117254
+ return firstEditable?.pmStart ?? 1;
117255
+ }
117256
+ if (runtimePos >= runtimeStorySize) {
117257
+ return lastStoryRestorableEntry(entries)?.pmEnd ?? pmDocSize - 1;
117258
+ }
117259
+ let previous = null;
117260
+ for (const entry of entries) {
117261
+ if (!isStoryRuntimeRestorableEntry(entry)) {
117262
+ continue;
117263
+ }
117264
+ if (entry.runtimeStart === entry.runtimeEnd && runtimePos === entry.runtimeStart) {
117265
+ return previous?.pmEnd ?? entry.pmStart;
117266
+ }
117267
+ if (runtimePos > entry.runtimeStart && runtimePos <= entry.runtimeEnd) {
117268
+ return entry.pmStart + (runtimePos - entry.runtimeStart);
117269
+ }
117270
+ if (runtimePos <= entry.runtimeStart) {
117271
+ if (runtimePos === entry.runtimeStart && previous) {
117272
+ return previous.pmEnd;
117273
+ }
117274
+ return nearestRuntimeGapPm(runtimePos, previous, entry);
117275
+ }
117276
+ previous = entry;
117277
+ }
117278
+ return lastStoryRestorableEntry(entries)?.pmEnd ?? 1;
117279
+ };
117039
117280
  return {
117040
117281
  runtimeToPm,
117282
+ runtimeToPmWithBias,
117041
117283
  runtimeToPmWithContext(input) {
117042
117284
  const targetEntry = input.editableTarget ? findRestorableEntryForTarget(entries, input.editableTarget, input.runtimePos) : void 0;
117043
117285
  if (targetEntry) {
@@ -119785,9 +120027,10 @@ function buildAnchorPmRange(anchor, positionMap) {
119785
120027
  return null;
119786
120028
  }
119787
120029
  if (anchor.kind === "range") {
120030
+ const mapBoundary = (runtimePos, bias) => positionMap.runtimeToPmWithBias ? positionMap.runtimeToPmWithBias(runtimePos, bias) : positionMap.runtimeToPm(runtimePos);
119788
120031
  return {
119789
- from: positionMap.runtimeToPm(anchor.from),
119790
- to: positionMap.runtimeToPm(anchor.to),
120032
+ from: mapBoundary(anchor.from, anchor.assoc?.start ?? 1),
120033
+ to: mapBoundary(anchor.to, anchor.assoc?.end ?? -1),
119791
120034
  allowInline: true
119792
120035
  };
119793
120036
  }
@@ -121494,13 +121737,6 @@ function resolvePmPageBodyPatchPlan(input) {
121494
121737
  ...input.patchPlan.addedPages,
121495
121738
  ...input.patchPlan.removedPages
121496
121739
  ];
121497
- if (membershipChangedPageIds.length > 0 && input.currentDoc.childCount !== input.nextDoc.childCount) {
121498
- return {
121499
- status: "fallback",
121500
- reason: "page-membership-change",
121501
- pageIds: membershipChangedPageIds
121502
- };
121503
- }
121504
121740
  if (input.currentDoc.childCount !== input.previousSurface.blocks.length || input.nextDoc.childCount !== input.nextSurface.blocks.length) {
121505
121741
  return {
121506
121742
  status: "fallback",
@@ -121577,6 +121813,21 @@ function resolvePmPageBodyPatchPlan(input) {
121577
121813
  ]
121578
121814
  );
121579
121815
  }
121816
+ if (membershipChangedPageIds.length > 0 && !input.currentDoc.eq(input.nextDoc)) {
121817
+ const membershipSpan = resolveTopLevelMembershipPatchSpan({
121818
+ pageIds: membershipChangedPageIds,
121819
+ previousSurface: input.previousSurface,
121820
+ nextSurface: input.nextSurface
121821
+ });
121822
+ if (!membershipSpan) {
121823
+ return {
121824
+ status: "fallback",
121825
+ reason: "page-membership-change",
121826
+ pageIds: membershipChangedPageIds
121827
+ };
121828
+ }
121829
+ rawSpans.push(membershipSpan);
121830
+ }
121580
121831
  if (rawSpans.length === 0) {
121581
121832
  if (membershipChangedPageIds.length > 0 && !input.currentDoc.eq(input.nextDoc)) {
121582
121833
  return {
@@ -121653,17 +121904,17 @@ function resolveNestedPatchSpan(input) {
121653
121904
  (id) => findNestedBlockSpanById(input.nextSurface.blocks, input.nextDoc, id)
121654
121905
  );
121655
121906
  if (previousLocated.some((span) => !span) || nextLocated.some((span) => !span)) {
121656
- return { status: "fallback", reason: "nested-span-unavailable" };
121907
+ return { status: "top-level" };
121657
121908
  }
121658
121909
  if (previousLocated.some(isUnpatchableVerticalMergeContinuationSpan) || nextLocated.some(isUnpatchableVerticalMergeContinuationSpan)) {
121659
- return { status: "fallback", reason: "split-row-continuation" };
121910
+ return { status: "top-level" };
121660
121911
  }
121661
121912
  const previousSpans = previousLocated;
121662
121913
  const nextSpans = nextLocated;
121663
121914
  const previousKinds = new Set(previousSpans.map((span) => span.block.kind));
121664
121915
  const nextKinds = new Set(nextSpans.map((span) => span.block.kind));
121665
121916
  if (previousKinds.size !== nextKinds.size || [...previousKinds].some((kind) => !nextKinds.has(kind))) {
121666
- return { status: "fallback", reason: "nested-span-unavailable" };
121917
+ return { status: "top-level" };
121667
121918
  }
121668
121919
  return {
121669
121920
  status: "nested",
@@ -121686,6 +121937,46 @@ function isUnpatchableVerticalMergeContinuationSpan(span) {
121686
121937
  span?.insideVerticalMergeContinuation && span.fromPm >= span.toPm
121687
121938
  );
121688
121939
  }
121940
+ function resolveTopLevelMembershipPatchSpan(input) {
121941
+ const previousBlocks = input.previousSurface.blocks;
121942
+ const nextBlocks = input.nextSurface.blocks;
121943
+ let prefix = 0;
121944
+ while (prefix < previousBlocks.length && prefix < nextBlocks.length && sameTopLevelBlockIdentity(previousBlocks[prefix], nextBlocks[prefix])) {
121945
+ prefix += 1;
121946
+ }
121947
+ let suffix = 0;
121948
+ while (suffix < previousBlocks.length - prefix && suffix < nextBlocks.length - prefix && sameTopLevelBlockIdentity(
121949
+ previousBlocks[previousBlocks.length - 1 - suffix],
121950
+ nextBlocks[nextBlocks.length - 1 - suffix]
121951
+ )) {
121952
+ suffix += 1;
121953
+ }
121954
+ let fromBlockIndex = prefix;
121955
+ let toBlockIndex = previousBlocks.length - suffix;
121956
+ let nextFromBlockIndex = prefix;
121957
+ let nextToBlockIndex = nextBlocks.length - suffix;
121958
+ if (fromBlockIndex === toBlockIndex && nextFromBlockIndex === nextToBlockIndex) {
121959
+ if (previousBlocks.length === 0 && nextBlocks.length === 0) return null;
121960
+ fromBlockIndex = 0;
121961
+ toBlockIndex = previousBlocks.length;
121962
+ nextFromBlockIndex = 0;
121963
+ nextToBlockIndex = nextBlocks.length;
121964
+ }
121965
+ return {
121966
+ pageIds: [...input.pageIds],
121967
+ fromBlockIndex,
121968
+ toBlockIndex,
121969
+ nextFromBlockIndex,
121970
+ nextToBlockIndex,
121971
+ replacedBlockCount: Math.max(
121972
+ toBlockIndex - fromBlockIndex,
121973
+ nextToBlockIndex - nextFromBlockIndex
121974
+ )
121975
+ };
121976
+ }
121977
+ function sameTopLevelBlockIdentity(left, right) {
121978
+ return Boolean(left && right && left.blockId === right.blockId && left.kind === right.kind);
121979
+ }
121689
121980
  function resolveTopLevelFragmentPatchSpans(input) {
121690
121981
  const changedIds = uniqueStableFragmentIds(input.changedFragmentIds);
121691
121982
  if (changedIds.length !== input.changedFragmentIds.length || changedIds.length === 0) {
@@ -121753,6 +122044,7 @@ function findBlockSpanByIdInContainer(input) {
121753
122044
  let cursor = input.contentStartPm;
121754
122045
  for (let blockIndex = 0; blockIndex < input.blocks.length; blockIndex += 1) {
121755
122046
  const block = input.blocks[blockIndex];
122047
+ if (blockIndex >= input.containerNode.childCount) return null;
121756
122048
  const childNode = input.containerNode.child(blockIndex);
121757
122049
  const fromPm = cursor;
121758
122050
  const toPm = fromPm + childNode.nodeSize;
@@ -121792,10 +122084,12 @@ function findBlockSpanByIdInTable(input) {
121792
122084
  let rowCursor = input.tableStartPm + 1;
121793
122085
  for (let rowIndex = 0; rowIndex < input.table.rows.length; rowIndex += 1) {
121794
122086
  const row2 = input.table.rows[rowIndex];
122087
+ if (rowIndex >= input.tableNode.childCount) return null;
121795
122088
  const rowNode = input.tableNode.child(rowIndex);
121796
122089
  let cellCursor = rowCursor + 1;
121797
122090
  for (let cellIndex = 0; cellIndex < row2.cells.length; cellIndex += 1) {
121798
122091
  const cell = row2.cells[cellIndex];
122092
+ if (cellIndex >= rowNode.childCount) return null;
121799
122093
  const cellNode = rowNode.child(cellIndex);
121800
122094
  const found = findBlockSpanByIdInContainer({
121801
122095
  blocks: cell.content,
@@ -133061,6 +133355,216 @@ function createOutlineFamily(runtime) {
133061
133355
  };
133062
133356
  }
133063
133357
 
133358
+ // src/api/v3/ai/object.ts
133359
+ var applyObjectActionMetadata = {
133360
+ name: "ai.applyObjectAction",
133361
+ status: "live-with-adapter",
133362
+ sourceLayer: "runtime-core",
133363
+ liveEvidence: {
133364
+ runnerTest: "test/api/v3/ai/ai-object-actions.test.ts",
133365
+ commit: "refactor-09-object-actions"
133366
+ },
133367
+ uxIntent: {
133368
+ uiVisible: true,
133369
+ expectsUxResponse: "inline-change",
133370
+ expectedDelta: "image object layout changes"
133371
+ },
133372
+ agentMetadata: {
133373
+ readOrMutate: "mutate",
133374
+ boundedScope: "scope",
133375
+ auditCategory: "object-action-apply",
133376
+ contextPromptShape: "Apply an image object layout operation to an opaque actionHandle from ai.listObjectActions; preserve-only objects return typed blockers."
133377
+ },
133378
+ stateClass: "A-canonical",
133379
+ persistsTo: "canonical",
133380
+ broadcastsVia: "crdt",
133381
+ rwdReference: "\xA7AI API \xA7 ai.applyObjectAction"
133382
+ };
133383
+ function createObjectActionFamily(runtime) {
133384
+ const compiler = createScopeCompilerService(runtime);
133385
+ return {
133386
+ listObjectActions(input) {
133387
+ const bundle = compiler.compileBundleById(input.handle.scopeId, input.nowUtc);
133388
+ if (!bundle) {
133389
+ return {
133390
+ scopeId: input.handle.scopeId,
133391
+ actions: [],
133392
+ blockers: [`scope-not-resolvable:${input.handle.scopeId}`]
133393
+ };
133394
+ }
133395
+ const seed = documentSeed2(runtime);
133396
+ return {
133397
+ scopeId: input.handle.scopeId,
133398
+ actions: bundle.evidence.editableTargets?.entries.filter(isSupportedImageObjectEvidence).map((entry) => projectObjectDescriptor(runtime, seed, entry)) ?? []
133399
+ };
133400
+ },
133401
+ applyObjectAction(input) {
133402
+ emitUxResponse(runtime, {
133403
+ apiFn: applyObjectActionMetadata.name,
133404
+ intent: "object-action-apply",
133405
+ mockOrLive: applyObjectActionMetadata.status,
133406
+ uiVisible: true,
133407
+ expectedDelta: applyObjectActionMetadata.uxIntent.expectedDelta
133408
+ });
133409
+ const nowUtc = currentAuditTimestamp(runtime);
133410
+ const proposalId = input.proposalId ?? mockId(documentSeed2(runtime), `object-action-${input.actionHandle || "missing-handle"}-${input.operation.kind}`);
133411
+ if (!input.actionHandle) {
133412
+ return blockedResult2(input, proposalId, {
133413
+ code: "object-action-handle-required",
133414
+ category: "unresolved-target",
133415
+ message: "Object actions require an actionHandle from ai.listObjectActions.",
133416
+ nextStep: "Call ai.listObjectActions for the owning scope and retry with one returned actionHandle.",
133417
+ operation: input.operation.kind
133418
+ });
133419
+ }
133420
+ const target = resolveObjectActionHandle(runtime, documentSeed2(runtime), input.actionHandle);
133421
+ if (!target) {
133422
+ return blockedResult2(input, proposalId, {
133423
+ code: `object-action-handle-not-found:${input.actionHandle}`,
133424
+ category: "unresolved-target",
133425
+ message: "No current object action matches the supplied opaque action handle.",
133426
+ nextStep: "Refresh the scope bundle or call ai.listObjectActions before retrying.",
133427
+ actionHandle: input.actionHandle,
133428
+ operation: input.operation.kind
133429
+ });
133430
+ }
133431
+ if (!isSupportedImageTarget(target)) {
133432
+ return blockedResult2(input, proposalId, {
133433
+ code: "object-action-image-target-required",
133434
+ category: "blocked-target",
133435
+ message: "The supplied object action handle no longer identifies a mutable image target.",
133436
+ nextStep: "Refresh object actions; chart, OLE, custom XML, opaque, and stale object targets remain refused.",
133437
+ actionHandle: input.actionHandle,
133438
+ operation: input.operation.kind
133439
+ });
133440
+ }
133441
+ const mediaId = target.object?.mediaId;
133442
+ if (!mediaId) {
133443
+ return blockedResult2(input, proposalId, {
133444
+ code: "object-action-media-id-required",
133445
+ category: "blocked-target",
133446
+ message: "Image object layout mutation requires a mediaId-backed target.",
133447
+ nextStep: "Retry only with an image action handle whose readback includes mediaId.",
133448
+ actionHandle: input.actionHandle,
133449
+ operation: input.operation.kind
133450
+ });
133451
+ }
133452
+ const before = runtime.getCanonicalDocument();
133453
+ runtime.dispatch({
133454
+ type: "image.set-layout",
133455
+ mediaId,
133456
+ dimensions: {
133457
+ widthEmu: input.operation.dimensions.widthEmu,
133458
+ heightEmu: input.operation.dimensions.heightEmu
133459
+ },
133460
+ origin: { source: "api", timestamp: nowUtc }
133461
+ });
133462
+ const changed = runtime.getCanonicalDocument() !== before;
133463
+ if (!changed) {
133464
+ return blockedResult2(input, proposalId, {
133465
+ code: `object-action-noop:${input.operation.kind}:${input.actionHandle}`,
133466
+ category: "runtime-noop",
133467
+ message: "The runtime accepted the image target but the object operation produced no document change.",
133468
+ nextStep: "Refresh object actions and verify the image media item still exists before retrying.",
133469
+ actionHandle: input.actionHandle,
133470
+ operation: input.operation.kind
133471
+ });
133472
+ }
133473
+ return {
133474
+ proposalId,
133475
+ applied: true,
133476
+ changed: true,
133477
+ actionHandle: input.actionHandle,
133478
+ operation: input.operation.kind,
133479
+ commandReference: {
133480
+ command: "image.set-layout",
133481
+ actorId: input.actorId ?? "v3-ai-api",
133482
+ origin: input.origin ?? "agent",
133483
+ emittedAtUtc: nowUtc
133484
+ },
133485
+ readback: objectReadback(runtime, target),
133486
+ posture: "supported",
133487
+ support: objectActionSupport()
133488
+ };
133489
+ }
133490
+ };
133491
+ }
133492
+ function isSupportedImageObjectEvidence(entry) {
133493
+ return entry.kind === "object-anchor" && entry.commandFamily === "object" && entry.runtimeCommand.status === "supported" && entry.runtimeCommand.intents.includes("image-layout") && isImageKind(entry.object?.objectKind) && typeof entry.object?.mediaId === "string";
133494
+ }
133495
+ function projectObjectDescriptor(runtime, seed, entry) {
133496
+ return {
133497
+ actionHandle: objectActionHandle(seed, entry.targetKey),
133498
+ objectKind: entry.object?.objectKind ?? "unknown",
133499
+ targetKind: entry.kind,
133500
+ relation: entry.relation,
133501
+ callableOperations: ["set-image-layout"],
133502
+ supportedOperations: ["set-image-layout"],
133503
+ readback: objectReadback(runtime, entry),
133504
+ reason: entry.runtimeCommand.reason
133505
+ };
133506
+ }
133507
+ function resolveObjectActionHandle(runtime, seed, actionHandle) {
133508
+ return collectEditableTargetRefs(runtime.getCanonicalDocument()).find(
133509
+ (target) => objectActionHandle(seed, target.targetKey) === actionHandle && isSupportedImageTarget(target)
133510
+ ) ?? null;
133511
+ }
133512
+ function isSupportedImageTarget(target) {
133513
+ return target.kind === "object-anchor" && target.commandFamily === "object" && isImageKind(target.object?.objectKind) && typeof target.object?.mediaId === "string" && target.object.mediaId.length > 0 && onlyObjectActionBlockers(target.posture.blockers, ["unmodeled-target"]);
133514
+ }
133515
+ function isImageKind(kind) {
133516
+ return kind === "legacy-image" || kind === "picture";
133517
+ }
133518
+ function onlyObjectActionBlockers(blockers, allowed) {
133519
+ const allowedSet = new Set(allowed);
133520
+ return blockers.every((blocker2) => allowedSet.has(blocker2));
133521
+ }
133522
+ function objectReadback(runtime, target) {
133523
+ const mediaId = target.object?.mediaId;
133524
+ if (!mediaId) return {};
133525
+ const media = runtime.getCanonicalDocument().media.items[mediaId];
133526
+ return {
133527
+ mediaId,
133528
+ ...media?.widthEmu !== void 0 ? { widthEmu: media.widthEmu } : {},
133529
+ ...media?.heightEmu !== void 0 ? { heightEmu: media.heightEmu } : {}
133530
+ };
133531
+ }
133532
+ function blockedResult2(input, proposalId, detail) {
133533
+ return {
133534
+ proposalId,
133535
+ applied: false,
133536
+ changed: false,
133537
+ actionHandle: input.actionHandle,
133538
+ operation: input.operation.kind,
133539
+ reason: detail.code,
133540
+ blockers: [detail.code],
133541
+ blockerDetails: [detail]
133542
+ };
133543
+ }
133544
+ function objectActionSupport() {
133545
+ return {
133546
+ undo: "supported",
133547
+ replay: "supported",
133548
+ exportReopen: "supported",
133549
+ audit: "command-reference"
133550
+ };
133551
+ }
133552
+ function objectActionHandle(seed, targetKey) {
133553
+ return `object-action:${hashOpaque3(`${seed}::${targetKey}`)}`;
133554
+ }
133555
+ function documentSeed2(runtime) {
133556
+ const state = runtime.getSessionState();
133557
+ return state.documentId ?? "document";
133558
+ }
133559
+ function hashOpaque3(input) {
133560
+ let h = 2166136261;
133561
+ for (let i = 0; i < input.length; i += 1) {
133562
+ h ^= input.charCodeAt(i);
133563
+ h = Math.imul(h, 16777619);
133564
+ }
133565
+ return (h >>> 0).toString(36).padStart(7, "0");
133566
+ }
133567
+
133064
133568
  // src/api/v3/ai/actions.ts
133065
133569
  function actionMethodMetadata(method, readOrMutate, auditCategory, contextPromptShape, uxIntent) {
133066
133570
  return {
@@ -133105,6 +133609,17 @@ var locateAllMetadata = actionMethodMetadata(
133105
133609
  "Find scope/table-text targets by query; table text matches include readback {text,isEmpty}.",
133106
133610
  { uiVisible: false, expectsUxResponse: "none" }
133107
133611
  );
133612
+ var createPlaceholderScopesMetadata = actionMethodMetadata(
133613
+ "createPlaceholderScopes",
133614
+ "mutate",
133615
+ "actions-placeholder-scopes",
133616
+ "Create marker-backed workflow scopes for bracketed placeholder matches in paragraphs, tables, and lists using one bounded regex search.",
133617
+ {
133618
+ uiVisible: true,
133619
+ expectsUxResponse: "scope-created",
133620
+ expectedDelta: "placeholder text ranges become workflow scopes"
133621
+ }
133622
+ );
133108
133623
  var rewriteMetadata = actionMethodMetadata(
133109
133624
  "rewrite",
133110
133625
  "mutate",
@@ -133317,6 +133832,7 @@ var ACTION_METHODS = Object.freeze([
133317
133832
  "discover",
133318
133833
  "locate",
133319
133834
  "locateAll",
133835
+ "createPlaceholderScopes",
133320
133836
  "rewrite",
133321
133837
  "rewriteAll",
133322
133838
  "insertText",
@@ -133342,6 +133858,8 @@ var DEFAULT_LOCATE_LIMIT = 20;
133342
133858
  var DEFAULT_REWRITE_ALL_LIMIT = 10;
133343
133859
  var DEFAULT_TABLE_TEXT_SCOPE_LIMIT = 3;
133344
133860
  var DEFAULT_PLAN_STEP_LIMIT = 20;
133861
+ var DEFAULT_PLACEHOLDER_QUERY = "\\[[^\\[\\]]{1,200}\\]";
133862
+ var DEFAULT_PLACEHOLDER_LIMIT = 200;
133345
133863
  function createActionsFamily(runtime) {
133346
133864
  const category = {
133347
133865
  discover(input) {
@@ -133396,6 +133914,9 @@ function createActionsFamily(runtime) {
133396
133914
  locateAll(input) {
133397
133915
  return locateAll(runtime, input);
133398
133916
  },
133917
+ createPlaceholderScopes(input) {
133918
+ return createPlaceholderScopes(runtime, input ?? {});
133919
+ },
133399
133920
  rewrite(input) {
133400
133921
  if (input.text === void 0) {
133401
133922
  return blockedApply(
@@ -134164,6 +134685,169 @@ function locateAll(runtime, input) {
134164
134685
  ...matches.length === 0 ? { blockers: Object.freeze([`actions:locate:not-found:${input.query}`]) } : {}
134165
134686
  };
134166
134687
  }
134688
+ function createPlaceholderScopes(runtime, input) {
134689
+ const query = input.query ?? DEFAULT_PLACEHOLDER_QUERY;
134690
+ if (!query) {
134691
+ const detail = blocker(
134692
+ "actions:placeholder-scopes:query-required",
134693
+ "input",
134694
+ "Placeholder scope creation requires a non-empty query.",
134695
+ "Retry with a non-empty query string or omit query to use the bracketed-placeholder default."
134696
+ );
134697
+ return {
134698
+ status: "blocked",
134699
+ totalHits: 0,
134700
+ created: 0,
134701
+ blocked: 0,
134702
+ scopes: Object.freeze([]),
134703
+ blockers: Object.freeze([detail.code]),
134704
+ blockerDetails: Object.freeze([detail])
134705
+ };
134706
+ }
134707
+ const limit = Math.max(0, input.limit ?? DEFAULT_PLACEHOLDER_LIMIT);
134708
+ let hits;
134709
+ try {
134710
+ hits = runtime.findAllText(query, {
134711
+ regex: input.regex ?? true,
134712
+ matchCase: input.matchCase ?? true,
134713
+ limit
134714
+ });
134715
+ } catch {
134716
+ const detail = blocker(
134717
+ "actions:placeholder-scopes:invalid-query",
134718
+ "input",
134719
+ "The placeholder query could not be compiled or searched.",
134720
+ "Retry with a valid literal query or JavaScript regex pattern."
134721
+ );
134722
+ return {
134723
+ status: "blocked",
134724
+ totalHits: 0,
134725
+ created: 0,
134726
+ blocked: 0,
134727
+ scopes: Object.freeze([]),
134728
+ blockers: Object.freeze([detail.code]),
134729
+ blockerDetails: Object.freeze([detail])
134730
+ };
134731
+ }
134732
+ if (hits.length === 0) {
134733
+ return {
134734
+ status: "not-found",
134735
+ totalHits: 0,
134736
+ created: 0,
134737
+ blocked: 0,
134738
+ scopes: Object.freeze([]),
134739
+ blockers: Object.freeze([`actions:placeholder-scopes:not-found:${query}`])
134740
+ };
134741
+ }
134742
+ const surface = runtime.getRenderSnapshot().surface;
134743
+ const orderedHits = hits.filter((hit) => hit.kind === "range").map((hit, originalIndex) => ({
134744
+ hit,
134745
+ originalIndex,
134746
+ text: surface ? textForSurfaceRange(surface.blocks, hit.from, hit.to) : ""
134747
+ })).sort((a, b) => b.hit.from - a.hit.from || b.originalIndex - a.originalIndex);
134748
+ if (orderedHits.length === 0) {
134749
+ const detail = blocker(
134750
+ "actions:placeholder-scopes:no-range-hits",
134751
+ "unsupported",
134752
+ "The placeholder query matched only projections that cannot be converted into workflow ranges.",
134753
+ "Retry with a text query that resolves to concrete document text ranges."
134754
+ );
134755
+ return {
134756
+ status: "blocked",
134757
+ totalHits: hits.length,
134758
+ created: 0,
134759
+ blocked: hits.length,
134760
+ scopes: Object.freeze([]),
134761
+ blockers: Object.freeze([detail.code]),
134762
+ blockerDetails: Object.freeze([detail])
134763
+ };
134764
+ }
134765
+ const compiler = createScopeCompilerService(runtime);
134766
+ const byOriginalIndex = /* @__PURE__ */ new Map();
134767
+ for (const item of orderedHits) {
134768
+ const text = item.text || query;
134769
+ const label = input.labelPrefix === void 0 ? text : `${input.labelPrefix} ${item.originalIndex + 1}`;
134770
+ const result = createScopeFromAnchor(runtime, {
134771
+ anchor: { from: item.hit.from, to: item.hit.to },
134772
+ mode: input.mode ?? "edit",
134773
+ label,
134774
+ ...input.visibility ? { visibility: input.visibility } : {},
134775
+ ...input.guardPolicy ? { guardPolicy: input.guardPolicy } : {},
134776
+ ...input.assoc ? { assoc: input.assoc } : {},
134777
+ ...input.stableRefHint ? { stableRefHint: input.stableRefHint } : {}
134778
+ });
134779
+ if (result.status === "created") {
134780
+ const compiled = compiler.compileScopeById(result.scopeId)?.scope;
134781
+ byOriginalIndex.set(item.originalIndex, {
134782
+ status: "created",
134783
+ text,
134784
+ excerpt: excerpt(text),
134785
+ scopeId: result.scopeId,
134786
+ ...compiled?.handle ? { handle: compiled.handle } : {}
134787
+ });
134788
+ continue;
134789
+ }
134790
+ const detail = blockerWithOwner(
134791
+ `actions:placeholder-scopes:create-refused:${result.reason}`,
134792
+ "blocked",
134793
+ "The placeholder match could not be converted into a workflow scope.",
134794
+ "Refresh the document and retry; if it still refuses, route the target to the workflow/scope writer owner.",
134795
+ "L06"
134796
+ );
134797
+ byOriginalIndex.set(item.originalIndex, {
134798
+ status: "blocked",
134799
+ text,
134800
+ excerpt: excerpt(text),
134801
+ blockers: Object.freeze([detail.code]),
134802
+ blockerDetails: Object.freeze([detail])
134803
+ });
134804
+ }
134805
+ const scopes = orderedHits.map((item) => byOriginalIndex.get(item.originalIndex)).filter((value) => value !== void 0);
134806
+ const created = scopes.filter((scope) => scope.status === "created").length;
134807
+ const blocked2 = scopes.length - created;
134808
+ const blockerDetails = scopes.flatMap((scope) => scope.blockerDetails ?? []);
134809
+ return {
134810
+ status: created === scopes.length ? "created" : created > 0 ? "partial" : "blocked",
134811
+ totalHits: hits.length,
134812
+ created,
134813
+ blocked: blocked2,
134814
+ scopes: Object.freeze(scopes),
134815
+ ...blockerDetails.length > 0 ? {
134816
+ blockers: Object.freeze(blockerDetails.map((detail) => detail.code)),
134817
+ blockerDetails: Object.freeze(blockerDetails)
134818
+ } : {}
134819
+ };
134820
+ }
134821
+ function textForSurfaceRange(blocks, from, to) {
134822
+ let acc = "";
134823
+ const visit = (items) => {
134824
+ for (const block of items) {
134825
+ if (block.kind === "paragraph") {
134826
+ for (const segment of block.segments) {
134827
+ if (segment.kind !== "text") continue;
134828
+ if (segment.from >= to) return;
134829
+ if (segment.to <= from) continue;
134830
+ acc += segment.text.slice(
134831
+ Math.max(0, from - segment.from),
134832
+ Math.min(segment.text.length, to - segment.from)
134833
+ );
134834
+ }
134835
+ continue;
134836
+ }
134837
+ if (block.kind === "table") {
134838
+ for (const row2 of block.rows) {
134839
+ for (const cell of row2.cells) visit(cell.content);
134840
+ }
134841
+ continue;
134842
+ }
134843
+ if (block.kind === "sdt_block") {
134844
+ visit(block.children);
134845
+ }
134846
+ }
134847
+ };
134848
+ visit(blocks);
134849
+ return acc;
134850
+ }
134167
134851
  function validateTemplateTargets(runtime, input) {
134168
134852
  if (!Array.isArray(input.targets) || input.targets.length === 0) {
134169
134853
  const detail = blocker(
@@ -134584,6 +135268,15 @@ function applyRewrite(runtime, target, input) {
134584
135268
  if (!listTextTarget.ok) return blockedApplyFromResolution(listTextTarget);
134585
135269
  return applyEditableTextRewrite(runtime, listTextTarget.target, input);
134586
135270
  }
135271
+ const tableTextTarget = uniqueTableTextActionForScope(runtime, target.handle);
135272
+ if (tableTextTarget.ok && tableTextTarget.action.readback?.text === target.scope.content.text) {
135273
+ return applyTableScopedMarkerRewrite(
135274
+ runtime,
135275
+ target,
135276
+ tableTextTarget.action,
135277
+ input
135278
+ );
135279
+ }
134587
135280
  const rewriteBlocker = scopeRewriteCapabilityBlocker(target.scope);
134588
135281
  if (rewriteBlocker) {
134589
135282
  return blockedApply(
@@ -134716,6 +135409,106 @@ function applyEditableTextRewrite(runtime, target, input) {
134716
135409
  afterReadback
134717
135410
  };
134718
135411
  }
135412
+ function applyTableScopedMarkerRewrite(runtime, target, action, input) {
135413
+ const workflowScope = runtime.getScope(target.handle.scopeId);
135414
+ const editableTarget = tableEditableTargetForActionHandle(
135415
+ runtime,
135416
+ action.actionHandle
135417
+ );
135418
+ if (!workflowScope || workflowScope.anchor.kind !== "range" || !editableTarget) {
135419
+ return blockedApply(
135420
+ `actions:rewrite:table-marker-target-unresolved:${target.handle.scopeId}`,
135421
+ "unresolved-target",
135422
+ "The table-contained marker scope could not be joined to a current table text target.",
135423
+ "Refresh placeholder scopes and retry; route persistent failures to L08 table scope target mapping."
135424
+ );
135425
+ }
135426
+ const before = runtime.getCanonicalDocument();
135427
+ try {
135428
+ runtime.dispatch({
135429
+ type: "selection.set",
135430
+ selection: {
135431
+ anchor: workflowScope.anchor.from,
135432
+ head: workflowScope.anchor.to,
135433
+ isCollapsed: workflowScope.anchor.from === workflowScope.anchor.to,
135434
+ activeRange: {
135435
+ kind: "range",
135436
+ from: workflowScope.anchor.from,
135437
+ to: workflowScope.anchor.to,
135438
+ assoc: workflowScope.anchor.assoc ?? { start: -1, end: 1 }
135439
+ }
135440
+ },
135441
+ origin: actionOrigin(runtime, input)
135442
+ });
135443
+ runtime.applyActiveStoryTextCommand({
135444
+ type: "text.insert",
135445
+ text: input.text,
135446
+ editableTarget,
135447
+ origin: actionOrigin(runtime, input)
135448
+ });
135449
+ } catch {
135450
+ return blockedApply(
135451
+ `actions:rewrite:table-marker-command-refused:${target.handle.scopeId}`,
135452
+ "blocked",
135453
+ "The table-contained marker scope could not be rewritten by the runtime text command.",
135454
+ "Retry with an exact table text actionHandle; route repeated failures to L07 table text command support.",
135455
+ [
135456
+ blockerWithOwner(
135457
+ `actions:rewrite:table-marker-command-refused:${target.handle.scopeId}`,
135458
+ "blocked",
135459
+ "The table-contained marker scope could not be rewritten by the runtime text command.",
135460
+ "Retry with an exact table text actionHandle; route repeated failures to L07 table text command support.",
135461
+ "L07 runtime text command support"
135462
+ )
135463
+ ]
135464
+ );
135465
+ }
135466
+ const changed = runtime.getCanonicalDocument() !== before;
135467
+ const paragraph = resolveParagraphTextTarget2(
135468
+ runtime.getCanonicalDocument(),
135469
+ editableTarget
135470
+ );
135471
+ const afterText = paragraph ? collectInlineText4(paragraph.children) : void 0;
135472
+ const afterReadback = afterText === void 0 ? void 0 : { text: afterText, isEmpty: afterText.length === 0 };
135473
+ if (!changed || afterReadback?.text !== input.text) {
135474
+ return {
135475
+ status: "blocked",
135476
+ applied: false,
135477
+ changed,
135478
+ target: summarizeTarget({ kind: "table-text", action }),
135479
+ posture: "suspect-readback",
135480
+ blockers: Object.freeze([
135481
+ `actions:rewrite:table-marker-readback-mismatch:${target.handle.scopeId}`
135482
+ ]),
135483
+ blockerDetails: Object.freeze([
135484
+ blocker(
135485
+ `actions:rewrite:table-marker-readback-mismatch:${target.handle.scopeId}`,
135486
+ "blocked",
135487
+ "The table-contained marker rewrite did not produce the requested exact table-cell readback.",
135488
+ "Treat the mutation as suspect. Re-read the target and export before claiming success."
135489
+ )
135490
+ ]),
135491
+ ...afterReadback ? { afterReadback } : {}
135492
+ };
135493
+ }
135494
+ return {
135495
+ status: "applied",
135496
+ applied: true,
135497
+ changed: true,
135498
+ target: summarizeTarget({
135499
+ kind: "table-text",
135500
+ action: { ...action, readback: afterReadback }
135501
+ }),
135502
+ commandReference: {
135503
+ command: "text.insert",
135504
+ actorId: input.actorId ?? "v3-ai-api",
135505
+ origin: input.origin ?? "agent",
135506
+ emittedAtUtc: currentAuditTimestamp(runtime)
135507
+ },
135508
+ ...action.readback ? { beforeReadback: action.readback } : {},
135509
+ afterReadback
135510
+ };
135511
+ }
134719
135512
  function projectRewriteScopeResult(runtime, result, target, beforeText, proposedText, documentMutated) {
134720
135513
  if (!result.applied) return projectApplyResult(result, target);
134721
135514
  const compiledAfter = createScopeCompilerService(runtime).compileScopeById(
@@ -134832,6 +135625,51 @@ function tableTextActionsForScope(runtime, handle) {
134832
135625
  });
134833
135626
  return result.actions.filter((action) => action.family === "table-text");
134834
135627
  }
135628
+ function uniqueTableTextActionForScope(runtime, handle) {
135629
+ const actions2 = tableTextActionsForScope(runtime, handle).filter(
135630
+ (action) => action.readback !== void 0
135631
+ );
135632
+ if (actions2.length === 1 && actions2[0]) {
135633
+ return { ok: true, action: actions2[0] };
135634
+ }
135635
+ if (actions2.length === 0) {
135636
+ return {
135637
+ ok: false,
135638
+ blockers: Object.freeze(["actions:rewrite:table-text-target-missing"]),
135639
+ blockerDetails: Object.freeze([
135640
+ blocker(
135641
+ "actions:rewrite:table-text-target-missing",
135642
+ "unsupported",
135643
+ "The scope does not expose a unique command-safe table text target.",
135644
+ "Use a paragraph/list scope, or retry with an exact table text actionHandle returned by ai.actions.locateAll."
135645
+ )
135646
+ ])
135647
+ };
135648
+ }
135649
+ return {
135650
+ ok: false,
135651
+ blockers: Object.freeze(["actions:rewrite:table-text-target-ambiguous"]),
135652
+ blockerDetails: Object.freeze([
135653
+ blocker(
135654
+ "actions:rewrite:table-text-target-ambiguous",
135655
+ "ambiguous-target",
135656
+ "The scope exposes multiple table text targets, so broad rewrite would be ambiguous.",
135657
+ "Retry with the exact table text actionHandle returned by ai.actions.locateAll or ai.listTableActions."
135658
+ )
135659
+ ])
135660
+ };
135661
+ }
135662
+ function tableEditableTargetForActionHandle(runtime, actionHandle) {
135663
+ for (const target of collectEditableTargetRefs(runtime.getCanonicalDocument())) {
135664
+ if (target.commandFamily !== "text-leaf" || target.table?.operationScope !== "text") {
135665
+ continue;
135666
+ }
135667
+ if (deriveTableActionHandleForTarget(runtime, target.targetKey) === actionHandle) {
135668
+ return target;
135669
+ }
135670
+ }
135671
+ return null;
135672
+ }
134835
135673
  function editableTextActionsForScope(runtime, handle) {
134836
135674
  const bundle = createScopeCompilerService(runtime).compileBundleById(
134837
135675
  handle.scopeId,
@@ -136355,6 +137193,7 @@ function createApiV3(handle, opts) {
136355
137193
  ...createStatsFamily(handle),
136356
137194
  ...createOutlineFamily(handle),
136357
137195
  ...createTableActionFamily(handle),
137196
+ ...createObjectActionFamily(handle),
136358
137197
  ...createActionsFamily(handle)
136359
137198
  };
136360
137199
  const runtime = {
@@ -139162,6 +140001,9 @@ var WordReviewEditor = (0, import_react71.forwardRef)(
139162
140001
  onUpdateFields: () => {
139163
140002
  activeRuntime.updateFields();
139164
140003
  },
140004
+ onUpdateTableOfContents: () => {
140005
+ activeRuntime.updateTableOfContents();
140006
+ },
139165
140007
  onAskAgentForSelection: () => {
139166
140008
  const { selection } = snapshot;
139167
140009
  onEventRef.current?.({