@beyondwork/docx-react-component 1.0.47 → 1.0.49

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 (80) hide show
  1. package/README.md +16 -11
  2. package/package.json +30 -41
  3. package/src/api/public-types.ts +199 -13
  4. package/src/compare/diff-engine.ts +4 -0
  5. package/src/core/commands/add-scope.ts +257 -0
  6. package/src/core/commands/formatting-commands.ts +2 -0
  7. package/src/core/commands/index.ts +9 -1
  8. package/src/core/commands/text-commands.ts +3 -1
  9. package/src/core/schema/text-schema.ts +95 -1
  10. package/src/core/selection/anchor-conversion.ts +112 -0
  11. package/src/core/selection/review-anchors.ts +108 -3
  12. package/src/core/state/text-transaction.ts +103 -7
  13. package/src/internal/harness-debug-ports.ts +168 -0
  14. package/src/io/chart-preview-resolver.ts +59 -1
  15. package/src/io/docx-session.ts +226 -38
  16. package/src/io/export/serialize-main-document.ts +46 -0
  17. package/src/io/export/serialize-paragraph-formatting.ts +8 -0
  18. package/src/io/export/serialize-run-formatting.ts +10 -1
  19. package/src/io/export/serialize-settings.ts +421 -0
  20. package/src/io/export/serialize-styles.ts +10 -0
  21. package/src/io/normalize/normalize-text.ts +1 -0
  22. package/src/io/ooxml/chart/chart-style-table.ts +543 -0
  23. package/src/io/ooxml/chart/color-palette.ts +101 -0
  24. package/src/io/ooxml/chart/compose-series-color.ts +147 -0
  25. package/src/io/ooxml/chart/parse-axis.ts +277 -0
  26. package/src/io/ooxml/chart/parse-chart-space.ts +885 -0
  27. package/src/io/ooxml/chart/parse-series.ts +635 -0
  28. package/src/io/ooxml/chart/resolve-color.ts +261 -0
  29. package/src/io/ooxml/chart/types.ts +439 -0
  30. package/src/io/ooxml/parse-block-structure.ts +99 -0
  31. package/src/io/ooxml/parse-complex-content.ts +90 -2
  32. package/src/io/ooxml/parse-main-document.ts +156 -1
  33. package/src/io/ooxml/parse-paragraph-formatting.ts +46 -0
  34. package/src/io/ooxml/parse-run-formatting.ts +49 -0
  35. package/src/io/ooxml/parse-scope-markers.ts +184 -0
  36. package/src/io/ooxml/parse-settings-blueprint.ts +349 -0
  37. package/src/io/ooxml/parse-settings.ts +97 -1
  38. package/src/io/ooxml/parse-styles.ts +65 -0
  39. package/src/io/ooxml/parse-theme.ts +2 -127
  40. package/src/io/ooxml/property-grab-bag.ts +211 -0
  41. package/src/io/ooxml/xml-attr-helpers.ts +59 -1
  42. package/src/io/ooxml/xml-parser.ts +142 -0
  43. package/src/model/canonical-document.ts +160 -0
  44. package/src/model/scope-markers.ts +144 -0
  45. package/src/runtime/collab/base-doc-fingerprint.ts +99 -0
  46. package/src/runtime/collab/checkpoint-election.ts +75 -0
  47. package/src/runtime/collab/checkpoint-scheduler.ts +204 -0
  48. package/src/runtime/collab/checkpoint-store.ts +115 -0
  49. package/src/runtime/collab/event-types.ts +27 -0
  50. package/src/runtime/collab/index.ts +29 -0
  51. package/src/runtime/collab/remote-cursor-awareness.ts +167 -0
  52. package/src/runtime/collab/runtime-collab-sync.ts +330 -0
  53. package/src/runtime/collab/workflow-shared.ts +247 -0
  54. package/src/runtime/document-locations.ts +1 -9
  55. package/src/runtime/document-outline.ts +1 -9
  56. package/src/runtime/document-runtime.ts +288 -65
  57. package/src/runtime/editor-surface/capabilities.ts +63 -50
  58. package/src/runtime/hyperlink-color-resolver.ts +119 -0
  59. package/src/runtime/layout/layout-engine-version.ts +8 -1
  60. package/src/runtime/prerender/cache-envelope.ts +19 -7
  61. package/src/runtime/prerender/cache-key.ts +25 -14
  62. package/src/runtime/prerender/canonical-document-hash.ts +63 -0
  63. package/src/runtime/prerender/customxml-cache.ts +211 -0
  64. package/src/runtime/prerender/customxml-probe.ts +78 -0
  65. package/src/runtime/prerender/prerender-document.ts +74 -7
  66. package/src/runtime/scope-resolver.ts +148 -0
  67. package/src/runtime/scope-tag-registry.ts +10 -0
  68. package/src/runtime/surface-projection.ts +102 -37
  69. package/src/runtime/theme-color-resolver.ts +188 -0
  70. package/src/runtime/workflow-markup.ts +7 -18
  71. package/src/ui/WordReviewEditor.tsx +48 -2
  72. package/src/ui/editor-runtime-boundary.ts +42 -1
  73. package/src/ui/headless/selection-helpers.ts +10 -23
  74. package/src/ui/runtime-shortcut-dispatch.ts +12 -7
  75. package/src/ui/unsupported-previews-policy.ts +23 -0
  76. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +10 -0
  77. package/src/ui-tailwind/editor-surface/perf-probe.ts +1 -0
  78. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +47 -0
  79. package/src/ui-tailwind/page-stack/use-visible-block-range.ts +88 -0
  80. package/src/ui-tailwind/tw-review-workspace.tsx +16 -1
package/README.md CHANGED
@@ -224,17 +224,22 @@ The CCEP corpus is kept on `main` as a maintainer-safe smoke-doc source set for
224
224
 
225
225
  Engineering work is organized into 9 lanes (5 active now + 2 later polish + 2 final) — full plan index, status, and worktree map at [`docs/plans/README.md`](docs/plans/README.md). Each lane has a single self-contained doc at `docs/plans/lane-N-<name>.md`:
226
226
 
227
- | Lane | Mission | Status |
228
- |---|---|---|
229
- | 1. [**Editing Foundation**](docs/plans/lane-1-editing-foundation.md) | Close I1–I6 + ship the SelectionLayer / EditLayer / StructureLayer / SurfaceLayer split | active |
230
- | 2. [**Render Performance**](docs/plans/lane-2-render-performance.md) | <32 ms typing P50 + <1 s cold-open on 200-page CCEP; never stuck on loading | active |
231
- | 3. [**Layout Engine + OOXML Fidelity**](docs/plans/lane-3-layout-engine-ooxml-fidelity.md) | Land P9 anchor-index + P10 incremental relayout + drain O2/O3/O4 round-trip + tables/TOC/page numbers/front pages/image structures | active |
232
- | 4. [**Collab + CLM/Vallor Integration**](docs/plans/lane-4-collab-clm-vallor.md) | Promote collab plumbing first-class user mode; wire Sunday CLM × React E2E demo | active |
233
- | 5. [**Charts (independent)**](docs/plans/lane-5-charts.md) | Move charts from preserve-only opaque preview to Word-accurate native rendering (Stages 1–7) | active |
234
- | 6. [**Visual Chrome / Layout Polish**](docs/plans/lane-6-visual-chrome-layout-polish.md) | True-to-Word visuals: discrete paper cards, native page chrome, float-wrap, validation bar | LATER |
235
- | 7. [**Bugs / Gaps / Cross-cutting**](docs/plans/lane-7-bugs-gaps-cross-cutting.md) | Drain V#/O#/X# register; trigger-gated work; infrastructure hardening | LATER |
236
- | 8. [**API Ergonomics / Errors / Backwards Compatibility**](docs/plans/lane-8-api-ergonomics.md) | Refactor `docs/reference/public-api.md` end-to-end with groups + per-code error catalog + ergonomics fixes; maintain backwards compat | LATER (Tracks A+C shipped 2026-04-19) |
237
- | 9. [**Shipping**](docs/plans/lane-9-shipping.md) | Production-readiness: API freeze, semver discipline, changelog, telemetry, customer migration guides, doc completeness audit | FINAL |
227
+ **Progress snapshot — 2026-04-19 (post PR #189 + #190 + #191 + #192 merged to `main`; Lane 3 split into 3a + 3b on 2026-04-19 deep evening):**
228
+
229
+ | # | Lane | % to goal | Status summary |
230
+ |---|---|---|---|
231
+ | 1 | [**Editing Foundation**](docs/plans/lane-1-editing-foundation.md) | **65%** | I1–I6 + I3 widening + I4 + S1 + X3 + I8 shipped; I2B / I7 / R.1–R.5 backlog |
232
+ | 2 | [**Render Performance**](docs/plans/lane-2-render-performance.md) | **85%** | All Phases 1.x + 2.2 + 2.4b + 2.5 (A+B) + 2.9 shipped; Tasks 2.1 / 2.3 left |
233
+ | 3a | [**Layout & Render Engine**](docs/plans/lane-3a-layout-render-engine.md) | **45%** | P1–P6 + P8 + P14.a/b/e shipped; P9 (now unblocked) + P10 + P11 + P12 + P14.c/d backlog |
234
+ | 3b | [**OOXML Fidelity & Round-Trip**](docs/plans/lane-3b-ooxml-fidelity.md) | **55%** | V1 + O3 + O4 + O2 (all 4 slices) + V7 closed; 🚨 O8 + L2.c + V6 + V2a/b/c + R3–R5 backlog |
235
+ | 4 | [**Collab + CLM/Vallor**](docs/plans/lane-4-collab-clm-vallor.md) | **80%** | P1–P14 + all P11 sub-bullets + P12 + perf-parity + P13 A/B/C shipped; P15 / P16 / P17 left |
236
+ | 5 | [**Charts (independent)**](docs/plans/lane-5-charts.md) | **30%** | Stages 0–2 shipped (parsers + theme); Stages 3–7 (SVG renderers + pixel-diff) left |
237
+ | 6 | [**Visual Chrome / Layout Polish**](docs/plans/lane-6-visual-chrome-layout-polish.md) | **0%** | LATER activates after Lane 3b V2c + Lane 2 Phase 2.2 ship; discrete paper cards, native chrome, float-wrap, validation bar |
238
+ | 7 | [**Bugs / Gaps / Cross-cutting**](docs/plans/lane-7-bugs-gaps-cross-cutting.md) | **0%** | LATER — drain V#/O#/X# register, trigger-gated work, infrastructure hardening |
239
+ | 8 | [**API Ergonomics / Errors / BC**](docs/plans/lane-8-api-ergonomics.md) | **40%** | LATER — Tracks A+C shipped (error catalog + ergonomics fixes); Tracks B+D+E + public-api.md end-to-end refactor remain |
240
+ | 9 | [**Shipping (v2.0.0)**](docs/plans/lane-9-shipping.md) | **0%** | FINAL — API freeze, semver discipline, changelog, telemetry, customer migration guides, doc completeness audit |
241
+
242
+ **Aggregate v2.0.0 readiness:** ~70% — long poles are Lane 3 O8 + P9/P10, Lane 4 P15/P16/P17, Lane 1 R.1–R.5, Lane 5 Stage 4 SVG renderers (Lane 5 ships on independent v2.x cadence). Detailed task-completeness factor + per-lane backlog inventory in [`docs/plans/coordination-prompt.md`](docs/plans/coordination-prompt.md).
238
243
 
239
244
  ### Technical Wiki
240
245
 
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "@beyondwork/docx-react-component",
3
3
  "publisher": "beyondwork",
4
- "version": "1.0.47",
4
+ "version": "1.0.49",
5
5
  "description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
6
- "packageManager": "pnpm@10.30.3",
7
6
  "type": "module",
8
7
  "sideEffects": [
9
8
  "**/*.css"
@@ -93,35 +92,6 @@
93
92
  "./ui-tailwind/theme/editor-theme.css": "./src/ui-tailwind/theme/editor-theme.css"
94
93
  },
95
94
  "types": "./src/index.ts",
96
- "scripts": {
97
- "build": "tsup",
98
- "test": "bash scripts/run-workspace-tests.sh",
99
- "test:repo": "node scripts/ci-check-layout-engine-version.mjs && node scripts/run-repo-tests.mjs core",
100
- "test:repo:all": "node scripts/run-repo-tests.mjs all",
101
- "test:repo:optional": "node scripts/run-repo-tests.mjs optional",
102
- "test:repo:browser-ui": "node scripts/run-repo-tests.mjs browser-ui",
103
- "test:wcag-audit": "node scripts/run-repo-tests.mjs wcag-audit",
104
- "test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
105
- "test:visual": "VISUAL_SMOKE_PROFILE=bare pnpm exec playwright test --project=chromium",
106
- "test:visual:chrome": "VISUAL_SMOKE_PROFILE=chrome-cycle pnpm exec playwright test --project=chromium",
107
- "visual:list-runs": "node scripts/visual-smoke-list-runs.mjs",
108
- "mcp:visual-smoke": "node scripts/visual-smoke-mcp.mjs",
109
- "lint": "pnpm run lint:no-authored-js && pnpm run lint:docs-contracts && pnpm run lint:tsgo && pnpm run lint:tsgo:harness",
110
- "lint:docs-contracts": "bash scripts/check-reference-load-contract.sh",
111
- "lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
112
- "lint:tsgo": "tsgo --noEmit -p tsconfig.build.json",
113
- "lint:tsgo:harness": "pnpm --filter @docx-react-component/react-word-editor-harness lint:tsgo",
114
- "context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
115
- "wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
116
- "wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
117
- "wave:launch": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main",
118
- "wave:launch:managed": "bash scripts/wave-launch.sh",
119
- "wave:status": "bash scripts/wave-status.sh",
120
- "wave:watch": "bash scripts/wave-watch.sh --follow",
121
- "wave:dashboard:current": "bash scripts/wave-dashboard-attach.sh current",
122
- "wave:dashboard:global": "bash scripts/wave-dashboard-attach.sh global",
123
- "harness:dev": "pnpm --filter @docx-react-component/react-word-editor-harness dev"
124
- },
125
95
  "keywords": [
126
96
  "docx",
127
97
  "word",
@@ -205,14 +175,33 @@
205
175
  "y-protocols": "^1.0.7",
206
176
  "yjs": "^13.6.30"
207
177
  },
208
- "pnpm": {
209
- "onlyBuiltDependencies": [
210
- "esbuild",
211
- "sharp"
212
- ],
213
- "overrides": {
214
- "react": "19.2.4",
215
- "react-dom": "19.2.4"
216
- }
178
+ "scripts": {
179
+ "build": "tsup",
180
+ "test": "bash scripts/run-workspace-tests.sh",
181
+ "test:repo": "node scripts/ci-check-layout-engine-version.mjs && node scripts/run-repo-tests.mjs core",
182
+ "test:repo:all": "node scripts/run-repo-tests.mjs all",
183
+ "test:repo:optional": "node scripts/run-repo-tests.mjs optional",
184
+ "test:repo:browser-ui": "node scripts/run-repo-tests.mjs browser-ui",
185
+ "test:wcag-audit": "node scripts/run-repo-tests.mjs wcag-audit",
186
+ "test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
187
+ "test:visual": "VISUAL_SMOKE_PROFILE=bare pnpm exec playwright test --project=chromium",
188
+ "test:visual:chrome": "VISUAL_SMOKE_PROFILE=chrome-cycle pnpm exec playwright test --project=chromium",
189
+ "visual:list-runs": "node scripts/visual-smoke-list-runs.mjs",
190
+ "mcp:visual-smoke": "node scripts/visual-smoke-mcp.mjs",
191
+ "lint": "pnpm run lint:no-authored-js && pnpm run lint:docs-contracts && pnpm run lint:tsgo && pnpm run lint:tsgo:harness",
192
+ "lint:docs-contracts": "bash scripts/check-reference-load-contract.sh",
193
+ "lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
194
+ "lint:tsgo": "tsgo --noEmit -p tsconfig.build.json",
195
+ "lint:tsgo:harness": "pnpm --filter @docx-react-component/react-word-editor-harness lint:tsgo",
196
+ "context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
197
+ "wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
198
+ "wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
199
+ "wave:launch": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main",
200
+ "wave:launch:managed": "bash scripts/wave-launch.sh",
201
+ "wave:status": "bash scripts/wave-status.sh",
202
+ "wave:watch": "bash scripts/wave-watch.sh --follow",
203
+ "wave:dashboard:current": "bash scripts/wave-dashboard-attach.sh current",
204
+ "wave:dashboard:global": "bash scripts/wave-dashboard-attach.sh global",
205
+ "harness:dev": "pnpm --filter @docx-react-component/react-word-editor-harness dev"
217
206
  }
218
- }
207
+ }
@@ -1,8 +1,10 @@
1
1
  import type { PersistedEditorSnapshot as RuntimePersistedEditorSnapshot } from "../core/state/editor-state.ts";
2
- import type { CanonicalParagraphFormatting, CanonicalRunFormatting } from "../model/canonical-document.ts";
2
+ import type { HarnessDebugPorts } from "../internal/harness-debug-ports.ts";
3
+ import type { CanonicalParagraphFormatting, CanonicalRunFormatting, TextMark } from "../model/canonical-document.ts";
3
4
  import type { WordReviewEditorLayoutFacet } from "../runtime/layout/public-facet.ts";
4
5
  import type { RenderFrameRect } from "../runtime/render/index.ts";
5
6
  import type { ScopeRailPosture } from "../runtime/workflow-rail-segments.ts";
7
+ import type { SharedWorkflowState } from "../runtime/collab/workflow-shared.ts";
6
8
  import type {
7
9
  MetadataPersistenceMode,
8
10
  ScopeMetadataPersistence,
@@ -46,6 +48,7 @@ export type {
46
48
  };
47
49
 
48
50
  export type { CanonicalParagraphFormatting, CanonicalRunFormatting };
51
+ export type { SharedWorkflowState };
49
52
 
50
53
  export type {
51
54
  WordReviewEditorLayoutFacet,
@@ -750,6 +753,25 @@ export interface InsertImageOptions {
750
753
  altText?: string;
751
754
  }
752
755
 
756
+ /** Re-export canonical `TextMark` so hosts can construct explicit-mode directives for I7 `replaceText`. */
757
+ export type { TextMark };
758
+
759
+ /**
760
+ * I7 — `replaceText` / `text.insert` formatting directive.
761
+ *
762
+ * - `paragraph-default` (default): inserted text carries no run marks; the paragraph-level
763
+ * cascade applies. Pre-I7 behavior.
764
+ * - `match-replaced-range`: walk text units within the replaced range; if all share the
765
+ * same marks, apply them to the inserted text. Mixed ranges fall back to
766
+ * `paragraph-default`. For collapsed (empty) ranges the marks of the run immediately
767
+ * left of the caret are used (Word-matching behavior).
768
+ * - `explicit`: apply the caller-supplied `marks` verbatim.
769
+ */
770
+ export type TextFormattingDirective =
771
+ | { mode: "paragraph-default" }
772
+ | { mode: "match-replaced-range" }
773
+ | { mode: "explicit"; marks: TextMark[] };
774
+
753
775
  export type SurfaceTextMark =
754
776
  | "bold"
755
777
  | "italic"
@@ -1685,6 +1707,16 @@ export interface AddCommentParams {
1685
1707
  anchor?: EditorAnchorProjection;
1686
1708
  body?: string;
1687
1709
  authorId?: string;
1710
+ /**
1711
+ * I8 — When `true`, anchors that would be rejected with reason
1712
+ * `comment_anchor_table_adjacent` are auto-snapped to the enclosing
1713
+ * paragraph boundaries before the comment is created. Defaults to
1714
+ * `false` for back-compat. Removed once Lane 3 §O8 lands the deep
1715
+ * serializer fix and mid-run-near-table anchors no longer need
1716
+ * mitigation — the option stays useful for hosts that prefer
1717
+ * boundary-aligned comments regardless.
1718
+ */
1719
+ snapToSafeBoundary?: boolean;
1688
1720
  }
1689
1721
 
1690
1722
  export interface AddCommentResult {
@@ -1697,6 +1729,38 @@ export interface AddCommentReplyResult {
1697
1729
  entryId: string;
1698
1730
  }
1699
1731
 
1732
+ /**
1733
+ * S1 — Scope marker anchoring. Parameters for `WordReviewEditorRef.addScope`.
1734
+ * The runtime inserts a pair of zero-width `scope_marker_*` inline nodes at
1735
+ * the anchor's range boundaries so PM handles position bookkeeping through
1736
+ * text edits structurally (no remap-on-mutation required).
1737
+ */
1738
+ export interface AddScopeParams {
1739
+ /** Range anchor (typically `getRenderSnapshot().selection.activeRange`). */
1740
+ anchor: EditorAnchorProjection;
1741
+ /** Optional explicit scopeId; runtime mints one when omitted. */
1742
+ scopeId?: string;
1743
+ /** Workflow mode the scope enters (default `"comment"`). */
1744
+ mode?: WorkflowScopeMode;
1745
+ /** 3-mode persistence (A/B/C). Defaults to the overlay default. */
1746
+ persistence?: WorkflowMetadataPersistence;
1747
+ /**
1748
+ * Optional metadata payload. Persistence mode decides whether this is
1749
+ * held runtime-only, split across doc + workblock, or fully embedded.
1750
+ */
1751
+ metadata?: Partial<WorkflowMetadataEntry>;
1752
+ /** Non-main-body stories (footnote / header / endnote). */
1753
+ storyTarget?: EditorStoryTarget;
1754
+ /** Optional display label for the scope card / rail. */
1755
+ label?: string;
1756
+ }
1757
+
1758
+ export interface AddScopeResult {
1759
+ scopeId: string;
1760
+ /** Range anchor derived from the just-inserted marker positions. */
1761
+ anchor: EditorAnchorProjection;
1762
+ }
1763
+
1700
1764
  export interface ExportDocxOptions {
1701
1765
  fileName?: string;
1702
1766
  reason?: string;
@@ -1975,6 +2039,7 @@ export interface WorkflowBlockedCommandReason {
1975
2039
  code:
1976
2040
  | "outside_workflow_scope"
1977
2041
  | "workflow_comment_only"
2042
+ | "workflow_round_locked"
1978
2043
  | "workflow_view_only"
1979
2044
  | "workflow_preserve_only"
1980
2045
  | "workflow_blocked_import"
@@ -2803,6 +2868,24 @@ export interface WordReviewEditorRef {
2803
2868
  addCommentReply(commentId: string, body: string): AddCommentReplyResult;
2804
2869
  editCommentBody(commentId: string, body: string): void;
2805
2870
  deleteComment(commentId: string): void;
2871
+ /**
2872
+ * S1 — Insert a workflow scope anchored by marker nodes. Returns
2873
+ * `{scopeId, anchor}` where the anchor is derived from the newly-inserted
2874
+ * markers' positions. Subsequent text edits inside / around / through the
2875
+ * scope update the anchor structurally (no remap bookkeeping).
2876
+ */
2877
+ addScope(params: AddScopeParams): AddScopeResult;
2878
+ /**
2879
+ * S1 — Resolve a scopeId to a live `WorkflowScope` with an anchor derived
2880
+ * from the marker positions currently in the document. Returns null when
2881
+ * the scope has been removed or was never added.
2882
+ */
2883
+ getScope(scopeId: string): WorkflowScope | null;
2884
+ /**
2885
+ * S1 — Remove a scope's markers from the document and drop the scope's
2886
+ * metadata record. No-op when the scopeId is unknown.
2887
+ */
2888
+ removeScope(scopeId: string): void;
2806
2889
  acceptChange(changeId: string): void;
2807
2890
  rejectChange(changeId: string): void;
2808
2891
  acceptAllChanges(): void;
@@ -2831,7 +2914,11 @@ export interface WordReviewEditorRef {
2831
2914
  isDirty(): boolean;
2832
2915
  getFormattingState(): FormattingStateSnapshot;
2833
2916
  getStyleCatalog(): StyleCatalogSnapshot;
2834
- replaceText(text: string, target?: EditorAnchorProjection): void;
2917
+ replaceText(
2918
+ text: string,
2919
+ target?: EditorAnchorProjection,
2920
+ formatting?: TextFormattingDirective,
2921
+ ): void;
2835
2922
  toggleBulletedList(): void;
2836
2923
  toggleNumberedList(): void;
2837
2924
  toggleBold(): void;
@@ -2919,6 +3006,7 @@ export interface WordReviewEditorRef {
2919
3006
  setImageFrame(mediaId: string, offsets: { horizontalOffsetEmu?: number; verticalOffsetEmu?: number }): void;
2920
3007
  setWorkflowOverlay(overlay: WorkflowOverlay): void;
2921
3008
  clearWorkflowOverlay(): void;
3009
+ setSharedWorkflowState(state: SharedWorkflowState | null): void;
2922
3010
  getWorkflowScopeSnapshot(): WorkflowScopeSnapshot | null;
2923
3011
  getInteractionGuardSnapshot(): InteractionGuardSnapshot;
2924
3012
  getWorkflowMarkupSnapshot(): WorkflowMarkupSnapshot;
@@ -3158,20 +3246,54 @@ export interface WordReviewEditorProps {
3158
3246
  chromeOptions?: Partial<WordReviewEditorChromeOptions>;
3159
3247
  markupDisplay?: "clean" | "simple" | "all";
3160
3248
  /**
3161
- * Harness/debug-only preview toggle for preserve-only features
3162
- * (charts, SmartArt, shapes, WordArt, VML rendered as typed atoms;
3163
- * the "N preserve-only features detected" banner; lock-callouts).
3249
+ * @internal HARNESS-ONLY debug-ports token.
3250
+ *
3251
+ * **Do not set this from downstream consumer code.** This prop
3252
+ * replaces the former `showUnsupportedObjectPreviews?: boolean`
3253
+ * toggle, which regressed to `true` three times through merges
3254
+ * (PRs #124, #131, #160) and leaked preserve-only preview chrome
3255
+ * (charts, SmartArt, shapes, WordArt, VML, "N preserve-only
3256
+ * features detected" banner, lock-callouts) into consumer apps.
3257
+ *
3258
+ * A valid value is only obtainable from the internal module
3259
+ * `src/internal/harness-debug-ports.ts` via
3260
+ * `__createHarnessDebugPorts()`. That module is deliberately **not
3261
+ * listed in `package.json#exports`**, so downstream consumers
3262
+ * cannot import it through any public subpath. The token type is
3263
+ * branded with a module-local `unique symbol`, so downstream
3264
+ * callers cannot construct a value structurally either — any
3265
+ * attempt to set this prop from outside the in-repo harness will
3266
+ * produce a TypeScript error.
3267
+ *
3268
+ * At runtime, even a successfully-constructed token is
3269
+ * gated by `globalThis.__DOCX_REACT_COMPONENT_HARNESS__ === true`,
3270
+ * which only the harness sets via `__markHarnessEnvironment()`.
3271
+ * The permissions field (`unsupportedObjectPreviews`) is forced
3272
+ * to `false` outside the harness environment regardless of input.
3164
3273
  *
3165
- * MUST default to `false`. Consumer hosts MUST NOT hard-code `true` —
3166
- * this is a dev opt-in gated by the harness dev drawer's
3167
- * `debugMode && showUnsupportedObjectPreviews` AND-gate. Flipping the
3168
- * default or dropping the AND-gate is a regression that has landed
3169
- * three times (PRs #124, #131, #160); the invariant is locked by
3274
+ * Invariant locked at
3170
3275
  * `test/ui/unsupported-previews-invariant.test.ts`.
3276
+ */
3277
+ __harnessDebugPorts?: HarnessDebugPorts;
3278
+ /**
3279
+ * Declarative policy for showing preserve-only previews
3280
+ * (charts, SmartArt, shapes, WordArt, VML) and the
3281
+ * "N preserve-only features detected" banner. Orthogonal to the
3282
+ * harness-only `__harnessDebugPorts` token: hosts can opt into
3283
+ * previews without a harness environment.
3284
+ *
3285
+ * - `"never"` — previews are hidden (default).
3286
+ * - `"review-only"` — previews show when `reviewMode === "review"`
3287
+ * and hide when `reviewMode === "editing"`.
3288
+ * - `"always"` — previews show in every review mode.
3171
3289
  *
3172
- * @default false
3290
+ * Effective visibility rule:
3291
+ * `policy === "always"` OR
3292
+ * (`policy === "review-only"` AND `reviewMode === "review"`)
3293
+ *
3294
+ * @default "never"
3173
3295
  */
3174
- showUnsupportedObjectPreviews?: boolean;
3296
+ unsupportedPreviewsPolicy?: "never" | "review-only" | "always";
3175
3297
  showReviewPanel?: boolean;
3176
3298
  chromeVisibility?: Partial<WordReviewEditorChromeVisibility>;
3177
3299
  hostAdapter?: EditorHostAdapter;
@@ -3231,11 +3353,75 @@ export interface WordReviewEditorProps {
3231
3353
  * `shortcut.zoom-reset`.
3232
3354
  */
3233
3355
  onZoomRequested?: (direction: "in" | "out" | "reset") => void;
3356
+ /**
3357
+ * Optional: fires when the user invokes Replace (Ctrl+H) with the
3358
+ * editor focused. When wired, the editor calls this callback and
3359
+ * suppresses the browser's native Find-and-Replace fallback; when
3360
+ * omitted, the shortcut falls through to the browser. The callback
3361
+ * receives the current selection so the host can pre-populate its
3362
+ * Replace panel with the selected text.
3363
+ *
3364
+ * Capability id: `shortcut.replace`.
3365
+ */
3366
+ onReplaceRequested?: (context: ShortcutDelegationContext) => void;
3367
+ /**
3368
+ * Optional: fires when the user invokes Go To (Ctrl+G, Cmd+Option+G,
3369
+ * or F5) with the editor focused. When wired, the editor calls this
3370
+ * callback and suppresses the browser's native navigation; when
3371
+ * omitted, the shortcut falls through to the browser default. Use
3372
+ * the context's `selectionRange` to scope navigation (e.g.,
3373
+ * "go to the same clause in a sibling document").
3374
+ *
3375
+ * Capability id: `shortcut.go-to`.
3376
+ */
3377
+ onGoToRequested?: (context: ShortcutDelegationContext) => void;
3378
+ /**
3379
+ * Optional: fires when the user invokes Check Spelling (F7) with
3380
+ * the editor focused. The mounted editor does not ship a built-in
3381
+ * spell checker; when wired, this callback hands control off so
3382
+ * the host can open its own spell-check UI (or invoke a native
3383
+ * browser spellcheck). When omitted, F7 falls through to the
3384
+ * browser default.
3385
+ *
3386
+ * Capability id: `shortcut.spell`.
3387
+ */
3388
+ onSpellRequested?: (context: ShortcutDelegationContext) => void;
3389
+ /**
3390
+ * Optional: fires when the user invokes Thesaurus (Shift+F7). When
3391
+ * wired, the host opens its own thesaurus panel, typically seeded
3392
+ * with the currently-selected word from `context.selectionText`.
3393
+ * When omitted, Shift+F7 falls through to the browser default.
3394
+ *
3395
+ * Capability id: `shortcut.thesaurus`.
3396
+ */
3397
+ onThesaurusRequested?: (context: ShortcutDelegationContext) => void;
3398
+ /**
3399
+ * Optional: fires when the user invokes Extend-selection mode
3400
+ * (F8 in Word; repeated presses extend by word / sentence / paragraph).
3401
+ * The mounted editor does not today implement the F8 state machine;
3402
+ * when wired, the host owns the state-machine + UI feedback. When
3403
+ * omitted, F8 falls through to the browser default.
3404
+ *
3405
+ * Capability id: `shortcut.extend-selection`.
3406
+ */
3407
+ onExtendSelectionRequested?: (context: ShortcutDelegationContext) => void;
3408
+ /**
3409
+ * Optional: fires when the user invokes Return-to-last-edit
3410
+ * (Shift+F5). The mounted editor does not today track the
3411
+ * last-edit location; when wired, the host owns the history stack
3412
+ * and performs the scroll/selection move. When omitted, Shift+F5
3413
+ * falls through to the browser default.
3414
+ *
3415
+ * Capability id: `shortcut.last-edit`.
3416
+ */
3417
+ onLastEditRequested?: (context: ShortcutDelegationContext) => void;
3234
3418
  }
3235
3419
 
3236
3420
  /**
3237
3421
  * Selection context handed to host-delegated shortcut callbacks
3238
- * (`onFindRequested`, future `onReplaceRequested`, etc.) so the host
3422
+ * (`onFindRequested`, `onReplaceRequested`, `onGoToRequested`,
3423
+ * `onSpellRequested`, `onThesaurusRequested`,
3424
+ * `onExtendSelectionRequested`, `onLastEditRequested`) so the host
3239
3425
  * can pre-populate its own UI with the user's current selection.
3240
3426
  *
3241
3427
  * - `selectionText` is truncated to the first 500 characters —
@@ -515,6 +515,8 @@ function getInlineLength(node: InlineNode): number {
515
515
  case "footnote_ref":
516
516
  case "bookmark_start":
517
517
  case "bookmark_end":
518
+ case "scope_marker_start":
519
+ case "scope_marker_end":
518
520
  case "chart_preview":
519
521
  case "smartart_preview":
520
522
  case "shape":
@@ -566,6 +568,8 @@ function getInlineDisplayText(node: InlineNode): string {
566
568
  return "[Footnote]";
567
569
  case "bookmark_start":
568
570
  case "bookmark_end":
571
+ case "scope_marker_start":
572
+ case "scope_marker_end":
569
573
  return "";
570
574
  case "chart_preview":
571
575
  return "[Chart]";