@beyondwork/docx-react-component 1.0.42 → 1.0.43

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 (51) hide show
  1. package/package.json +30 -41
  2. package/src/api/editor-state-types.ts +110 -0
  3. package/src/api/public-types.ts +194 -1
  4. package/src/core/commands/index.ts +33 -8
  5. package/src/core/search/search-text.ts +15 -2
  6. package/src/index.ts +13 -0
  7. package/src/io/docx-session.ts +672 -2
  8. package/src/io/load-scheduler.ts +230 -0
  9. package/src/io/normalize/normalize-text.ts +83 -0
  10. package/src/io/ooxml/workflow-payload-validator.ts +97 -1
  11. package/src/io/ooxml/workflow-payload.ts +172 -1
  12. package/src/runtime/collab-session.ts +1 -1
  13. package/src/runtime/document-runtime.ts +364 -36
  14. package/src/runtime/editor-state-channel.ts +544 -0
  15. package/src/runtime/editor-state-integration.ts +217 -0
  16. package/src/runtime/layout/index.ts +2 -0
  17. package/src/runtime/layout/inert-layout-facet.ts +2 -0
  18. package/src/runtime/layout/layout-engine-instance.ts +17 -2
  19. package/src/runtime/layout/paginated-layout-engine.ts +211 -14
  20. package/src/runtime/layout/public-facet.ts +400 -1
  21. package/src/runtime/perf-counters.ts +28 -0
  22. package/src/runtime/render/render-frame-types.ts +17 -0
  23. package/src/runtime/render/render-kernel.ts +172 -29
  24. package/src/runtime/surface-projection.ts +10 -5
  25. package/src/runtime/workflow-markup.ts +71 -16
  26. package/src/ui/WordReviewEditor.tsx +67 -45
  27. package/src/ui/editor-command-bag.ts +14 -0
  28. package/src/ui/editor-runtime-boundary.ts +110 -11
  29. package/src/ui/editor-shell-view.tsx +10 -0
  30. package/src/ui/editor-surface-controller.tsx +5 -0
  31. package/src/ui/headless/selection-helpers.ts +10 -0
  32. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +62 -2
  33. package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +281 -0
  34. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +48 -0
  35. package/src/ui-tailwind/editor-surface/paste-plain-text.ts +72 -0
  36. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +118 -8
  37. package/src/ui-tailwind/editor-surface/pm-schema.ts +152 -4
  38. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +35 -7
  39. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +265 -0
  40. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +8 -255
  41. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +9 -0
  42. package/src/ui-tailwind/index.ts +5 -1
  43. package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +57 -0
  44. package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +71 -0
  45. package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +73 -0
  46. package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +74 -0
  47. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +477 -0
  48. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +374 -0
  49. package/src/ui-tailwind/review/comment-markdown-renderer.tsx +155 -0
  50. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +77 -16
  51. package/src/ui-tailwind/tw-review-workspace.tsx +172 -94
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.42",
4
+ "version": "1.0.43",
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/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",
@@ -204,14 +174,33 @@
204
174
  "y-protocols": "^1.0.7",
205
175
  "yjs": "^13.6.30"
206
176
  },
207
- "pnpm": {
208
- "onlyBuiltDependencies": [
209
- "esbuild",
210
- "sharp"
211
- ],
212
- "overrides": {
213
- "react": "19.2.4",
214
- "react-dom": "19.2.4"
215
- }
177
+ "scripts": {
178
+ "build": "tsup",
179
+ "test": "bash scripts/run-workspace-tests.sh",
180
+ "test:repo": "node scripts/run-repo-tests.mjs core",
181
+ "test:repo:all": "node scripts/run-repo-tests.mjs all",
182
+ "test:repo:optional": "node scripts/run-repo-tests.mjs optional",
183
+ "test:repo:browser-ui": "node scripts/run-repo-tests.mjs browser-ui",
184
+ "test:wcag-audit": "node scripts/run-repo-tests.mjs wcag-audit",
185
+ "test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
186
+ "test:visual": "VISUAL_SMOKE_PROFILE=bare pnpm exec playwright test --project=chromium",
187
+ "test:visual:chrome": "VISUAL_SMOKE_PROFILE=chrome-cycle pnpm exec playwright test --project=chromium",
188
+ "visual:list-runs": "node scripts/visual-smoke-list-runs.mjs",
189
+ "mcp:visual-smoke": "node scripts/visual-smoke-mcp.mjs",
190
+ "lint": "pnpm run lint:no-authored-js && pnpm run lint:docs-contracts && pnpm run lint:tsgo && pnpm run lint:tsgo:harness",
191
+ "lint:docs-contracts": "bash scripts/check-reference-load-contract.sh",
192
+ "lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
193
+ "lint:tsgo": "tsgo --noEmit -p tsconfig.build.json",
194
+ "lint:tsgo:harness": "pnpm --filter @docx-react-component/react-word-editor-harness lint:tsgo",
195
+ "context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
196
+ "wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
197
+ "wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
198
+ "wave:launch": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main",
199
+ "wave:launch:managed": "bash scripts/wave-launch.sh",
200
+ "wave:status": "bash scripts/wave-status.sh",
201
+ "wave:watch": "bash scripts/wave-watch.sh --follow",
202
+ "wave:dashboard:current": "bash scripts/wave-dashboard-attach.sh current",
203
+ "wave:dashboard:global": "bash scripts/wave-dashboard-attach.sh global",
204
+ "harness:dev": "pnpm --filter @docx-react-component/react-word-editor-harness dev"
216
205
  }
217
- }
206
+ }
@@ -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
  | {
@@ -2299,6 +2337,25 @@ export interface AutosaveConfig {
2299
2337
  debounceMs?: number;
2300
2338
  }
2301
2339
 
2340
+ /**
2341
+ * Fired after a plain-text paste or drop has been dispatched through
2342
+ * the runtime-owned input callbacks. Rich-text paste (HTML, Office
2343
+ * clipboard) does NOT produce this event — those payloads still fire
2344
+ * `onBlockedInput` with a distinguishing message.
2345
+ *
2346
+ * See `docs/plans/editor-paste-drop.md` for the full semantics.
2347
+ */
2348
+ export interface WordReviewEditorPasteEvent {
2349
+ type: "paste_applied";
2350
+ documentId: string;
2351
+ /** Count of segments dispatched across text + split + hard_break. */
2352
+ segmentCount: number;
2353
+ /** Total character count across all text segments in the payload. */
2354
+ charCount: number;
2355
+ /** Whether the event originated from a paste or a drop. */
2356
+ source: "paste" | "drop";
2357
+ }
2358
+
2302
2359
  export type WordReviewEditorEvent =
2303
2360
  | {
2304
2361
  type: "ready";
@@ -2442,6 +2499,7 @@ export type WordReviewEditorEvent =
2442
2499
  command: string;
2443
2500
  reasons: WorkflowBlockedCommandReason[];
2444
2501
  }
2502
+ | WordReviewEditorPasteEvent
2445
2503
  | {
2446
2504
  /**
2447
2505
  * Scope card mode selector fired a mode change. Host relays to the
@@ -2520,8 +2578,82 @@ export type WordReviewEditorEvent =
2520
2578
  version?: number;
2521
2579
  } | null;
2522
2580
  defaultPolicy: MetadataConflictPolicy;
2581
+ }
2582
+ | {
2583
+ /**
2584
+ * Schema 1.2 — emitted when a keyed namespace fails to resolve
2585
+ * during load. Subsystem fallback behavior is per `onResolveError`
2586
+ * policy.
2587
+ */
2588
+ type: "editor_state_part_load_failed";
2589
+ documentId: string;
2590
+ failure: EditorStatePartLoadFailure;
2591
+ }
2592
+ | {
2593
+ /**
2594
+ * Schema 1.2 — emitted when a debounced persist call fails.
2595
+ * The editor retains the dirty snapshot for retry.
2596
+ */
2597
+ type: "editor_state_part_persist_failed";
2598
+ documentId: string;
2599
+ failure: EditorStatePartPersistFailure;
2600
+ }
2601
+ | {
2602
+ /**
2603
+ * Schema 1.2 — emitted when the saved location in a docx differs
2604
+ * from the current host policy; the current policy wins and the
2605
+ * next serialize honors it.
2606
+ */
2607
+ type: "editor_state_policy_migrated";
2608
+ documentId: string;
2609
+ migration: EditorStatePolicyMigration;
2610
+ }
2611
+ | {
2612
+ /**
2613
+ * Schema 1.2 — emitted when an unknown namespace is encountered
2614
+ * in the payload (forward-compat warning; preserved opaquely).
2615
+ */
2616
+ type: "editor_state_unknown_namespace";
2617
+ documentId: string;
2618
+ namespace: string;
2619
+ }
2620
+ | {
2621
+ /**
2622
+ * Emitted once per load-pipeline stage when the staged loader is
2623
+ * active. Purely observational — hosts can render a progress chip
2624
+ * or ignore entirely. See `docs/plans/fastload.md` for the stage
2625
+ * ordering contract.
2626
+ */
2627
+ type: "load-stage";
2628
+ documentId: string;
2629
+ stage: LoadStage;
2630
+ durationMs: number;
2631
+ }
2632
+ | {
2633
+ /**
2634
+ * Emitted once sub-parts (headers, footers, footnotes, endnotes,
2635
+ * theme, settings) have finished their post-ready idle hydration.
2636
+ * Before this event fires, `canonicalDocument.subParts.*` may be
2637
+ * empty arrays / defaults; after it fires, they carry the parsed
2638
+ * values. Calling `ensureSubPartsHydrated` eagerly forces
2639
+ * hydration synchronously.
2640
+ */
2641
+ type: "subparts-hydrated";
2642
+ documentId: string;
2523
2643
  };
2524
2644
 
2645
+ /**
2646
+ * Ordered list of stages emitted by the staged load pipeline. The
2647
+ * skeleton-ready stage corresponds to the `ready` event firing.
2648
+ */
2649
+ export type LoadStage =
2650
+ | "opc"
2651
+ | "body"
2652
+ | "styles-numbering-comments"
2653
+ | "skeleton-ready"
2654
+ | "subparts"
2655
+ | "compatibility";
2656
+
2525
2657
  export interface LoadResult {
2526
2658
  source?: ExternalDocumentSource;
2527
2659
  }
@@ -2767,6 +2899,35 @@ export interface WordReviewEditorRef {
2767
2899
  * registered.
2768
2900
  */
2769
2901
  convertScopesToExternal(scopeIds: string[]): Promise<void>;
2902
+ /**
2903
+ * Schema 1.2 — editor-state persistence channel. Configures which
2904
+ * subsystems persist in-document / rowstore / key-only. Each call
2905
+ * merges into the current policy map. Mismatched locations with an
2906
+ * already-loaded docx fire `editor_state_policy_migrated` and
2907
+ * schedule the next serialize to honor the new location.
2908
+ */
2909
+ configureEditorStatePolicy(policy: EditorStatePolicy): void;
2910
+ /**
2911
+ * Register the host's pull-resolver. Required before the editor
2912
+ * loads a 1.2 docx whose policy includes any keyed location.
2913
+ */
2914
+ registerEditorStateResolver(resolver: EditorStateResolver): void;
2915
+ /**
2916
+ * Register the host's persister. Required before any subsystem
2917
+ * mutation under keyed policy fires a debounced persist.
2918
+ */
2919
+ registerEditorStatePersister(persister: EditorStatePersister): void;
2920
+ /**
2921
+ * Return the entry key currently associated with a namespace under
2922
+ * keyed policy, or undefined when the namespace is in-document /
2923
+ * key not yet assigned.
2924
+ */
2925
+ getEditorStateKey(namespace: EditorStateNamespace): string | undefined;
2926
+ /**
2927
+ * Re-attempt any persists that failed since the last successful
2928
+ * write. Pass a namespace to scope the retry; omit to retry all.
2929
+ */
2930
+ retryPendingPersist(namespace?: EditorStateNamespace): Promise<void>;
2770
2931
  setHostAnnotationOverlay(overlay: HostAnnotationOverlay): void;
2771
2932
  clearHostAnnotationOverlay(): void;
2772
2933
  getHostAnnotationSnapshot(): HostAnnotationSnapshot;
@@ -2866,6 +3027,38 @@ export interface WordReviewEditorProps {
2866
3027
  currentUser: EditorUser;
2867
3028
  ydoc?: import('yjs').Doc;
2868
3029
  awareness?: import("y-protocols/awareness").Awareness;
3030
+ /**
3031
+ * Optional collab session built via `createCollabSession(...)`. When
3032
+ * set, the `"collab"` chrome preset mounts the top nav (presence
3033
+ * strip, role + audience chips, tamper banner, negotiation action
3034
+ * bar, send-to-supplier button). P9a–f components are pure
3035
+ * presentational; this prop is what wires them to live state. Pass
3036
+ * `undefined` to keep the chrome in non-collab mode even when
3037
+ * `chromePreset === "collab"`.
3038
+ */
3039
+ collabSession?: import("../runtime/collab-session.ts").CollabSession;
3040
+ /**
3041
+ * Transport status signal for the presence strip + role chip (P9b /
3042
+ * P9c). Optional — defaults to "offline" when omitted.
3043
+ */
3044
+ collabTransportStatus?: "connected" | "syncing" | "offline";
3045
+ /**
3046
+ * Identifier for the currently-focused comment, used by the collab
3047
+ * top nav's audience chip + negotiation action bar (P9c / P9e). The
3048
+ * host derives this from its own selection / rail-focus signal.
3049
+ */
3050
+ activeCommentId?: string;
3051
+ /**
3052
+ * Baseline metadata for the send-to-supplier flow (P9f). Required
3053
+ * to enable the button; when omitted, the button stays present but
3054
+ * the confirmation modal becomes a no-op pass-through.
3055
+ */
3056
+ collabSendBaseline?: {
3057
+ originDocumentId: string;
3058
+ originPayloadId: string;
3059
+ originContentHash: string;
3060
+ payloadXml: string;
3061
+ };
2869
3062
  initialDocx?: Uint8Array | ArrayBuffer;
2870
3063
  initialSessionState?: EditorSessionState;
2871
3064
  initialSnapshot?: PersistedEditorSnapshot;
@@ -1791,22 +1791,46 @@ function combineMappingSteps(
1791
1791
  // Suggesting mode: creates revision records instead of (or alongside) text mutations
1792
1792
  // ---------------------------------------------------------------------------
1793
1793
 
1794
- let suggestingRevisionCounter = 0;
1795
-
1794
+ /**
1795
+ * Builds a suggesting-mode revision id that is deterministic across
1796
+ * clients. The id is a pure function of `(existing, timestamp, authorId)`
1797
+ * so that origin and replica — both of whom see the same `existing`
1798
+ * revisions record after a Yjs-ordered replay and receive the same
1799
+ * `timestamp` + `authorId` on the broadcast command event — produce
1800
+ * the same id for the same authored revision.
1801
+ *
1802
+ * Previously this used a module-level counter; that counter was shared
1803
+ * across every `DocumentRuntime` in a single process and drifted between
1804
+ * origin and replica whenever either side had other suggesting-mode work
1805
+ * bump the counter before replay. Cross-client `change.accept(changeId)`
1806
+ * / `change.reject(changeId)` then missed because the target id only
1807
+ * existed on one side.
1808
+ */
1796
1809
  function createSuggestingRevisionId(
1797
1810
  existing: Record<string, unknown>,
1798
1811
  timestamp: string,
1812
+ authorId: string,
1799
1813
  ): string {
1800
- suggestingRevisionCounter += 1;
1801
1814
  const ts = timestamp.replace(/[^0-9]/gu, "");
1802
- let id = `change-${ts}-s${suggestingRevisionCounter}`;
1815
+ const authorMarker = sanitizeAuthorMarker(authorId);
1816
+ const prefix = `change-${authorMarker}-${ts}-s`;
1817
+ let n = 1;
1818
+ let id = `${prefix}${n}`;
1803
1819
  while (existing[id]) {
1804
- suggestingRevisionCounter += 1;
1805
- id = `change-${ts}-s${suggestingRevisionCounter}`;
1820
+ n += 1;
1821
+ id = `${prefix}${n}`;
1806
1822
  }
1807
1823
  return id;
1808
1824
  }
1809
1825
 
1826
+ function sanitizeAuthorMarker(authorId: string): string {
1827
+ // OOXML `w:id` accepts alphanumerics plus `._-`; keep within that set so
1828
+ // the id survives round-trips unchanged (see `sanitizeRevisionId` on the
1829
+ // serializer side).
1830
+ const cleaned = authorId.replace(/[^A-Za-z0-9._-]/g, "-");
1831
+ return cleaned.length > 0 ? cleaned : "u";
1832
+ }
1833
+
1810
1834
  function createAuthoredRevision(
1811
1835
  existing: Record<string, unknown>,
1812
1836
  kind: "insertion" | "deletion",
@@ -1816,7 +1840,7 @@ function createAuthoredRevision(
1816
1840
  timestamp: string,
1817
1841
  metadata: Partial<NonNullable<CanonicalRevisionRecord["metadata"]>> = {},
1818
1842
  ): CanonicalRevisionRecord {
1819
- const changeId = createSuggestingRevisionId(existing, timestamp);
1843
+ const changeId = createSuggestingRevisionId(existing, timestamp, authorId);
1820
1844
  return {
1821
1845
  changeId,
1822
1846
  kind,
@@ -2066,6 +2090,7 @@ function applySuggestingInsert(
2066
2090
  const replacementSuggestionId = createSuggestingRevisionId(
2067
2091
  reviewState.document.review.revisions,
2068
2092
  context.timestamp,
2093
+ authorId,
2069
2094
  );
2070
2095
 
2071
2096
  // Step 3: Create deletion revision for the selected range (text stays in place).
@@ -2324,7 +2349,7 @@ function applySuggestingInsertUnit(
2324
2349
  result.mapping,
2325
2350
  );
2326
2351
  const replacementSuggestionId = from !== to
2327
- ? createSuggestingRevisionId(reviewState.document.review.revisions, context.timestamp)
2352
+ ? createSuggestingRevisionId(reviewState.document.review.revisions, context.timestamp, authorId)
2328
2353
  : undefined;
2329
2354
 
2330
2355
  // If non-collapsed, mark selected range as deletion (positions are pre-mapping, content preserved)
@@ -26,7 +26,7 @@ export interface SecondaryStorySearchResult extends SurfaceSearchResult {
26
26
  storyTarget: EditorStoryTarget;
27
27
  }
28
28
 
29
- interface ProjectedSurfaceText {
29
+ export interface ProjectedSurfaceText {
30
30
  text: string;
31
31
  offsetMap: Array<number | null>;
32
32
  }
@@ -112,7 +112,20 @@ export function searchSurfaceBlocks(
112
112
  query: string,
113
113
  options: SearchTextOptions = {},
114
114
  ): SurfaceSearchResult[] {
115
- const projection = projectSurfaceText(blocks);
115
+ return searchProjectedSurfaceText(projectSurfaceText(blocks), query, options);
116
+ }
117
+
118
+ /**
119
+ * Search a pre-projected surface text. Hoist `projectSurfaceText(blocks)` out
120
+ * of a per-query loop to avoid rebuilding the projection N times for N queries
121
+ * against the same surface — L7 Phase 1.5 discovered this cost dominates
122
+ * `collectFieldMarkup` on the CCEP large-tables fixture.
123
+ */
124
+ export function searchProjectedSurfaceText(
125
+ projection: ProjectedSurfaceText,
126
+ query: string,
127
+ options: SearchTextOptions = {},
128
+ ): SurfaceSearchResult[] {
116
129
  return findSearchMatches(projection.text, query, options)
117
130
  .map((match) => {
118
131
  const range = resolveProjectedRuntimeRange(
package/src/index.ts CHANGED
@@ -267,4 +267,17 @@ export type {
267
267
  ScopeMetadataPersistence,
268
268
  ScopeMetadataResolver,
269
269
  ScopeMetadataStorageRef,
270
+ // Schema 1.2 — editor-state persistence types
271
+ EditorStateNamespace,
272
+ EditorStateLocation,
273
+ EditorStateResolveErrorMode,
274
+ EditorStatePolicyEntry,
275
+ EditorStatePolicy,
276
+ EditorStateStorageRef,
277
+ EditorStateBlob,
278
+ EditorStateResolver,
279
+ EditorStatePersister,
280
+ EditorStatePolicyMigration,
281
+ EditorStatePartLoadFailure,
282
+ EditorStatePartPersistFailure,
270
283
  } from "./api/public-types.ts";