@beyondwork/docx-react-component 1.0.19 → 1.0.21

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 (71) hide show
  1. package/package.json +44 -25
  2. package/src/api/public-types.ts +336 -0
  3. package/src/api/session-state.ts +2 -0
  4. package/src/core/commands/formatting-commands.ts +1 -1
  5. package/src/core/commands/index.ts +14 -2
  6. package/src/core/search/search-text.ts +28 -0
  7. package/src/core/state/editor-state.ts +3 -0
  8. package/src/index.ts +21 -0
  9. package/src/io/docx-session.ts +363 -17
  10. package/src/io/export/serialize-comments.ts +104 -34
  11. package/src/io/export/serialize-footnotes.ts +198 -1
  12. package/src/io/export/serialize-headers-footers.ts +203 -10
  13. package/src/io/export/serialize-main-document.ts +83 -3
  14. package/src/io/export/split-review-boundaries.ts +181 -19
  15. package/src/io/normalize/normalize-text.ts +82 -8
  16. package/src/io/ooxml/highlight-colors.ts +39 -0
  17. package/src/io/ooxml/parse-comments.ts +85 -19
  18. package/src/io/ooxml/parse-fields.ts +396 -0
  19. package/src/io/ooxml/parse-footnotes.ts +240 -2
  20. package/src/io/ooxml/parse-headers-footers.ts +431 -7
  21. package/src/io/ooxml/parse-inline-media.ts +15 -1
  22. package/src/io/ooxml/parse-main-document.ts +396 -14
  23. package/src/io/ooxml/parse-revisions.ts +317 -38
  24. package/src/legal/bookmarks.ts +44 -0
  25. package/src/legal/cross-references.ts +59 -1
  26. package/src/model/canonical-document.ts +117 -1
  27. package/src/model/snapshot.ts +85 -1
  28. package/src/review/store/revision-store.ts +6 -0
  29. package/src/review/store/revision-types.ts +1 -0
  30. package/src/runtime/document-navigation.ts +52 -13
  31. package/src/runtime/document-runtime.ts +1521 -75
  32. package/src/runtime/read-only-diagnostics-runtime.ts +8 -0
  33. package/src/runtime/session-capabilities.ts +33 -3
  34. package/src/runtime/surface-projection.ts +86 -25
  35. package/src/runtime/table-schema.ts +2 -2
  36. package/src/runtime/view-state.ts +24 -6
  37. package/src/runtime/workflow-markup.ts +349 -0
  38. package/src/ui/WordReviewEditor.tsx +915 -1314
  39. package/src/ui/editor-command-bag.ts +120 -0
  40. package/src/ui/editor-runtime-boundary.ts +1448 -0
  41. package/src/ui/editor-shell-view.tsx +134 -0
  42. package/src/ui/editor-surface-controller.tsx +55 -0
  43. package/src/ui/headless/revision-decoration-model.ts +4 -4
  44. package/src/ui/runtime-snapshot-selectors.ts +197 -0
  45. package/src/ui/workflow-surface-blocked-rails.ts +94 -0
  46. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +18 -2
  47. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +129 -0
  48. package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
  49. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +34 -0
  50. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +27 -2
  51. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +128 -0
  52. package/src/ui-tailwind/editor-surface/perf-probe.ts +86 -14
  53. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +2 -2
  54. package/src/ui-tailwind/editor-surface/pm-decorations.ts +237 -0
  55. package/src/ui-tailwind/editor-surface/pm-position-map.ts +1 -1
  56. package/src/ui-tailwind/editor-surface/pm-schema.ts +139 -8
  57. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +98 -48
  58. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +55 -0
  59. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +7 -1
  60. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +190 -48
  61. package/src/ui-tailwind/page-chrome-model.ts +27 -0
  62. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +7 -7
  63. package/src/ui-tailwind/review/tw-health-panel.tsx +31 -2
  64. package/src/ui-tailwind/review/tw-review-rail.tsx +3 -3
  65. package/src/ui-tailwind/review/tw-revision-sidebar.tsx +15 -15
  66. package/src/ui-tailwind/theme/editor-theme.css +130 -0
  67. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +543 -5
  68. package/src/ui-tailwind/tw-review-workspace.tsx +316 -19
  69. package/src/validation/compatibility-engine.ts +27 -4
  70. package/src/validation/compatibility-report.ts +1 -0
  71. package/src/validation/docx-comment-proof.ts +220 -0
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@beyondwork/docx-react-component",
3
3
  "publisher": "beyondwork",
4
- "version": "1.0.19",
4
+ "version": "1.0.21",
5
5
  "description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
6
+ "packageManager": "pnpm@10.30.3",
6
7
  "type": "module",
7
8
  "sideEffects": [
8
9
  "**/*.css"
@@ -73,6 +74,14 @@
73
74
  "types": "./src/core/commands/table-structure-commands.ts",
74
75
  "import": "./src/core/commands/table-structure-commands.ts"
75
76
  },
77
+ "./core/commands/style-commands": {
78
+ "types": "./src/core/commands/style-commands.ts",
79
+ "import": "./src/core/commands/style-commands.ts"
80
+ },
81
+ "./core/commands/section-layout-commands": {
82
+ "types": "./src/core/commands/section-layout-commands.ts",
83
+ "import": "./src/core/commands/section-layout-commands.ts"
84
+ },
76
85
  "./core/state/editor-state": {
77
86
  "types": "./src/core/state/editor-state.ts",
78
87
  "import": "./src/core/state/editor-state.ts"
@@ -80,6 +89,30 @@
80
89
  "./ui-tailwind/theme/editor-theme.css": "./src/ui-tailwind/theme/editor-theme.css"
81
90
  },
82
91
  "types": "./src/index.ts",
92
+ "scripts": {
93
+ "build": "tsup",
94
+ "test": "bash scripts/run-workspace-tests.sh",
95
+ "test:repo": "node scripts/run-repo-tests.mjs core",
96
+ "test:repo:all": "node scripts/run-repo-tests.mjs all",
97
+ "test:repo:optional": "node scripts/run-repo-tests.mjs optional",
98
+ "test:wcag-audit": "node scripts/run-repo-tests.mjs wcag-audit",
99
+ "test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
100
+ "lint": "pnpm run lint:no-authored-js && pnpm run lint:docs-contracts && pnpm run lint:tsgo && pnpm run lint:tsgo:harness",
101
+ "lint:docs-contracts": "bash scripts/check-reference-load-contract.sh",
102
+ "lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
103
+ "lint:tsgo": "tsgo --noEmit -p tsconfig.build.json",
104
+ "lint:tsgo:harness": "pnpm --filter @docx-react-component/react-word-editor-harness lint:tsgo",
105
+ "context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
106
+ "wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
107
+ "wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
108
+ "wave:launch": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main",
109
+ "wave:launch:managed": "bash scripts/wave-launch.sh",
110
+ "wave:status": "bash scripts/wave-status.sh",
111
+ "wave:watch": "bash scripts/wave-watch.sh --follow",
112
+ "wave:dashboard:current": "bash scripts/wave-dashboard-attach.sh current",
113
+ "wave:dashboard:global": "bash scripts/wave-dashboard-attach.sh global",
114
+ "harness:dev": "pnpm --filter @docx-react-component/react-word-editor-harness dev"
115
+ },
83
116
  "keywords": [
84
117
  "docx",
85
118
  "word",
@@ -142,28 +175,14 @@
142
175
  "tsup": "^8.3.0",
143
176
  "tsx": "^4.21.0"
144
177
  },
145
- "scripts": {
146
- "build": "tsup",
147
- "test": "bash scripts/run-workspace-tests.sh",
148
- "test:repo": "node scripts/run-repo-tests.mjs core",
149
- "test:repo:all": "node scripts/run-repo-tests.mjs all",
150
- "test:repo:optional": "node scripts/run-repo-tests.mjs optional",
151
- "test:wcag-audit": "node scripts/run-repo-tests.mjs wcag-audit",
152
- "test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
153
- "lint": "pnpm run lint:no-authored-js && pnpm run lint:docs-contracts && pnpm run lint:tsgo && pnpm run lint:tsgo:harness",
154
- "lint:docs-contracts": "bash scripts/check-reference-load-contract.sh",
155
- "lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
156
- "lint:tsgo": "tsgo --noEmit -p tsconfig.build.json",
157
- "lint:tsgo:harness": "pnpm --filter @docx-react-component/react-word-editor-harness lint:tsgo",
158
- "context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
159
- "wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
160
- "wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
161
- "wave:launch": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main",
162
- "wave:launch:managed": "bash scripts/wave-launch.sh",
163
- "wave:status": "bash scripts/wave-status.sh",
164
- "wave:watch": "bash scripts/wave-watch.sh --follow",
165
- "wave:dashboard:current": "bash scripts/wave-dashboard-attach.sh current",
166
- "wave:dashboard:global": "bash scripts/wave-dashboard-attach.sh global",
167
- "harness:dev": "pnpm --filter @docx-react-component/react-word-editor-harness dev"
178
+ "pnpm": {
179
+ "onlyBuiltDependencies": [
180
+ "esbuild",
181
+ "sharp"
182
+ ],
183
+ "overrides": {
184
+ "react": "19.2.4",
185
+ "react-dom": "19.2.4"
186
+ }
168
187
  }
169
- }
188
+ }
@@ -1,4 +1,13 @@
1
1
  import type { PersistedEditorSnapshot as RuntimePersistedEditorSnapshot } from "../core/state/editor-state.ts";
2
+ import type {
3
+ FieldFamily as FieldFamilyType,
4
+ FieldRefreshStatus as FieldRefreshStatusType,
5
+ SupportedFieldFamily as SupportedFieldFamilyType,
6
+ } from "../model/canonical-document.ts";
7
+
8
+ export type FieldFamily = FieldFamilyType;
9
+ export type FieldRefreshStatus = FieldRefreshStatusType;
10
+ export type SupportedFieldFamily = SupportedFieldFamilyType;
2
11
 
3
12
  export type ExternalDocumentSource =
4
13
  | {
@@ -349,6 +358,70 @@ export interface DocumentHeadingSnapshot {
349
358
  sectionIndex: number;
350
359
  }
351
360
 
361
+ /** A single field entry in the runtime field snapshot. */
362
+ export interface FieldEntrySnapshot {
363
+ /** Stable index of the field in document order. */
364
+ index: number;
365
+ /** Classified field family. */
366
+ fieldFamily: FieldFamily;
367
+ /** Whether this family is in the supported refresh slice. */
368
+ supported: boolean;
369
+ /** Raw field instruction string. */
370
+ instruction: string;
371
+ /** Target bookmark for REF/PAGEREF/NOTEREF fields. */
372
+ fieldTarget?: string;
373
+ /** Current refresh status. */
374
+ refreshStatus: FieldRefreshStatus;
375
+ /** Resolved display text (from field children). */
376
+ displayText: string;
377
+ }
378
+
379
+ /** Runtime field snapshot — read-only view of all fields in the document. */
380
+ export interface FieldSnapshot {
381
+ /** Total number of fields in the document. */
382
+ totalCount: number;
383
+ /** Number of fields in the supported refresh slice. */
384
+ supportedCount: number;
385
+ /** Number of fields that are preserve-only. */
386
+ preserveOnlyCount: number;
387
+ /** Ordered field entries. */
388
+ fields: FieldEntrySnapshot[];
389
+ }
390
+
391
+ /** Options for runtime-backed field refresh. */
392
+ export interface UpdateFieldsOptions {
393
+ /** When true, only refresh fields in the supported slice. Default true. */
394
+ supportedOnly?: boolean;
395
+ }
396
+
397
+ /** Result of a runtime-backed field refresh operation. */
398
+ export interface UpdateFieldsResult {
399
+ /** Total number of fields in the document. */
400
+ totalCount: number;
401
+ /** Number of fields that were refreshed. */
402
+ updatedCount: number;
403
+ /** Number of preserve-only fields left unchanged. */
404
+ preserveOnlyCount: number;
405
+ }
406
+
407
+ /** Options for runtime-backed TOC refresh. */
408
+ export interface TocRefreshOptions {
409
+ /** Maximum heading outline level to include (1–9, default 3). */
410
+ maxLevel?: number;
411
+ }
412
+
413
+ /** Result of a TOC refresh operation. */
414
+ export interface TocRefreshResult {
415
+ /** Number of TOC entries generated from heading structure. */
416
+ entryCount: number;
417
+ /** Heading entries that populated the TOC. */
418
+ entries: Array<{
419
+ level: number;
420
+ text: string;
421
+ pageIndex: number;
422
+ }>;
423
+ }
424
+
352
425
  export interface StyleCatalogEntrySnapshot {
353
426
  styleId: string;
354
427
  displayName: string;
@@ -450,6 +523,17 @@ export type SurfaceInlineSegment =
450
523
  noteKind: "footnote" | "endnote";
451
524
  noteId: string;
452
525
  label: string;
526
+ }
527
+ | {
528
+ segmentId: string;
529
+ kind: "field_ref";
530
+ from: number;
531
+ to: number;
532
+ fieldFamily: SupportedFieldFamily;
533
+ fieldTarget?: string;
534
+ instruction: string;
535
+ refreshStatus: FieldRefreshStatus;
536
+ label: string;
453
537
  };
454
538
 
455
539
  export interface SurfaceTableCellSnapshot {
@@ -488,6 +572,7 @@ export type SurfaceBlockSnapshot =
488
572
  numberingSuffix?: "tab" | "space" | "nothing";
489
573
  alignment?: "left" | "center" | "right" | "both" | "distribute";
490
574
  spacing?: { before?: number; after?: number; line?: number; lineRule?: string };
575
+ contextualSpacing?: boolean;
491
576
  indentation?: { left?: number; right?: number; firstLine?: number; hanging?: number };
492
577
  borders?: { top?: unknown; left?: unknown; bottom?: unknown; right?: unknown; bar?: unknown; between?: unknown };
493
578
  shading?: { fill?: string; color?: string; val?: string };
@@ -639,6 +724,61 @@ export interface CompatibilityPanelSnapshot {
639
724
 
640
725
  export type ViewMode = "editing" | "review" | "view";
641
726
 
727
+ /**
728
+ * Runtime document-mode — controls whether editing is unrestricted, restricted
729
+ * to suggestions, or fully read-only at the document level.
730
+ *
731
+ * Distinct from `ViewMode` (editor posture) and `WorkspaceMode` (shell layout).
732
+ * `DocumentMode` reflects document-level editing authority, not presentation.
733
+ */
734
+ export type DocumentMode = "editing" | "suggesting" | "viewing";
735
+
736
+ /**
737
+ * A single permission range parsed from `w:permStart` / `w:permEnd` in the
738
+ * source package. Ranges carry stable ids, edit-rights metadata, and an
739
+ * enforcement reason that the UI can display.
740
+ */
741
+ export interface ProtectionRange {
742
+ /** Stable range id from `w:id` on the originating `w:permStart`. */
743
+ rangeId: string;
744
+ /** Start offset of the editable range when the runtime can map it safely. */
745
+ start?: number;
746
+ /** End offset of the editable range when the runtime can map it safely. */
747
+ end?: number;
748
+ /** Editor group allowed to edit this range (`w:edGrp`), if specified. */
749
+ editorGroup?: string;
750
+ /** Specific editor identity (`w:ed`), if specified. */
751
+ editor?: string;
752
+ /** Whether this range is currently enforced by the runtime. */
753
+ enforced: boolean;
754
+ /** Human-readable reason for the enforcement state. */
755
+ enforcementReason: string;
756
+ }
757
+
758
+ /**
759
+ * Snapshot of the document's protection posture. Descriptive and bounded —
760
+ * no public bypass around protected ranges, export blockers, or preserve-only
761
+ * regions.
762
+ */
763
+ export interface ProtectionSnapshot {
764
+ /** Whether document-level protection is declared in the source package. */
765
+ hasDocumentProtection: boolean;
766
+ /**
767
+ * The declared protection edit type from `w:documentProtection` in
768
+ * `settings.xml` (e.g. `"readOnly"`, `"comments"`, `"trackedChanges"`,
769
+ * `"forms"`, `"none"`). `undefined` when no protection element exists.
770
+ */
771
+ editType?: string;
772
+ /** Whether the declared protection is enforced (`w:enforcement="1"`). */
773
+ enforcementActive: boolean;
774
+ /** All parsed permission ranges from the source package. */
775
+ ranges: ProtectionRange[];
776
+ /** Count of permission ranges that are currently enforced. */
777
+ enforcedRangeCount: number;
778
+ /** Count of permission ranges preserved but not live-enforced. */
779
+ preservedRangeCount: number;
780
+ }
781
+
642
782
  export type CaretAffinity = "forward" | "backward" | "none";
643
783
 
644
784
  export interface ActiveListContext {
@@ -670,6 +810,7 @@ export interface LayoutMeasurement {
670
810
 
671
811
  export interface EditorViewStateSnapshot {
672
812
  viewMode: ViewMode;
813
+ documentMode: DocumentMode;
673
814
  workspaceMode: WorkspaceMode;
674
815
  zoomLevel: ZoomLevel;
675
816
  activeStory: EditorStoryTarget;
@@ -697,6 +838,7 @@ export interface RuntimeRenderSnapshot {
697
838
  isReady: boolean;
698
839
  isDirty: boolean;
699
840
  readOnly: boolean;
841
+ documentMode: DocumentMode;
700
842
  selection: SelectionSnapshot;
701
843
  activeStory: EditorStoryTarget;
702
844
  pageLayout?: PageLayoutSnapshot;
@@ -708,6 +850,7 @@ export interface RuntimeRenderSnapshot {
708
850
  fatalError?: EditorError;
709
851
  commandState: CommandStateSnapshot;
710
852
  surface?: EditorSurfaceSnapshot;
853
+ protectionSnapshot: ProtectionSnapshot;
711
854
  }
712
855
 
713
856
  export interface EditorSessionState {
@@ -721,6 +864,7 @@ export interface EditorSessionState {
721
864
  canonicalDocument: RuntimePersistedEditorSnapshot["canonicalDocument"];
722
865
  compatibility: CompatibilityReport;
723
866
  warningLog: EditorWarning[];
867
+ protectionSnapshot?: ProtectionSnapshot;
724
868
  sourcePackage?: RuntimePersistedEditorSnapshot["sourcePackage"];
725
869
  }
726
870
 
@@ -736,6 +880,7 @@ export interface PersistedEditorSnapshot {
736
880
  canonicalDocument: RuntimePersistedEditorSnapshot["canonicalDocument"];
737
881
  compatibility: CompatibilityReport;
738
882
  warningLog: EditorWarning[];
883
+ protectionSnapshot?: ProtectionSnapshot;
739
884
  sourcePackage?: RuntimePersistedEditorSnapshot["sourcePackage"];
740
885
  }
741
886
 
@@ -762,6 +907,169 @@ export interface ExportResult {
762
907
  delivery: ExportDelivery;
763
908
  }
764
909
 
910
+ export type WorkflowScopeMode = "edit" | "suggest" | "comment" | "view";
911
+
912
+ export interface WorkflowCandidateRange {
913
+ candidateId: string;
914
+ storyTarget?: EditorStoryTarget;
915
+ anchor: EditorAnchorProjection;
916
+ label?: string;
917
+ source?: "search" | "playbook" | "host" | "ai";
918
+ }
919
+
920
+ export interface WorkflowScope {
921
+ scopeId: string;
922
+ mode: WorkflowScopeMode;
923
+ anchor: EditorAnchorProjection;
924
+ storyTarget?: EditorStoryTarget;
925
+ workItemId?: string;
926
+ label?: string;
927
+ domain?: "legal" | "commercial" | "finance" | "other";
928
+ }
929
+
930
+ export interface WorkflowWorkItem {
931
+ workItemId: string;
932
+ title: string;
933
+ description?: string;
934
+ domain?: "legal" | "commercial" | "finance" | "other";
935
+ status?: "pending" | "active" | "done" | "blocked";
936
+ scopeIds: string[];
937
+ }
938
+
939
+ export interface WorkflowOverlay {
940
+ overlayVersion: "workflow-overlay/1";
941
+ candidates?: WorkflowCandidateRange[];
942
+ scopes: WorkflowScope[];
943
+ workItems?: WorkflowWorkItem[];
944
+ activeWorkItemId?: string | null;
945
+ }
946
+
947
+ export interface WorkflowBlockedCommandReason {
948
+ code:
949
+ | "outside_workflow_scope"
950
+ | "workflow_comment_only"
951
+ | "workflow_view_only"
952
+ | "workflow_preserve_only"
953
+ | "workflow_blocked_import"
954
+ | "document_read_only"
955
+ | "document_viewing_mode"
956
+ | "protected_range"
957
+ | "unsupported_surface";
958
+ message: string;
959
+ scopeId?: string;
960
+ workItemId?: string;
961
+ anchor?: EditorAnchorProjection;
962
+ storyTarget?: EditorStoryTarget;
963
+ }
964
+
965
+ export interface WorkflowScopeSnapshot {
966
+ overlayPresent: boolean;
967
+ activeWorkItemId?: string | null;
968
+ activeWorkItem?: WorkflowWorkItem;
969
+ scopes: WorkflowScope[];
970
+ candidates: WorkflowCandidateRange[];
971
+ blockedReasons: WorkflowBlockedCommandReason[];
972
+ }
973
+
974
+ export interface InteractionGuardSnapshot {
975
+ blockedReasons: WorkflowBlockedCommandReason[];
976
+ }
977
+
978
+ export type WorkflowMarkupKind =
979
+ | "highlight"
980
+ | "comment"
981
+ | "revision"
982
+ | "field"
983
+ | "protected_range"
984
+ | "opaque_fragment";
985
+
986
+ export interface WorkflowMarkupBase {
987
+ markupId: string;
988
+ kind: WorkflowMarkupKind;
989
+ anchor: EditorAnchorProjection;
990
+ storyTarget?: EditorStoryTarget;
991
+ label: string;
992
+ excerpt?: string;
993
+ }
994
+
995
+ export interface WorkflowHighlightMarkup extends WorkflowMarkupBase {
996
+ kind: "highlight";
997
+ color: string;
998
+ text: string;
999
+ }
1000
+
1001
+ export interface WorkflowCommentMarkup extends WorkflowMarkupBase {
1002
+ kind: "comment";
1003
+ commentId: string;
1004
+ status: CommentSidebarThreadSnapshot["status"];
1005
+ warningCount: number;
1006
+ entryCount: number;
1007
+ createdAt: string;
1008
+ createdBy: string;
1009
+ }
1010
+
1011
+ export interface WorkflowRevisionMarkup extends WorkflowMarkupBase {
1012
+ kind: "revision";
1013
+ revisionId: string;
1014
+ revisionKind: TrackedChangeEntrySnapshot["kind"];
1015
+ status: TrackedChangeEntrySnapshot["status"];
1016
+ actionability: TrackedChangeEntrySnapshot["actionability"];
1017
+ authorId: string;
1018
+ createdAt: string;
1019
+ preserveOnlyReason?: string;
1020
+ }
1021
+
1022
+ export interface WorkflowFieldMarkup extends WorkflowMarkupBase {
1023
+ kind: "field";
1024
+ fieldIndex: number;
1025
+ fieldFamily: FieldFamily;
1026
+ fieldTarget?: string;
1027
+ refreshStatus: FieldRefreshStatus;
1028
+ displayText: string;
1029
+ }
1030
+
1031
+ export interface WorkflowProtectedRangeMarkup extends WorkflowMarkupBase {
1032
+ kind: "protected_range";
1033
+ rangeId: string;
1034
+ enforced: boolean;
1035
+ enforcementReason: string;
1036
+ editorGroup?: string;
1037
+ editor?: string;
1038
+ }
1039
+
1040
+ export interface WorkflowOpaqueFragmentMarkup extends WorkflowMarkupBase {
1041
+ kind: "opaque_fragment";
1042
+ fragmentId: string;
1043
+ warningId: string;
1044
+ detail: string;
1045
+ blockedReasonCode: "workflow_preserve_only" | "workflow_blocked_import";
1046
+ }
1047
+
1048
+ export type WorkflowMarkupItem =
1049
+ | WorkflowHighlightMarkup
1050
+ | WorkflowCommentMarkup
1051
+ | WorkflowRevisionMarkup
1052
+ | WorkflowFieldMarkup
1053
+ | WorkflowProtectedRangeMarkup
1054
+ | WorkflowOpaqueFragmentMarkup;
1055
+
1056
+ export interface WorkflowMarkupSnapshot {
1057
+ totalCount: number;
1058
+ items: WorkflowMarkupItem[];
1059
+ highlights: WorkflowHighlightMarkup[];
1060
+ comments: WorkflowCommentMarkup[];
1061
+ revisions: WorkflowRevisionMarkup[];
1062
+ fields: WorkflowFieldMarkup[];
1063
+ protectedRanges: WorkflowProtectedRangeMarkup[];
1064
+ opaqueFragments: WorkflowOpaqueFragmentMarkup[];
1065
+ }
1066
+
1067
+ export interface WorkflowCandidateRangeOptions {
1068
+ kinds?: WorkflowMarkupKind[];
1069
+ includeDetached?: boolean;
1070
+ source?: WorkflowCandidateRange["source"];
1071
+ }
1072
+
765
1073
  export type AutosaveState =
766
1074
  | { status: "idle" }
767
1075
  | { status: "saving" }
@@ -858,6 +1166,22 @@ export type WordReviewEditorEvent =
858
1166
  type: "export_completed";
859
1167
  documentId: string;
860
1168
  result: ExportResult;
1169
+ }
1170
+ | {
1171
+ type: "workflow_overlay_changed";
1172
+ documentId: string;
1173
+ snapshot: WorkflowScopeSnapshot;
1174
+ }
1175
+ | {
1176
+ type: "workflow_active_work_item_changed";
1177
+ documentId: string;
1178
+ activeWorkItemId: string | null;
1179
+ }
1180
+ | {
1181
+ type: "command_blocked";
1182
+ documentId: string;
1183
+ command: string;
1184
+ reasons: WorkflowBlockedCommandReason[];
861
1185
  };
862
1186
 
863
1187
  export interface LoadResult {
@@ -980,7 +1304,12 @@ export interface WordReviewEditorRef {
980
1304
  closeStory(): void;
981
1305
  getPageLayoutSnapshot(): PageLayoutSnapshot | null;
982
1306
  getDocumentNavigationSnapshot(): DocumentNavigationSnapshot;
1307
+ getFieldSnapshot(): FieldSnapshot;
1308
+ updateFields(options?: UpdateFieldsOptions): UpdateFieldsResult;
1309
+ updateTableOfContents(options?: TocRefreshOptions): TocRefreshResult;
983
1310
  getViewState(): EditorViewStateSnapshot;
1311
+ setDocumentMode(mode: DocumentMode): void;
1312
+ getProtectionSnapshot(): ProtectionSnapshot;
984
1313
  setWorkspaceMode(mode: WorkspaceMode): void;
985
1314
  setZoom(level: ZoomLevel): void;
986
1315
  insertSectionBreak(type: SectionBreakType, options?: { afterSectionIndex?: number }): void;
@@ -993,6 +1322,13 @@ export interface WordReviewEditorRef {
993
1322
  setHeaderFooterLink(sectionIndex: number, params: HeaderFooterLinkPatch): void;
994
1323
  setImageLayout(mediaId: string, dimensions: { widthEmu: number; heightEmu: number }): void;
995
1324
  setImageFrame(mediaId: string, offsets: { horizontalOffsetEmu?: number; verticalOffsetEmu?: number }): void;
1325
+ setWorkflowOverlay(overlay: WorkflowOverlay): void;
1326
+ clearWorkflowOverlay(): void;
1327
+ getWorkflowScopeSnapshot(): WorkflowScopeSnapshot | null;
1328
+ getInteractionGuardSnapshot(): InteractionGuardSnapshot;
1329
+ getWorkflowMarkupSnapshot(): WorkflowMarkupSnapshot;
1330
+ getWorkflowCandidateRanges(options?: WorkflowCandidateRangeOptions): WorkflowCandidateRange[];
1331
+ replaceWorkflowMarkupText(markupId: string, text: string): void;
996
1332
  }
997
1333
 
998
1334
  export interface WordReviewEditorProps {
@@ -31,6 +31,7 @@ export function editorSessionStateFromPersistedSnapshot(
31
31
  canonicalDocument: snapshot.canonicalDocument,
32
32
  compatibility: snapshot.compatibility,
33
33
  warningLog: snapshot.warningLog,
34
+ protectionSnapshot: snapshot.protectionSnapshot,
34
35
  sourcePackage: snapshot.sourcePackage,
35
36
  });
36
37
  }
@@ -53,6 +54,7 @@ export function persistedSnapshotFromEditorSessionState(
53
54
  canonicalDocument: sessionState.canonicalDocument,
54
55
  compatibility: sessionState.compatibility,
55
56
  warningLog: sessionState.warningLog,
57
+ protectionSnapshot: sessionState.protectionSnapshot,
56
58
  sourcePackage: sessionState.sourcePackage,
57
59
  });
58
60
  }
@@ -973,7 +973,7 @@ function setMarkValue(
973
973
  ): TextMark[] | undefined {
974
974
  const nextMarks = cloneMarks(marks).filter((candidate) => {
975
975
  if (markType === "backgroundColor") {
976
- return candidate.type !== "backgroundColor";
976
+ return candidate.type !== "backgroundColor" && candidate.type !== "highlight";
977
977
  }
978
978
  return candidate.type !== markType;
979
979
  });
@@ -60,6 +60,7 @@ export type EditorCommand =
60
60
  document: CanonicalDocumentEnvelope;
61
61
  mapping?: TransactionMapping;
62
62
  selection?: SelectionSnapshot;
63
+ protectionSelection?: SelectionSnapshot;
63
64
  origin?: CommandOrigin;
64
65
  }
65
66
  | {
@@ -110,6 +111,7 @@ export type EditorCommand =
110
111
  | {
111
112
  type: "comment.add";
112
113
  comment: CommentThreadRecord;
114
+ selection?: SelectionSnapshot;
113
115
  origin?: CommandOrigin;
114
116
  }
115
117
  | {
@@ -615,7 +617,12 @@ export function remapSelection(
615
617
  }
616
618
 
617
619
  if (activeRange.kind === "node") {
618
- return createSelectionSnapshot(activeRange.at, activeRange.at);
620
+ return {
621
+ anchor: activeRange.at,
622
+ head: activeRange.at,
623
+ isCollapsed: true,
624
+ activeRange,
625
+ };
619
626
  }
620
627
 
621
628
  return {
@@ -680,7 +687,12 @@ function normalizeSelection(selection: SelectionSnapshot): SelectionSnapshot {
680
687
  }
681
688
 
682
689
  if (activeRange.kind === "node") {
683
- return createSelectionSnapshot(activeRange.at, activeRange.at);
690
+ return {
691
+ anchor: activeRange.at,
692
+ head: activeRange.at,
693
+ isCollapsed: true,
694
+ activeRange,
695
+ };
684
696
  }
685
697
 
686
698
  return {
@@ -3,6 +3,7 @@ import type {
3
3
  SearchOptions as PublicSearchOptions,
4
4
  SecondaryStorySurface,
5
5
  SurfaceBlockSnapshot,
6
+ WorkflowCandidateRange,
6
7
  } from "../../api/public-types";
7
8
 
8
9
  export interface SearchTextOptions extends PublicSearchOptions {
@@ -156,6 +157,33 @@ export function searchSecondaryStories(
156
157
  return results;
157
158
  }
158
159
 
160
+ /**
161
+ * Derive workflow candidate ranges from search results.
162
+ *
163
+ * Maps search hits into `WorkflowCandidateRange` values suitable for
164
+ * inclusion in a host-fed `WorkflowOverlay`. The runtime search results
165
+ * supply runtime-mapped `from`/`to` positions that align with the
166
+ * canonical document model.
167
+ */
168
+ export function deriveCandidateRangesFromSearch(
169
+ results: readonly SurfaceSearchResult[],
170
+ storyTarget?: EditorStoryTarget,
171
+ source: WorkflowCandidateRange["source"] = "search",
172
+ ): WorkflowCandidateRange[] {
173
+ return results.map((result, index) => ({
174
+ candidateId: `search-candidate-${index}`,
175
+ storyTarget,
176
+ anchor: {
177
+ kind: "range" as const,
178
+ from: result.from,
179
+ to: result.to,
180
+ assoc: { start: 1 as const, end: -1 as const },
181
+ },
182
+ label: result.excerpt,
183
+ source,
184
+ }));
185
+ }
186
+
159
187
  export function projectSurfaceText(
160
188
  blocks: readonly SurfaceBlockSnapshot[],
161
189
  ): ProjectedSurfaceText {
@@ -10,6 +10,7 @@ import {
10
10
  assertPersistedEditorSnapshot as assertModelPersistedEditorSnapshot,
11
11
  createPersistedEditorSnapshot as createModelPersistedEditorSnapshot,
12
12
  validatePersistedEditorSnapshot as validateModelPersistedEditorSnapshot,
13
+ type ProtectionSnapshotRecord as ModelProtectionSnapshotRecord,
13
14
  type PersistedEditorSnapshot as ModelPersistedEditorSnapshot,
14
15
  } from "../../model/snapshot.ts";
15
16
  import {
@@ -638,6 +639,7 @@ export function createPersistedEditorSnapshot(
638
639
  editorBuild: string;
639
640
  savedAt: string;
640
641
  compatibility?: CompatibilityReport;
642
+ protectionSnapshot?: ModelProtectionSnapshotRecord;
641
643
  },
642
644
  ): PersistedEditorSnapshot {
643
645
  const snapshot = createModelPersistedEditorSnapshot({
@@ -647,6 +649,7 @@ export function createPersistedEditorSnapshot(
647
649
  canonicalDocument: state.document,
648
650
  compatibility: (options.compatibility ?? state.compatibility) as never,
649
651
  warningLog: state.warnings as never,
652
+ protectionSnapshot: options.protectionSnapshot,
650
653
  sourcePackage: state.sourcePackage,
651
654
  });
652
655
  return snapshot as PersistedEditorSnapshot;
package/src/index.ts CHANGED
@@ -6,6 +6,8 @@ export {
6
6
  EDITOR_SESSION_STATE_VERSION,
7
7
  } from "./api/session-state.ts";
8
8
  export type {
9
+ LoadRequest,
10
+ LoadSourcePolicy,
9
11
  EditorSessionState,
10
12
  EditorHostAdapter,
11
13
  WordReviewEditorProps,
@@ -72,4 +74,23 @@ export type {
72
74
  SurfaceTextMark,
73
75
  EditorSurfaceSnapshot,
74
76
  CommandStateSnapshot,
77
+ WorkflowScopeMode,
78
+ WorkflowCandidateRange,
79
+ WorkflowScope,
80
+ WorkflowWorkItem,
81
+ WorkflowOverlay,
82
+ WorkflowBlockedCommandReason,
83
+ WorkflowScopeSnapshot,
84
+ InteractionGuardSnapshot,
85
+ WorkflowMarkupKind,
86
+ WorkflowMarkupBase,
87
+ WorkflowHighlightMarkup,
88
+ WorkflowCommentMarkup,
89
+ WorkflowRevisionMarkup,
90
+ WorkflowFieldMarkup,
91
+ WorkflowProtectedRangeMarkup,
92
+ WorkflowOpaqueFragmentMarkup,
93
+ WorkflowMarkupItem,
94
+ WorkflowMarkupSnapshot,
95
+ WorkflowCandidateRangeOptions,
75
96
  } from "./api/public-types.ts";