@beyondwork/docx-react-component 1.0.42 → 1.0.45

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 (82) hide show
  1. package/README.md +17 -0
  2. package/package.json +5 -4
  3. package/src/api/editor-state-types.ts +110 -0
  4. package/src/api/public-types.ts +333 -4
  5. package/src/core/commands/formatting-commands.ts +7 -1
  6. package/src/core/commands/index.ts +60 -10
  7. package/src/core/commands/text-commands.ts +59 -0
  8. package/src/core/search/search-text.ts +15 -2
  9. package/src/core/selection/review-anchors.ts +131 -21
  10. package/src/index.ts +29 -1
  11. package/src/io/chart-preview-resolver.ts +281 -0
  12. package/src/io/docx-session.ts +692 -2
  13. package/src/io/export/build-app-properties-xml.ts +1 -1
  14. package/src/io/export/serialize-comments.ts +38 -9
  15. package/src/io/export/twip.ts +1 -1
  16. package/src/io/load-scheduler.ts +230 -0
  17. package/src/io/normalize/normalize-text.ts +116 -0
  18. package/src/io/ooxml/parse-comments.ts +0 -33
  19. package/src/io/ooxml/parse-complex-content.ts +14 -0
  20. package/src/io/ooxml/parse-main-document.ts +4 -0
  21. package/src/io/ooxml/workflow-payload-validator.ts +97 -1
  22. package/src/io/ooxml/workflow-payload.ts +172 -1
  23. package/src/preservation/opaque-region.ts +5 -0
  24. package/src/review/store/comment-remapping.ts +2 -2
  25. package/src/runtime/collab-session.ts +1 -1
  26. package/src/runtime/document-runtime.ts +661 -42
  27. package/src/runtime/edit-dispatch/dispatch-text-command.ts +98 -0
  28. package/src/runtime/edit-dispatch/index.ts +2 -0
  29. package/src/runtime/edit-dispatch/list-aware-dispatch.ts +125 -0
  30. package/src/runtime/editor-state-channel.ts +544 -0
  31. package/src/runtime/editor-state-integration.ts +217 -0
  32. package/src/runtime/editor-surface/capabilities.ts +411 -0
  33. package/src/runtime/layout/index.ts +2 -0
  34. package/src/runtime/layout/inert-layout-facet.ts +4 -0
  35. package/src/runtime/layout/layout-engine-instance.ts +63 -2
  36. package/src/runtime/layout/layout-engine-version.ts +41 -0
  37. package/src/runtime/layout/paginated-layout-engine.ts +211 -14
  38. package/src/runtime/layout/public-facet.ts +430 -1
  39. package/src/runtime/perf-counters.ts +28 -0
  40. package/src/runtime/prerender/cache-envelope.ts +29 -0
  41. package/src/runtime/prerender/cache-key.ts +66 -0
  42. package/src/runtime/prerender/font-fingerprint.ts +17 -0
  43. package/src/runtime/prerender/graph-canonicalize.ts +121 -0
  44. package/src/runtime/prerender/indexeddb-cache.ts +184 -0
  45. package/src/runtime/prerender/prerender-document.ts +145 -0
  46. package/src/runtime/render/block-fragment-projection.ts +2 -0
  47. package/src/runtime/render/render-frame-types.ts +17 -0
  48. package/src/runtime/render/render-kernel.ts +172 -29
  49. package/src/runtime/selection/post-edit-validator.ts +77 -0
  50. package/src/runtime/surface-projection.ts +45 -7
  51. package/src/runtime/workflow-markup.ts +71 -16
  52. package/src/ui/WordReviewEditor.tsx +142 -237
  53. package/src/ui/editor-command-bag.ts +14 -0
  54. package/src/ui/editor-runtime-boundary.ts +115 -12
  55. package/src/ui/editor-shell-view.tsx +10 -0
  56. package/src/ui/editor-surface-controller.tsx +5 -0
  57. package/src/ui/headless/selection-helpers.ts +10 -0
  58. package/src/ui/runtime-shortcut-dispatch.ts +28 -68
  59. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +62 -2
  60. package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +281 -0
  61. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +48 -0
  62. package/src/ui-tailwind/editor-surface/paste-plain-text.ts +72 -0
  63. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +118 -8
  64. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +76 -165
  65. package/src/ui-tailwind/editor-surface/pm-schema.ts +170 -4
  66. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +58 -7
  67. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -0
  68. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +265 -0
  69. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +8 -255
  70. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +47 -0
  71. package/src/ui-tailwind/index.ts +5 -1
  72. package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +57 -0
  73. package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +71 -0
  74. package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +73 -0
  75. package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +74 -0
  76. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +477 -0
  77. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +374 -0
  78. package/src/ui-tailwind/page-stack/use-visible-block-range.ts +157 -0
  79. package/src/ui-tailwind/review/comment-markdown-renderer.tsx +155 -0
  80. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +77 -16
  81. package/src/ui-tailwind/theme/editor-theme.css +47 -14
  82. package/src/ui-tailwind/tw-review-workspace.tsx +303 -123
package/README.md CHANGED
@@ -216,9 +216,26 @@ Current integration honesty:
216
216
 
217
217
  - [`docs/maintainers/README.md`](docs/maintainers/README.md)
218
218
  - [`docs/ccep-contract-templates/README.md`](docs/ccep-contract-templates/README.md)
219
+ - [`docs/plans/README.md`](docs/plans/README.md) — active development lanes + open-issues register
219
220
 
220
221
  The CCEP corpus is kept on `main` as a maintainer-safe smoke-doc source set for agreement-heavy validation and wrapper or agent benchmarking.
221
222
 
223
+ ### Active Development Lanes
224
+
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
+
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 |
238
+
222
239
  ### Technical Wiki
223
240
 
224
241
  - [`docs/wiki/`](docs/wiki/) — Feature-by-feature technical documentation (25+ topics covering OOXML, ProseMirror, runtime, and platform)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beyondwork/docx-react-component",
3
3
  "publisher": "beyondwork",
4
- "version": "1.0.42",
4
+ "version": "1.0.45",
5
5
  "description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
6
6
  "packageManager": "pnpm@10.30.3",
7
7
  "type": "module",
@@ -96,7 +96,7 @@
96
96
  "scripts": {
97
97
  "build": "tsup",
98
98
  "test": "bash scripts/run-workspace-tests.sh",
99
- "test:repo": "node scripts/run-repo-tests.mjs core",
99
+ "test:repo": "node scripts/ci-check-layout-engine-version.mjs && node scripts/run-repo-tests.mjs core",
100
100
  "test:repo:all": "node scripts/run-repo-tests.mjs all",
101
101
  "test:repo:optional": "node scripts/run-repo-tests.mjs optional",
102
102
  "test:repo:browser-ui": "node scripts/run-repo-tests.mjs browser-ui",
@@ -165,9 +165,9 @@
165
165
  "react": "^19.2.0",
166
166
  "react-dom": "^19.2.0",
167
167
  "tailwindcss": "^4.2.2",
168
- "yjs": "^13.6.0",
169
168
  "y-prosemirror": "^1.2.0",
170
- "y-protocols": "^1.0.0"
169
+ "y-protocols": "^1.0.0",
170
+ "yjs": "^13.6.0"
171
171
  },
172
172
  "peerDependenciesMeta": {
173
173
  "yjs": {
@@ -186,6 +186,7 @@
186
186
  "@types/react": "19.2.14",
187
187
  "@types/react-dom": "19.2.3",
188
188
  "@typescript/native-preview": "7.0.0-dev.20260409.1",
189
+ "fake-indexeddb": "^6.2.5",
189
190
  "jsdom": "^29.0.1",
190
191
  "pixelmatch": "^7.1.0",
191
192
  "pngjs": "^7.0.0",
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Editor-State Persistence Channel (schema 1.2).
3
+ *
4
+ * Makes the .docx act as a load/save API for editor-level overlay
5
+ * state (host annotations, workflow overlay, workflow metadata, work
6
+ * items, and future subsystems). Storage policy per subsystem is
7
+ * host-controlled across three locations:
8
+ *
9
+ * - `"in-document"` — full blob serialized into /customXml/item1.xml
10
+ * - `"rowstore"` — doc carries a key reference; host persists the blob
11
+ * - `"key-only"` — doc carries only a correlation key; host rehydrates
12
+ *
13
+ * See docs/plans/editor-state-persistence.md for the full design. 1.2
14
+ * extends schema 1.1 additively (unknown namespaces are preserved
15
+ * opaquely per existing forward-compat rules).
16
+ */
17
+ export type EditorStateNamespace =
18
+ | "hostAnnotations"
19
+ | "workflowOverlay"
20
+ | "workflowMetadata"
21
+ | "workItems";
22
+
23
+ export type EditorStateLocation = "in-document" | "rowstore" | "key-only";
24
+
25
+ export type EditorStateResolveErrorMode = "block" | "empty" | "retain-last-known";
26
+
27
+ /**
28
+ * Host-configured per-namespace persistence policy. Default (when
29
+ * absent) is {location: "in-document", onResolveError: "block",
30
+ * debounceMs: 250}, which preserves current behavior.
31
+ */
32
+ export interface EditorStatePolicyEntry {
33
+ location: EditorStateLocation;
34
+ /** Host-provided correlation key (e.g. review_session_id). Editor generates a UUID if absent. */
35
+ key?: string;
36
+ onResolveError?: EditorStateResolveErrorMode;
37
+ debounceMs?: number;
38
+ }
39
+
40
+ export type EditorStatePolicy = Partial<
41
+ Record<EditorStateNamespace, EditorStatePolicyEntry>
42
+ >;
43
+
44
+ /**
45
+ * Reference stored in the docx when the subsystem is backed by a
46
+ * host-owned store. The editor never interprets entryKey — it round-
47
+ * trips verbatim via resolver + persister callbacks.
48
+ */
49
+ export interface EditorStateStorageRef {
50
+ namespace: EditorStateNamespace;
51
+ location: Exclude<EditorStateLocation, "in-document">;
52
+ entryKey: string;
53
+ schemaVersion: string;
54
+ }
55
+
56
+ /**
57
+ * Typed blob the resolver returns and the persister accepts.
58
+ * `data` is the subsystem-specific payload (e.g. a HostAnnotationOverlay
59
+ * for the "hostAnnotations" namespace). Use `schemaVersion` to pin the
60
+ * blob shape so older hosts don't silently load newer blobs.
61
+ */
62
+ export interface EditorStateBlob {
63
+ namespace: EditorStateNamespace;
64
+ schemaVersion: string;
65
+ data: unknown;
66
+ }
67
+
68
+ /**
69
+ * Host-supplied pull-resolver. Returns `null` when the entry key is
70
+ * unknown or revoked. Matches the ScopeMetadataResolver undefined
71
+ * convention but allows null explicitly per the design doc §5 for
72
+ * symmetry with Promise<T | null> idioms. Either is acceptable as
73
+ * "not found"; internal code normalizes.
74
+ */
75
+ export type EditorStateResolver = (
76
+ namespace: EditorStateNamespace,
77
+ entryKey: string,
78
+ ) => Promise<EditorStateBlob | null>;
79
+
80
+ /**
81
+ * Host-supplied persister. Called after the editor's debounce window
82
+ * when a subsystem's state has changed under `rowstore` or `key-only`
83
+ * policy. Must be idempotent within the debounce window.
84
+ */
85
+ export type EditorStatePersister = (
86
+ namespace: EditorStateNamespace,
87
+ entryKey: string,
88
+ blob: EditorStateBlob,
89
+ ) => Promise<void>;
90
+
91
+ export interface EditorStatePolicyMigration {
92
+ namespace: EditorStateNamespace;
93
+ from: EditorStateLocation;
94
+ to: EditorStateLocation;
95
+ key?: string;
96
+ }
97
+
98
+ export interface EditorStatePartLoadFailure {
99
+ namespace: EditorStateNamespace;
100
+ entryKey?: string;
101
+ error: Error;
102
+ fallback: "empty" | "retain-last-known";
103
+ }
104
+
105
+ export interface EditorStatePartPersistFailure {
106
+ namespace: EditorStateNamespace;
107
+ entryKey: string;
108
+ error: Error;
109
+ attemptCount: number;
110
+ }
@@ -9,6 +9,20 @@ import type {
9
9
  ScopeMetadataResolver,
10
10
  ScopeMetadataStorageRef,
11
11
  } from "./scope-metadata-resolver-types.ts";
12
+ import type {
13
+ EditorStateNamespace,
14
+ EditorStateLocation,
15
+ EditorStateResolveErrorMode,
16
+ EditorStatePolicyEntry,
17
+ EditorStatePolicy,
18
+ EditorStateStorageRef,
19
+ EditorStateBlob,
20
+ EditorStateResolver,
21
+ EditorStatePersister,
22
+ EditorStatePolicyMigration,
23
+ EditorStatePartLoadFailure,
24
+ EditorStatePartPersistFailure,
25
+ } from "./editor-state-types.ts";
12
26
 
13
27
  export type {
14
28
  MetadataPersistenceMode,
@@ -16,6 +30,20 @@ export type {
16
30
  ScopeMetadataResolver,
17
31
  ScopeMetadataStorageRef,
18
32
  };
33
+ export type {
34
+ EditorStateNamespace,
35
+ EditorStateLocation,
36
+ EditorStateResolveErrorMode,
37
+ EditorStatePolicyEntry,
38
+ EditorStatePolicy,
39
+ EditorStateStorageRef,
40
+ EditorStateBlob,
41
+ EditorStateResolver,
42
+ EditorStatePersister,
43
+ EditorStatePolicyMigration,
44
+ EditorStatePartLoadFailure,
45
+ EditorStatePartPersistFailure,
46
+ };
19
47
 
20
48
  export type { CanonicalParagraphFormatting, CanonicalRunFormatting };
21
49
 
@@ -34,6 +62,8 @@ export type {
34
62
  PublicPageRegion,
35
63
  PublicPageRegions,
36
64
  PublicPageSpan,
65
+ PublicRegionBlock,
66
+ PublicRegionKind,
37
67
  PublicResolvedPageStories,
38
68
  PublicResolvedParagraphFormatting,
39
69
  PublicResolvedRunFormatting,
@@ -753,7 +783,7 @@ export type SurfaceInlineSegment =
753
783
  textColor?: string;
754
784
  };
755
785
  hyperlinkHref?: string;
756
- /** Cascaded run formatting for this text segment (docDefaults → paragraph style chain → character style → direct). Added in Task 11. TODO Task 15: wire up from resolveEffectiveRunFormatting in surface-projection. */
786
+ /** Cascaded run formatting for this text segment (docDefaults → paragraph style chain → character style → direct). Populated in `src/runtime/surface-projection.ts` via `resolveEffectiveRunFormatting`. Present only when the resolved cascade has at least one field. */
757
787
  resolvedRunFormatting?: CanonicalRunFormatting;
758
788
  }
759
789
  | {
@@ -787,6 +817,14 @@ export type SurfaceInlineSegment =
787
817
  blockedReasonCode?: "workflow_preserve_only" | "workflow_blocked_import";
788
818
  presentation?: "inline-chip" | "quiet-marker" | "text-box" | "checkbox";
789
819
  displayText?: string;
820
+ /**
821
+ * For chart/SmartArt/WordArt/VML opaque inlines, the media-catalog id
822
+ * of the preview bitmap extracted from `mc:Fallback` (when Word cached
823
+ * one). Resolved by the surface into a blob URL and rendered by the
824
+ * corresponding PM atom so reviewers see the chart picture instead of
825
+ * a text badge. Absent when the object has no cached fallback image.
826
+ */
827
+ previewMediaId?: string;
790
828
  state: "locked-preserve-only";
791
829
  }
792
830
  | {
@@ -943,7 +981,16 @@ export type SurfaceBlockSnapshot =
943
981
  detail: string;
944
982
  featureKey?: string;
945
983
  blockedReasonCode?: "workflow_preserve_only" | "workflow_blocked_import";
946
- state: "locked-preserve-only";
984
+ /**
985
+ * When set, this opaque_block is a size-preserving placeholder generated
986
+ * by viewport culling, NOT a real preserved fragment. The PM schema uses
987
+ * this to claim the original block's position span without rendering
988
+ * content.
989
+ *
990
+ * See docs/plans/lane-2-render-performance.md.
991
+ */
992
+ placeholderSize?: number;
993
+ state: "locked-preserve-only" | "placeholder-culled";
947
994
  };
948
995
 
949
996
  export interface SecondaryStorySurface {
@@ -959,6 +1006,15 @@ export interface EditorSurfaceSnapshot {
959
1006
  blocks: SurfaceBlockSnapshot[];
960
1007
  lockedFragmentIds: string[];
961
1008
  secondaryStories: SecondaryStorySurface[];
1009
+ /**
1010
+ * Block index range rendered as real (non-placeholder) in this snapshot.
1011
+ * Blocks outside this range are placeholder opaque_blocks carrying the
1012
+ * original position range but no content. `null` (default) = all blocks
1013
+ * are real (legacy behavior).
1014
+ *
1015
+ * See docs/plans/lane-2-render-performance.md.
1016
+ */
1017
+ viewportBlockRange: { start: number; end: number } | null;
962
1018
  }
963
1019
 
964
1020
  export type EditorWarningCode =
@@ -1631,6 +1687,16 @@ export interface AddCommentParams {
1631
1687
  authorId?: string;
1632
1688
  }
1633
1689
 
1690
+ export interface AddCommentResult {
1691
+ commentId: string;
1692
+ anchor: EditorAnchorProjection;
1693
+ }
1694
+
1695
+ export interface AddCommentReplyResult {
1696
+ commentId: string;
1697
+ entryId: string;
1698
+ }
1699
+
1634
1700
  export interface ExportDocxOptions {
1635
1701
  fileName?: string;
1636
1702
  reason?: string;
@@ -2299,6 +2365,25 @@ export interface AutosaveConfig {
2299
2365
  debounceMs?: number;
2300
2366
  }
2301
2367
 
2368
+ /**
2369
+ * Fired after a plain-text paste or drop has been dispatched through
2370
+ * the runtime-owned input callbacks. Rich-text paste (HTML, Office
2371
+ * clipboard) does NOT produce this event — those payloads still fire
2372
+ * `onBlockedInput` with a distinguishing message.
2373
+ *
2374
+ * See `docs/plans/editor-paste-drop.md` for the full semantics.
2375
+ */
2376
+ export interface WordReviewEditorPasteEvent {
2377
+ type: "paste_applied";
2378
+ documentId: string;
2379
+ /** Count of segments dispatched across text + split + hard_break. */
2380
+ segmentCount: number;
2381
+ /** Total character count across all text segments in the payload. */
2382
+ charCount: number;
2383
+ /** Whether the event originated from a paste or a drop. */
2384
+ source: "paste" | "drop";
2385
+ }
2386
+
2302
2387
  export type WordReviewEditorEvent =
2303
2388
  | {
2304
2389
  type: "ready";
@@ -2442,6 +2527,7 @@ export type WordReviewEditorEvent =
2442
2527
  command: string;
2443
2528
  reasons: WorkflowBlockedCommandReason[];
2444
2529
  }
2530
+ | WordReviewEditorPasteEvent
2445
2531
  | {
2446
2532
  /**
2447
2533
  * Scope card mode selector fired a mode change. Host relays to the
@@ -2520,8 +2606,82 @@ export type WordReviewEditorEvent =
2520
2606
  version?: number;
2521
2607
  } | null;
2522
2608
  defaultPolicy: MetadataConflictPolicy;
2609
+ }
2610
+ | {
2611
+ /**
2612
+ * Schema 1.2 — emitted when a keyed namespace fails to resolve
2613
+ * during load. Subsystem fallback behavior is per `onResolveError`
2614
+ * policy.
2615
+ */
2616
+ type: "editor_state_part_load_failed";
2617
+ documentId: string;
2618
+ failure: EditorStatePartLoadFailure;
2619
+ }
2620
+ | {
2621
+ /**
2622
+ * Schema 1.2 — emitted when a debounced persist call fails.
2623
+ * The editor retains the dirty snapshot for retry.
2624
+ */
2625
+ type: "editor_state_part_persist_failed";
2626
+ documentId: string;
2627
+ failure: EditorStatePartPersistFailure;
2628
+ }
2629
+ | {
2630
+ /**
2631
+ * Schema 1.2 — emitted when the saved location in a docx differs
2632
+ * from the current host policy; the current policy wins and the
2633
+ * next serialize honors it.
2634
+ */
2635
+ type: "editor_state_policy_migrated";
2636
+ documentId: string;
2637
+ migration: EditorStatePolicyMigration;
2638
+ }
2639
+ | {
2640
+ /**
2641
+ * Schema 1.2 — emitted when an unknown namespace is encountered
2642
+ * in the payload (forward-compat warning; preserved opaquely).
2643
+ */
2644
+ type: "editor_state_unknown_namespace";
2645
+ documentId: string;
2646
+ namespace: string;
2647
+ }
2648
+ | {
2649
+ /**
2650
+ * Emitted once per load-pipeline stage when the staged loader is
2651
+ * active. Purely observational — hosts can render a progress chip
2652
+ * or ignore entirely. See `docs/plans/fastload.md` for the stage
2653
+ * ordering contract.
2654
+ */
2655
+ type: "load-stage";
2656
+ documentId: string;
2657
+ stage: LoadStage;
2658
+ durationMs: number;
2659
+ }
2660
+ | {
2661
+ /**
2662
+ * Emitted once sub-parts (headers, footers, footnotes, endnotes,
2663
+ * theme, settings) have finished their post-ready idle hydration.
2664
+ * Before this event fires, `canonicalDocument.subParts.*` may be
2665
+ * empty arrays / defaults; after it fires, they carry the parsed
2666
+ * values. Calling `ensureSubPartsHydrated` eagerly forces
2667
+ * hydration synchronously.
2668
+ */
2669
+ type: "subparts-hydrated";
2670
+ documentId: string;
2523
2671
  };
2524
2672
 
2673
+ /**
2674
+ * Ordered list of stages emitted by the staged load pipeline. The
2675
+ * skeleton-ready stage corresponds to the `ready` event firing.
2676
+ */
2677
+ export type LoadStage =
2678
+ | "opc"
2679
+ | "body"
2680
+ | "styles-numbering-comments"
2681
+ | "skeleton-ready"
2682
+ | "subparts"
2683
+ | "compatibility";
2684
+
2525
2685
  export interface LoadResult {
2526
2686
  source?: ExternalDocumentSource;
2527
2687
  }
@@ -2561,11 +2721,50 @@ export interface EditorTelemetryEvent {
2561
2721
  detail?: Record<string, unknown>;
2562
2722
  }
2563
2723
 
2724
+ /**
2725
+ * Stage 0B.1 — parameters passed to `EditorHostAdapter.renderChartPreview`
2726
+ * when the importer encounters a `c:chartSpace` that has no cached
2727
+ * `mc:Fallback` bitmap. Hosts receive the raw chart-part XML plus the
2728
+ * theme XML and the intended display size, and return either preview
2729
+ * bytes (typically PNG or SVG) or `null` to fall back to the typed
2730
+ * badge.
2731
+ *
2732
+ * Field stability: only additive changes; no field is ever renamed or
2733
+ * removed. Host implementations should use structural narrowing to
2734
+ * ignore unknown fields in future versions.
2735
+ */
2736
+ export interface ChartPreviewResolveParams {
2737
+ /** Chart part body (`word/charts/chartN.xml`). UTF-8 string. */
2738
+ chartXml: string;
2739
+ /** Absolute package path of the chart part (e.g. `/word/charts/chart1.xml`). Useful as a cache key. */
2740
+ chartPartPath: string;
2741
+ /** Body of `theme1.xml` when the package ships one; `undefined` otherwise. */
2742
+ themeXml: string | undefined;
2743
+ /** Intended display width in EMU (extracted from the drawing's `wp:extent`). */
2744
+ widthEmu: number;
2745
+ /** Intended display height in EMU. */
2746
+ heightEmu: number;
2747
+ }
2748
+
2564
2749
  export interface EditorHostAdapter {
2565
2750
  load?(params: LoadRequest): Promise<LoadResult>;
2566
2751
  saveSession?(params: SaveSessionParams): Promise<SaveSessionResult>;
2567
2752
  saveExport?(params: SaveExportParams): Promise<SaveExportResult>;
2568
2753
  logEvent?(event: EditorTelemetryEvent): void;
2754
+ /**
2755
+ * Stage 0B.1: render a chart to bitmap/SVG preview bytes. Called at
2756
+ * import time for every `c:chartSpace` that ships without a cached
2757
+ * `mc:Fallback` blip. Return `null` to fall back to the typed badge
2758
+ * (Stage 0 behavior). Return a `Uint8Array` to inject the bytes as
2759
+ * a synthetic `MediaItem` and render the preview through the
2760
+ * existing `chart_atom` path.
2761
+ *
2762
+ * Content type is inferred from the first few bytes: `image/png`
2763
+ * for a PNG magic number, `image/svg+xml` otherwise. Hosts that
2764
+ * need a different content type should extend this contract in a
2765
+ * follow-up.
2766
+ */
2767
+ renderChartPreview?(params: ChartPreviewResolveParams): Promise<Uint8Array | null>;
2569
2768
  }
2570
2769
 
2571
2770
  export interface EditorDatastoreAdapter {
@@ -2580,11 +2779,11 @@ export interface WordReviewEditorRef {
2580
2779
  blur(): void;
2581
2780
  undo(): void;
2582
2781
  redo(): void;
2583
- addComment(params: AddCommentParams): string;
2782
+ addComment(params: AddCommentParams): AddCommentResult;
2584
2783
  openComment(commentId: string): void;
2585
2784
  resolveComment(commentId: string): void;
2586
2785
  reopenComment(commentId: string): void;
2587
- addCommentReply(commentId: string, body: string): void;
2786
+ addCommentReply(commentId: string, body: string): AddCommentReplyResult;
2588
2787
  editCommentBody(commentId: string, body: string): void;
2589
2788
  deleteComment(commentId: string): void;
2590
2789
  acceptChange(changeId: string): void;
@@ -2767,6 +2966,35 @@ export interface WordReviewEditorRef {
2767
2966
  * registered.
2768
2967
  */
2769
2968
  convertScopesToExternal(scopeIds: string[]): Promise<void>;
2969
+ /**
2970
+ * Schema 1.2 — editor-state persistence channel. Configures which
2971
+ * subsystems persist in-document / rowstore / key-only. Each call
2972
+ * merges into the current policy map. Mismatched locations with an
2973
+ * already-loaded docx fire `editor_state_policy_migrated` and
2974
+ * schedule the next serialize to honor the new location.
2975
+ */
2976
+ configureEditorStatePolicy(policy: EditorStatePolicy): void;
2977
+ /**
2978
+ * Register the host's pull-resolver. Required before the editor
2979
+ * loads a 1.2 docx whose policy includes any keyed location.
2980
+ */
2981
+ registerEditorStateResolver(resolver: EditorStateResolver): void;
2982
+ /**
2983
+ * Register the host's persister. Required before any subsystem
2984
+ * mutation under keyed policy fires a debounced persist.
2985
+ */
2986
+ registerEditorStatePersister(persister: EditorStatePersister): void;
2987
+ /**
2988
+ * Return the entry key currently associated with a namespace under
2989
+ * keyed policy, or undefined when the namespace is in-document /
2990
+ * key not yet assigned.
2991
+ */
2992
+ getEditorStateKey(namespace: EditorStateNamespace): string | undefined;
2993
+ /**
2994
+ * Re-attempt any persists that failed since the last successful
2995
+ * write. Pass a namespace to scope the retry; omit to retry all.
2996
+ */
2997
+ retryPendingPersist(namespace?: EditorStateNamespace): Promise<void>;
2770
2998
  setHostAnnotationOverlay(overlay: HostAnnotationOverlay): void;
2771
2999
  clearHostAnnotationOverlay(): void;
2772
3000
  getHostAnnotationSnapshot(): HostAnnotationSnapshot;
@@ -2866,6 +3094,38 @@ export interface WordReviewEditorProps {
2866
3094
  currentUser: EditorUser;
2867
3095
  ydoc?: import('yjs').Doc;
2868
3096
  awareness?: import("y-protocols/awareness").Awareness;
3097
+ /**
3098
+ * Optional collab session built via `createCollabSession(...)`. When
3099
+ * set, the `"collab"` chrome preset mounts the top nav (presence
3100
+ * strip, role + audience chips, tamper banner, negotiation action
3101
+ * bar, send-to-supplier button). P9a–f components are pure
3102
+ * presentational; this prop is what wires them to live state. Pass
3103
+ * `undefined` to keep the chrome in non-collab mode even when
3104
+ * `chromePreset === "collab"`.
3105
+ */
3106
+ collabSession?: import("../runtime/collab-session.ts").CollabSession;
3107
+ /**
3108
+ * Transport status signal for the presence strip + role chip (P9b /
3109
+ * P9c). Optional — defaults to "offline" when omitted.
3110
+ */
3111
+ collabTransportStatus?: "connected" | "syncing" | "offline";
3112
+ /**
3113
+ * Identifier for the currently-focused comment, used by the collab
3114
+ * top nav's audience chip + negotiation action bar (P9c / P9e). The
3115
+ * host derives this from its own selection / rail-focus signal.
3116
+ */
3117
+ activeCommentId?: string;
3118
+ /**
3119
+ * Baseline metadata for the send-to-supplier flow (P9f). Required
3120
+ * to enable the button; when omitted, the button stays present but
3121
+ * the confirmation modal becomes a no-op pass-through.
3122
+ */
3123
+ collabSendBaseline?: {
3124
+ originDocumentId: string;
3125
+ originPayloadId: string;
3126
+ originContentHash: string;
3127
+ payloadXml: string;
3128
+ };
2869
3129
  initialDocx?: Uint8Array | ArrayBuffer;
2870
3130
  initialSessionState?: EditorSessionState;
2871
3131
  initialSnapshot?: PersistedEditorSnapshot;
@@ -2880,6 +3140,20 @@ export interface WordReviewEditorProps {
2880
3140
  chromePreset?: WordReviewEditorChromePreset;
2881
3141
  chromeOptions?: Partial<WordReviewEditorChromeOptions>;
2882
3142
  markupDisplay?: "clean" | "simple" | "all";
3143
+ /**
3144
+ * Harness/debug-only preview toggle for preserve-only features
3145
+ * (charts, SmartArt, shapes, WordArt, VML rendered as typed atoms;
3146
+ * the "N preserve-only features detected" banner; lock-callouts).
3147
+ *
3148
+ * MUST default to `false`. Consumer hosts MUST NOT hard-code `true` —
3149
+ * this is a dev opt-in gated by the harness dev drawer's
3150
+ * `debugMode && showUnsupportedObjectPreviews` AND-gate. Flipping the
3151
+ * default or dropping the AND-gate is a regression that has landed
3152
+ * three times (PRs #124, #131, #160); the invariant is locked by
3153
+ * `test/ui/unsupported-previews-invariant.test.ts`.
3154
+ *
3155
+ * @default false
3156
+ */
2883
3157
  showUnsupportedObjectPreviews?: boolean;
2884
3158
  showReviewPanel?: boolean;
2885
3159
  chromeVisibility?: Partial<WordReviewEditorChromeVisibility>;
@@ -2904,6 +3178,61 @@ export interface WordReviewEditorProps {
2904
3178
  * inline "Comments panel" icon appears only when a callback is wired.
2905
3179
  */
2906
3180
  onReviewSidebarComments?: () => void;
3181
+ /**
3182
+ * Optional: fires when the user invokes the Find shortcut
3183
+ * (Ctrl/Cmd+F) with the editor focused. When a host wires this
3184
+ * callback, the editor treats the shortcut as host-delegated and
3185
+ * suppresses the browser's native Find flow; when omitted, the
3186
+ * shortcut falls through to the browser default. The callback
3187
+ * receives the current selection so the host can pre-populate its
3188
+ * Find panel with the selected text.
3189
+ *
3190
+ * Capability id: `shortcut.find`. See
3191
+ * `src/runtime/editor-surface/capabilities.ts` for the full
3192
+ * capability contract.
3193
+ */
3194
+ onFindRequested?: (context: ShortcutDelegationContext) => void;
3195
+ /**
3196
+ * Optional: fires when the user invokes Print (Ctrl/Cmd+P) with
3197
+ * the editor focused. When wired, the editor calls this callback
3198
+ * and suppresses the browser's native print dialog — the host is
3199
+ * expected to open its own print flow (e.g. a PDF export pipeline
3200
+ * that preserves review chrome). When omitted, Ctrl/Cmd+P falls
3201
+ * through to the browser's print dialog.
3202
+ *
3203
+ * Capability id: `shortcut.print`.
3204
+ */
3205
+ onPrintRequested?: () => void;
3206
+ /**
3207
+ * Optional: fires when the user invokes a zoom shortcut
3208
+ * (Ctrl/Cmd+Plus, Ctrl/Cmd+Minus, Ctrl/Cmd+0). When wired, the
3209
+ * editor suppresses the browser's native zoom and delegates the
3210
+ * direction to the host. When omitted, the browser handles zoom
3211
+ * as usual.
3212
+ *
3213
+ * Capability ids: `shortcut.zoom-in`, `shortcut.zoom-out`,
3214
+ * `shortcut.zoom-reset`.
3215
+ */
3216
+ onZoomRequested?: (direction: "in" | "out" | "reset") => void;
3217
+ }
3218
+
3219
+ /**
3220
+ * Selection context handed to host-delegated shortcut callbacks
3221
+ * (`onFindRequested`, future `onReplaceRequested`, etc.) so the host
3222
+ * can pre-populate its own UI with the user's current selection.
3223
+ *
3224
+ * - `selectionText` is truncated to the first 500 characters —
3225
+ * Find / Replace panels typically only need a snippet, and unbounded
3226
+ * text would be wasteful for large selections.
3227
+ * - `selectionRange` is the same shape exposed via the
3228
+ * `selection_changed` editor event, so hosts can reuse selection
3229
+ * plumbing.
3230
+ */
3231
+ export interface ShortcutDelegationContext {
3232
+ /** The user-visible text of the selection, truncated to 500 chars. Empty string when collapsed. */
3233
+ selectionText: string;
3234
+ /** The selection range as a SelectionSnapshot. */
3235
+ selectionRange: SelectionSnapshot;
2907
3236
  }
2908
3237
 
2909
3238
  export interface WordReviewEditorChromeVisibility {
@@ -624,7 +624,13 @@ function applyAlignment(
624
624
  return true;
625
625
  }
626
626
 
627
- function applyIndentation(paragraph: ParagraphNode, delta: -1 | 1): boolean {
627
+ /**
628
+ * Adjusts the paragraph's indentation (or list level if the paragraph carries
629
+ * `numbering`) by ±INDENT_STEP_TWIPS. Mutates `paragraph` in place — caller
630
+ * must clone first if the source is shared. Returns `false` when no change
631
+ * occurred (already at the 0 / 8 bound, or no-op).
632
+ */
633
+ export function applyIndentation(paragraph: ParagraphNode, delta: -1 | 1): boolean {
628
634
  if (paragraph.numbering) {
629
635
  const nextLevel = clamp(paragraph.numbering.level + delta, 0, 8);
630
636
  if (nextLevel === paragraph.numbering.level) {