@beyondwork/docx-react-component 1.0.133 → 1.0.134

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 (70) hide show
  1. package/dist/api/public-types.cjs +3 -1
  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 +1 -1
  5. package/dist/api/v3.cjs +688 -45
  6. package/dist/api/v3.d.cts +2 -2
  7. package/dist/api/v3.d.ts +2 -2
  8. package/dist/api/v3.js +3 -3
  9. package/dist/{chunk-S3PEKX6H.js → chunk-3YR47WTD.js} +53 -547
  10. package/dist/{chunk-57HTKX3P.js → chunk-74R5B2EZ.js} +1 -1
  11. package/dist/{chunk-KL4TZSZV.js → chunk-7Y6JCIK3.js} +1 -1
  12. package/dist/{chunk-3JEE5RJU.js → chunk-EBSI6VQX.js} +457 -16
  13. package/dist/{chunk-OTRVGNZQ.js → chunk-ESEEWELA.js} +547 -2
  14. package/dist/{chunk-224TSMEB.js → chunk-IJD6D7HU.js} +137 -41
  15. package/dist/{chunk-CVSD3UNK.js → chunk-O4EDZR44.js} +3 -1
  16. package/dist/{chunk-ZFCZ7XXH.js → chunk-VA24T4EB.js} +1 -1
  17. package/dist/core/commands/formatting-commands.d.cts +1 -1
  18. package/dist/core/commands/formatting-commands.d.ts +1 -1
  19. package/dist/core/commands/image-commands.d.cts +1 -1
  20. package/dist/core/commands/image-commands.d.ts +1 -1
  21. package/dist/core/commands/section-layout-commands.d.cts +1 -1
  22. package/dist/core/commands/section-layout-commands.d.ts +1 -1
  23. package/dist/core/commands/style-commands.d.cts +1 -1
  24. package/dist/core/commands/style-commands.d.ts +1 -1
  25. package/dist/core/commands/table-structure-commands.d.cts +1 -1
  26. package/dist/core/commands/table-structure-commands.d.ts +1 -1
  27. package/dist/core/commands/text-commands.d.cts +1 -1
  28. package/dist/core/commands/text-commands.d.ts +1 -1
  29. package/dist/core/selection/mapping.d.cts +1 -1
  30. package/dist/core/selection/mapping.d.ts +1 -1
  31. package/dist/core/state/editor-state.d.cts +1 -1
  32. package/dist/core/state/editor-state.d.ts +1 -1
  33. package/dist/index.cjs +1289 -615
  34. package/dist/index.d.cts +4 -4
  35. package/dist/index.d.ts +4 -4
  36. package/dist/index.js +105 -19
  37. package/dist/io/docx-session.d.cts +3 -3
  38. package/dist/io/docx-session.d.ts +3 -3
  39. package/dist/{loader-B2H99237.d.cts → loader-CK3lZy4h.d.cts} +2 -2
  40. package/dist/{loader-DfTjqVwn.d.ts → loader-CQXplstv.d.ts} +2 -2
  41. package/dist/{public-types-S8gTYwKo.d.cts → public-types-BR1SYK2F.d.cts} +140 -3
  42. package/dist/{public-types-B5lOUIrP.d.ts → public-types-DXNZVKrS.d.ts} +140 -3
  43. package/dist/public-types.cjs +3 -1
  44. package/dist/public-types.d.cts +1 -1
  45. package/dist/public-types.d.ts +1 -1
  46. package/dist/public-types.js +1 -1
  47. package/dist/runtime/collab.d.cts +2 -2
  48. package/dist/runtime/collab.d.ts +2 -2
  49. package/dist/runtime/document-runtime.cjs +591 -54
  50. package/dist/runtime/document-runtime.d.cts +1 -1
  51. package/dist/runtime/document-runtime.d.ts +1 -1
  52. package/dist/runtime/document-runtime.js +4 -4
  53. package/dist/{session-CBDIOYXA.d.ts → session-C9UjrhJF.d.ts} +2 -2
  54. package/dist/{session-CR2A1hGZ.d.cts → session-CSbwkgII.d.cts} +2 -2
  55. package/dist/session.d.cts +4 -4
  56. package/dist/session.d.ts +4 -4
  57. package/dist/tailwind.cjs +54 -546
  58. package/dist/tailwind.d.cts +1 -1
  59. package/dist/tailwind.d.ts +1 -1
  60. package/dist/tailwind.js +4 -4
  61. package/dist/{types-yty2K-hk.d.cts → types-CZtAueri.d.cts} +1 -1
  62. package/dist/{types-B-90ywjU.d.ts → types-RzkCXDNV.d.ts} +1 -1
  63. package/dist/ui-tailwind/editor-surface/search-plugin.d.cts +2 -2
  64. package/dist/ui-tailwind/editor-surface/search-plugin.d.ts +2 -2
  65. package/dist/ui-tailwind/editor-surface/search-plugin.js +2 -2
  66. package/dist/ui-tailwind.cjs +54 -546
  67. package/dist/ui-tailwind.d.cts +2 -2
  68. package/dist/ui-tailwind.d.ts +2 -2
  69. package/dist/ui-tailwind.js +4 -4
  70. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -15747,7 +15747,8 @@ function buildResolvedSections(document2) {
15747
15747
  const mainSurface = createEditorSurfaceSnapshot(
15748
15748
  document2,
15749
15749
  createSelectionSnapshot(0, 0),
15750
- MAIN_STORY_TARGET
15750
+ MAIN_STORY_TARGET,
15751
+ { editableTargetsByBlockPath: NO_EDITABLE_TARGETS_INDEX }
15751
15752
  );
15752
15753
  const sections = [];
15753
15754
  let sectionStart = 0;
@@ -35768,9 +35769,11 @@ function applyRevisionAction(options) {
35768
35769
  );
35769
35770
  }
35770
35771
  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
- )) {
35772
+ const touchesStructuralContent = slice.some(
35773
+ (unit) => unit.kind === "opaque_block" || unit.kind === "structural_block"
35774
+ );
35775
+ const touchesParagraphBoundary = slice.some((unit) => unit.kind === "paragraph_break");
35776
+ if (touchesStructuralContent || touchesParagraphBoundary && !canApplyRuntimeTextBlockDeletion(revision, options.intent)) {
35774
35777
  return skippedResult(
35775
35778
  options,
35776
35779
  "structural-range",
@@ -35778,7 +35781,7 @@ function applyRevisionAction(options) {
35778
35781
  );
35779
35782
  }
35780
35783
  if (slice.some(
35781
- (unit) => unit.kind === "image" || unit.kind === "opaque_inline"
35784
+ (unit) => unit.kind === "image" || unit.kind === "opaque_inline" || unit.kind === "protected_inline"
35782
35785
  )) {
35783
35786
  return skippedResult(
35784
35787
  options,
@@ -35833,6 +35836,9 @@ function applyRevisionAction(options) {
35833
35836
  detachedRevisionIds: findNewDetachedRevisionIds(options.store, nextStore)
35834
35837
  };
35835
35838
  }
35839
+ function canApplyRuntimeTextBlockDeletion(revision, intent) {
35840
+ 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"));
35841
+ }
35836
35842
  function applyPairedMoveAction(options, revision) {
35837
35843
  const resultingStatus = toResultingStatus(options.intent);
35838
35844
  return {
@@ -36936,6 +36942,11 @@ function executeEditorCommand(state, command, context) {
36936
36942
  );
36937
36943
  return buildDocumentReplaceTransaction(state, context, result);
36938
36944
  }
36945
+ case "fragment.insert-tracked": {
36946
+ const result = applySuggestingFragmentInsert(state, command.fragment, context);
36947
+ if (result) return result;
36948
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
36949
+ }
36939
36950
  case "runtime.set-read-only":
36940
36951
  return createTransaction(
36941
36952
  {
@@ -38181,7 +38192,7 @@ function applyTextCommand(state, timestamp, apply) {
38181
38192
  const reviewState = remapReviewStateAfterContentChange(
38182
38193
  state,
38183
38194
  result.document,
38184
- result.mapping
38195
+ result.mapping ?? createEmptyMapping()
38185
38196
  );
38186
38197
  const scopeTagTouches = collectScopeTagTouches(
38187
38198
  state.document.review.comments,
@@ -38521,6 +38532,38 @@ function isSingleParagraphSuggestingRange(document2, from, to) {
38521
38532
  );
38522
38533
  return ranges.some((range) => from >= range.start && to <= range.end);
38523
38534
  }
38535
+ function isTextOnlySuggestingRange(document2, from, to) {
38536
+ const story = parseTextStory(document2.content);
38537
+ const range = normalizeTextStoryRange(story, from, to);
38538
+ if (!range) {
38539
+ return isSingleParagraphSuggestingRange(document2, from, to);
38540
+ }
38541
+ const rootStoryTextOnly = story.units.slice(range.from, range.to).every(isSuggestingTextReplacementUnit);
38542
+ if (rootStoryTextOnly) return true;
38543
+ return isSingleParagraphSuggestingRange(document2, from, to);
38544
+ }
38545
+ function normalizeTextStoryRange(story, from, to) {
38546
+ const start = Math.min(from, to);
38547
+ const end = Math.max(from, to);
38548
+ if (start < 0 || end > story.size) return null;
38549
+ return { from: start, to: end };
38550
+ }
38551
+ function isSuggestingTextReplacementUnit(unit) {
38552
+ switch (unit.kind) {
38553
+ case "text":
38554
+ case "tab":
38555
+ case "hard_break":
38556
+ case "paragraph_break":
38557
+ case "scope_marker":
38558
+ return true;
38559
+ case "protected_inline":
38560
+ case "image":
38561
+ case "opaque_inline":
38562
+ case "opaque_block":
38563
+ case "structural_block":
38564
+ return false;
38565
+ }
38566
+ }
38524
38567
  function collectSuggestingParagraphRanges(blocks, startCursor, output, addRootParagraphBoundaries) {
38525
38568
  let cursor = startCursor;
38526
38569
  for (let index = 0; index < blocks.length; index += 1) {
@@ -38593,10 +38636,10 @@ function applySuggestingInsert(state, text, context, formatting) {
38593
38636
  const from = Math.min(selection.anchor, selection.head);
38594
38637
  const to = Math.max(selection.anchor, selection.head);
38595
38638
  const isCollapsed = from === to;
38596
- if (!isCollapsed && !isSingleParagraphSuggestingRange(state.document, from, to)) {
38639
+ if (!isCollapsed && !isTextOnlySuggestingRange(state.document, from, to)) {
38597
38640
  return createSuggestingUnsupportedTransaction(
38598
38641
  state,
38599
- "Suggesting mode does not yet support multi-paragraph replacement ranges."
38642
+ "Suggesting mode text replacement ranges must contain only editable text and paragraph breaks."
38600
38643
  );
38601
38644
  }
38602
38645
  if (isCollapsed) {
@@ -38705,7 +38748,7 @@ function applySuggestingInsert(state, text, context, formatting) {
38705
38748
  const reviewState = remapReviewStateAfterContentChange(
38706
38749
  state,
38707
38750
  result.document,
38708
- result.mapping
38751
+ result.mapping ?? createEmptyMapping()
38709
38752
  );
38710
38753
  const replacementSuggestionId = createSuggestingRevisionId(
38711
38754
  reviewState.document.review.revisions,
@@ -38770,6 +38813,131 @@ function applySuggestingInsert(state, text, context, formatting) {
38770
38813
  activeCommentId: reviewState.activeCommentId
38771
38814
  }
38772
38815
  },
38816
+ {
38817
+ historyBoundary: "push",
38818
+ markDirty: true,
38819
+ mapping: result.mapping ?? createEmptyMapping(),
38820
+ effects: {
38821
+ ...reviewState.effects,
38822
+ revisionAuthored: { changeId: insertionRevision.changeId, kind: "insertion" }
38823
+ }
38824
+ }
38825
+ );
38826
+ }
38827
+ function applySuggestingFragmentInsert(state, fragment, context) {
38828
+ if (state.readOnly) {
38829
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
38830
+ }
38831
+ if (fragment.blocks.length === 0) {
38832
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
38833
+ }
38834
+ if (!isTrackableSuggestingFragment(fragment)) {
38835
+ return createSuggestingUnsupportedTransaction(
38836
+ state,
38837
+ "Suggesting mode structured fragment replacement supports paragraph/text fragments only."
38838
+ );
38839
+ }
38840
+ const authorId = context.defaultAuthorId ?? "unknown";
38841
+ const selection = state.selection;
38842
+ const from = Math.min(selection.anchor, selection.head);
38843
+ const to = Math.max(selection.anchor, selection.head);
38844
+ const isCollapsed = from === to;
38845
+ if (!isCollapsed && !isTextOnlySuggestingRange(state.document, from, to)) {
38846
+ return createSuggestingUnsupportedTransaction(
38847
+ state,
38848
+ "Suggesting mode structured fragment replacement ranges must contain only editable text and paragraph breaks."
38849
+ );
38850
+ }
38851
+ const storyBefore = parseTextStory(state.document.content);
38852
+ const insertSelection = createSelectionSnapshot(to, to);
38853
+ const result = structureLayer.applyFragmentInsert(
38854
+ state.document,
38855
+ insertSelection,
38856
+ fragment,
38857
+ context
38858
+ );
38859
+ if (!result.changed) {
38860
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
38861
+ }
38862
+ const storyAfter = parseTextStory(result.document.content);
38863
+ const insertedFrom = to;
38864
+ const insertedTo = to + Math.max(0, storyAfter.size - storyBefore.size);
38865
+ if (insertedTo <= insertedFrom) {
38866
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
38867
+ }
38868
+ const reviewState = remapReviewStateAfterContentChange(
38869
+ state,
38870
+ result.document,
38871
+ result.mapping ?? createEmptyMapping()
38872
+ );
38873
+ const replacementSuggestionId = !isCollapsed ? createSuggestingRevisionId(
38874
+ reviewState.document.review.revisions,
38875
+ context.timestamp,
38876
+ authorId
38877
+ ) : void 0;
38878
+ let deletionRevision = !isCollapsed ? createAuthoredRevision(
38879
+ reviewState.document.review.revisions,
38880
+ "deletion",
38881
+ from,
38882
+ to,
38883
+ authorId,
38884
+ context.timestamp,
38885
+ createSuggestionMetadata({
38886
+ suggestionId: replacementSuggestionId,
38887
+ semanticKind: "replacement"
38888
+ })
38889
+ ) : void 0;
38890
+ const insertionRevision = createAuthoredRevision(
38891
+ {
38892
+ ...reviewState.document.review.revisions,
38893
+ ...deletionRevision ? { [deletionRevision.changeId]: deletionRevision } : {}
38894
+ },
38895
+ "insertion",
38896
+ insertedFrom,
38897
+ insertedTo,
38898
+ authorId,
38899
+ context.timestamp,
38900
+ createSuggestionMetadata(
38901
+ deletionRevision ? {
38902
+ suggestionId: replacementSuggestionId,
38903
+ semanticKind: "replacement",
38904
+ linkedRevisionIds: [deletionRevision.changeId]
38905
+ } : {
38906
+ semanticKind: "structural-change"
38907
+ }
38908
+ )
38909
+ );
38910
+ if (deletionRevision) {
38911
+ deletionRevision = {
38912
+ ...deletionRevision,
38913
+ metadata: {
38914
+ ...deletionRevision.metadata,
38915
+ linkedRevisionIds: [insertionRevision.changeId]
38916
+ }
38917
+ };
38918
+ }
38919
+ const finalDocument = {
38920
+ ...reviewState.document,
38921
+ review: {
38922
+ ...reviewState.document.review,
38923
+ revisions: {
38924
+ ...reviewState.document.review.revisions,
38925
+ ...deletionRevision ? { [deletionRevision.changeId]: deletionRevision } : {},
38926
+ [insertionRevision.changeId]: insertionRevision
38927
+ }
38928
+ }
38929
+ };
38930
+ return createTransaction(
38931
+ {
38932
+ ...state,
38933
+ document: finalDocument,
38934
+ selection: createSelectionSnapshot(insertedTo, insertedTo),
38935
+ warnings: reviewState.warnings,
38936
+ runtime: {
38937
+ ...state.runtime,
38938
+ activeCommentId: reviewState.activeCommentId
38939
+ }
38940
+ },
38773
38941
  {
38774
38942
  historyBoundary: "push",
38775
38943
  markDirty: true,
@@ -38781,6 +38949,10 @@ function applySuggestingInsert(state, text, context, formatting) {
38781
38949
  }
38782
38950
  );
38783
38951
  }
38952
+ function isTrackableSuggestingFragment(fragment) {
38953
+ const story = parseTextStory({ type: "doc", children: fragment.blocks });
38954
+ return story.units.every(isSuggestingTextReplacementUnit);
38955
+ }
38784
38956
  function applySuggestingDelete(state, direction, context) {
38785
38957
  if (state.readOnly) {
38786
38958
  return createTransaction(state, { historyBoundary: "skip", markDirty: false });
@@ -38790,10 +38962,10 @@ function applySuggestingDelete(state, direction, context) {
38790
38962
  const from = Math.min(selection.anchor, selection.head);
38791
38963
  const to = Math.max(selection.anchor, selection.head);
38792
38964
  const isCollapsed = from === to;
38793
- if (!isCollapsed && !isSingleParagraphSuggestingRange(state.document, from, to)) {
38965
+ if (!isCollapsed && !isTextOnlySuggestingRange(state.document, from, to)) {
38794
38966
  return createSuggestingUnsupportedTransaction(
38795
38967
  state,
38796
- "Suggesting mode does not yet support multi-paragraph deletion ranges."
38968
+ "Suggesting mode text deletion ranges must contain only editable text and paragraph breaks."
38797
38969
  );
38798
38970
  }
38799
38971
  let deleteFrom;
@@ -49918,11 +50090,9 @@ function compileParagraphReplacement(entry, proposed, options) {
49918
50090
  };
49919
50091
  }
49920
50092
  if (proposed.proposedContent.kind === "structured") {
49921
- if (options.posture === "suggest-mode") {
49922
- return null;
49923
- }
49924
50093
  const fragment = proposed.proposedContent.structured;
49925
50094
  if (!isStructuredReplacementContent(fragment)) return null;
50095
+ const stepKind = options.posture === "suggest-mode" ? "fragment-replace-tracked" : "fragment-replace";
49926
50096
  const blockCount = fragment.blocks.length;
49927
50097
  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
50098
  const actionVerb = proposed.operation === "insert-before" ? "insert before" : proposed.operation === "insert-after" ? "insert after" : "replace";
@@ -49933,8 +50103,8 @@ function compileParagraphReplacement(entry, proposed, options) {
49933
50103
  operation: proposed.operation,
49934
50104
  steps: Object.freeze([
49935
50105
  {
49936
- kind: "fragment-replace",
49937
- summary: actionSummary,
50106
+ kind: stepKind,
50107
+ summary: stepKind === "fragment-replace" ? actionSummary : `suggest-mode ${actionSummary}`,
49938
50108
  ...textLeafEditableTargetHint(entry, blockRange) ? { editableTargetHint: textLeafEditableTargetHint(entry, blockRange) } : {},
49939
50109
  range: { from: operationRange.from, to: operationRange.to },
49940
50110
  fragment
@@ -50021,7 +50191,7 @@ function compileScopeKind(entry, options) {
50021
50191
  };
50022
50192
  }
50023
50193
  function compileScopeReplacement(entry, proposed, options) {
50024
- if (entry.handle.provenance !== "marker-backed" || proposed.operation !== "replace" || options.posture !== "direct-edit") {
50194
+ if (entry.handle.provenance !== "marker-backed" || proposed.operation !== "replace") {
50025
50195
  return null;
50026
50196
  }
50027
50197
  const markerRange = buildScopePositionMap(options.document).markerScopes.get(
@@ -50030,14 +50200,15 @@ function compileScopeReplacement(entry, proposed, options) {
50030
50200
  if (!markerRange) return null;
50031
50201
  if (proposed.proposedContent.kind === "text") {
50032
50202
  const text = proposed.proposedContent.text ?? "";
50203
+ const stepKind = options.posture === "suggest-mode" ? "text-insert-tracked" : "text-replace";
50033
50204
  return {
50034
50205
  scopeId: entry.handle.scopeId,
50035
50206
  targetKind: "scope",
50036
50207
  operation: proposed.operation,
50037
50208
  steps: Object.freeze([
50038
50209
  {
50039
- kind: "text-replace",
50040
- summary: `replace multi-paragraph scope ${entry.handle.scopeId} text [${markerRange.from}..${markerRange.to}] (len ${text.length})`,
50210
+ kind: stepKind,
50211
+ 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
50212
  range: { from: markerRange.from, to: markerRange.to },
50042
50213
  text,
50043
50214
  ...proposed.formatting ? { formatting: proposed.formatting } : {}
@@ -50050,14 +50221,15 @@ function compileScopeReplacement(entry, proposed, options) {
50050
50221
  if (proposed.proposedContent.kind === "structured") {
50051
50222
  const fragment = proposed.proposedContent.structured;
50052
50223
  if (!isStructuredReplacementContent(fragment)) return null;
50224
+ const stepKind = options.posture === "suggest-mode" ? "fragment-replace-tracked" : "fragment-replace";
50053
50225
  return {
50054
50226
  scopeId: entry.handle.scopeId,
50055
50227
  targetKind: "scope",
50056
50228
  operation: proposed.operation,
50057
50229
  steps: Object.freeze([
50058
50230
  {
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))`,
50231
+ kind: stepKind,
50232
+ 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
50233
  range: { from: markerRange.from, to: markerRange.to },
50062
50234
  fragment
50063
50235
  }
@@ -50439,6 +50611,99 @@ function paragraphFirstMarkerStart(paragraph, knownScopeIds) {
50439
50611
  }
50440
50612
  return null;
50441
50613
  }
50614
+ function paragraphHasMarkerEnd(paragraph, scopeId) {
50615
+ return paragraph.children.some(
50616
+ (child) => child.type === "scope_marker_end" && child.scopeId === scopeId
50617
+ );
50618
+ }
50619
+ function paragraphSameParagraphMarkerScopeId(paragraph, knownScopeIds) {
50620
+ const markerScopeId = paragraphFirstMarkerStart(paragraph, knownScopeIds);
50621
+ if (!markerScopeId) return null;
50622
+ return paragraphHasMarkerEnd(paragraph, markerScopeId) ? markerScopeId : null;
50623
+ }
50624
+ function markerStableRefOverride(markerScopeId, semanticPath, overlay) {
50625
+ const hint = stableRefHintForScopeId(markerScopeId, overlay);
50626
+ if (hint === "semantic-path") {
50627
+ return {
50628
+ kind: "semantic-path",
50629
+ value: semanticPath.join("/")
50630
+ };
50631
+ }
50632
+ return { kind: "scope-id", value: markerScopeId };
50633
+ }
50634
+ function enumerateNestedMarkerBackedParagraphs(blocks, input) {
50635
+ const out = [];
50636
+ for (let childIndex = 0; childIndex < blocks.length; childIndex += 1) {
50637
+ const block = blocks[childIndex];
50638
+ if (!block) continue;
50639
+ if (block.type === "paragraph") {
50640
+ const markerScopeId = paragraphSameParagraphMarkerScopeId(
50641
+ block,
50642
+ input.knownOverlayScopeIds
50643
+ );
50644
+ if (!markerScopeId) continue;
50645
+ const kind = detectParagraphKind(block);
50646
+ const semanticPath = [
50647
+ ...input.semanticPrefix,
50648
+ kind,
50649
+ String(childIndex)
50650
+ ];
50651
+ const handle = buildHandle(
50652
+ markerScopeId,
50653
+ input.documentId,
50654
+ semanticPath,
50655
+ "marker-backed",
50656
+ "marker-backed",
50657
+ input.parentScopeId,
50658
+ markerStableRefOverride(markerScopeId, semanticPath, input.overlay)
50659
+ );
50660
+ out.push({
50661
+ kind,
50662
+ handle,
50663
+ paragraph: block,
50664
+ blockIndex: input.rootBlockIndex,
50665
+ classifications: input.classificationIndex.get(markerScopeId) ?? Object.freeze([])
50666
+ });
50667
+ continue;
50668
+ }
50669
+ if (block.type === "sdt") {
50670
+ out.push(
50671
+ ...enumerateNestedMarkerBackedParagraphs(block.children, {
50672
+ ...input,
50673
+ semanticPrefix: [
50674
+ ...input.semanticPrefix,
50675
+ "sdt",
50676
+ String(childIndex)
50677
+ ]
50678
+ })
50679
+ );
50680
+ continue;
50681
+ }
50682
+ if (block.type === "table") {
50683
+ for (let rowIdx = 0; rowIdx < block.rows.length; rowIdx += 1) {
50684
+ const row2 = block.rows[rowIdx];
50685
+ for (let cellIdx = 0; cellIdx < row2.cells.length; cellIdx += 1) {
50686
+ const cell = row2.cells[cellIdx];
50687
+ out.push(
50688
+ ...enumerateNestedMarkerBackedParagraphs(cell.children, {
50689
+ ...input,
50690
+ semanticPrefix: [
50691
+ ...input.semanticPrefix,
50692
+ "table",
50693
+ String(childIndex),
50694
+ "row",
50695
+ String(rowIdx),
50696
+ "cell",
50697
+ String(cellIdx)
50698
+ ]
50699
+ })
50700
+ );
50701
+ }
50702
+ }
50703
+ }
50704
+ }
50705
+ return out;
50706
+ }
50442
50707
  function enumerateFieldsInParagraph(paragraph, blockIndex, documentId, parentScopeId) {
50443
50708
  const out = [];
50444
50709
  for (let i = 0; i < paragraph.children.length; i += 1) {
@@ -50721,6 +50986,17 @@ function enumerateScopes(document2, inputs = {}) {
50721
50986
  cellIndex: cellIdx,
50722
50987
  classifications: Object.freeze([])
50723
50988
  });
50989
+ for (const nested of enumerateNestedMarkerBackedParagraphs(cell.children, {
50990
+ documentId,
50991
+ rootBlockIndex: index,
50992
+ semanticPrefix: cellSemanticPath,
50993
+ parentScopeId: cellScopeId,
50994
+ knownOverlayScopeIds,
50995
+ classificationIndex,
50996
+ overlay: inputs.overlay
50997
+ })) {
50998
+ results.push(nested);
50999
+ }
50724
51000
  }
50725
51001
  }
50726
51002
  }
@@ -51206,16 +51482,12 @@ function replaceTextCapability(scope, context) {
51206
51482
  );
51207
51483
  }
51208
51484
  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
51485
  return supported(
51217
51486
  "compile-supported:scope:multi-paragraph-text-replace",
51218
- evidenceWarnings(context)
51487
+ [
51488
+ ...scope.workflow.effectiveMode === "suggest" ? ["guard:suggest-mode"] : [],
51489
+ ...evidenceWarnings(context)
51490
+ ]
51219
51491
  );
51220
51492
  }
51221
51493
  if (!PARAGRAPH_LIKE.has(scope.kind)) {
@@ -51269,34 +51541,24 @@ function replaceFragmentCapability(scope, context) {
51269
51541
  );
51270
51542
  }
51271
51543
  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
51544
  return supported(
51282
51545
  "compile-supported:scope:multi-paragraph-fragment-replace",
51283
- evidenceWarnings(context)
51546
+ [
51547
+ ...scope.workflow.effectiveMode === "suggest" ? ["guard:suggest-mode"] : [],
51548
+ ...evidenceWarnings(context)
51549
+ ]
51284
51550
  );
51285
51551
  }
51286
51552
  if (!PARAGRAPH_LIKE.has(scope.kind)) {
51287
51553
  const reason = `compile-refused:${scope.kind}`;
51288
51554
  return unsupported(reason, [reason], evidenceWarnings(context));
51289
51555
  }
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
51556
  return supported(
51298
- "compile-supported:paragraph-like:fragment-replace",
51299
- evidenceWarnings(context)
51557
+ scope.workflow.effectiveMode === "suggest" ? "compile-supported:paragraph-like:fragment-replace-tracked" : "compile-supported:paragraph-like:fragment-replace",
51558
+ [
51559
+ ...scope.workflow.effectiveMode === "suggest" ? ["guard:suggest-mode"] : [],
51560
+ ...evidenceWarnings(context)
51561
+ ]
51300
51562
  );
51301
51563
  }
51302
51564
  function formattingCapability(scope, context) {
@@ -52350,6 +52612,14 @@ function supportedNonTextCommandEvidence(target) {
52350
52612
  if (target.commandFamily === "link-bookmark") {
52351
52613
  return linkBookmarkCommandEvidence(target, []);
52352
52614
  }
52615
+ if (target.commandFamily === "object" && isImageObjectTarget(target) && typeof target.object?.mediaId === "string" && target.object.mediaId.length > 0 && onlyBlockers(target.posture.blockers, ["unmodeled-target"])) {
52616
+ return {
52617
+ status: "supported",
52618
+ commandFamily: target.commandFamily,
52619
+ intents: commandIntentsForTarget(target),
52620
+ reason: "l07:image-layout-target-supported"
52621
+ };
52622
+ }
52353
52623
  return null;
52354
52624
  }
52355
52625
  function onlyBlockers(blockers, allowed) {
@@ -54850,9 +55120,7 @@ function applyScopeReplacement(inputs) {
54850
55120
  });
54851
55121
  if (!plan) {
54852
55122
  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" ? [
55123
+ const blockers = resolvedScope.kind === "scope" && proposed.operation !== "replace" ? [
54856
55124
  `compile-refused:scope:operation-not-implemented:${proposed.operation}`
54857
55125
  ] : resolvedScope.kind === "scope" ? multiParagraphReplacementBlockers(
54858
55126
  proposed.proposedContent.kind === "structured" ? "fragment" : "text"
@@ -72310,7 +72578,20 @@ function createDocumentRuntime(options) {
72310
72578
  selection: prepared.selection
72311
72579
  };
72312
72580
  resolvedReplayTextTarget = prepared.textTarget;
72313
- } else if (command.type === "field.refresh" || command.type === "toc.refresh" || command.type === "bookmark.edit-content" || command.type === "hyperlink.update-destination") {
72581
+ } else {
72582
+ const selectedListItemDeleteCommand = createSelectedListItemDeleteReplayCommand({
72583
+ command,
72584
+ document: replayState.document,
72585
+ selection: replayState.selection,
72586
+ surface: replaySnapshot.surface?.blocks ?? [],
72587
+ storyTarget: replayStory,
72588
+ timestamp: context.timestamp
72589
+ });
72590
+ if (selectedListItemDeleteCommand) {
72591
+ executableCommand = selectedListItemDeleteCommand;
72592
+ }
72593
+ }
72594
+ if (command.type === "field.refresh" || command.type === "toc.refresh" || command.type === "bookmark.edit-content" || command.type === "hyperlink.update-destination") {
72314
72595
  const prepared = prepareModeledTargetCommandForExecution(
72315
72596
  command,
72316
72597
  replayState.document,
@@ -72454,7 +72735,20 @@ function createDocumentRuntime(options) {
72454
72735
  selection: prepared.selection
72455
72736
  };
72456
72737
  resolvedReplayTextTarget = prepared.textTarget;
72457
- } else if (command.type === "field.refresh" || command.type === "toc.refresh" || command.type === "bookmark.edit-content" || command.type === "hyperlink.update-destination") {
72738
+ } else {
72739
+ const selectedListItemDeleteCommand = createSelectedListItemDeleteReplayCommand({
72740
+ command,
72741
+ document: stateForCommand.document,
72742
+ selection: stateForCommand.selection,
72743
+ surface: snapshotForCommand.surface?.blocks ?? [],
72744
+ storyTarget: replayStory,
72745
+ timestamp: context.timestamp
72746
+ });
72747
+ if (selectedListItemDeleteCommand) {
72748
+ executableCommand = selectedListItemDeleteCommand;
72749
+ }
72750
+ }
72751
+ if (command.type === "field.refresh" || command.type === "toc.refresh" || command.type === "bookmark.edit-content" || command.type === "hyperlink.update-destination") {
72458
72752
  const prepared = prepareModeledTargetCommandForExecution(
72459
72753
  command,
72460
72754
  stateForCommand.document,
@@ -72875,6 +73169,50 @@ function createDocumentRuntime(options) {
72875
73169
  } catch (error) {
72876
73170
  emitError(toRuntimeError(error));
72877
73171
  }
73172
+ } else if (step.kind === "fragment-replace-tracked" && step.range && step.fragment && Array.isArray(step.fragment.blocks)) {
73173
+ const editableTarget = resolveEditableTargetHint(step.editableTargetHint);
73174
+ if (editableTarget === null) {
73175
+ emit2({
73176
+ type: "command_blocked",
73177
+ documentId: state.documentId,
73178
+ command: "applyScopeReplacement",
73179
+ reasons: [{
73180
+ code: "unsupported_surface",
73181
+ message: "Scope replacement editable target no longer resolves."
73182
+ }]
73183
+ });
73184
+ continue;
73185
+ }
73186
+ const dispatchRange = mapSemanticStepRangeToEditableTarget(
73187
+ step.range,
73188
+ step.editableTargetHint,
73189
+ editableTarget?.range
73190
+ );
73191
+ const anchor = {
73192
+ kind: "range",
73193
+ from: dispatchRange.from,
73194
+ to: dispatchRange.to,
73195
+ assoc: { start: -1, end: 1 }
73196
+ };
73197
+ const timestamp = clock();
73198
+ try {
73199
+ applyTextCommandInActiveStory(
73200
+ {
73201
+ type: "fragment.insert-tracked",
73202
+ fragment: step.fragment,
73203
+ ...editableTarget?.target ? { editableTarget: editableTarget.target } : {},
73204
+ origin: createOrigin("api", timestamp)
73205
+ },
73206
+ {
73207
+ selection: createSelectionFromPublicAnchor(anchor),
73208
+ blockedCommandName: "applyScopeReplacement",
73209
+ documentModeOverride: "suggesting",
73210
+ skipWorkflowGuard: true
73211
+ }
73212
+ );
73213
+ } catch (error) {
73214
+ emitError(toRuntimeError(error));
73215
+ }
72878
73216
  } else if (step.kind === "text-insert-tracked" && step.range && typeof step.text === "string") {
72879
73217
  const editableTarget = resolveEditableTargetHint(step.editableTargetHint);
72880
73218
  if (editableTarget === null) {
@@ -74857,6 +75195,38 @@ function createDocumentRuntime(options) {
74857
75195
  const preSelection = selection;
74858
75196
  const preActiveStory = activeStory;
74859
75197
  const priorDocument = state.document;
75198
+ const selectedListItemDelete = createSelectedListItemDeleteReplacement({
75199
+ command: commandForDispatch,
75200
+ document: state.document,
75201
+ editableTarget,
75202
+ selection,
75203
+ targetResolution,
75204
+ timestamp
75205
+ });
75206
+ if (selectedListItemDelete && activeStory.kind === "main" && context.documentMode !== "suggesting") {
75207
+ const replacementCommand = {
75208
+ type: "document.replace",
75209
+ document: selectedListItemDelete.document,
75210
+ selection: selectedListItemDelete.selection,
75211
+ mapping: selectedListItemDelete.mapping,
75212
+ protectionSelection: selection,
75213
+ origin: commandForDispatch.origin
75214
+ };
75215
+ const transaction = executeEditorCommand(baseState, replacementCommand, context);
75216
+ commit(transaction);
75217
+ options.onCommandApplied?.(commandForDispatch, transaction, context, {
75218
+ preSelection,
75219
+ activeStory: preActiveStory,
75220
+ priorDocument
75221
+ });
75222
+ return completeDispatch(classifyAck({
75223
+ command: commandForDispatch,
75224
+ opId,
75225
+ priorState: baseState,
75226
+ transaction,
75227
+ newRevisionToken: state.revisionToken
75228
+ }));
75229
+ }
74860
75230
  if (activeStory.kind === "main") {
74861
75231
  const mainTransaction = executeEditorCommand(baseState, commandForDispatch, context);
74862
75232
  commit(mainTransaction);
@@ -76051,7 +76421,8 @@ function getStoryPlainText(document2, storyTarget, cache) {
76051
76421
  const plainText = createEditorSurfaceSnapshot(
76052
76422
  document2,
76053
76423
  createSelectionSnapshot(0, 0),
76054
- storyTarget
76424
+ storyTarget,
76425
+ { editableTargetsByBlockPath: NO_EDITABLE_TARGETS_INDEX }
76055
76426
  ).plainText;
76056
76427
  cache.set(key, plainText);
76057
76428
  return plainText;
@@ -77727,6 +78098,172 @@ function stripStoryTarget2(selection) {
77727
78098
  function isTopLevelMainStoryBlockPath(blockPath) {
77728
78099
  return typeof blockPath === "string" && /^main\/block\[\d+\]$/u.test(blockPath);
77729
78100
  }
78101
+ function createSelectedListItemDeleteReplacement(input) {
78102
+ const { command, document: document2, editableTarget, selection, targetResolution, timestamp } = input;
78103
+ if (command.type !== "text.delete-backward" && command.type !== "text.delete-forward") {
78104
+ return null;
78105
+ }
78106
+ if (selection.isCollapsed || editableTarget?.listAddress?.operationScope !== "list-text" || targetResolution?.kind !== "accepted") {
78107
+ return null;
78108
+ }
78109
+ const selectionFrom = Math.min(selection.anchor, selection.head);
78110
+ const selectionTo = Math.max(selection.anchor, selection.head);
78111
+ if (selectionFrom !== targetResolution.range.from || selectionTo !== targetResolution.range.to) {
78112
+ return null;
78113
+ }
78114
+ const root = document2.content;
78115
+ const replacement = removeNumberedParagraphAtMainStoryPath(root.children, editableTarget.blockPath);
78116
+ if (!replacement) {
78117
+ return null;
78118
+ }
78119
+ const nextDocument = {
78120
+ ...document2,
78121
+ updatedAt: timestamp,
78122
+ content: {
78123
+ ...root,
78124
+ children: replacement.blocks
78125
+ }
78126
+ };
78127
+ const nextStorySize = parseTextStory(nextDocument.content).size;
78128
+ const nextAnchor = Math.min(selectionFrom, nextStorySize);
78129
+ return {
78130
+ document: nextDocument,
78131
+ selection: createSelectionSnapshot(nextAnchor, nextAnchor),
78132
+ mapping: {
78133
+ steps: [{
78134
+ from: selectionFrom,
78135
+ to: selectionTo,
78136
+ insertSize: 0
78137
+ }],
78138
+ metadata: {
78139
+ invalidatesStructures: true
78140
+ }
78141
+ }
78142
+ };
78143
+ }
78144
+ function removeNumberedParagraphAtMainStoryPath(blocks, blockPath) {
78145
+ const tokens = parseMainStoryBlockPathTokens(blockPath);
78146
+ if (!tokens) return null;
78147
+ return removeNumberedParagraphFromBlocks(blocks, tokens);
78148
+ }
78149
+ function removeNumberedParagraphFromBlocks(blocks, tokens) {
78150
+ const [token, ...rest] = tokens;
78151
+ if (!token || token.kind !== "block") return null;
78152
+ const block = blocks[token.index];
78153
+ if (!block) return null;
78154
+ if (rest.length === 0) {
78155
+ if (block.type !== "paragraph" || !block.numbering) {
78156
+ return null;
78157
+ }
78158
+ if (blocks.length === 1) {
78159
+ return { blocks: [{ type: "paragraph", children: [] }] };
78160
+ }
78161
+ return {
78162
+ blocks: [
78163
+ ...blocks.slice(0, token.index),
78164
+ ...blocks.slice(token.index + 1)
78165
+ ]
78166
+ };
78167
+ }
78168
+ const next = rest[0];
78169
+ if (block.type === "table" && next?.kind === "row") {
78170
+ const updatedTable = removeNumberedParagraphFromTable(block, rest);
78171
+ if (!updatedTable) return null;
78172
+ return {
78173
+ blocks: [
78174
+ ...blocks.slice(0, token.index),
78175
+ updatedTable,
78176
+ ...blocks.slice(token.index + 1)
78177
+ ]
78178
+ };
78179
+ }
78180
+ if ((block.type === "sdt" || block.type === "custom_xml") && next?.kind === "block") {
78181
+ const updatedChildren = removeNumberedParagraphFromBlocks(block.children, rest);
78182
+ if (!updatedChildren) return null;
78183
+ return {
78184
+ blocks: [
78185
+ ...blocks.slice(0, token.index),
78186
+ { ...block, children: updatedChildren.blocks },
78187
+ ...blocks.slice(token.index + 1)
78188
+ ]
78189
+ };
78190
+ }
78191
+ return null;
78192
+ }
78193
+ function removeNumberedParagraphFromTable(table, tokens) {
78194
+ const [rowToken, cellToken, ...childTokens] = tokens;
78195
+ if (rowToken?.kind !== "row" || cellToken?.kind !== "cell" || childTokens[0]?.kind !== "block") {
78196
+ return null;
78197
+ }
78198
+ const row2 = table.rows[rowToken.index];
78199
+ const cell = row2?.cells[cellToken.index];
78200
+ if (!row2 || !cell) return null;
78201
+ const updatedChildren = removeNumberedParagraphFromBlocks(cell.children, childTokens);
78202
+ if (!updatedChildren) return null;
78203
+ const nextCells = [
78204
+ ...row2.cells.slice(0, cellToken.index),
78205
+ { ...cell, children: updatedChildren.blocks },
78206
+ ...row2.cells.slice(cellToken.index + 1)
78207
+ ];
78208
+ const nextRows = [
78209
+ ...table.rows.slice(0, rowToken.index),
78210
+ { ...row2, cells: nextCells },
78211
+ ...table.rows.slice(rowToken.index + 1)
78212
+ ];
78213
+ return { ...table, rows: nextRows };
78214
+ }
78215
+ function createSelectedListItemDeleteReplayCommand(input) {
78216
+ const { command, document: document2, selection, surface, storyTarget, timestamp } = input;
78217
+ if (command.type !== "text.delete-backward" && command.type !== "text.delete-forward") {
78218
+ return null;
78219
+ }
78220
+ const editableTarget = command.editableTarget;
78221
+ if (!editableTarget) {
78222
+ return null;
78223
+ }
78224
+ const targetResolution = resolveEditableTextTarget({
78225
+ document: document2,
78226
+ selection,
78227
+ surface,
78228
+ target: editableTarget,
78229
+ activeStoryKey: canonicalEditableTargetStoryKey(storyTarget)
78230
+ });
78231
+ const replacement = createSelectedListItemDeleteReplacement({
78232
+ command,
78233
+ document: document2,
78234
+ editableTarget,
78235
+ selection,
78236
+ targetResolution,
78237
+ timestamp
78238
+ });
78239
+ if (!replacement || storyTarget.kind !== "main") {
78240
+ return null;
78241
+ }
78242
+ return {
78243
+ type: "document.replace",
78244
+ document: replacement.document,
78245
+ selection: replacement.selection,
78246
+ mapping: replacement.mapping,
78247
+ protectionSelection: selection,
78248
+ origin: command.origin
78249
+ };
78250
+ }
78251
+ function parseMainStoryBlockPathTokens(blockPath) {
78252
+ const parts = blockPath?.split("/") ?? [];
78253
+ if (parts[0] !== "main" || parts.length < 2) {
78254
+ return null;
78255
+ }
78256
+ const tokens = [];
78257
+ for (const part of parts.slice(1)) {
78258
+ const match = /^(block|row|cell)\[(\d+)\]$/u.exec(part);
78259
+ if (!match) return null;
78260
+ tokens.push({
78261
+ kind: match[1],
78262
+ index: Number.parseInt(match[2], 10)
78263
+ });
78264
+ }
78265
+ return tokens;
78266
+ }
77730
78267
  function toInternalSelectionSnapshot2(selection) {
77731
78268
  return {
77732
78269
  anchor: selection.anchor,
@@ -94621,18 +95158,7 @@ function findScrollAnchor(root, options) {
94621
95158
  offsetWithinBlock: viewportTopFramePx - blockTop
94622
95159
  };
94623
95160
  }
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
- };
95161
+ return null;
94636
95162
  }
94637
95163
  return null;
94638
95164
  }
@@ -94650,17 +95176,9 @@ function resolveScrollTopForAnchor(root, anchor, options) {
94650
95176
  const rect3 = geometry.rects[0];
94651
95177
  return rect3.topPx + anchor.offsetWithinBlock;
94652
95178
  }
95179
+ return null;
94653
95180
  }
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}`);
95181
+ return null;
94664
95182
  }
94665
95183
 
94666
95184
  // src/ui-tailwind/chrome/tw-alert-banner.tsx
@@ -103245,6 +103763,14 @@ var EDITOR_ACTION_REGISTRY = [
103245
103763
  targetKinds: ["generated-field"],
103246
103764
  callback: "onUpdateFields"
103247
103765
  }),
103766
+ mk({
103767
+ id: "toc-refresh",
103768
+ label: "Refresh table of contents",
103769
+ description: "Refresh TOC entries through the runtime table-of-contents updater.",
103770
+ group: "misc",
103771
+ targetKinds: ["toc-field"],
103772
+ callback: "onUpdateTableOfContents"
103773
+ }),
103248
103774
  // -------- Workflow scope --------
103249
103775
  mk({
103250
103776
  id: "scope-open-card",
@@ -104138,6 +104664,18 @@ function hasAncestorAttributeValue(el, attribute, expected, root) {
104138
104664
  }
104139
104665
  return false;
104140
104666
  }
104667
+ function hasAncestorAttributeValueInsensitive(el, attribute, expected, root) {
104668
+ const normalizedExpected = expected.toLowerCase();
104669
+ let cursor = el;
104670
+ while (cursor) {
104671
+ if (cursor.getAttribute?.(attribute)?.toLowerCase() === normalizedExpected) {
104672
+ return true;
104673
+ }
104674
+ if (cursor === root) return false;
104675
+ cursor = cursor.parentElement;
104676
+ }
104677
+ return false;
104678
+ }
104141
104679
  function resolveTargetKind(target, options = {}) {
104142
104680
  const kinds = [];
104143
104681
  const el = toElement(target);
@@ -104152,7 +104690,12 @@ function resolveTargetKind(target, options = {}) {
104152
104690
  const insideListItem = insideNumberingMarker || hasAncestorAttributeValue(el, "data-numbered", "true", root);
104153
104691
  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
104692
  if (insideListItem) kinds.push("list-item");
104155
- if (insideGeneratedField) kinds.push("generated-field");
104693
+ if (insideGeneratedField) {
104694
+ kinds.push("generated-field");
104695
+ if (hasAncestorAttributeValueInsensitive(el, "data-field-family", "TOC", root)) {
104696
+ kinds.push("toc-field");
104697
+ }
104698
+ }
104156
104699
  if (hasAncestorTag(el, "a", root)) kinds.push("hyperlink");
104157
104700
  if (!insideNumberingMarker && hasAncestorTag(el, "img", root)) {
104158
104701
  kinds.push("image");
@@ -105225,294 +105768,6 @@ function resolveSkeletalPageOverlayRectsFromLayout(facet) {
105225
105768
  }
105226
105769
  return rects;
105227
105770
  }
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
105771
  var resolvePageOverlayRectsFromGeometry2 = resolvePageOverlayRectsFromGeometry;
105517
105772
  function resolvePageOverlayRectsFromUiApi(ui, pageCount, visiblePageIndexRange, pageIds) {
105518
105773
  if (pageCount <= 0) return [];
@@ -105570,17 +105825,6 @@ var TwPageStackOverlayLayer = ({
105570
105825
  },
105571
105826
  []
105572
105827
  );
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
105828
  const refreshRectsNow = React23.useCallback(() => {
105585
105829
  const pageCount = facet.getPageCount();
105586
105830
  const skeletalRects = resolveSkeletalPageOverlayRectsFromLayout(facet);
@@ -105621,51 +105865,11 @@ var TwPageStackOverlayLayer = ({
105621
105865
  setRectsIfChanged(skeletalRects);
105622
105866
  return;
105623
105867
  }
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
- }
105868
+ incrementInvalidationCounter("overlay.page.skeletal_fallback");
105869
+ setRectsIfChanged(skeletalRects);
105664
105870
  }, [
105665
105871
  facet,
105666
105872
  geometryFacet,
105667
- reconcilePaperRectsWithFlow,
105668
- scrollRoot,
105669
105873
  setRectsIfChanged,
105670
105874
  ui
105671
105875
  ]);
@@ -105696,33 +105900,6 @@ var TwPageStackOverlayLayer = ({
105696
105900
  }
105697
105901
  };
105698
105902
  }, [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
105903
  if (rects.length === 0) {
105727
105904
  return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
105728
105905
  "div",
@@ -107814,25 +107991,23 @@ var TwPageStackChromeLayerInner = ({
107814
107991
  const pageCount = facet.getPageCount();
107815
107992
  const uiRects = resolveUiPageRects(pageCount);
107816
107993
  if (uiRects !== null) return uiRects;
107817
- if (!geometryFacet) return [];
107818
- const warm = resolvePageOverlayRectsFromGeometry2(
107819
- geometryFacet,
107820
- pageCount,
107821
- visiblePageIndexRange
107994
+ if (geometryFacet) {
107995
+ const warm = resolvePageOverlayRectsFromGeometry2(
107996
+ geometryFacet,
107997
+ pageCount,
107998
+ visiblePageIndexRange
107999
+ );
108000
+ if (warm !== null) return warm;
108001
+ }
108002
+ return resolveSkeletalPageOverlayRectsFromLayout(facet).filter(
108003
+ (rect3) => !visiblePageIndexRange || rect3.pageIndex >= visiblePageIndexRange.start && rect3.pageIndex < visiblePageIndexRange.end
107822
108004
  );
107823
- return warm ?? [];
107824
108005
  });
107825
108006
  const overlayRootRef = import_react33.default.useRef(null);
107826
108007
  const rafHandleRef = import_react33.default.useRef(null);
107827
108008
  const [activeStoryPageIndex, setActiveStoryPageIndex] = import_react33.default.useState(null);
107828
108009
  const refreshRectsNow = import_react33.default.useCallback(() => {
107829
108010
  const pageCount = facet.getPageCount();
107830
- const reconcileDomRects = (baseRects) => reconcilePageStackRectsWithFlow({
107831
- baseRects,
107832
- pageCount,
107833
- scrollRoot,
107834
- originElement: overlayRootRef.current
107835
- });
107836
108011
  const uiRects = resolveUiPageRects(pageCount);
107837
108012
  if (uiRects !== null) {
107838
108013
  setRects(uiRects);
@@ -107851,47 +108026,11 @@ var TwPageStackChromeLayerInner = ({
107851
108026
  setRects([]);
107852
108027
  return;
107853
108028
  }
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]);
108029
+ const skeletalRects = resolveSkeletalPageOverlayRectsFromLayout(facet).filter(
108030
+ (rect3) => !visiblePageIndexRange || rect3.pageIndex >= visiblePageIndexRange.start && rect3.pageIndex < visiblePageIndexRange.end
108031
+ );
108032
+ setRects(skeletalRects);
108033
+ }, [facet, geometryFacet, resolveUiPageRects, visiblePageIndexRange]);
107895
108034
  const refreshRects = import_react33.default.useCallback(() => {
107896
108035
  if (!scrollRoot) {
107897
108036
  refreshRectsNow();
@@ -107931,33 +108070,6 @@ var TwPageStackChromeLayerInner = ({
107931
108070
  },
107932
108071
  [onOpenStory]
107933
108072
  );
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
108073
  import_react33.default.useLayoutEffect(() => {
107962
108074
  if (!pmSurfaceElement) return;
107963
108075
  const overlay = overlayRootRef.current;
@@ -109439,45 +109551,17 @@ var TwFloatingImageLayer = ({
109439
109551
  const [pageRects, setPageRects] = React39.useState([]);
109440
109552
  const refreshPageRectsNow = React39.useCallback(() => {
109441
109553
  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) {
109456
- setPageRects([]);
109457
- return;
109458
- }
109459
- const origin = overlayRootRef.current;
109460
- if (!origin) {
109554
+ if (!geometryFacet) {
109461
109555
  setPageRects([]);
109462
109556
  return;
109463
109557
  }
109464
- const widgets = measureWidgetsViaBoundingRect(scrollRoot, origin, {
109558
+ const geometryRects = resolvePageOverlayRectsFromGeometry2(
109559
+ geometryFacet,
109465
109560
  pageCount,
109466
109561
  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
109562
  );
109480
- }, [facet, geometryFacet, scrollRoot, visiblePageIndexRange]);
109563
+ setPageRects(geometryRects ?? []);
109564
+ }, [facet, geometryFacet, visiblePageIndexRange]);
109481
109565
  const refreshPageRects = React39.useCallback(() => {
109482
109566
  if (!scrollRoot) {
109483
109567
  refreshPageRectsNow();
@@ -109507,47 +109591,6 @@ var TwFloatingImageLayer = ({
109507
109591
  }
109508
109592
  };
109509
109593
  }, [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
109594
  const items = React39.useMemo(() => {
109552
109595
  const viewportScale = geometryFacet?.getViewport().pxPerTwip;
109553
109596
  const pxPerTwip = typeof viewportScale === "number" && viewportScale > 0 ? viewportScale : void 0;
@@ -112526,7 +112569,7 @@ function createOverlaysFamily(ctx) {
112526
112569
  return {
112527
112570
  handle: {
112528
112571
  kind: "table-action",
112529
- id: tableActionHandle2(documentSeed2(), targetKey),
112572
+ id: tableActionHandle2(documentSeed3(), targetKey),
112530
112573
  targetKind,
112531
112574
  commandFamily,
112532
112575
  scope: tableActionScope(entry)
@@ -112550,9 +112593,9 @@ function createOverlaysFamily(ctx) {
112550
112593
  };
112551
112594
  }
112552
112595
  function tableActionHandle2(seed, targetKey) {
112553
- return `table-action:${hashOpaque3(`${seed}::${targetKey}`)}`;
112596
+ return `table-action:${hashOpaque4(`${seed}::${targetKey}`)}`;
112554
112597
  }
112555
- function hashOpaque3(input) {
112598
+ function hashOpaque4(input) {
112556
112599
  let h = 2166136261;
112557
112600
  for (let i = 0; i < input.length; i += 1) {
112558
112601
  h ^= input.charCodeAt(i);
@@ -112560,7 +112603,7 @@ function createOverlaysFamily(ctx) {
112560
112603
  }
112561
112604
  return (h >>> 0).toString(36).padStart(7, "0");
112562
112605
  }
112563
- function documentSeed2() {
112606
+ function documentSeed3() {
112564
112607
  return ctx.handle.getSessionState().documentId ?? "document";
112565
112608
  }
112566
112609
  function modeledTargetSourceIdentity(target) {
@@ -117036,8 +117079,39 @@ function buildPositionMap(surface) {
117036
117079
  }
117037
117080
  return lastStoryRestorableEntry(entries)?.pmEnd ?? 1;
117038
117081
  };
117082
+ const runtimeToPmWithBias = (runtimePos, bias) => {
117083
+ if (bias > 0) return runtimeToPm(runtimePos);
117084
+ const firstEditable = entries.find(isStoryRuntimeRestorableEntry);
117085
+ if (runtimePos <= 0) {
117086
+ return firstEditable?.pmStart ?? 1;
117087
+ }
117088
+ if (runtimePos >= runtimeStorySize) {
117089
+ return lastStoryRestorableEntry(entries)?.pmEnd ?? pmDocSize - 1;
117090
+ }
117091
+ let previous = null;
117092
+ for (const entry of entries) {
117093
+ if (!isStoryRuntimeRestorableEntry(entry)) {
117094
+ continue;
117095
+ }
117096
+ if (entry.runtimeStart === entry.runtimeEnd && runtimePos === entry.runtimeStart) {
117097
+ return previous?.pmEnd ?? entry.pmStart;
117098
+ }
117099
+ if (runtimePos > entry.runtimeStart && runtimePos <= entry.runtimeEnd) {
117100
+ return entry.pmStart + (runtimePos - entry.runtimeStart);
117101
+ }
117102
+ if (runtimePos <= entry.runtimeStart) {
117103
+ if (runtimePos === entry.runtimeStart && previous) {
117104
+ return previous.pmEnd;
117105
+ }
117106
+ return nearestRuntimeGapPm(runtimePos, previous, entry);
117107
+ }
117108
+ previous = entry;
117109
+ }
117110
+ return lastStoryRestorableEntry(entries)?.pmEnd ?? 1;
117111
+ };
117039
117112
  return {
117040
117113
  runtimeToPm,
117114
+ runtimeToPmWithBias,
117041
117115
  runtimeToPmWithContext(input) {
117042
117116
  const targetEntry = input.editableTarget ? findRestorableEntryForTarget(entries, input.editableTarget, input.runtimePos) : void 0;
117043
117117
  if (targetEntry) {
@@ -119785,9 +119859,10 @@ function buildAnchorPmRange(anchor, positionMap) {
119785
119859
  return null;
119786
119860
  }
119787
119861
  if (anchor.kind === "range") {
119862
+ const mapBoundary = (runtimePos, bias) => positionMap.runtimeToPmWithBias ? positionMap.runtimeToPmWithBias(runtimePos, bias) : positionMap.runtimeToPm(runtimePos);
119788
119863
  return {
119789
- from: positionMap.runtimeToPm(anchor.from),
119790
- to: positionMap.runtimeToPm(anchor.to),
119864
+ from: mapBoundary(anchor.from, anchor.assoc?.start ?? 1),
119865
+ to: mapBoundary(anchor.to, anchor.assoc?.end ?? -1),
119791
119866
  allowInline: true
119792
119867
  };
119793
119868
  }
@@ -121494,13 +121569,6 @@ function resolvePmPageBodyPatchPlan(input) {
121494
121569
  ...input.patchPlan.addedPages,
121495
121570
  ...input.patchPlan.removedPages
121496
121571
  ];
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
121572
  if (input.currentDoc.childCount !== input.previousSurface.blocks.length || input.nextDoc.childCount !== input.nextSurface.blocks.length) {
121505
121573
  return {
121506
121574
  status: "fallback",
@@ -121577,6 +121645,21 @@ function resolvePmPageBodyPatchPlan(input) {
121577
121645
  ]
121578
121646
  );
121579
121647
  }
121648
+ if (membershipChangedPageIds.length > 0 && !input.currentDoc.eq(input.nextDoc)) {
121649
+ const membershipSpan = resolveTopLevelMembershipPatchSpan({
121650
+ pageIds: membershipChangedPageIds,
121651
+ previousSurface: input.previousSurface,
121652
+ nextSurface: input.nextSurface
121653
+ });
121654
+ if (!membershipSpan) {
121655
+ return {
121656
+ status: "fallback",
121657
+ reason: "page-membership-change",
121658
+ pageIds: membershipChangedPageIds
121659
+ };
121660
+ }
121661
+ rawSpans.push(membershipSpan);
121662
+ }
121580
121663
  if (rawSpans.length === 0) {
121581
121664
  if (membershipChangedPageIds.length > 0 && !input.currentDoc.eq(input.nextDoc)) {
121582
121665
  return {
@@ -121653,17 +121736,17 @@ function resolveNestedPatchSpan(input) {
121653
121736
  (id) => findNestedBlockSpanById(input.nextSurface.blocks, input.nextDoc, id)
121654
121737
  );
121655
121738
  if (previousLocated.some((span) => !span) || nextLocated.some((span) => !span)) {
121656
- return { status: "fallback", reason: "nested-span-unavailable" };
121739
+ return { status: "top-level" };
121657
121740
  }
121658
121741
  if (previousLocated.some(isUnpatchableVerticalMergeContinuationSpan) || nextLocated.some(isUnpatchableVerticalMergeContinuationSpan)) {
121659
- return { status: "fallback", reason: "split-row-continuation" };
121742
+ return { status: "top-level" };
121660
121743
  }
121661
121744
  const previousSpans = previousLocated;
121662
121745
  const nextSpans = nextLocated;
121663
121746
  const previousKinds = new Set(previousSpans.map((span) => span.block.kind));
121664
121747
  const nextKinds = new Set(nextSpans.map((span) => span.block.kind));
121665
121748
  if (previousKinds.size !== nextKinds.size || [...previousKinds].some((kind) => !nextKinds.has(kind))) {
121666
- return { status: "fallback", reason: "nested-span-unavailable" };
121749
+ return { status: "top-level" };
121667
121750
  }
121668
121751
  return {
121669
121752
  status: "nested",
@@ -121686,6 +121769,46 @@ function isUnpatchableVerticalMergeContinuationSpan(span) {
121686
121769
  span?.insideVerticalMergeContinuation && span.fromPm >= span.toPm
121687
121770
  );
121688
121771
  }
121772
+ function resolveTopLevelMembershipPatchSpan(input) {
121773
+ const previousBlocks = input.previousSurface.blocks;
121774
+ const nextBlocks = input.nextSurface.blocks;
121775
+ let prefix = 0;
121776
+ while (prefix < previousBlocks.length && prefix < nextBlocks.length && sameTopLevelBlockIdentity(previousBlocks[prefix], nextBlocks[prefix])) {
121777
+ prefix += 1;
121778
+ }
121779
+ let suffix = 0;
121780
+ while (suffix < previousBlocks.length - prefix && suffix < nextBlocks.length - prefix && sameTopLevelBlockIdentity(
121781
+ previousBlocks[previousBlocks.length - 1 - suffix],
121782
+ nextBlocks[nextBlocks.length - 1 - suffix]
121783
+ )) {
121784
+ suffix += 1;
121785
+ }
121786
+ let fromBlockIndex = prefix;
121787
+ let toBlockIndex = previousBlocks.length - suffix;
121788
+ let nextFromBlockIndex = prefix;
121789
+ let nextToBlockIndex = nextBlocks.length - suffix;
121790
+ if (fromBlockIndex === toBlockIndex && nextFromBlockIndex === nextToBlockIndex) {
121791
+ if (previousBlocks.length === 0 && nextBlocks.length === 0) return null;
121792
+ fromBlockIndex = 0;
121793
+ toBlockIndex = previousBlocks.length;
121794
+ nextFromBlockIndex = 0;
121795
+ nextToBlockIndex = nextBlocks.length;
121796
+ }
121797
+ return {
121798
+ pageIds: [...input.pageIds],
121799
+ fromBlockIndex,
121800
+ toBlockIndex,
121801
+ nextFromBlockIndex,
121802
+ nextToBlockIndex,
121803
+ replacedBlockCount: Math.max(
121804
+ toBlockIndex - fromBlockIndex,
121805
+ nextToBlockIndex - nextFromBlockIndex
121806
+ )
121807
+ };
121808
+ }
121809
+ function sameTopLevelBlockIdentity(left, right) {
121810
+ return Boolean(left && right && left.blockId === right.blockId && left.kind === right.kind);
121811
+ }
121689
121812
  function resolveTopLevelFragmentPatchSpans(input) {
121690
121813
  const changedIds = uniqueStableFragmentIds(input.changedFragmentIds);
121691
121814
  if (changedIds.length !== input.changedFragmentIds.length || changedIds.length === 0) {
@@ -121753,6 +121876,7 @@ function findBlockSpanByIdInContainer(input) {
121753
121876
  let cursor = input.contentStartPm;
121754
121877
  for (let blockIndex = 0; blockIndex < input.blocks.length; blockIndex += 1) {
121755
121878
  const block = input.blocks[blockIndex];
121879
+ if (blockIndex >= input.containerNode.childCount) return null;
121756
121880
  const childNode = input.containerNode.child(blockIndex);
121757
121881
  const fromPm = cursor;
121758
121882
  const toPm = fromPm + childNode.nodeSize;
@@ -121792,10 +121916,12 @@ function findBlockSpanByIdInTable(input) {
121792
121916
  let rowCursor = input.tableStartPm + 1;
121793
121917
  for (let rowIndex = 0; rowIndex < input.table.rows.length; rowIndex += 1) {
121794
121918
  const row2 = input.table.rows[rowIndex];
121919
+ if (rowIndex >= input.tableNode.childCount) return null;
121795
121920
  const rowNode = input.tableNode.child(rowIndex);
121796
121921
  let cellCursor = rowCursor + 1;
121797
121922
  for (let cellIndex = 0; cellIndex < row2.cells.length; cellIndex += 1) {
121798
121923
  const cell = row2.cells[cellIndex];
121924
+ if (cellIndex >= rowNode.childCount) return null;
121799
121925
  const cellNode = rowNode.child(cellIndex);
121800
121926
  const found = findBlockSpanByIdInContainer({
121801
121927
  blocks: cell.content,
@@ -133061,6 +133187,216 @@ function createOutlineFamily(runtime) {
133061
133187
  };
133062
133188
  }
133063
133189
 
133190
+ // src/api/v3/ai/object.ts
133191
+ var applyObjectActionMetadata = {
133192
+ name: "ai.applyObjectAction",
133193
+ status: "live-with-adapter",
133194
+ sourceLayer: "runtime-core",
133195
+ liveEvidence: {
133196
+ runnerTest: "test/api/v3/ai/ai-object-actions.test.ts",
133197
+ commit: "refactor-09-object-actions"
133198
+ },
133199
+ uxIntent: {
133200
+ uiVisible: true,
133201
+ expectsUxResponse: "inline-change",
133202
+ expectedDelta: "image object layout changes"
133203
+ },
133204
+ agentMetadata: {
133205
+ readOrMutate: "mutate",
133206
+ boundedScope: "scope",
133207
+ auditCategory: "object-action-apply",
133208
+ contextPromptShape: "Apply an image object layout operation to an opaque actionHandle from ai.listObjectActions; preserve-only objects return typed blockers."
133209
+ },
133210
+ stateClass: "A-canonical",
133211
+ persistsTo: "canonical",
133212
+ broadcastsVia: "crdt",
133213
+ rwdReference: "\xA7AI API \xA7 ai.applyObjectAction"
133214
+ };
133215
+ function createObjectActionFamily(runtime) {
133216
+ const compiler = createScopeCompilerService(runtime);
133217
+ return {
133218
+ listObjectActions(input) {
133219
+ const bundle = compiler.compileBundleById(input.handle.scopeId, input.nowUtc);
133220
+ if (!bundle) {
133221
+ return {
133222
+ scopeId: input.handle.scopeId,
133223
+ actions: [],
133224
+ blockers: [`scope-not-resolvable:${input.handle.scopeId}`]
133225
+ };
133226
+ }
133227
+ const seed = documentSeed2(runtime);
133228
+ return {
133229
+ scopeId: input.handle.scopeId,
133230
+ actions: bundle.evidence.editableTargets?.entries.filter(isSupportedImageObjectEvidence).map((entry) => projectObjectDescriptor(runtime, seed, entry)) ?? []
133231
+ };
133232
+ },
133233
+ applyObjectAction(input) {
133234
+ emitUxResponse(runtime, {
133235
+ apiFn: applyObjectActionMetadata.name,
133236
+ intent: "object-action-apply",
133237
+ mockOrLive: applyObjectActionMetadata.status,
133238
+ uiVisible: true,
133239
+ expectedDelta: applyObjectActionMetadata.uxIntent.expectedDelta
133240
+ });
133241
+ const nowUtc = currentAuditTimestamp(runtime);
133242
+ const proposalId = input.proposalId ?? mockId(documentSeed2(runtime), `object-action-${input.actionHandle || "missing-handle"}-${input.operation.kind}`);
133243
+ if (!input.actionHandle) {
133244
+ return blockedResult2(input, proposalId, {
133245
+ code: "object-action-handle-required",
133246
+ category: "unresolved-target",
133247
+ message: "Object actions require an actionHandle from ai.listObjectActions.",
133248
+ nextStep: "Call ai.listObjectActions for the owning scope and retry with one returned actionHandle.",
133249
+ operation: input.operation.kind
133250
+ });
133251
+ }
133252
+ const target = resolveObjectActionHandle(runtime, documentSeed2(runtime), input.actionHandle);
133253
+ if (!target) {
133254
+ return blockedResult2(input, proposalId, {
133255
+ code: `object-action-handle-not-found:${input.actionHandle}`,
133256
+ category: "unresolved-target",
133257
+ message: "No current object action matches the supplied opaque action handle.",
133258
+ nextStep: "Refresh the scope bundle or call ai.listObjectActions before retrying.",
133259
+ actionHandle: input.actionHandle,
133260
+ operation: input.operation.kind
133261
+ });
133262
+ }
133263
+ if (!isSupportedImageTarget(target)) {
133264
+ return blockedResult2(input, proposalId, {
133265
+ code: "object-action-image-target-required",
133266
+ category: "blocked-target",
133267
+ message: "The supplied object action handle no longer identifies a mutable image target.",
133268
+ nextStep: "Refresh object actions; chart, OLE, custom XML, opaque, and stale object targets remain refused.",
133269
+ actionHandle: input.actionHandle,
133270
+ operation: input.operation.kind
133271
+ });
133272
+ }
133273
+ const mediaId = target.object?.mediaId;
133274
+ if (!mediaId) {
133275
+ return blockedResult2(input, proposalId, {
133276
+ code: "object-action-media-id-required",
133277
+ category: "blocked-target",
133278
+ message: "Image object layout mutation requires a mediaId-backed target.",
133279
+ nextStep: "Retry only with an image action handle whose readback includes mediaId.",
133280
+ actionHandle: input.actionHandle,
133281
+ operation: input.operation.kind
133282
+ });
133283
+ }
133284
+ const before = runtime.getCanonicalDocument();
133285
+ runtime.dispatch({
133286
+ type: "image.set-layout",
133287
+ mediaId,
133288
+ dimensions: {
133289
+ widthEmu: input.operation.dimensions.widthEmu,
133290
+ heightEmu: input.operation.dimensions.heightEmu
133291
+ },
133292
+ origin: { source: "api", timestamp: nowUtc }
133293
+ });
133294
+ const changed = runtime.getCanonicalDocument() !== before;
133295
+ if (!changed) {
133296
+ return blockedResult2(input, proposalId, {
133297
+ code: `object-action-noop:${input.operation.kind}:${input.actionHandle}`,
133298
+ category: "runtime-noop",
133299
+ message: "The runtime accepted the image target but the object operation produced no document change.",
133300
+ nextStep: "Refresh object actions and verify the image media item still exists before retrying.",
133301
+ actionHandle: input.actionHandle,
133302
+ operation: input.operation.kind
133303
+ });
133304
+ }
133305
+ return {
133306
+ proposalId,
133307
+ applied: true,
133308
+ changed: true,
133309
+ actionHandle: input.actionHandle,
133310
+ operation: input.operation.kind,
133311
+ commandReference: {
133312
+ command: "image.set-layout",
133313
+ actorId: input.actorId ?? "v3-ai-api",
133314
+ origin: input.origin ?? "agent",
133315
+ emittedAtUtc: nowUtc
133316
+ },
133317
+ readback: objectReadback(runtime, target),
133318
+ posture: "supported",
133319
+ support: objectActionSupport()
133320
+ };
133321
+ }
133322
+ };
133323
+ }
133324
+ function isSupportedImageObjectEvidence(entry) {
133325
+ 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";
133326
+ }
133327
+ function projectObjectDescriptor(runtime, seed, entry) {
133328
+ return {
133329
+ actionHandle: objectActionHandle(seed, entry.targetKey),
133330
+ objectKind: entry.object?.objectKind ?? "unknown",
133331
+ targetKind: entry.kind,
133332
+ relation: entry.relation,
133333
+ callableOperations: ["set-image-layout"],
133334
+ supportedOperations: ["set-image-layout"],
133335
+ readback: objectReadback(runtime, entry),
133336
+ reason: entry.runtimeCommand.reason
133337
+ };
133338
+ }
133339
+ function resolveObjectActionHandle(runtime, seed, actionHandle) {
133340
+ return collectEditableTargetRefs(runtime.getCanonicalDocument()).find(
133341
+ (target) => objectActionHandle(seed, target.targetKey) === actionHandle && isSupportedImageTarget(target)
133342
+ ) ?? null;
133343
+ }
133344
+ function isSupportedImageTarget(target) {
133345
+ 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"]);
133346
+ }
133347
+ function isImageKind(kind) {
133348
+ return kind === "legacy-image" || kind === "picture";
133349
+ }
133350
+ function onlyObjectActionBlockers(blockers, allowed) {
133351
+ const allowedSet = new Set(allowed);
133352
+ return blockers.every((blocker2) => allowedSet.has(blocker2));
133353
+ }
133354
+ function objectReadback(runtime, target) {
133355
+ const mediaId = target.object?.mediaId;
133356
+ if (!mediaId) return {};
133357
+ const media = runtime.getCanonicalDocument().media.items[mediaId];
133358
+ return {
133359
+ mediaId,
133360
+ ...media?.widthEmu !== void 0 ? { widthEmu: media.widthEmu } : {},
133361
+ ...media?.heightEmu !== void 0 ? { heightEmu: media.heightEmu } : {}
133362
+ };
133363
+ }
133364
+ function blockedResult2(input, proposalId, detail) {
133365
+ return {
133366
+ proposalId,
133367
+ applied: false,
133368
+ changed: false,
133369
+ actionHandle: input.actionHandle,
133370
+ operation: input.operation.kind,
133371
+ reason: detail.code,
133372
+ blockers: [detail.code],
133373
+ blockerDetails: [detail]
133374
+ };
133375
+ }
133376
+ function objectActionSupport() {
133377
+ return {
133378
+ undo: "supported",
133379
+ replay: "supported",
133380
+ exportReopen: "supported",
133381
+ audit: "command-reference"
133382
+ };
133383
+ }
133384
+ function objectActionHandle(seed, targetKey) {
133385
+ return `object-action:${hashOpaque3(`${seed}::${targetKey}`)}`;
133386
+ }
133387
+ function documentSeed2(runtime) {
133388
+ const state = runtime.getSessionState();
133389
+ return state.documentId ?? "document";
133390
+ }
133391
+ function hashOpaque3(input) {
133392
+ let h = 2166136261;
133393
+ for (let i = 0; i < input.length; i += 1) {
133394
+ h ^= input.charCodeAt(i);
133395
+ h = Math.imul(h, 16777619);
133396
+ }
133397
+ return (h >>> 0).toString(36).padStart(7, "0");
133398
+ }
133399
+
133064
133400
  // src/api/v3/ai/actions.ts
133065
133401
  function actionMethodMetadata(method, readOrMutate, auditCategory, contextPromptShape, uxIntent) {
133066
133402
  return {
@@ -133105,6 +133441,17 @@ var locateAllMetadata = actionMethodMetadata(
133105
133441
  "Find scope/table-text targets by query; table text matches include readback {text,isEmpty}.",
133106
133442
  { uiVisible: false, expectsUxResponse: "none" }
133107
133443
  );
133444
+ var createPlaceholderScopesMetadata = actionMethodMetadata(
133445
+ "createPlaceholderScopes",
133446
+ "mutate",
133447
+ "actions-placeholder-scopes",
133448
+ "Create marker-backed workflow scopes for bracketed placeholder matches in paragraphs, tables, and lists using one bounded regex search.",
133449
+ {
133450
+ uiVisible: true,
133451
+ expectsUxResponse: "scope-created",
133452
+ expectedDelta: "placeholder text ranges become workflow scopes"
133453
+ }
133454
+ );
133108
133455
  var rewriteMetadata = actionMethodMetadata(
133109
133456
  "rewrite",
133110
133457
  "mutate",
@@ -133317,6 +133664,7 @@ var ACTION_METHODS = Object.freeze([
133317
133664
  "discover",
133318
133665
  "locate",
133319
133666
  "locateAll",
133667
+ "createPlaceholderScopes",
133320
133668
  "rewrite",
133321
133669
  "rewriteAll",
133322
133670
  "insertText",
@@ -133342,6 +133690,8 @@ var DEFAULT_LOCATE_LIMIT = 20;
133342
133690
  var DEFAULT_REWRITE_ALL_LIMIT = 10;
133343
133691
  var DEFAULT_TABLE_TEXT_SCOPE_LIMIT = 3;
133344
133692
  var DEFAULT_PLAN_STEP_LIMIT = 20;
133693
+ var DEFAULT_PLACEHOLDER_QUERY = "\\[[^\\[\\]]{1,200}\\]";
133694
+ var DEFAULT_PLACEHOLDER_LIMIT = 200;
133345
133695
  function createActionsFamily(runtime) {
133346
133696
  const category = {
133347
133697
  discover(input) {
@@ -133396,6 +133746,9 @@ function createActionsFamily(runtime) {
133396
133746
  locateAll(input) {
133397
133747
  return locateAll(runtime, input);
133398
133748
  },
133749
+ createPlaceholderScopes(input) {
133750
+ return createPlaceholderScopes(runtime, input ?? {});
133751
+ },
133399
133752
  rewrite(input) {
133400
133753
  if (input.text === void 0) {
133401
133754
  return blockedApply(
@@ -134164,6 +134517,169 @@ function locateAll(runtime, input) {
134164
134517
  ...matches.length === 0 ? { blockers: Object.freeze([`actions:locate:not-found:${input.query}`]) } : {}
134165
134518
  };
134166
134519
  }
134520
+ function createPlaceholderScopes(runtime, input) {
134521
+ const query = input.query ?? DEFAULT_PLACEHOLDER_QUERY;
134522
+ if (!query) {
134523
+ const detail = blocker(
134524
+ "actions:placeholder-scopes:query-required",
134525
+ "input",
134526
+ "Placeholder scope creation requires a non-empty query.",
134527
+ "Retry with a non-empty query string or omit query to use the bracketed-placeholder default."
134528
+ );
134529
+ return {
134530
+ status: "blocked",
134531
+ totalHits: 0,
134532
+ created: 0,
134533
+ blocked: 0,
134534
+ scopes: Object.freeze([]),
134535
+ blockers: Object.freeze([detail.code]),
134536
+ blockerDetails: Object.freeze([detail])
134537
+ };
134538
+ }
134539
+ const limit = Math.max(0, input.limit ?? DEFAULT_PLACEHOLDER_LIMIT);
134540
+ let hits;
134541
+ try {
134542
+ hits = runtime.findAllText(query, {
134543
+ regex: input.regex ?? true,
134544
+ matchCase: input.matchCase ?? true,
134545
+ limit
134546
+ });
134547
+ } catch {
134548
+ const detail = blocker(
134549
+ "actions:placeholder-scopes:invalid-query",
134550
+ "input",
134551
+ "The placeholder query could not be compiled or searched.",
134552
+ "Retry with a valid literal query or JavaScript regex pattern."
134553
+ );
134554
+ return {
134555
+ status: "blocked",
134556
+ totalHits: 0,
134557
+ created: 0,
134558
+ blocked: 0,
134559
+ scopes: Object.freeze([]),
134560
+ blockers: Object.freeze([detail.code]),
134561
+ blockerDetails: Object.freeze([detail])
134562
+ };
134563
+ }
134564
+ if (hits.length === 0) {
134565
+ return {
134566
+ status: "not-found",
134567
+ totalHits: 0,
134568
+ created: 0,
134569
+ blocked: 0,
134570
+ scopes: Object.freeze([]),
134571
+ blockers: Object.freeze([`actions:placeholder-scopes:not-found:${query}`])
134572
+ };
134573
+ }
134574
+ const surface = runtime.getRenderSnapshot().surface;
134575
+ const orderedHits = hits.filter((hit) => hit.kind === "range").map((hit, originalIndex) => ({
134576
+ hit,
134577
+ originalIndex,
134578
+ text: surface ? textForSurfaceRange(surface.blocks, hit.from, hit.to) : ""
134579
+ })).sort((a, b) => b.hit.from - a.hit.from || b.originalIndex - a.originalIndex);
134580
+ if (orderedHits.length === 0) {
134581
+ const detail = blocker(
134582
+ "actions:placeholder-scopes:no-range-hits",
134583
+ "unsupported",
134584
+ "The placeholder query matched only projections that cannot be converted into workflow ranges.",
134585
+ "Retry with a text query that resolves to concrete document text ranges."
134586
+ );
134587
+ return {
134588
+ status: "blocked",
134589
+ totalHits: hits.length,
134590
+ created: 0,
134591
+ blocked: hits.length,
134592
+ scopes: Object.freeze([]),
134593
+ blockers: Object.freeze([detail.code]),
134594
+ blockerDetails: Object.freeze([detail])
134595
+ };
134596
+ }
134597
+ const compiler = createScopeCompilerService(runtime);
134598
+ const byOriginalIndex = /* @__PURE__ */ new Map();
134599
+ for (const item of orderedHits) {
134600
+ const text = item.text || query;
134601
+ const label = input.labelPrefix === void 0 ? text : `${input.labelPrefix} ${item.originalIndex + 1}`;
134602
+ const result = createScopeFromAnchor(runtime, {
134603
+ anchor: { from: item.hit.from, to: item.hit.to },
134604
+ mode: input.mode ?? "edit",
134605
+ label,
134606
+ ...input.visibility ? { visibility: input.visibility } : {},
134607
+ ...input.guardPolicy ? { guardPolicy: input.guardPolicy } : {},
134608
+ ...input.assoc ? { assoc: input.assoc } : {},
134609
+ ...input.stableRefHint ? { stableRefHint: input.stableRefHint } : {}
134610
+ });
134611
+ if (result.status === "created") {
134612
+ const compiled = compiler.compileScopeById(result.scopeId)?.scope;
134613
+ byOriginalIndex.set(item.originalIndex, {
134614
+ status: "created",
134615
+ text,
134616
+ excerpt: excerpt(text),
134617
+ scopeId: result.scopeId,
134618
+ ...compiled?.handle ? { handle: compiled.handle } : {}
134619
+ });
134620
+ continue;
134621
+ }
134622
+ const detail = blockerWithOwner(
134623
+ `actions:placeholder-scopes:create-refused:${result.reason}`,
134624
+ "blocked",
134625
+ "The placeholder match could not be converted into a workflow scope.",
134626
+ "Refresh the document and retry; if it still refuses, route the target to the workflow/scope writer owner.",
134627
+ "L06"
134628
+ );
134629
+ byOriginalIndex.set(item.originalIndex, {
134630
+ status: "blocked",
134631
+ text,
134632
+ excerpt: excerpt(text),
134633
+ blockers: Object.freeze([detail.code]),
134634
+ blockerDetails: Object.freeze([detail])
134635
+ });
134636
+ }
134637
+ const scopes = orderedHits.map((item) => byOriginalIndex.get(item.originalIndex)).filter((value) => value !== void 0);
134638
+ const created = scopes.filter((scope) => scope.status === "created").length;
134639
+ const blocked2 = scopes.length - created;
134640
+ const blockerDetails = scopes.flatMap((scope) => scope.blockerDetails ?? []);
134641
+ return {
134642
+ status: created === scopes.length ? "created" : created > 0 ? "partial" : "blocked",
134643
+ totalHits: hits.length,
134644
+ created,
134645
+ blocked: blocked2,
134646
+ scopes: Object.freeze(scopes),
134647
+ ...blockerDetails.length > 0 ? {
134648
+ blockers: Object.freeze(blockerDetails.map((detail) => detail.code)),
134649
+ blockerDetails: Object.freeze(blockerDetails)
134650
+ } : {}
134651
+ };
134652
+ }
134653
+ function textForSurfaceRange(blocks, from, to) {
134654
+ let acc = "";
134655
+ const visit = (items) => {
134656
+ for (const block of items) {
134657
+ if (block.kind === "paragraph") {
134658
+ for (const segment of block.segments) {
134659
+ if (segment.kind !== "text") continue;
134660
+ if (segment.from >= to) return;
134661
+ if (segment.to <= from) continue;
134662
+ acc += segment.text.slice(
134663
+ Math.max(0, from - segment.from),
134664
+ Math.min(segment.text.length, to - segment.from)
134665
+ );
134666
+ }
134667
+ continue;
134668
+ }
134669
+ if (block.kind === "table") {
134670
+ for (const row2 of block.rows) {
134671
+ for (const cell of row2.cells) visit(cell.content);
134672
+ }
134673
+ continue;
134674
+ }
134675
+ if (block.kind === "sdt_block") {
134676
+ visit(block.children);
134677
+ }
134678
+ }
134679
+ };
134680
+ visit(blocks);
134681
+ return acc;
134682
+ }
134167
134683
  function validateTemplateTargets(runtime, input) {
134168
134684
  if (!Array.isArray(input.targets) || input.targets.length === 0) {
134169
134685
  const detail = blocker(
@@ -134584,6 +135100,15 @@ function applyRewrite(runtime, target, input) {
134584
135100
  if (!listTextTarget.ok) return blockedApplyFromResolution(listTextTarget);
134585
135101
  return applyEditableTextRewrite(runtime, listTextTarget.target, input);
134586
135102
  }
135103
+ const tableTextTarget = uniqueTableTextActionForScope(runtime, target.handle);
135104
+ if (tableTextTarget.ok && tableTextTarget.action.readback?.text === target.scope.content.text) {
135105
+ return applyTableScopedMarkerRewrite(
135106
+ runtime,
135107
+ target,
135108
+ tableTextTarget.action,
135109
+ input
135110
+ );
135111
+ }
134587
135112
  const rewriteBlocker = scopeRewriteCapabilityBlocker(target.scope);
134588
135113
  if (rewriteBlocker) {
134589
135114
  return blockedApply(
@@ -134716,6 +135241,106 @@ function applyEditableTextRewrite(runtime, target, input) {
134716
135241
  afterReadback
134717
135242
  };
134718
135243
  }
135244
+ function applyTableScopedMarkerRewrite(runtime, target, action, input) {
135245
+ const workflowScope = runtime.getScope(target.handle.scopeId);
135246
+ const editableTarget = tableEditableTargetForActionHandle(
135247
+ runtime,
135248
+ action.actionHandle
135249
+ );
135250
+ if (!workflowScope || workflowScope.anchor.kind !== "range" || !editableTarget) {
135251
+ return blockedApply(
135252
+ `actions:rewrite:table-marker-target-unresolved:${target.handle.scopeId}`,
135253
+ "unresolved-target",
135254
+ "The table-contained marker scope could not be joined to a current table text target.",
135255
+ "Refresh placeholder scopes and retry; route persistent failures to L08 table scope target mapping."
135256
+ );
135257
+ }
135258
+ const before = runtime.getCanonicalDocument();
135259
+ try {
135260
+ runtime.dispatch({
135261
+ type: "selection.set",
135262
+ selection: {
135263
+ anchor: workflowScope.anchor.from,
135264
+ head: workflowScope.anchor.to,
135265
+ isCollapsed: workflowScope.anchor.from === workflowScope.anchor.to,
135266
+ activeRange: {
135267
+ kind: "range",
135268
+ from: workflowScope.anchor.from,
135269
+ to: workflowScope.anchor.to,
135270
+ assoc: workflowScope.anchor.assoc ?? { start: -1, end: 1 }
135271
+ }
135272
+ },
135273
+ origin: actionOrigin(runtime, input)
135274
+ });
135275
+ runtime.applyActiveStoryTextCommand({
135276
+ type: "text.insert",
135277
+ text: input.text,
135278
+ editableTarget,
135279
+ origin: actionOrigin(runtime, input)
135280
+ });
135281
+ } catch {
135282
+ return blockedApply(
135283
+ `actions:rewrite:table-marker-command-refused:${target.handle.scopeId}`,
135284
+ "blocked",
135285
+ "The table-contained marker scope could not be rewritten by the runtime text command.",
135286
+ "Retry with an exact table text actionHandle; route repeated failures to L07 table text command support.",
135287
+ [
135288
+ blockerWithOwner(
135289
+ `actions:rewrite:table-marker-command-refused:${target.handle.scopeId}`,
135290
+ "blocked",
135291
+ "The table-contained marker scope could not be rewritten by the runtime text command.",
135292
+ "Retry with an exact table text actionHandle; route repeated failures to L07 table text command support.",
135293
+ "L07 runtime text command support"
135294
+ )
135295
+ ]
135296
+ );
135297
+ }
135298
+ const changed = runtime.getCanonicalDocument() !== before;
135299
+ const paragraph = resolveParagraphTextTarget2(
135300
+ runtime.getCanonicalDocument(),
135301
+ editableTarget
135302
+ );
135303
+ const afterText = paragraph ? collectInlineText4(paragraph.children) : void 0;
135304
+ const afterReadback = afterText === void 0 ? void 0 : { text: afterText, isEmpty: afterText.length === 0 };
135305
+ if (!changed || afterReadback?.text !== input.text) {
135306
+ return {
135307
+ status: "blocked",
135308
+ applied: false,
135309
+ changed,
135310
+ target: summarizeTarget({ kind: "table-text", action }),
135311
+ posture: "suspect-readback",
135312
+ blockers: Object.freeze([
135313
+ `actions:rewrite:table-marker-readback-mismatch:${target.handle.scopeId}`
135314
+ ]),
135315
+ blockerDetails: Object.freeze([
135316
+ blocker(
135317
+ `actions:rewrite:table-marker-readback-mismatch:${target.handle.scopeId}`,
135318
+ "blocked",
135319
+ "The table-contained marker rewrite did not produce the requested exact table-cell readback.",
135320
+ "Treat the mutation as suspect. Re-read the target and export before claiming success."
135321
+ )
135322
+ ]),
135323
+ ...afterReadback ? { afterReadback } : {}
135324
+ };
135325
+ }
135326
+ return {
135327
+ status: "applied",
135328
+ applied: true,
135329
+ changed: true,
135330
+ target: summarizeTarget({
135331
+ kind: "table-text",
135332
+ action: { ...action, readback: afterReadback }
135333
+ }),
135334
+ commandReference: {
135335
+ command: "text.insert",
135336
+ actorId: input.actorId ?? "v3-ai-api",
135337
+ origin: input.origin ?? "agent",
135338
+ emittedAtUtc: currentAuditTimestamp(runtime)
135339
+ },
135340
+ ...action.readback ? { beforeReadback: action.readback } : {},
135341
+ afterReadback
135342
+ };
135343
+ }
134719
135344
  function projectRewriteScopeResult(runtime, result, target, beforeText, proposedText, documentMutated) {
134720
135345
  if (!result.applied) return projectApplyResult(result, target);
134721
135346
  const compiledAfter = createScopeCompilerService(runtime).compileScopeById(
@@ -134832,6 +135457,51 @@ function tableTextActionsForScope(runtime, handle) {
134832
135457
  });
134833
135458
  return result.actions.filter((action) => action.family === "table-text");
134834
135459
  }
135460
+ function uniqueTableTextActionForScope(runtime, handle) {
135461
+ const actions2 = tableTextActionsForScope(runtime, handle).filter(
135462
+ (action) => action.readback !== void 0
135463
+ );
135464
+ if (actions2.length === 1 && actions2[0]) {
135465
+ return { ok: true, action: actions2[0] };
135466
+ }
135467
+ if (actions2.length === 0) {
135468
+ return {
135469
+ ok: false,
135470
+ blockers: Object.freeze(["actions:rewrite:table-text-target-missing"]),
135471
+ blockerDetails: Object.freeze([
135472
+ blocker(
135473
+ "actions:rewrite:table-text-target-missing",
135474
+ "unsupported",
135475
+ "The scope does not expose a unique command-safe table text target.",
135476
+ "Use a paragraph/list scope, or retry with an exact table text actionHandle returned by ai.actions.locateAll."
135477
+ )
135478
+ ])
135479
+ };
135480
+ }
135481
+ return {
135482
+ ok: false,
135483
+ blockers: Object.freeze(["actions:rewrite:table-text-target-ambiguous"]),
135484
+ blockerDetails: Object.freeze([
135485
+ blocker(
135486
+ "actions:rewrite:table-text-target-ambiguous",
135487
+ "ambiguous-target",
135488
+ "The scope exposes multiple table text targets, so broad rewrite would be ambiguous.",
135489
+ "Retry with the exact table text actionHandle returned by ai.actions.locateAll or ai.listTableActions."
135490
+ )
135491
+ ])
135492
+ };
135493
+ }
135494
+ function tableEditableTargetForActionHandle(runtime, actionHandle) {
135495
+ for (const target of collectEditableTargetRefs(runtime.getCanonicalDocument())) {
135496
+ if (target.commandFamily !== "text-leaf" || target.table?.operationScope !== "text") {
135497
+ continue;
135498
+ }
135499
+ if (deriveTableActionHandleForTarget(runtime, target.targetKey) === actionHandle) {
135500
+ return target;
135501
+ }
135502
+ }
135503
+ return null;
135504
+ }
134835
135505
  function editableTextActionsForScope(runtime, handle) {
134836
135506
  const bundle = createScopeCompilerService(runtime).compileBundleById(
134837
135507
  handle.scopeId,
@@ -136355,6 +137025,7 @@ function createApiV3(handle, opts) {
136355
137025
  ...createStatsFamily(handle),
136356
137026
  ...createOutlineFamily(handle),
136357
137027
  ...createTableActionFamily(handle),
137028
+ ...createObjectActionFamily(handle),
136358
137029
  ...createActionsFamily(handle)
136359
137030
  };
136360
137031
  const runtime = {
@@ -139162,6 +139833,9 @@ var WordReviewEditor = (0, import_react71.forwardRef)(
139162
139833
  onUpdateFields: () => {
139163
139834
  activeRuntime.updateFields();
139164
139835
  },
139836
+ onUpdateTableOfContents: () => {
139837
+ activeRuntime.updateTableOfContents();
139838
+ },
139165
139839
  onAskAgentForSelection: () => {
139166
139840
  const { selection } = snapshot;
139167
139841
  onEventRef.current?.({