@beyondwork/docx-react-component 1.0.106 → 1.0.109

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 (190) hide show
  1. package/package.json +19 -5
  2. package/src/api/geometry-overlay-rects.ts +5 -0
  3. package/src/api/package-version.ts +1 -1
  4. package/src/api/page-anchor-id.ts +5 -0
  5. package/src/api/public-types.ts +16 -9
  6. package/src/api/table-node-specs.ts +6 -0
  7. package/src/api/v3/_create.ts +2 -1
  8. package/src/api/v3/_page-anchor-id.ts +52 -0
  9. package/src/api/v3/_runtime-handle.ts +92 -1
  10. package/src/api/v3/ai/_audit-time.ts +5 -0
  11. package/src/api/v3/ai/_pe2-evidence.ts +38 -0
  12. package/src/api/v3/ai/attach.ts +7 -2
  13. package/src/api/v3/ai/replacement.ts +101 -18
  14. package/src/api/v3/ai/resolve.ts +2 -2
  15. package/src/api/v3/ai/review.ts +177 -3
  16. package/src/api/v3/index.ts +1 -0
  17. package/src/api/v3/runtime/collab.ts +462 -0
  18. package/src/api/v3/runtime/document.ts +503 -20
  19. package/src/api/v3/runtime/geometry.ts +97 -0
  20. package/src/api/v3/runtime/layout.ts +744 -0
  21. package/src/api/v3/runtime/perf-probe.ts +14 -0
  22. package/src/api/v3/runtime/viewport.ts +9 -8
  23. package/src/api/v3/ui/_types.ts +149 -55
  24. package/src/api/v3/ui/chrome-preset-model.ts +5 -5
  25. package/src/api/v3/ui/debug.ts +115 -2
  26. package/src/api/v3/ui/index.ts +13 -0
  27. package/src/api/v3/ui/overlays.ts +0 -8
  28. package/src/api/v3/ui/surface.ts +56 -0
  29. package/src/api/v3/ui/viewport.ts +22 -9
  30. package/src/core/commands/image-commands.ts +1 -0
  31. package/src/core/commands/index.ts +6 -0
  32. package/src/core/schema/text-schema.ts +43 -5
  33. package/src/core/selection/mapping.ts +8 -1
  34. package/src/core/selection/review-anchors.ts +5 -1
  35. package/src/core/state/text-transaction.ts +8 -2
  36. package/src/io/export/serialize-revisions.ts +149 -1
  37. package/src/io/normalize/normalize-text.ts +6 -0
  38. package/src/io/ooxml/parse-bookmark-references.ts +55 -0
  39. package/src/io/ooxml/parse-fields.ts +24 -2
  40. package/src/io/ooxml/parse-headers-footers.ts +38 -5
  41. package/src/io/ooxml/parse-main-document.ts +153 -9
  42. package/src/io/ooxml/parse-numbering.ts +20 -0
  43. package/src/io/ooxml/parse-revisions.ts +19 -8
  44. package/src/io/opc/package-reader.ts +98 -8
  45. package/src/model/anchor.ts +4 -3
  46. package/src/model/canonical-document.ts +220 -2
  47. package/src/model/canonical-hash.ts +221 -0
  48. package/src/model/canonical-layout-inputs.ts +245 -6
  49. package/src/model/layout/index.ts +1 -0
  50. package/src/model/layout/page-graph-types.ts +118 -1
  51. package/src/model/review/revision-types.ts +14 -3
  52. package/src/preservation/store.ts +20 -4
  53. package/src/review/README.md +1 -1
  54. package/src/review/store/revision-actions.ts +14 -2
  55. package/src/runtime/collab/event-types.ts +67 -1
  56. package/src/runtime/collab/runtime-collab-sync.ts +177 -5
  57. package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
  58. package/src/runtime/document-heading-outline.ts +147 -0
  59. package/src/runtime/document-navigation.ts +8 -243
  60. package/src/runtime/document-runtime.ts +240 -97
  61. package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
  62. package/src/runtime/formatting/layout-inputs.ts +38 -5
  63. package/src/runtime/formatting/numbering/geometry.ts +28 -2
  64. package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
  65. package/src/runtime/geometry/caret-geometry.ts +5 -6
  66. package/src/runtime/geometry/geometry-facet.ts +60 -10
  67. package/src/runtime/geometry/geometry-index.ts +591 -20
  68. package/src/runtime/geometry/geometry-types.ts +59 -0
  69. package/src/runtime/geometry/hit-test.ts +11 -1
  70. package/src/runtime/geometry/overlay-rects.ts +5 -3
  71. package/src/runtime/geometry/project-anchors.ts +1 -1
  72. package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
  73. package/src/runtime/layout/index.ts +6 -0
  74. package/src/runtime/layout/layout-engine-instance.ts +6 -1
  75. package/src/runtime/layout/layout-engine-version.ts +181 -16
  76. package/src/runtime/layout/layout-facet-types.ts +6 -0
  77. package/src/runtime/layout/page-graph.ts +21 -4
  78. package/src/runtime/layout/paginated-layout-engine.ts +139 -15
  79. package/src/runtime/layout/project-block-fragments.ts +265 -7
  80. package/src/runtime/layout/public-facet.ts +78 -24
  81. package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
  82. package/src/runtime/layout/table-row-split.ts +92 -35
  83. package/src/runtime/prerender/cache-envelope.ts +2 -2
  84. package/src/runtime/prerender/cache-key.ts +5 -4
  85. package/src/runtime/prerender/customxml-cache.ts +0 -1
  86. package/src/runtime/render/render-kernel.ts +1 -1
  87. package/src/runtime/revision-runtime.ts +112 -10
  88. package/src/runtime/scopes/_scope-dependencies.ts +1 -0
  89. package/src/runtime/scopes/action-validation.ts +22 -2
  90. package/src/runtime/scopes/capabilities.ts +316 -0
  91. package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
  92. package/src/runtime/scopes/compiler-service.ts +108 -4
  93. package/src/runtime/scopes/content-control-evidence.ts +79 -0
  94. package/src/runtime/scopes/create-issue.ts +5 -5
  95. package/src/runtime/scopes/evidence.ts +91 -0
  96. package/src/runtime/scopes/formatting/apply.ts +2 -0
  97. package/src/runtime/scopes/geometry-evidence.ts +130 -0
  98. package/src/runtime/scopes/index.ts +54 -0
  99. package/src/runtime/scopes/issue-lifecycle.ts +224 -0
  100. package/src/runtime/scopes/layout-evidence.ts +374 -0
  101. package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
  102. package/src/runtime/scopes/preservation-boundary.ts +7 -1
  103. package/src/runtime/scopes/replacement/apply.ts +97 -34
  104. package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
  105. package/src/runtime/scopes/semantic-scope-types.ts +242 -3
  106. package/src/runtime/scopes/visualization.ts +28 -0
  107. package/src/runtime/surface-projection.ts +44 -5
  108. package/src/runtime/telemetry/perf-probe.ts +216 -0
  109. package/src/runtime/virtualized-rendering.ts +36 -1
  110. package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
  111. package/src/runtime/workflow/coordinator.ts +39 -11
  112. package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
  113. package/src/runtime/workflow/index.ts +3 -0
  114. package/src/runtime/workflow/overlay-lane-types.ts +58 -0
  115. package/src/runtime/workflow/overlay-lanes.ts +168 -10
  116. package/src/runtime/workflow/overlay-store.ts +2 -2
  117. package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
  118. package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
  119. package/src/session/_sync-legacy.ts +17 -27
  120. package/src/session/import/loader.ts +6 -4
  121. package/src/session/import/source-package-evidence.ts +186 -2
  122. package/src/session/index.ts +5 -6
  123. package/src/session/session.ts +30 -56
  124. package/src/session/types.ts +8 -13
  125. package/src/shell/session-bootstrap.ts +155 -81
  126. package/src/ui/WordReviewEditor.tsx +520 -12
  127. package/src/ui/editor-shell-view.tsx +14 -4
  128. package/src/ui/editor-surface-controller.tsx +5 -3
  129. package/src/ui/headless/selection-tool-resolver.ts +1 -2
  130. package/src/ui/presence-overlay-lane.ts +0 -1
  131. package/src/ui/ui-controller-factory.ts +7 -0
  132. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
  133. package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
  134. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
  135. package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
  136. package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
  137. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
  138. package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
  139. package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
  140. package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
  141. package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
  142. package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
  143. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
  144. package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
  145. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
  146. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
  147. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
  148. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
  149. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
  150. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
  151. package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
  152. package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
  153. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
  154. package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
  155. package/src/ui-tailwind/debug/README.md +4 -1
  156. package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
  157. package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
  158. package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
  159. package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
  160. package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
  161. package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
  162. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
  163. package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
  164. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
  165. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
  166. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
  167. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
  168. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
  169. package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
  170. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
  171. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
  172. package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
  173. package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
  174. package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
  175. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
  176. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
  177. package/src/ui-tailwind/review-workspace/types.ts +26 -12
  178. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
  179. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
  180. package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
  181. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
  182. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
  183. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
  184. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
  185. package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
  186. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
  187. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
  188. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
  189. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
  190. package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
@@ -11,6 +11,7 @@ import React, {
11
11
 
12
12
  import type {
13
13
  AutosaveState,
14
+ CommentSidebarThreadSnapshot,
14
15
  CompatibilityReport,
15
16
  DocumentNavigationSnapshot,
16
17
  EditorAnchorProjection,
@@ -137,7 +138,13 @@ import { findTextMatchesForRuntime, searchRuntimeDocument } from "../shell/searc
137
138
  import {
138
139
  type TwProseMirrorSurfaceRef,
139
140
  } from "../ui-tailwind/editor-surface/tw-prosemirror-surface";
140
- import { TwInlineFindBar } from "../ui-tailwind/chrome/tw-inline-find-bar";
141
+ import {
142
+ TwNavigationCommandBar,
143
+ type NavigationCommandMode,
144
+ type NavigationGoToGroup,
145
+ type NavigationSearchScope,
146
+ type NavigationSearchScopeOption,
147
+ } from "../ui-tailwind/chrome/tw-navigation-command-bar";
141
148
  import { PAGE_CHROME_DEFAULTS } from "../ui-tailwind/editor-surface/pm-page-break-decorations.ts";
142
149
  import type { MediaPreviewDescriptor } from "../ui-tailwind/editor-surface/pm-state-from-snapshot";
143
150
  import {
@@ -229,14 +236,197 @@ import type {
229
236
  ChromeHostPosture,
230
237
  ChromePosture,
231
238
  OverlayAnchorQuery,
239
+ UiObjectDragCommitResult,
240
+ UiObjectDragSession,
241
+ UiObjectDragStartInput,
242
+ UiObjectDragUpdateInput,
232
243
  ViewportState,
233
244
  } from "../api/v3/ui/_types.ts";
234
245
 
235
246
  export {
236
247
  __createFallbackRuntime,
248
+ __preloadSnapshotSourceForRuntime,
237
249
  __resolveWordReviewEditorSource,
238
250
  } from "../shell/session-bootstrap.ts";
239
251
 
252
+ const EMU_PER_CSS_PX = 9525;
253
+ let nextObjectDragSessionId = 0;
254
+
255
+ function createMountedObjectDragSession(
256
+ runtime: WordReviewEditorRuntime,
257
+ input: UiObjectDragStartInput,
258
+ ): UiObjectDragSession {
259
+ nextObjectDragSessionId += 1;
260
+ const sessionId = `object-drag-${nextObjectDragSessionId}`;
261
+ const start = findObjectDragSegment(runtime.getRenderSnapshot(), input.objectId);
262
+ let latestDelta = { x: 0, y: 0 };
263
+ let closed: "committed" | "cancelled" | null = null;
264
+
265
+ const readDelta = (update?: UiObjectDragUpdateInput) => {
266
+ if (!update) return latestDelta;
267
+ const x = update.deltaX ?? (update.clientX != null ? update.clientX - input.clientX : latestDelta.x);
268
+ const y = update.deltaY ?? (update.clientY != null ? update.clientY - input.clientY : latestDelta.y);
269
+ latestDelta = { x, y };
270
+ return latestDelta;
271
+ };
272
+ const snapshotFor = (
273
+ status: "active" | "committed" | "cancelled",
274
+ update?: UiObjectDragUpdateInput,
275
+ ) => {
276
+ const delta = readDelta(update);
277
+ return {
278
+ objectId: input.objectId,
279
+ handle: input.handle,
280
+ status,
281
+ deltaPx: delta,
282
+ previewRect: previewObjectDragRect(input, delta),
283
+ };
284
+ };
285
+
286
+ return {
287
+ id: sessionId,
288
+ objectId: input.objectId,
289
+ handle: input.handle,
290
+ update(update) {
291
+ if (closed) return snapshotFor(closed, update);
292
+ return snapshotFor("active", update);
293
+ },
294
+ commit(update) {
295
+ if (closed) {
296
+ return {
297
+ ...snapshotFor(closed, update),
298
+ changed: false,
299
+ reason: `object-drag-${closed}`,
300
+ };
301
+ }
302
+ const active = snapshotFor("committed", update);
303
+ const result = commitMountedObjectDrag(runtime, input, start, active.deltaPx);
304
+ closed = "committed";
305
+ return { ...active, ...result };
306
+ },
307
+ cancel() {
308
+ closed = "cancelled";
309
+ return {
310
+ ...snapshotFor("cancelled"),
311
+ changed: false,
312
+ reason: "object-drag-cancelled",
313
+ };
314
+ },
315
+ };
316
+ }
317
+
318
+ function previewObjectDragRect(
319
+ input: UiObjectDragStartInput,
320
+ delta: { x: number; y: number },
321
+ ) {
322
+ const rect = input.initialRect;
323
+ const next = {
324
+ leftPx: rect.leftPx,
325
+ topPx: rect.topPx,
326
+ widthPx: rect.widthPx,
327
+ heightPx: rect.heightPx,
328
+ space: rect.space,
329
+ ...(rect.precision ? { precision: rect.precision } : {}),
330
+ };
331
+ if (input.handle === "move") {
332
+ return { ...next, leftPx: next.leftPx + delta.x, topPx: next.topPx + delta.y };
333
+ }
334
+ if (input.handle === "rotate") return next;
335
+ const west = input.handle.includes("w");
336
+ const east = input.handle.includes("e");
337
+ const north = input.handle.includes("n");
338
+ const south = input.handle.includes("s");
339
+ const minPx = 1;
340
+ return {
341
+ ...next,
342
+ leftPx: west ? next.leftPx + delta.x : next.leftPx,
343
+ topPx: north ? next.topPx + delta.y : next.topPx,
344
+ widthPx: Math.max(minPx, next.widthPx + (east ? delta.x : 0) - (west ? delta.x : 0)),
345
+ heightPx: Math.max(minPx, next.heightPx + (south ? delta.y : 0) - (north ? delta.y : 0)),
346
+ };
347
+ }
348
+
349
+ function commitMountedObjectDrag(
350
+ runtime: WordReviewEditorRuntime,
351
+ input: UiObjectDragStartInput,
352
+ segment: ObjectDragSegment | null,
353
+ delta: { x: number; y: number },
354
+ ): Pick<UiObjectDragCommitResult, "changed" | "reason"> {
355
+ if (!segment) return { changed: false, reason: "object-not-found" };
356
+ if (segment.kind !== "image") {
357
+ return { changed: false, reason: "object-kind-not-supported" };
358
+ }
359
+ if (input.handle === "rotate") {
360
+ return { changed: false, reason: "rotate-not-supported" };
361
+ }
362
+
363
+ if (input.handle === "move") {
364
+ if (segment.display !== "floating" && segment.anchor?.display !== "floating") {
365
+ return { changed: false, reason: "inline-object-move-not-supported" };
366
+ }
367
+ applyRuntimeImageReposition(runtime, input.objectId, {
368
+ horizontalOffsetEmu: Math.round((segment.anchor?.positionH?.offset ?? 0) + delta.x * EMU_PER_CSS_PX),
369
+ verticalOffsetEmu: Math.round((segment.anchor?.positionV?.offset ?? 0) + delta.y * EMU_PER_CSS_PX),
370
+ });
371
+ return { changed: true };
372
+ }
373
+
374
+ const startWidth =
375
+ segment.anchor?.extent?.widthEmu ??
376
+ Math.round(input.initialRect.widthPx * EMU_PER_CSS_PX);
377
+ const startHeight =
378
+ segment.anchor?.extent?.heightEmu ??
379
+ Math.round(input.initialRect.heightPx * EMU_PER_CSS_PX);
380
+ const west = input.handle.includes("w");
381
+ const east = input.handle.includes("e");
382
+ const north = input.handle.includes("n");
383
+ const south = input.handle.includes("s");
384
+ const minEmu = EMU_PER_CSS_PX;
385
+ const widthEmu = Math.max(
386
+ minEmu,
387
+ Math.round(startWidth + (east ? delta.x : 0) * EMU_PER_CSS_PX - (west ? delta.x : 0) * EMU_PER_CSS_PX),
388
+ );
389
+ const heightEmu = Math.max(
390
+ minEmu,
391
+ Math.round(startHeight + (south ? delta.y : 0) * EMU_PER_CSS_PX - (north ? delta.y : 0) * EMU_PER_CSS_PX),
392
+ );
393
+ applyRuntimeImageResize(runtime, input.objectId, { widthEmu, heightEmu });
394
+ return { changed: true };
395
+ }
396
+
397
+ interface ObjectDragSegment {
398
+ kind: string;
399
+ mediaId?: string;
400
+ from?: number;
401
+ to?: number;
402
+ display?: "inline" | "floating";
403
+ anchor?: {
404
+ display?: "inline" | "floating";
405
+ extent?: { widthEmu: number; heightEmu: number };
406
+ positionH?: { offset?: number };
407
+ positionV?: { offset?: number };
408
+ };
409
+ }
410
+
411
+ function findObjectDragSegment(
412
+ snapshot: RuntimeRenderSnapshot,
413
+ objectId: string,
414
+ ): ObjectDragSegment | null {
415
+ for (const block of snapshot.surface?.blocks ?? []) {
416
+ if (!("segments" in block)) continue;
417
+ for (const segment of (block as { segments?: unknown[] }).segments ?? []) {
418
+ const candidate = segment as ObjectDragSegment;
419
+ if (
420
+ (candidate.kind === "image" || candidate.kind === "shape") &&
421
+ candidate.mediaId === objectId
422
+ ) {
423
+ return candidate;
424
+ }
425
+ }
426
+ }
427
+ return null;
428
+ }
429
+
240
430
  /**
241
431
  * Map a host-facing `markupDisplay` prop value to the narrow
242
432
  * `ChromeMarkupDisplay` enum on `ChromeHostPosture` (`"final" | "markup"
@@ -1064,9 +1254,15 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
1064
1254
 
1065
1255
  const [activeRailTab, setActiveRailTab] = useState<ReviewRailTab>("comments");
1066
1256
  const [inlineFindOpen, setInlineFindOpen] = useState(false);
1257
+ const [navigationCommandMode, setNavigationCommandMode] =
1258
+ useState<NavigationCommandMode>("find");
1067
1259
  const [inlineFindQuery, setInlineFindQuery] = useState("");
1260
+ const [inlineReplaceText, setInlineReplaceText] = useState("");
1261
+ const [inlineSearchScope, setInlineSearchScope] =
1262
+ useState<NavigationSearchScope>("main");
1068
1263
  const [inlineFindResults, setInlineFindResults] = useState<readonly SearchResultSnapshot[]>([]);
1069
1264
  const [inlineFindActiveIndex, setInlineFindActiveIndex] = useState(0);
1265
+ const [activeGoToGroupId, setActiveGoToGroupId] = useState("pages");
1070
1266
  const [localMarkupDisplay, setLocalMarkupDisplay] =
1071
1267
  useState<WorkflowMarkupMode | null>(() => suggestionsEnabled ? "all" : null);
1072
1268
  const [activeRevisionId, setActiveRevisionId] = useState<string | undefined>();
@@ -1219,6 +1415,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
1219
1415
  viewportChannelRef.current!.subscribe(listener),
1220
1416
  subscribeOverlays: (listener) =>
1221
1417
  overlaysChannelRef.current!.subscribe(listener),
1418
+ beginObjectDrag: (input) =>
1419
+ createMountedObjectDragSession(activeRuntime, input),
1222
1420
  }),
1223
1421
  }),
1224
1422
  [activeRuntime],
@@ -2609,6 +2807,21 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
2609
2807
  },
2610
2808
  [activeRuntime],
2611
2809
  );
2810
+ const inlineFindSearchOptions = useMemo<SearchOptions>(() => {
2811
+ const base: SearchOptions = { limit: 500 };
2812
+ if (inlineSearchScope === "story") {
2813
+ return { ...base, inStory: snapshot.activeStory };
2814
+ }
2815
+ if (inlineSearchScope === "scope") {
2816
+ const scopeId = interactionGuardSnapshot.matchedScopeId;
2817
+ return scopeId ? { ...base, inScope: scopeId } : base;
2818
+ }
2819
+ return base;
2820
+ }, [
2821
+ inlineSearchScope,
2822
+ interactionGuardSnapshot.matchedScopeId,
2823
+ snapshot.activeStory,
2824
+ ]);
2612
2825
  const refreshInlineFindResults = useCallback(
2613
2826
  (query: string, requestedIndex = 0) => {
2614
2827
  const normalizedQuery = query.trim();
@@ -2622,7 +2835,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
2622
2835
  activeRuntime,
2623
2836
  surfaceRef.current,
2624
2837
  normalizedQuery,
2625
- { limit: 500 },
2838
+ inlineFindSearchOptions,
2626
2839
  );
2627
2840
  const nextIndex =
2628
2841
  results.length > 0 ? wrapSearchResultIndex(requestedIndex, results.length) : 0;
@@ -2630,14 +2843,24 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
2630
2843
  setInlineFindActiveIndex(nextIndex);
2631
2844
  navigateInlineFindResult(results, nextIndex);
2632
2845
  },
2633
- [activeRuntime, navigateInlineFindResult],
2846
+ [activeRuntime, inlineFindSearchOptions, navigateInlineFindResult],
2634
2847
  );
2635
- const handleOpenInlineFind = useCallback(() => {
2848
+ const openNavigationCommandBar = useCallback((mode: NavigationCommandMode) => {
2849
+ setNavigationCommandMode(mode);
2636
2850
  setInlineFindOpen(true);
2637
2851
  if (inlineFindQuery.trim()) {
2638
2852
  refreshInlineFindResults(inlineFindQuery, inlineFindActiveIndex);
2639
2853
  }
2640
2854
  }, [inlineFindActiveIndex, inlineFindQuery, refreshInlineFindResults]);
2855
+ const handleOpenInlineFind = useCallback(() => {
2856
+ openNavigationCommandBar("find");
2857
+ }, [openNavigationCommandBar]);
2858
+ const handleOpenInlineReplace = useCallback(() => {
2859
+ openNavigationCommandBar("replace");
2860
+ }, [openNavigationCommandBar]);
2861
+ const handleOpenInlineGoTo = useCallback(() => {
2862
+ openNavigationCommandBar("go-to");
2863
+ }, [openNavigationCommandBar]);
2641
2864
  const handleCloseInlineFind = useCallback(() => {
2642
2865
  setInlineFindOpen(false);
2643
2866
  surfaceRef.current?.clearSearch();
@@ -2649,6 +2872,35 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
2649
2872
  },
2650
2873
  [refreshInlineFindResults],
2651
2874
  );
2875
+ const handleInlineSearchScopeChange = useCallback(
2876
+ (scope: NavigationSearchScope) => {
2877
+ setInlineSearchScope(scope);
2878
+ if (inlineFindQuery.trim()) {
2879
+ const options: SearchOptions =
2880
+ scope === "story"
2881
+ ? { limit: 500, inStory: snapshot.activeStory }
2882
+ : scope === "scope" && interactionGuardSnapshot.matchedScopeId
2883
+ ? { limit: 500, inScope: interactionGuardSnapshot.matchedScopeId }
2884
+ : { limit: 500 };
2885
+ const results = searchRuntimeDocument(
2886
+ activeRuntime,
2887
+ surfaceRef.current,
2888
+ inlineFindQuery.trim(),
2889
+ options,
2890
+ );
2891
+ setInlineFindResults(results);
2892
+ setInlineFindActiveIndex(0);
2893
+ navigateInlineFindResult(results, 0);
2894
+ }
2895
+ },
2896
+ [
2897
+ activeRuntime,
2898
+ inlineFindQuery,
2899
+ interactionGuardSnapshot.matchedScopeId,
2900
+ navigateInlineFindResult,
2901
+ snapshot.activeStory,
2902
+ ],
2903
+ );
2652
2904
  const handleInlineFindNext = useCallback(() => {
2653
2905
  if (inlineFindResults.length === 0) {
2654
2906
  refreshInlineFindResults(inlineFindQuery, 0);
@@ -2667,6 +2919,26 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
2667
2919
  navigateInlineFindResult,
2668
2920
  refreshInlineFindResults,
2669
2921
  ]);
2922
+ const handleInlineReplaceCurrent = useCallback(() => {
2923
+ const result = inlineFindResults[inlineFindActiveIndex];
2924
+ if (!result) {
2925
+ refreshInlineFindResults(inlineFindQuery, 0);
2926
+ return;
2927
+ }
2928
+ applyRuntimeSelection(
2929
+ activeRuntime,
2930
+ createSelectionFromAnchor(result.anchor, result.storyTarget),
2931
+ );
2932
+ activeRuntime.replaceText(inlineReplaceText);
2933
+ refreshInlineFindResults(inlineFindQuery, inlineFindActiveIndex);
2934
+ }, [
2935
+ activeRuntime,
2936
+ inlineFindActiveIndex,
2937
+ inlineFindQuery,
2938
+ inlineFindResults,
2939
+ inlineReplaceText,
2940
+ refreshInlineFindResults,
2941
+ ]);
2670
2942
  const handleInlineFindPrevious = useCallback(() => {
2671
2943
  if (inlineFindResults.length === 0) {
2672
2944
  refreshInlineFindResults(inlineFindQuery, 0);
@@ -2685,6 +2957,127 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
2685
2957
  navigateInlineFindResult,
2686
2958
  refreshInlineFindResults,
2687
2959
  ]);
2960
+ const navigationSearchScopes = useMemo<readonly NavigationSearchScopeOption[]>(
2961
+ () => [
2962
+ { id: "main", label: "Main" },
2963
+ {
2964
+ id: "story",
2965
+ label: storyTargetLabel(snapshot.activeStory),
2966
+ disabled: snapshot.activeStory.kind === "main",
2967
+ disabledReason: "Already searching the main story.",
2968
+ },
2969
+ {
2970
+ id: "scope",
2971
+ label: "Scope",
2972
+ disabled: !interactionGuardSnapshot.matchedScopeId,
2973
+ disabledReason: "No workflow scope covers the current selection.",
2974
+ },
2975
+ ],
2976
+ [interactionGuardSnapshot.matchedScopeId, snapshot.activeStory],
2977
+ );
2978
+ const navigationGoToGroups = useMemo<readonly NavigationGoToGroup[]>(
2979
+ () =>
2980
+ buildNavigationGoToGroups({
2981
+ documentNavigation,
2982
+ comments: snapshot.comments.threads,
2983
+ revisions: snapshot.trackedChanges.revisions,
2984
+ workflowScopeSnapshot,
2985
+ }),
2986
+ [
2987
+ documentNavigation,
2988
+ snapshot.comments.threads,
2989
+ snapshot.trackedChanges.revisions,
2990
+ workflowScopeSnapshot,
2991
+ ],
2992
+ );
2993
+ const handleNavigationModeChange = useCallback(
2994
+ (mode: NavigationCommandMode) => {
2995
+ setNavigationCommandMode(mode);
2996
+ if (mode !== "go-to" && inlineFindQuery.trim()) {
2997
+ refreshInlineFindResults(inlineFindQuery, inlineFindActiveIndex);
2998
+ }
2999
+ },
3000
+ [inlineFindActiveIndex, inlineFindQuery, refreshInlineFindResults],
3001
+ );
3002
+ const handleGoToTarget = useCallback(
3003
+ (groupId: string, itemId: string) => {
3004
+ const group = navigationGoToGroups.find((candidate) => candidate.id === groupId);
3005
+ const item = group?.items.find((candidate) => candidate.id === itemId);
3006
+ if (!group || !item) return;
3007
+
3008
+ if (group.id === "pages") {
3009
+ const page = documentNavigation.pages.find(
3010
+ (candidate) => `page:${candidate.pageIndex}` === item.id,
3011
+ );
3012
+ if (!page) return;
3013
+ applyRuntimeSelection(
3014
+ activeRuntime,
3015
+ createCollapsedPublicSelection(page.startOffset),
3016
+ );
3017
+ handleCloseInlineFind();
3018
+ return;
3019
+ }
3020
+
3021
+ if (group.id === "headings") {
3022
+ const heading = documentNavigation.headings.find(
3023
+ (candidate) => `heading:${candidate.headingId}` === item.id,
3024
+ );
3025
+ if (!heading) return;
3026
+ applyRuntimeSelection(
3027
+ activeRuntime,
3028
+ createCollapsedPublicSelection(heading.offset),
3029
+ );
3030
+ handleCloseInlineFind();
3031
+ return;
3032
+ }
3033
+
3034
+ if (group.id === "comments") {
3035
+ const comment = snapshot.comments.threads.find(
3036
+ (candidate) => `comment:${candidate.commentId}` === item.id,
3037
+ );
3038
+ if (!comment) return;
3039
+ applyRuntimeSelection(activeRuntime, createSelectionFromAnchor(comment.anchor));
3040
+ activeRuntime.openComment(comment.commentId);
3041
+ handleCloseInlineFind();
3042
+ return;
3043
+ }
3044
+
3045
+ if (group.id === "changes") {
3046
+ const revision = snapshot.trackedChanges.revisions.find(
3047
+ (candidate) => `change:${candidate.revisionId}` === item.id,
3048
+ );
3049
+ if (!revision) return;
3050
+ applyRuntimeSelection(
3051
+ activeRuntime,
3052
+ createSelectionFromAnchor(revision.anchor, revision.storyTarget),
3053
+ );
3054
+ handleCloseInlineFind();
3055
+ return;
3056
+ }
3057
+
3058
+ if (group.id === "scopes") {
3059
+ const scope = workflowScopeSnapshot?.scopes.find(
3060
+ (candidate) => `scope:${candidate.scopeId}` === item.id,
3061
+ );
3062
+ if (!scope) return;
3063
+ applyRuntimeSelection(
3064
+ activeRuntime,
3065
+ createSelectionFromAnchor(scope.anchor, scope.storyTarget),
3066
+ );
3067
+ handleCloseInlineFind();
3068
+ }
3069
+ },
3070
+ [
3071
+ activeRuntime,
3072
+ documentNavigation.headings,
3073
+ documentNavigation.pages,
3074
+ handleCloseInlineFind,
3075
+ navigationGoToGroups,
3076
+ snapshot.comments.threads,
3077
+ snapshot.trackedChanges.revisions,
3078
+ workflowScopeSnapshot?.scopes,
3079
+ ],
3080
+ );
2688
3081
  const suggestionsSnapshot = activeRuntime.getSuggestionsSnapshot();
2689
3082
  const caretCommentPoint =
2690
3083
  snapshot.selection.isCollapsed && snapshot.selection.activeRange.kind === "range"
@@ -3117,11 +3510,19 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
3117
3510
  shortcut.shortcut === "zoom-out" ? "out" : "reset";
3118
3511
  onZoomRequested(direction);
3119
3512
  handled = true;
3120
- } else if (shortcut.shortcut === "replace" && onReplaceRequested) {
3121
- onReplaceRequested({ selectionText: "", selectionRange: snapshot.selection });
3513
+ } else if (shortcut.shortcut === "replace") {
3514
+ if (onReplaceRequested) {
3515
+ onReplaceRequested({ selectionText: "", selectionRange: snapshot.selection });
3516
+ } else {
3517
+ handleOpenInlineReplace();
3518
+ }
3122
3519
  handled = true;
3123
- } else if (shortcut.shortcut === "go-to" && onGoToRequested) {
3124
- onGoToRequested({ selectionText: "", selectionRange: snapshot.selection });
3520
+ } else if (shortcut.shortcut === "go-to") {
3521
+ if (onGoToRequested) {
3522
+ onGoToRequested({ selectionText: "", selectionRange: snapshot.selection });
3523
+ } else {
3524
+ handleOpenInlineGoTo();
3525
+ }
3125
3526
  handled = true;
3126
3527
  } else if (shortcut.shortcut === "spell" && onSpellRequested) {
3127
3528
  onSpellRequested({ selectionText: "", selectionRange: snapshot.selection });
@@ -3619,6 +4020,10 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
3619
4020
  // Browser-native clipboard commands are best-effort fallbacks.
3620
4021
  }
3621
4022
  };
4023
+ const createAgentRequestId = () =>
4024
+ typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
4025
+ ? `req-${crypto.randomUUID()}`
4026
+ : `req-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
3622
4027
 
3623
4028
  const defaultHost: EditorActionHostCallbacks = {
3624
4029
  onUndo: commands.onUndo,
@@ -3677,11 +4082,11 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
3677
4082
  : handleOpenInlineFind,
3678
4083
  onReplaceRequested: onReplaceRequested
3679
4084
  ? () => onReplaceRequested({ selectionText: "", selectionRange: snapshot.selection })
3680
- : undefined,
4085
+ : handleOpenInlineReplace,
3681
4086
  onPrintRequested,
3682
4087
  onGoToRequested: onGoToRequested
3683
4088
  ? () => onGoToRequested({ selectionText: "", selectionRange: snapshot.selection })
3684
- : undefined,
4089
+ : handleOpenInlineGoTo,
3685
4090
  onInsertRowAbove: commands.onAddRowBefore,
3686
4091
  onInsertRowBelow: commands.onAddRowAfter,
3687
4092
  onInsertColumnBefore: commands.onAddColumnBefore,
@@ -3691,6 +4096,17 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
3691
4096
  onDeleteTable: commands.onDeleteTable,
3692
4097
  onMergeCells: commands.onMergeCells,
3693
4098
  onSplitCell: commands.onSplitCell,
4099
+ onAskAgentForSelection: () => {
4100
+ const { selection } = snapshot;
4101
+ onEventRef.current?.({
4102
+ type: "agent-on-selection-requested",
4103
+ documentId,
4104
+ requestId: createAgentRequestId(),
4105
+ anchor: selection.activeRange,
4106
+ ...(selection.storyTarget ? { storyTarget: selection.storyTarget } : {}),
4107
+ selectionText: "",
4108
+ });
4109
+ },
3694
4110
  };
3695
4111
 
3696
4112
  return editorActionHost
@@ -3701,10 +4117,13 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
3701
4117
  commands,
3702
4118
  editorActionHost,
3703
4119
  handleOpenInlineFind,
4120
+ handleOpenInlineGoTo,
4121
+ handleOpenInlineReplace,
3704
4122
  onFindRequested,
3705
4123
  onGoToRequested,
3706
4124
  onPrintRequested,
3707
4125
  onReplaceRequested,
4126
+ documentId,
3708
4127
  snapshot.selection,
3709
4128
  styleCatalog,
3710
4129
  ]);
@@ -3730,7 +4149,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
3730
4149
  documentNavigation={documentNavigation}
3731
4150
  reviewMode={reviewMode}
3732
4151
  markupDisplay={liveMarkupDisplay}
3733
- showUnsupportedObjectPreviews={effectiveShowUnsupportedPreviews}
4152
+ unsupportedObjectPreviewsVisible={effectiveShowUnsupportedPreviews}
3734
4153
  activeRevisionId={activeRevisionId}
3735
4154
  activeSelectionToolKind={activeSelectionTool?.kind ?? null}
3736
4155
  showTrackedChanges={showTrackedChanges}
@@ -3874,13 +4293,25 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
3874
4293
  onOpenInlineFind={handleOpenInlineFind}
3875
4294
  inlineFindBar={
3876
4295
  inlineFindOpen ? (
3877
- <TwInlineFindBar
4296
+ <TwNavigationCommandBar
4297
+ mode={navigationCommandMode}
3878
4298
  query={inlineFindQuery}
4299
+ replacement={inlineReplaceText}
3879
4300
  activeIndex={inlineFindActiveIndex}
3880
4301
  resultCount={inlineFindResults.length}
4302
+ searchScope={inlineSearchScope}
4303
+ searchScopes={navigationSearchScopes}
4304
+ goToGroups={navigationGoToGroups}
4305
+ activeGoToGroupId={activeGoToGroupId}
4306
+ onModeChange={handleNavigationModeChange}
3881
4307
  onQueryChange={handleInlineFindQueryChange}
4308
+ onReplacementChange={setInlineReplaceText}
4309
+ onSearchScopeChange={handleInlineSearchScopeChange}
3882
4310
  onPrevious={handleInlineFindPrevious}
3883
4311
  onNext={handleInlineFindNext}
4312
+ onReplaceCurrent={handleInlineReplaceCurrent}
4313
+ onGoToGroupChange={setActiveGoToGroupId}
4314
+ onGoToTarget={handleGoToTarget}
3884
4315
  onClose={handleCloseInlineFind}
3885
4316
  />
3886
4317
  ) : null
@@ -3930,6 +4361,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
3930
4361
  });
3931
4362
  }}
3932
4363
  onDeselectObject={() => activeRuntime.deselectObject()}
4364
+ onBeginObjectDrag={(input) => api.ui!.surface.beginObjectDrag(input)}
3933
4365
  onScopeAskAgent={(payload) => {
3934
4366
  // Resolve scope card + story through the mounted UI API seam so
3935
4367
  // the shell root does not re-own workflow facet reads.
@@ -4248,6 +4680,82 @@ function wrapSearchResultIndex(index: number, resultCount: number): number {
4248
4680
  return ((index % resultCount) + resultCount) % resultCount;
4249
4681
  }
4250
4682
 
4683
+ function storyTargetLabel(target: EditorStoryTarget): string {
4684
+ switch (target.kind) {
4685
+ case "main":
4686
+ return "Story";
4687
+ case "header":
4688
+ return "Header";
4689
+ case "footer":
4690
+ return "Footer";
4691
+ case "footnote":
4692
+ return "Footnote";
4693
+ case "endnote":
4694
+ return "Endnote";
4695
+ }
4696
+ }
4697
+
4698
+ function buildNavigationGoToGroups(input: {
4699
+ documentNavigation: DocumentNavigationSnapshot;
4700
+ comments: readonly CommentSidebarThreadSnapshot[];
4701
+ revisions: readonly TrackedChangeEntrySnapshot[];
4702
+ workflowScopeSnapshot: WorkflowScopeSnapshot | null;
4703
+ }): readonly NavigationGoToGroup[] {
4704
+ const { documentNavigation, comments, revisions, workflowScopeSnapshot } = input;
4705
+ return [
4706
+ {
4707
+ id: "pages",
4708
+ label: "Pages",
4709
+ emptyLabel: "No page targets available.",
4710
+ items: documentNavigation.pages.map((page) => ({
4711
+ id: `page:${page.pageIndex}`,
4712
+ label: `Page ${page.pageIndex + 1}`,
4713
+ detail: `Section ${page.sectionIndex + 1}`,
4714
+ })),
4715
+ },
4716
+ {
4717
+ id: "headings",
4718
+ label: "Headings",
4719
+ emptyLabel: "No headings in this document.",
4720
+ items: documentNavigation.headings.map((heading) => ({
4721
+ id: `heading:${heading.headingId}`,
4722
+ label: heading.text || `Heading ${heading.headingId}`,
4723
+ detail: `Level ${heading.level} · Page ${heading.pageIndex + 1}`,
4724
+ })),
4725
+ },
4726
+ {
4727
+ id: "comments",
4728
+ label: "Comments",
4729
+ emptyLabel: "No comments in this document.",
4730
+ items: comments.map((comment) => ({
4731
+ id: `comment:${comment.commentId}`,
4732
+ label: comment.anchorLabel || comment.excerpt || "Comment",
4733
+ detail: `${comment.status} · ${comment.excerpt || `${comment.entryCount} entries`}`,
4734
+ })),
4735
+ },
4736
+ {
4737
+ id: "changes",
4738
+ label: "Changes",
4739
+ emptyLabel: "No tracked changes in this document.",
4740
+ items: revisions.map((revision) => ({
4741
+ id: `change:${revision.revisionId}`,
4742
+ label: revision.label || revision.anchorLabel || "Tracked change",
4743
+ detail: `${revision.kind} · ${revision.status}`,
4744
+ })),
4745
+ },
4746
+ {
4747
+ id: "scopes",
4748
+ label: "Scopes",
4749
+ emptyLabel: "No workflow scopes available.",
4750
+ items: (workflowScopeSnapshot?.scopes ?? []).map((scope) => ({
4751
+ id: `scope:${scope.scopeId}`,
4752
+ label: scope.label || scope.scopeId,
4753
+ detail: `${scope.mode}${scope.workItemId ? ` · ${scope.workItemId}` : ""}`,
4754
+ })),
4755
+ },
4756
+ ];
4757
+ }
4758
+
4251
4759
  function readTableRowCantSplit(
4252
4760
  document: CanonicalDocumentEnvelope,
4253
4761
  tableBlockIndex: number,