@beyondwork/docx-react-component 1.0.70 → 1.0.72
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.
- package/README.md +964 -75
- package/package.json +1 -1
- package/src/api/public-types.ts +243 -1
- package/src/api/v3/_create.ts +16 -1
- package/src/api/v3/_runtime-handle.ts +2 -0
- package/src/api/v3/ai/evaluate.ts +113 -0
- package/src/api/v3/ai/outline.ts +140 -0
- package/src/api/v3/ai/replacement.ts +8 -0
- package/src/api/v3/ai/review.ts +342 -0
- package/src/api/v3/ai/stats.ts +62 -0
- package/src/api/v3/runtime/viewport.ts +181 -0
- package/src/api/v3/runtime/workflow.ts +114 -1
- package/src/api/v3/ui/_types.ts +35 -0
- package/src/api/v3/ui/index.ts +1 -0
- package/src/api/v3/ui/viewport.ts +112 -0
- package/src/compare/diff-engine.ts +2 -0
- package/src/core/commands/formatting-commands.ts +1 -0
- package/src/core/commands/table-structure-commands.ts +1 -0
- package/src/io/export/serialize-headers-footers.ts +1 -0
- package/src/io/export/serialize-main-document.ts +13 -0
- package/src/io/export/serialize-paragraph-formatting.ts +34 -0
- package/src/io/export/split-review-boundaries.ts +1 -0
- package/src/io/normalize/normalize-text.ts +11 -0
- package/src/io/ooxml/parse-main-document.ts +21 -5
- package/src/io/ooxml/parse-paragraph-formatting.ts +105 -0
- package/src/model/canonical-document.ts +401 -1
- package/src/runtime/formatting/formatting-context.ts +2 -1
- package/src/runtime/geometry/overlay-rects.ts +7 -10
- package/src/runtime/layout/layout-engine-version.ts +257 -1
- package/src/runtime/layout/paginated-layout-engine.ts +134 -8
- package/src/runtime/layout/resolved-formatting-state.ts +108 -13
- package/src/runtime/markdown-sanitizer.ts +21 -4
- package/src/runtime/render/render-kernel.ts +21 -1
- package/src/runtime/scopes/audit-bundle.ts +8 -0
- package/src/runtime/scopes/compiler-service.ts +1 -0
- package/src/runtime/scopes/enumerate-scopes.ts +61 -3
- package/src/runtime/scopes/replacement/apply.ts +49 -3
- package/src/runtime/scopes/semantic-scope-types.ts +8 -0
- package/src/runtime/surface-projection.ts +22 -0
- package/src/runtime/workflow/coordinator.ts +3 -0
- package/src/runtime/workflow/scope-writer.ts +34 -0
- package/src/session/export/embedded-reconstitute.ts +37 -3
- package/src/session/import/embedded-offload.ts +26 -1
- package/src/shell/media-previews.ts +8 -6
- package/src/ui/WordReviewEditor.tsx +1 -0
- package/src/ui/editor-surface-controller.tsx +11 -0
- package/src/ui/headless/selection-helpers.ts +2 -2
- package/src/ui/runtime-shortcut-dispatch.ts +4 -4
- package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +22 -4
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +11 -11
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +5 -0
- package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +18 -1
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +22 -6
- package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +18 -1
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +98 -3
- package/src/ui-tailwind/editor-surface/pm-schema.ts +18 -4
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +8 -1
- package/src/ui-tailwind/editor-surface/search-plugin.ts +2 -4
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +37 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +29 -4
- package/src/ui-tailwind/index.ts +4 -2
- package/src/ui-tailwind/page-chrome-model.ts +5 -7
- package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +5 -2
- package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +4 -1
- package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +4 -1
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +10 -1
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +4 -1
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +7 -1
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +7 -1
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +73 -8
- package/src/ui-tailwind/review/comment-markdown-renderer.tsx +1 -1
- package/src/ui-tailwind/review-workspace/page-chrome.ts +4 -4
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +1 -1
- package/src/ui-tailwind/tw-review-workspace.tsx +1 -0
package/README.md
CHANGED
|
@@ -11,136 +11,1025 @@ canonical: true
|
|
|
11
11
|
|
|
12
12
|
[](https://github.com/bwllaming/React-OOXML-Office/actions/workflows/ci.yml)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
This repository builds **backoffice work components** for the [Beyond Work](https://beyondwork.ai) runtime — embeddable, composable modules that handle document editing, review workflows, data processing, and task automation inside enterprise workspaces.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
The design principle across every component: **advanced tasks should be equally easy for AI agents and human users.** Every capability exposed through the UI is also available through a structured API. Every API is designed so an agent can inspect state, reason about it, and act on it without special accommodation. Humans and agents share the same runtime, the same commands, the same read models, and the same fidelity guarantees.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## Use This README For
|
|
19
|
+
|
|
20
|
+
- confirming what is shipped today vs coming
|
|
21
|
+
- installing the package
|
|
22
|
+
- finding the right documentation lane quickly
|
|
23
|
+
|
|
24
|
+
Do not use this README as the full package contract. The canonical consumer path starts in `docs/README.md`, [`docs/reference/public-api.md`](docs/reference/public-api.md) (V1 + V2), [`docs/reference/public-api-3.md`](docs/reference/public-api-3.md) (V3 additive surface), and [`docs/reference/integration-guide.md`](docs/reference/integration-guide.md).
|
|
25
|
+
|
|
26
|
+
The target end-state architecture — 11-layer stack, Runtime/AI/UI API split, semantic scope compiler, and Phase 0–7 migration plan — is documented in [`docs/architecture/00-overview.md`](docs/architecture/00-overview.md). Read it before designing cross-cutting changes. The technical wiki under [`docs/wiki/`](docs/wiki/) has feature-level pages (API v1/v2/v3, runtime-truth, debugging, doc-debug-service-guide, chrome-composition / chrome-composition-backlog, testing-and-fixtures, etc.); navigate via [`docs/README.md`](docs/README.md).
|
|
27
|
+
|
|
28
|
+
## Components
|
|
29
|
+
|
|
30
|
+
### Document Editor (`@beyondwork/docx-react-component`)
|
|
31
|
+
|
|
32
|
+
A fidelity-first React `.docx` editor centered on `WordReviewEditor`. Built for legal review, contract negotiation, and any workflow where documents need to survive a round-trip through Microsoft Word without damage.
|
|
33
|
+
|
|
34
|
+
**Shipped capabilities:**
|
|
35
|
+
- Full tracked-change and comment support (add, resolve, accept, reject) — including Lane 7b X4.a/X4.b container + range revision markers and move-promotion
|
|
36
|
+
- Suggesting mode where every edit automatically becomes a tracked change
|
|
37
|
+
- Round-trip OOXML preservation — open, edit, export, reopen in Word without repair prompts; preserve-first unknown handling (Lanes 7b/7c closed)
|
|
38
|
+
- Workflow overlays with scope-based editing constraints, marker-backed scope identity, and three-way scope-source discrimination (marker-backed / overlay-only / detached)
|
|
39
|
+
- Runtime-backed read models for document structure, review state, selection, compatibility, and fields
|
|
40
|
+
- Document comparison with LCS-based diffing and redline export
|
|
41
|
+
- Legal document analysis (bookmarks, cross-references, defined terms, signature blocks)
|
|
42
|
+
- Real-time collaboration via Yjs — Lane 4 Phase C shipped (R1 remote-replay coalescing, R3 idle-priority abort signal, pairRuntimes migration)
|
|
43
|
+
- Layout engine proven at **97.77%** (615/629) external layout-structure truth and **96.34%** (606/629) rendered-word geometry over the CCEP contract corpus (see [`services/truth-baseline/`](services/truth-baseline/))
|
|
44
|
+
- Charts v1 — native SVG renderer for 7 chart families with progressive time-slicing (Lane 5 Stages 0–6 shipped)
|
|
45
|
+
- Debug substrate Phase 0 + Phase 1 — runtime projector + 12-channel `TelemetryBus` + `DebugInspectorSnapshot` + `UxResponse` event stream
|
|
46
|
+
- Additive V3 public surface — `createApiV3(runtime)` with Runtime + AI API split, 31 functions under a 4-status taxonomy (`live` / `live-with-adapter` / `partial` / `mock`); Phase P' closed 2026-04-21
|
|
47
|
+
|
|
48
|
+
**Human-AI parity in practice:**
|
|
49
|
+
- The human clicks "Add Comment" on selected text; the agent calls `ref.addComment({ anchor, body })` (V1) or `api.runtime.review.resolveComment(...)` (V3) on the same selection
|
|
50
|
+
- The human reviews tracked changes in the sidebar; the agent reads `getTrackedChanges()` (V1) or `api.runtime.review.getChanges()` (V3) and makes the same accept/reject decisions
|
|
51
|
+
- The human exports via toolbar; the agent calls `exportDocx()` (V1) or `api.runtime.document.export()` (V3) after the same compatibility check
|
|
52
|
+
- Workflow overlays constrain both humans and agents identically — `getInteractionGuardSnapshot()` / `api.runtime.workflow.getGuard()` is the single source of posture truth
|
|
53
|
+
- The AI action policy module gates 37 discrete agent operations with risk classification and context validation
|
|
54
|
+
- Every `uiVisible` V3 function emits exactly one `UxResponse` event on the `api` telemetry channel — the stream the Phase Q debug UX consumes to render mock-or-live visual traces identically
|
|
55
|
+
|
|
56
|
+
### Workblocks
|
|
57
|
+
|
|
58
|
+
Composable task modules that automate business processes. Each workblock is a self-contained package of Python backend logic, React UI components, and a declarative task graph defined in `manifest.yaml`. They execute on the Beyond Work platform via Temporal workflows.
|
|
59
|
+
|
|
60
|
+
**Categories:**
|
|
61
|
+
- **Document processing** — invoice extraction, PDF template OCR, bulk processing, email-to-action
|
|
62
|
+
- **AI agents** — explore, flow debugging, data querying, task launching, authoring
|
|
63
|
+
- **Analytics** — execution history, company insights, timeline queries
|
|
64
|
+
- **Learning** — human correction capture, learning set curation, embedding management
|
|
65
|
+
- **Infrastructure** — registry, health checks, credential management (9 types)
|
|
66
|
+
|
|
67
|
+
**Human-AI parity in practice:**
|
|
68
|
+
- Users create workblocks through VS Code Studio with prompts and visual editing; authoring agents create them through the same manifest schema and file tools
|
|
69
|
+
- Users validate invoice fields in a React form; agents validate the same fields through typed task inputs/outputs
|
|
70
|
+
- Both humans and agents operate within the same workblock execution engine — same task graph, same data flow, same error handling
|
|
71
|
+
|
|
72
|
+
## The Beyond Work Runtime
|
|
73
|
+
|
|
74
|
+
These components plug into a four-layer platform:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
User-Facing (Layer 4)
|
|
78
|
+
neui (Next.js 15), bport (BFF), MCP server, VS Code extension
|
|
79
|
+
|
|
|
80
|
+
Workblocks (Layer 3)
|
|
81
|
+
20+ composable task modules: agents, data processing, credentials
|
|
82
|
+
|
|
|
83
|
+
Platform Services (Layer 2)
|
|
84
|
+
bworker (Go), modelproxy (LiteLLM), registry, pythonworker, Temporal
|
|
85
|
+
|
|
|
86
|
+
AWS Infrastructure (Layer 1)
|
|
87
|
+
EKS, Crossplane, Terraform, PostgreSQL, Redis, Qdrant, S3, SQS
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The document editor lives at Layer 4 as an embeddable surface. Workblocks live at Layer 3 and are orchestrated by the platform. Both expose their capabilities symmetrically to human UIs and agent APIs.
|
|
91
|
+
|
|
92
|
+
## Shipped vs Coming
|
|
93
|
+
|
|
94
|
+
The shipped contract today is docx-first. The table below delineates what is ready to build on vs what is planned end-state work. For the full migration narrative, see [`docs/architecture/00-overview.md`](docs/architecture/00-overview.md) §"Migration Plan" (Phases 0–7).
|
|
95
|
+
|
|
96
|
+
| Area | Shipped today | Coming |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| Package runtime | `docx` — the only shipped runtime contract | `xlsx` planned; `pdf` future work outside current package boundary |
|
|
99
|
+
| Public API | V1 flat-method `WordReviewEditorRef` (`public-stable`) + V2 facet projections + dual-error `EditorDiagnostic` (`advanced-supported`) + V3 `createApiV3(runtime)` Runtime + AI split (`advanced-supported`; Phase P' 2026-04-21) | V3 UI API surface (Phase 3 remainder); semantic scope compiler (Phase 4); replacement-scope lifecycle (Phase 5); semantic-scope-first AI layer (Phase 6) |
|
|
100
|
+
| Middle stack | Canonical model (`src/model/canonical-document.ts`); runtime core (`src/runtime/document-runtime.ts`); layout engine (`src/runtime/layout/**`); render kernel MVP (`src/runtime/render/**`) | Formal `src/session/**` package/session boundary (Phase 1); formatting semantics layer (Phase 2); explicit geometry projection layer (Phase 2); semantic scope compiler (Phase 4) |
|
|
101
|
+
| Layout / geometry | External truth baseline proven at 97.77% layout / 96.34% rendered-word on 629 CCEP docs | First-class runtime-owned geometry contract for caret/hit-test/replacement envelopes |
|
|
102
|
+
| Debug substrate | Runtime projector + `TelemetryBus` (12 channels, 8 active/wired, 4 reserved) + `DebugInspectorSnapshot` + `UxResponse` stream + `wrapRefForTelemetry` (Phase 0 + Phase 1 shipped 2026-04-20) | `services/debug/` Railway HTTPS service (Phase 2 in progress); MCP proxy + dev-drawer retarget (Phase 3 planned); Phase Q debug UX at `src/ui-tailwind/debug/**` |
|
|
103
|
+
| External oracles | OpenXML SDK validator MCP (round-trip proof); LibreOffice-backed `services/truth-baseline` for layout + geometry; visual-smoke MCP for CCEP templates | Phase F full oracle integration: stage-token lineage, alignment keys, known-divergence register, deterministic environment |
|
|
104
|
+
| BW / CLM integration | Stateless DOCX runtime service at `vendor/beyondwork/`; Yjs transport; timeline/audit substrate | Grouped service families aligned with Runtime/AI API (Phase 7); scope-native CLM primitives replacing raw `blob_ref` + `story/start/end` glue |
|
|
105
|
+
|
|
106
|
+
The shipped primitives are correct for their respective layers; the target architecture widens the middle and tops the stack, without replacing anything below.
|
|
19
107
|
|
|
20
108
|
## Install
|
|
21
109
|
|
|
22
110
|
```bash
|
|
23
|
-
pnpm add @beyondwork/docx-react-component
|
|
111
|
+
pnpm add @beyondwork/docx-react-component react react-dom tailwindcss \
|
|
112
|
+
prosemirror-commands prosemirror-keymap prosemirror-model \
|
|
113
|
+
prosemirror-state prosemirror-tables prosemirror-transform prosemirror-view
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Current packaging truth:
|
|
117
|
+
|
|
118
|
+
- the package is ESM-only
|
|
119
|
+
- exports point at shipped TypeScript source entry points
|
|
120
|
+
- `types` and subpath `types` entries resolve to the shipped source-backed TypeScript contracts
|
|
121
|
+
- consumers need a bundler or runtime that can resolve `.ts` and `.tsx` ESM imports
|
|
122
|
+
- consumers should import `@beyondwork/docx-react-component/ui-tailwind/theme/editor-theme.css` once and provide a Tailwind v4 CSS pipeline for it
|
|
123
|
+
- package and source identifiers remain docx-first until a deliberate rename lands
|
|
124
|
+
|
|
125
|
+
## Shipped Product
|
|
126
|
+
|
|
127
|
+
The primary shipped surface is:
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import { WordReviewEditor } from "@beyondwork/docx-react-component";
|
|
131
|
+
|
|
132
|
+
<WordReviewEditor />
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`WordReviewEditor` remains uncontrolled by default:
|
|
136
|
+
|
|
137
|
+
- host passes `initialDocx`, `initialSessionState`, or `initialSnapshot`, or provides `hostAdapter.load()` / `datastore.load()`
|
|
138
|
+
- runtime owns the live working session
|
|
139
|
+
- host receives events, warnings, errors, session state, compatibility snapshots, and exported artifacts
|
|
140
|
+
|
|
141
|
+
Persistence direction:
|
|
142
|
+
|
|
143
|
+
- `EditorSessionState` is the canonical host-facing live-session contract
|
|
144
|
+
- `EditorHostAdapter` is the preferred persistence boundary for `load`, `saveSession`, `saveExport`, and telemetry
|
|
145
|
+
- `EditorDatastoreAdapter` remains the legacy snapshot bridge for hosts that still persist `PersistedEditorSnapshot`
|
|
146
|
+
|
|
147
|
+
Snapshot/export note:
|
|
148
|
+
|
|
149
|
+
- when a session starts from a real `.docx`, persisted snapshots now carry embedded source-package provenance so later snapshot-origin `.docx` export can use the same package-backed exporter
|
|
150
|
+
- legacy snapshots without that provenance still load, but `.docx` export is intentionally blocked rather than falling back to a lossy minimal package
|
|
151
|
+
|
|
152
|
+
The current shipped ESM exports include:
|
|
153
|
+
|
|
154
|
+
- stable default entrypoints:
|
|
155
|
+
- `@beyondwork/docx-react-component`
|
|
156
|
+
- `@beyondwork/docx-react-component/public-types`
|
|
157
|
+
- `@beyondwork/docx-react-component/ui-tailwind`
|
|
158
|
+
- `@beyondwork/docx-react-component/legal`
|
|
159
|
+
- `@beyondwork/docx-react-component/compare`
|
|
160
|
+
- `@beyondwork/docx-react-component/ui-tailwind/theme/editor-theme.css`
|
|
161
|
+
- V3 public API (additive, Phase P' 2026-04-21):
|
|
162
|
+
- `@beyondwork/docx-react-component/api/v3` — `createApiV3(runtime)` + metadata primitives + `UxResponse`
|
|
163
|
+
- advanced-supported subpaths for wrapper teams and package-adjacent integrations:
|
|
164
|
+
- `@beyondwork/docx-react-component/runtime/document-runtime`
|
|
165
|
+
- `@beyondwork/docx-react-component/io/docx-session`
|
|
166
|
+
- `@beyondwork/docx-react-component/core/commands/*`
|
|
167
|
+
- `@beyondwork/docx-react-component/core/selection/mapping`
|
|
168
|
+
- `@beyondwork/docx-react-component/core/state/editor-state`
|
|
169
|
+
- `@beyondwork/docx-react-component/ui-tailwind/editor-surface/search-plugin`
|
|
170
|
+
- alias subpaths:
|
|
171
|
+
- `@beyondwork/docx-react-component/tailwind`
|
|
172
|
+
- `@beyondwork/docx-react-component/api/public-types`
|
|
173
|
+
|
|
174
|
+
Use [`docs/reference/public-api.md`](docs/reference/public-api.md) + `docs/reference/public-api.manifest.json` for the V1/V2 contract inventory, and [`docs/reference/public-api-3.md`](docs/reference/public-api-3.md) for the V3 surface. Stability guidance for each V3 namespace is in the `docs/wiki/api-v3.md` page (reached via `docs/README.md`).
|
|
175
|
+
|
|
176
|
+
## Product Contract
|
|
177
|
+
|
|
178
|
+
For every component this repo ships, the standard is:
|
|
179
|
+
|
|
180
|
+
> Open -> edit -> save -> reopen in the host application without damage.
|
|
181
|
+
|
|
182
|
+
For the current shipped `docx` implementation, that specifically means:
|
|
183
|
+
|
|
184
|
+
- open in recent Microsoft Word without repair prompts
|
|
185
|
+
- preserve supported content and review structures
|
|
186
|
+
- preserve unsupported but preservable OOXML
|
|
187
|
+
- remain editable in Word after export
|
|
188
|
+
|
|
189
|
+
For the normative API and host contract, use:
|
|
190
|
+
|
|
191
|
+
- [`docs/reference/public-api.md`](docs/reference/public-api.md)
|
|
192
|
+
- [`docs/reference/integration-guide.md`](docs/reference/integration-guide.md)
|
|
193
|
+
- [`docs/reference/ooxml-compliance.md`](docs/reference/ooxml-compliance.md)
|
|
194
|
+
|
|
195
|
+
## Documentation Map
|
|
196
|
+
|
|
197
|
+
### Start Here
|
|
198
|
+
|
|
199
|
+
- [`docs/README.md`](docs/README.md)
|
|
200
|
+
|
|
201
|
+
### Main Consumer Path
|
|
202
|
+
|
|
203
|
+
- [`docs/reference/quick-start.md`](docs/reference/quick-start.md)
|
|
204
|
+
- [`docs/reference/consumer-matrix.md`](docs/reference/consumer-matrix.md)
|
|
205
|
+
- [`docs/reference/public-api.md`](docs/reference/public-api.md) — V1 + V2 canonical contract
|
|
206
|
+
- [`docs/reference/public-api-3.md`](docs/reference/public-api-3.md) — V3 Runtime + AI API (additive)
|
|
207
|
+
- [`docs/reference/integration-guide.md`](docs/reference/integration-guide.md)
|
|
208
|
+
- [`docs/reference/glossary.md`](docs/reference/glossary.md)
|
|
209
|
+
|
|
210
|
+
### Architecture & Mental Model
|
|
211
|
+
|
|
212
|
+
- [`docs/architecture/00-overview.md`](docs/architecture/00-overview.md) — target end-state: 11-layer stack, Runtime/AI/UI API split, semantic-scope compiler, Phase 0–7 migration plan
|
|
213
|
+
- `docs/wiki/runtime-truth.md` — canonical-truth narrative: package > model > runtime > PM view, inverted-PM model, 10 perf invariants (navigate via [`docs/README.md`](docs/README.md))
|
|
214
|
+
- `docs/wiki/future-architecture.md` — forward-looking design anchors + proof status
|
|
215
|
+
|
|
216
|
+
### Wrapper And Agent Path
|
|
217
|
+
|
|
218
|
+
- [`docs/reference/consumer-matrix.md`](docs/reference/consumer-matrix.md)
|
|
219
|
+
- [`docs/reference/agent-integration-guide.md`](docs/reference/agent-integration-guide.md)
|
|
220
|
+
- [`docs/reference/public-api.md`](docs/reference/public-api.md) — V1 + V2
|
|
221
|
+
- [`docs/reference/public-api-3.md`](docs/reference/public-api-3.md) — V3
|
|
222
|
+
- [`docs/reference/service-wrapper-guidance.md`](docs/reference/service-wrapper-guidance.md)
|
|
223
|
+
- [`docs/reference/agent-capability-map.md`](docs/reference/agent-capability-map.md)
|
|
224
|
+
- [`docs/reference/agent-read-models-and-snapshots.md`](docs/reference/agent-read-models-and-snapshots.md)
|
|
225
|
+
- [`docs/reference/agent-workflow-and-suggestions.md`](docs/reference/agent-workflow-and-suggestions.md)
|
|
226
|
+
- [`docs/reference/scope-aware-selection-tooling.md`](docs/reference/scope-aware-selection-tooling.md)
|
|
227
|
+
- [`docs/reference/glossary.md`](docs/reference/glossary.md)
|
|
228
|
+
- [`docs/reference/editor-integration-style.md`](docs/reference/editor-integration-style.md)
|
|
229
|
+
- [`docs/reference/ui-theming-and-styling.md`](docs/reference/ui-theming-and-styling.md)
|
|
230
|
+
- [`docs/reference/beyondwork-runtime-environment.md`](docs/reference/beyondwork-runtime-environment.md)
|
|
231
|
+
|
|
232
|
+
Wrapper and agent docs stay on `main`, but they are downstream integration guidance rather than the canonical package contract.
|
|
233
|
+
|
|
234
|
+
Current integration honesty:
|
|
235
|
+
|
|
236
|
+
- the shipped React host surface is still `WordReviewEditor`
|
|
237
|
+
- `showReviewPanel={false}` only hides the bundled review rail
|
|
238
|
+
- a distinct public chromeless React surface is not yet shipped
|
|
239
|
+
- V3 is additive — React editor mounts keep using V1/V2; V3 is for stateless services, out-of-process agents, workblocks, and debug consumers
|
|
240
|
+
- `ai.*` mutations (`resolveReference`, `getScopeBundle`, `proposeReplacementScope`, `validateReplacementScope`, `applyReplacementScope`, `attachExplanation`, `createIssue`) are **mock today** pending Phase 4 (scope compiler) and Phase 5 (replacement lifecycle) — the shape is stable, the behavior will be replaced
|
|
241
|
+
|
|
242
|
+
### Maintainer Path
|
|
243
|
+
|
|
244
|
+
- [`docs/maintainers/README.md`](docs/maintainers/README.md)
|
|
245
|
+
- [`docs/ccep-contract-templates/README.md`](docs/ccep-contract-templates/README.md)
|
|
246
|
+
- [`docs/plans/README.md`](docs/plans/README.md) — current plans/audits router + archived execution history
|
|
247
|
+
|
|
248
|
+
The CCEP corpus is kept on `main` as a maintainer-safe smoke-doc source set for agreement-heavy validation and wrapper or agent benchmarking.
|
|
249
|
+
|
|
250
|
+
### Plans and audits
|
|
251
|
+
|
|
252
|
+
`docs/plans/` now keeps only the maintainer-facing artifacts that are still in
|
|
253
|
+
scope for current audit/debug work:
|
|
254
|
+
|
|
255
|
+
- `chrome-composition-audit.md`
|
|
256
|
+
- `chrome-component-index.json`
|
|
257
|
+
- `ux-inventory.md`
|
|
258
|
+
- `scope-audit.md` + `scope-audit-classification.json`
|
|
259
|
+
- `debug-tooling-v2-architecture.md`
|
|
260
|
+
- `doc-debug-stack.md`
|
|
261
|
+
- `doc-debug-test-protocol.md`
|
|
262
|
+
|
|
263
|
+
Closed lane and one-off execution plans now live under
|
|
264
|
+
`docs/plans/archived/`. The `gap-analysis/` and `v2/`
|
|
265
|
+
subdirectories remain only where current docs or generators still depend on
|
|
266
|
+
them. (The former `lane-prompts` subdirectory was deleted on 2026-04-21;
|
|
267
|
+
its content was consolidated into `docs/wiki/shipped-history.md`.)
|
|
268
|
+
|
|
269
|
+
### Technical Wiki
|
|
270
|
+
|
|
271
|
+
[`docs/wiki/`](docs/wiki/) is the feature-by-feature technical layer (40+ topics covering OOXML, ProseMirror, runtime, debug, and platform). Enter via [`docs/README.md`](docs/README.md). Highlights:
|
|
272
|
+
|
|
273
|
+
- `api-v1` · `api-v2` · `api-v3` — the three public API generations, delineated
|
|
274
|
+
- `runtime-truth` — the canonical-truth narrative every agent should internalize
|
|
275
|
+
- `debugging` + `doc-debug-service-guide` — runtime debug substrate + HTTPS debug service
|
|
276
|
+
- `chrome-composition` + `chrome-composition-backlog` — UX composition audit split: shipped vs outstanding
|
|
277
|
+
- `testing-and-fixtures` — F01–F52 corpus + CCEP 629-doc corpus + truth-baseline numbers + MCP tooling map
|
|
278
|
+
|
|
279
|
+
## Agent Integration
|
|
280
|
+
|
|
281
|
+
Agents consume the editor through a consistent pipeline. Two entry shapes, same pipeline:
|
|
282
|
+
|
|
283
|
+
**V1/V2 ref (React editor mounts):**
|
|
284
|
+
```
|
|
285
|
+
inspect --> locate --> mutate --> validate --> export
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**V3 `createApiV3(runtime)` (stateless services, workblocks, out-of-process agents):**
|
|
289
|
+
```
|
|
290
|
+
inspect --> listScopes --> getScopeBundle --> propose --> validate --> apply --> export
|
|
291
|
+
└── mock today (Phase 4/5) ──┘
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Beyond Work platform agents extend the V1/V2 pipeline with propose/approve gates:
|
|
295
|
+
```
|
|
296
|
+
inspect --> locate --> propose --> approve --> mutate --> validate --> export
|
|
24
297
|
```
|
|
25
298
|
|
|
26
|
-
|
|
299
|
+
The package exposes purpose-built read models — not a monolithic state dump. V1 getters and V3 equivalents dispatch through the same runtime:
|
|
300
|
+
|
|
301
|
+
| Read model | V1 getter | V3 equivalent | Purpose |
|
|
302
|
+
|---|---|---|---|
|
|
303
|
+
| Document surface | `getRenderSnapshot()` | (composed via `runtime.content.*`) | Structure, blocks, text, selection |
|
|
304
|
+
| Comments | `getComments()` | `api.runtime.review.getComments()` (partial) | Thread state, resolution, anchors |
|
|
305
|
+
| Tracked changes | `getTrackedChanges()` | `api.runtime.review.getChanges()` (partial) | Revision inventory, actionability |
|
|
306
|
+
| Workflow scope | `getWorkflowScopeSnapshot()` | `api.runtime.workflow.queryScopes()` (live) | Scope boundaries, work items, mode |
|
|
307
|
+
| Interaction guard | `getInteractionGuardSnapshot()` | `api.runtime.workflow.getGuard()` (live) | Effective mode, blocked reasons |
|
|
308
|
+
| Compatibility | `getCompatibilityReport()` | `api.runtime.document.validate()` (live-with-adapter) | Export risk assessment |
|
|
309
|
+
| Warnings | `getWarnings()` | (bundled into `validate()`) | Active fidelity or mutation warnings |
|
|
310
|
+
| Layout | `ref.layout.getPage(...)` | `api.runtime.layout.getPage(...)` (live-with-adapter) | Page/block layout reads |
|
|
311
|
+
| Debug snapshot | `runtime.debug.getSnapshot()` | same (shared projector) | Canonical/workflow/review/scope/layout one-shot JSON |
|
|
312
|
+
|
|
313
|
+
Agents should read the narrow snapshot that answers the next question, not pull the full render snapshot for every decision. Every V3 `uiVisible` call emits exactly one `UxResponse` on the `api` telemetry channel — subscribe via `docs/wiki/debugging.md` (navigate through `docs/README.md`) for mock-or-live traces.
|
|
314
|
+
|
|
315
|
+
## Packaging And Release
|
|
316
|
+
|
|
317
|
+
- `.github/workflows/publish.yml` publishes on `v*` tags after verifying the tag matches `package.json`
|
|
318
|
+
- `pnpm pack --dry-run` is the baseline package proof
|
|
319
|
+
- npm provenance is enabled in `publishConfig` and in the publish workflow invocation
|
|
320
|
+
- the published package currently ships source ESM entry points plus TypeScript source-backed `types` exports
|
|
321
|
+
- the Microsoft Open XML SDK remains CI/internal-service only, never part of the shipped browser runtime
|
|
322
|
+
|
|
323
|
+
## Contribution Rules
|
|
324
|
+
|
|
325
|
+
- respect the runtime-owned state model
|
|
326
|
+
- do not bypass commands and transactions
|
|
327
|
+
- do not treat the DOM as canonical state
|
|
328
|
+
- do not silently drop unknown OOXML
|
|
329
|
+
- keep docs honest about shipped versus planned behavior
|
|
330
|
+
- add or extend fixtures for compatibility-critical changes
|
|
331
|
+
- `bash scripts/validate-fixtures.sh` now uses the Railway validator service and requires `OPENXML_VALIDATOR_AUTH_TOKEN` when hitting the public domain
|
|
332
|
+
- `node scripts/validate-docs-navigation.mjs` enforces the core docs catalog, required indexes, and frontmatter contract
|
|
333
|
+
|
|
334
|
+
## Guiding Principle
|
|
335
|
+
|
|
336
|
+
This repo builds backoffice work components where humans and AI agents are first-class peers. Every capability is designed for both audiences from the start — not bolted on for one after the other. The result is tools that are powerful enough for complex enterprise workflows and structured enough that an agent can operate them safely and predictably.
|
|
337
|
+
|
|
338
|
+
## Using the package
|
|
339
|
+
|
|
340
|
+
### WordReviewEditor
|
|
341
|
+
|
|
342
|
+
`WordReviewEditor` is a React component for loading, editing, and exporting `.docx` files with full comment and tracked-change (redline) support. It is exported from `@beyondwork/docx-react-component`.
|
|
27
343
|
|
|
28
|
-
|
|
344
|
+
### Installation
|
|
29
345
|
|
|
30
346
|
```tsx
|
|
31
|
-
import {
|
|
32
|
-
|
|
347
|
+
import {
|
|
348
|
+
WordReviewEditor,
|
|
349
|
+
type WordReviewEditorRef,
|
|
350
|
+
type WordReviewEditorProps,
|
|
351
|
+
type WordReviewEditorEvent,
|
|
352
|
+
} from "@beyondwork/docx-react-component";
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Basic mount
|
|
356
|
+
|
|
357
|
+
```tsx
|
|
358
|
+
import { useRef } from "react";
|
|
359
|
+
import { WordReviewEditor, type WordReviewEditorRef } from "@beyondwork/docx-react-component";
|
|
360
|
+
|
|
361
|
+
export function MyEditor({ docxBytes }: { docxBytes: Uint8Array }) {
|
|
362
|
+
const editorRef = useRef<WordReviewEditorRef>(null);
|
|
33
363
|
|
|
34
|
-
export function App() {
|
|
35
364
|
return (
|
|
36
365
|
<WordReviewEditor
|
|
37
|
-
|
|
38
|
-
|
|
366
|
+
ref={editorRef}
|
|
367
|
+
documentId="doc-001"
|
|
368
|
+
currentUser={{ userId: "u1", displayName: "Alice" }}
|
|
369
|
+
initialDocx={docxBytes}
|
|
370
|
+
onEvent={(event) => console.log(event)}
|
|
39
371
|
/>
|
|
40
372
|
);
|
|
41
373
|
}
|
|
42
374
|
```
|
|
43
375
|
|
|
44
|
-
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
### Props reference
|
|
379
|
+
|
|
380
|
+
| Prop | Type | Description |
|
|
381
|
+
|---|---|---|
|
|
382
|
+
| `documentId` | `string` | **Required.** Stable identifier for this document. |
|
|
383
|
+
| `currentUser` | `EditorUser` | **Required.** The user performing edits and adding comments. |
|
|
384
|
+
| `initialDocx` | `Uint8Array \| ArrayBuffer` | Raw `.docx` bytes to load on first mount. |
|
|
385
|
+
| `initialSessionState` | `EditorSessionState` | Previously saved session state to restore. |
|
|
386
|
+
| `initialSnapshot` | `PersistedEditorSnapshot` | Previously saved snapshot to restore. |
|
|
387
|
+
| `externalDocSource` | `ExternalDocumentSource` | Alternative source with explicit `kind` (`"docx"`, `"session"`, `"snapshot"`). |
|
|
388
|
+
| `readOnly` | `boolean` | When `true`, all editing commands are disabled. |
|
|
389
|
+
| `reviewMode` | `"editing" \| "review"` | Shell layout hint — affects toolbar/panel arrangement but not editing authority. |
|
|
390
|
+
| `markupDisplay` | `"clean" \| "simple" \| "all"` | Controls tracked-change visibility. |
|
|
391
|
+
| `showReviewPanel` | `boolean` | Shows or hides the right-side comment and tracked-change panel. |
|
|
392
|
+
| `autosave` | `AutosaveConfig` | Enables automatic saving. |
|
|
393
|
+
| `hostAdapter` | `EditorHostAdapter` | Callbacks for `load`, `saveSession`, `saveExport`. |
|
|
394
|
+
| `datastore` | `EditorDatastoreAdapter` | Alternative persistence adapter with `load`, `saveSnapshot`. |
|
|
395
|
+
| `onEvent` | `(event: WordReviewEditorEvent) => void` | Unified event handler (see [Events](#events)). |
|
|
396
|
+
| `onWarning` | `(warning: EditorWarning) => void` | Fired for non-fatal warnings. |
|
|
397
|
+
| `onError` | `(error: EditorError) => void` | Fired for fatal errors. |
|
|
398
|
+
|
|
399
|
+
#### EditorUser
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
interface EditorUser {
|
|
403
|
+
userId: string;
|
|
404
|
+
displayName: string;
|
|
405
|
+
email?: string;
|
|
406
|
+
avatarUrl?: string;
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### AutosaveConfig
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
interface AutosaveConfig {
|
|
414
|
+
enabled?: boolean;
|
|
415
|
+
debounceMs?: number; // default: 2000
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
### Show / hide UI regions
|
|
422
|
+
|
|
423
|
+
#### Review panel
|
|
424
|
+
|
|
425
|
+
The right-side panel lists comment threads and tracked changes.
|
|
426
|
+
|
|
427
|
+
```tsx
|
|
428
|
+
<WordReviewEditor showReviewPanel={false} ... /> // hide panel
|
|
429
|
+
<WordReviewEditor showReviewPanel={true} ... /> // show panel (default)
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
#### Tracked-change display mode
|
|
433
|
+
|
|
434
|
+
`markupDisplay` controls how tracked changes appear in the document body.
|
|
435
|
+
|
|
436
|
+
| Value | Behaviour |
|
|
437
|
+
|---|---|
|
|
438
|
+
| `"clean"` | Show the accepted version — insertions visible, deletions hidden. |
|
|
439
|
+
| `"simple"` | Show a simplified view of changes without inline markup. |
|
|
440
|
+
| `"all"` | Show all insertion and deletion marks inline (Word's "Show Markup" mode). |
|
|
441
|
+
|
|
442
|
+
```tsx
|
|
443
|
+
<WordReviewEditor markupDisplay="clean" ... />
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
You can also change the display mode at runtime:
|
|
45
447
|
|
|
46
448
|
```ts
|
|
47
|
-
|
|
48
|
-
|
|
449
|
+
// no ref method for markupDisplay — pass as a prop; React re-renders propagate the change.
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
#### Document mode
|
|
453
|
+
|
|
454
|
+
`DocumentMode` controls editing authority, not just appearance.
|
|
455
|
+
|
|
456
|
+
| Mode | Effect |
|
|
457
|
+
|---|---|
|
|
458
|
+
| `"editing"` | Edits are applied directly (no tracking). |
|
|
459
|
+
| `"suggesting"` | Every edit is automatically wrapped in a tracked change. |
|
|
460
|
+
| `"viewing"` | Document is read-only regardless of the `readOnly` prop. |
|
|
461
|
+
|
|
462
|
+
Set via ref:
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
editorRef.current.setDocumentMode("suggesting");
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
Or pass `reviewMode="review"` as a prop to start in a review-friendly shell layout (the component internally maps this to `"suggesting"` document mode).
|
|
469
|
+
|
|
470
|
+
#### Read-only mode
|
|
49
471
|
|
|
50
|
-
|
|
51
|
-
|
|
472
|
+
```tsx
|
|
473
|
+
<WordReviewEditor readOnly={true} ... />
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
All editing, commenting, and tracked-change commands are blocked. The toolbar is still rendered but all buttons are disabled.
|
|
52
477
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
478
|
+
#### Workspace layout
|
|
479
|
+
|
|
480
|
+
```ts
|
|
481
|
+
editorRef.current.setWorkspaceMode("canvas"); // continuous scroll
|
|
482
|
+
editorRef.current.setWorkspaceMode("page"); // paginated view
|
|
56
483
|
```
|
|
57
484
|
|
|
58
|
-
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
### Imperative ref
|
|
488
|
+
|
|
489
|
+
Obtain the ref via `useRef<WordReviewEditorRef>()`:
|
|
490
|
+
|
|
491
|
+
```tsx
|
|
492
|
+
const editorRef = useRef<WordReviewEditorRef>(null);
|
|
493
|
+
<WordReviewEditor ref={editorRef} ... />
|
|
494
|
+
|
|
495
|
+
// then:
|
|
496
|
+
editorRef.current?.addComment({ body: "Needs revision" });
|
|
497
|
+
```
|
|
59
498
|
|
|
60
499
|
---
|
|
61
500
|
|
|
62
|
-
|
|
501
|
+
### Comment operations
|
|
502
|
+
|
|
503
|
+
#### Add a comment
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
addComment(params: AddCommentParams): string
|
|
507
|
+
// returns the new commentId
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
```ts
|
|
511
|
+
interface AddCommentParams {
|
|
512
|
+
anchor?: EditorAnchorProjection; // defaults to the current selection
|
|
513
|
+
body?: string;
|
|
514
|
+
authorId?: string; // defaults to currentUser.userId
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**Important**: if you want the comment to land on a specific text selection, capture the anchor *before* opening any draft UI (e.g. a modal or popover), because opening a modal typically collapses the editor selection.
|
|
519
|
+
|
|
520
|
+
```ts
|
|
521
|
+
// 1. Capture anchor while text is still selected
|
|
522
|
+
const snapshot = editorRef.current.getRenderSnapshot();
|
|
523
|
+
const anchor = snapshot.selection.activeRange;
|
|
524
|
+
|
|
525
|
+
// 2. Open your draft UI, let user type a message...
|
|
526
|
+
// 3. On submit:
|
|
527
|
+
const commentId = editorRef.current.addComment({ anchor, body: draftText });
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
#### Resolve a comment
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
editorRef.current.resolveComment(commentId);
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Marks the thread as resolved. The comment remains in the document and can be exported; it is moved to the resolved list in the sidebar.
|
|
537
|
+
|
|
538
|
+
#### Reopen a resolved comment
|
|
539
|
+
|
|
540
|
+
```ts
|
|
541
|
+
editorRef.current.reopenComment(commentId);
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
#### Delete a comment permanently
|
|
545
|
+
|
|
546
|
+
```ts
|
|
547
|
+
editorRef.current.deleteComment(commentId);
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Removes the comment entirely. Use this to clean up failed or unwanted drafts.
|
|
551
|
+
|
|
552
|
+
#### Add a reply to an existing thread
|
|
553
|
+
|
|
554
|
+
```ts
|
|
555
|
+
editorRef.current.addCommentReply(commentId, "Reply text here");
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
#### Edit a comment body
|
|
559
|
+
|
|
560
|
+
```ts
|
|
561
|
+
editorRef.current.editCommentBody(commentId, "Updated text");
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
#### Scroll to a comment
|
|
63
565
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
- Comments — anchored, threaded, mention-aware, with presentation/negotiation layer
|
|
68
|
-
- Workflow overlays and scope-based editing guards
|
|
69
|
-
- Document comparison (LCS-backed diffing + redline export)
|
|
566
|
+
```ts
|
|
567
|
+
editorRef.current.scrollToComment(commentId);
|
|
568
|
+
```
|
|
70
569
|
|
|
71
|
-
|
|
72
|
-
- OOXML round-trip — open, edit, export, reopen in Word without repair prompts
|
|
73
|
-
- Preserve-first handling for unknown fragments (opaque preservation)
|
|
74
|
-
- Layout engine — **97.77 %** external layout-structure truth and **96.34 %** rendered-word geometry on the CCEP contract corpus
|
|
75
|
-
- Charts v1 — native SVG renderer for 7 chart families, progressive time-sliced
|
|
570
|
+
#### Open (focus) a comment in the sidebar
|
|
76
571
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
572
|
+
```ts
|
|
573
|
+
editorRef.current.openComment(commentId);
|
|
574
|
+
```
|
|
80
575
|
|
|
81
|
-
|
|
82
|
-
- **V1/V2 ref-method API** — stable React imperative surface on `WordReviewEditorRef`
|
|
83
|
-
- **V3 stateless API** — `createApiV3(runtime)` exposes Runtime + AI families under a `live` / `live-with-adapter` / `partial` / `mock` taxonomy
|
|
84
|
-
- AI action policy — risk-classified gating for 37 discrete agent operations
|
|
85
|
-
- Every `uiVisible` V3 function emits exactly one `UxResponse` on the `api` telemetry channel — the stream the debug surfaces consume
|
|
576
|
+
#### Get all comments
|
|
86
577
|
|
|
87
|
-
|
|
88
|
-
|
|
578
|
+
```ts
|
|
579
|
+
const sidebar: CommentSidebarSnapshot = editorRef.current.getComments();
|
|
580
|
+
```
|
|
89
581
|
|
|
90
|
-
|
|
582
|
+
```ts
|
|
583
|
+
interface CommentSidebarSnapshot {
|
|
584
|
+
activeCommentId?: string;
|
|
585
|
+
openCommentIds: string[];
|
|
586
|
+
resolvedCommentIds: string[];
|
|
587
|
+
detachedCommentIds: string[];
|
|
588
|
+
totalCount: number;
|
|
589
|
+
threads: CommentSidebarThreadSnapshot[];
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
interface CommentSidebarThreadSnapshot {
|
|
593
|
+
commentId: string;
|
|
594
|
+
status: "open" | "resolved" | "detached";
|
|
595
|
+
anchor: EditorAnchorProjection;
|
|
596
|
+
excerpt: string; // the anchored text snippet
|
|
597
|
+
entries: CommentSidebarThreadEntrySnapshot[];
|
|
598
|
+
entryCount: number;
|
|
599
|
+
createdAt: string; // ISO 8601
|
|
600
|
+
createdBy: string; // userId
|
|
601
|
+
resolvedAt?: string;
|
|
602
|
+
resolvedBy?: string;
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
#### Detached comments
|
|
607
|
+
|
|
608
|
+
A comment becomes **detached** when the text it was anchored to is deleted. Detached comments:
|
|
609
|
+
|
|
610
|
+
- Still appear in `sidebar.detachedCommentIds` and have `status: "detached"`.
|
|
611
|
+
- Have `anchor.kind === "detached"` with a `lastKnownRange` and a `reason` (`"deleted"`, `"invalidatedByStructureChange"`, or `"importAmbiguity"`).
|
|
612
|
+
- Do **not** block DOCX export.
|
|
613
|
+
- Can be resolved, reopened, or deleted via the same methods above.
|
|
91
614
|
|
|
92
615
|
---
|
|
93
616
|
|
|
94
|
-
|
|
617
|
+
### Tracked-change operations
|
|
618
|
+
|
|
619
|
+
#### Get all tracked changes
|
|
620
|
+
|
|
621
|
+
```ts
|
|
622
|
+
const changes: TrackedChangesSnapshot = editorRef.current.getTrackedChanges();
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
```ts
|
|
626
|
+
interface TrackedChangesSnapshot {
|
|
627
|
+
pendingChangeIds: string[];
|
|
628
|
+
acceptedChangeIds: string[];
|
|
629
|
+
rejectedChangeIds: string[];
|
|
630
|
+
detachedChangeIds: string[];
|
|
631
|
+
actionableChangeIds: string[];
|
|
632
|
+
preserveOnlyChangeIds: string[];
|
|
633
|
+
totalCount: number;
|
|
634
|
+
revisions: TrackedChangeEntrySnapshot[];
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
interface TrackedChangeEntrySnapshot {
|
|
638
|
+
revisionId: string;
|
|
639
|
+
kind: "insertion" | "deletion" | "formatting" | "move" | "property-change";
|
|
640
|
+
status: "active" | "accepted" | "rejected" | "detached";
|
|
641
|
+
actionability: "actionable" | "preserve-only";
|
|
642
|
+
canAccept: boolean;
|
|
643
|
+
canReject: boolean;
|
|
644
|
+
anchor: EditorAnchorProjection;
|
|
645
|
+
anchorLabel: string;
|
|
646
|
+
excerpt?: string;
|
|
647
|
+
detail?: string;
|
|
648
|
+
authorId: string;
|
|
649
|
+
createdAt: string;
|
|
650
|
+
}
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
`preserve-only` revisions (`formatting`, `move`) can be displayed but cannot be individually accepted or rejected through the API.
|
|
654
|
+
|
|
655
|
+
#### Accept a single change
|
|
656
|
+
|
|
657
|
+
```ts
|
|
658
|
+
editorRef.current.acceptChange(revisionId);
|
|
659
|
+
```
|
|
95
660
|
|
|
96
|
-
|
|
661
|
+
#### Reject a single change
|
|
97
662
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
4. `EditorSurfaceSnapshot` + PM state + DOM
|
|
663
|
+
```ts
|
|
664
|
+
editorRef.current.rejectChange(revisionId);
|
|
665
|
+
```
|
|
102
666
|
|
|
103
|
-
|
|
667
|
+
#### Accept all pending changes
|
|
104
668
|
|
|
105
|
-
|
|
669
|
+
```ts
|
|
670
|
+
editorRef.current.acceptAllChanges();
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
#### Reject all pending changes
|
|
674
|
+
|
|
675
|
+
```ts
|
|
676
|
+
editorRef.current.rejectAllChanges();
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
#### Scroll to a tracked change
|
|
680
|
+
|
|
681
|
+
```ts
|
|
682
|
+
editorRef.current.scrollToRevision(revisionId);
|
|
683
|
+
```
|
|
106
684
|
|
|
107
685
|
---
|
|
108
686
|
|
|
109
|
-
|
|
687
|
+
### Export
|
|
110
688
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
| Known issues | [`KNOWN-ISSUES.md`](KNOWN-ISSUES.md), [`KNOWN_ISSUES.md`](KNOWN_ISSUES.md), [`KNOWN-ISSUES-VISUAL.md`](KNOWN-ISSUES-VISUAL.md) | Preview caveats + visual-fidelity gap inventory |
|
|
118
|
-
| Changelog | [`CHANGELOG.md`](CHANGELOG.md) | Release notes |
|
|
689
|
+
```ts
|
|
690
|
+
const result = await editorRef.current.exportDocx({ fileName: "output.docx" });
|
|
691
|
+
// result.bytes is the Uint8Array of the .docx file
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
Export will throw if any non-detached comment no longer maps to a serializable range in the document. To diagnose, check `getComments().threads` for entries where `status !== "detached"` but whose `anchor.kind` is unexpected.
|
|
119
695
|
|
|
120
696
|
---
|
|
121
697
|
|
|
122
|
-
|
|
698
|
+
### Events
|
|
123
699
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
700
|
+
All events are dispatched through the single `onEvent` prop. The `type` discriminator narrows the payload.
|
|
701
|
+
|
|
702
|
+
```tsx
|
|
703
|
+
<WordReviewEditor
|
|
704
|
+
onEvent={(event) => {
|
|
705
|
+
if (event.type === "comment_added") {
|
|
706
|
+
console.log("New comment:", event.commentId);
|
|
707
|
+
}
|
|
708
|
+
}}
|
|
709
|
+
/>
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
#### `ready`
|
|
713
|
+
|
|
714
|
+
Fired once after the document finishes loading and the editor is interactive.
|
|
715
|
+
|
|
716
|
+
```ts
|
|
717
|
+
{
|
|
718
|
+
type: "ready";
|
|
719
|
+
documentId: string;
|
|
720
|
+
sessionId: string;
|
|
721
|
+
source: "docx" | "session" | "snapshot";
|
|
722
|
+
stats: DocumentStats; // storyLength, commentCount, revisionCount
|
|
723
|
+
compatibility: CompatibilityReport;
|
|
724
|
+
comments: CommentSidebarSnapshot;
|
|
725
|
+
trackedChanges: TrackedChangesSnapshot;
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
#### `comment_added`
|
|
730
|
+
|
|
731
|
+
Fired when a new comment thread is created (via `addComment` or the toolbar).
|
|
732
|
+
|
|
733
|
+
```ts
|
|
734
|
+
{
|
|
735
|
+
type: "comment_added";
|
|
736
|
+
documentId: string;
|
|
737
|
+
commentId: string;
|
|
738
|
+
anchor: EditorAnchorProjection;
|
|
739
|
+
}
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
#### `comment_resolved`
|
|
743
|
+
|
|
744
|
+
Fired when a comment is resolved (via `resolveComment` or the sidebar).
|
|
745
|
+
|
|
746
|
+
```ts
|
|
747
|
+
{
|
|
748
|
+
type: "comment_resolved";
|
|
749
|
+
documentId: string;
|
|
750
|
+
commentId: string;
|
|
751
|
+
}
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
For a general-purpose "something about comments changed" signal (covering deletions, reply appends, body edits, reopen, and every other mutation — including Yjs-driven mutations that route through the editor's imperative ref), subscribe to `comments_changed`.
|
|
755
|
+
|
|
756
|
+
#### `comments_changed`
|
|
757
|
+
|
|
758
|
+
Fired once per commit whenever the runtime's comment snapshot differs from the previous commit. Use this when you render comments in a sibling component and need to know when to re-fetch via `editorRef.current.getComments()`.
|
|
759
|
+
|
|
760
|
+
```ts
|
|
761
|
+
{
|
|
762
|
+
type: "comments_changed";
|
|
763
|
+
documentId: string;
|
|
764
|
+
changedCommentIds: string[]; // always non-empty
|
|
765
|
+
}
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
Covers: `addComment`, `deleteComment`, `resolveComment`, `reopenComment`, `addCommentReply`, `editCommentBody`, and any remote-origin mutation that funnels through the same runtime APIs. Does NOT fire on selection/focus changes.
|
|
769
|
+
|
|
770
|
+
#### `change_accepted`
|
|
771
|
+
|
|
772
|
+
Fired when a tracked change is accepted.
|
|
773
|
+
|
|
774
|
+
```ts
|
|
775
|
+
{
|
|
776
|
+
type: "change_accepted";
|
|
777
|
+
documentId: string;
|
|
778
|
+
changeId: string;
|
|
779
|
+
}
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
#### `change_rejected`
|
|
783
|
+
|
|
784
|
+
Fired when a tracked change is rejected.
|
|
785
|
+
|
|
786
|
+
```ts
|
|
787
|
+
{
|
|
788
|
+
type: "change_rejected";
|
|
789
|
+
documentId: string;
|
|
790
|
+
changeId: string;
|
|
791
|
+
}
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
#### `selection_changed`
|
|
795
|
+
|
|
796
|
+
Fired whenever the editor cursor or selection changes.
|
|
797
|
+
|
|
798
|
+
```ts
|
|
799
|
+
{
|
|
800
|
+
type: "selection_changed";
|
|
801
|
+
documentId: string;
|
|
802
|
+
selection: SelectionSnapshot;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
interface SelectionSnapshot {
|
|
806
|
+
anchor: number;
|
|
807
|
+
head: number;
|
|
808
|
+
isCollapsed: boolean;
|
|
809
|
+
activeRange: EditorAnchorProjection;
|
|
810
|
+
storyTarget?: EditorStoryTarget;
|
|
811
|
+
}
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
Use `selection.activeRange` as the `anchor` argument to `addComment` — but capture it *before* opening any modal UI.
|
|
815
|
+
|
|
816
|
+
#### `dirty_changed`
|
|
817
|
+
|
|
818
|
+
Fired when the document transitions between clean and dirty (unsaved) states.
|
|
819
|
+
|
|
820
|
+
```ts
|
|
821
|
+
{
|
|
822
|
+
type: "dirty_changed";
|
|
823
|
+
documentId: string;
|
|
824
|
+
isDirty: boolean;
|
|
825
|
+
}
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
#### `story_changed`
|
|
829
|
+
|
|
830
|
+
Fired when the user navigates between document stories (e.g. main body -> header/footer).
|
|
831
|
+
|
|
832
|
+
```ts
|
|
833
|
+
{
|
|
834
|
+
type: "story_changed";
|
|
835
|
+
documentId: string;
|
|
836
|
+
activeStory: EditorStoryTarget;
|
|
837
|
+
}
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
#### `export_completed`
|
|
841
|
+
|
|
842
|
+
Fired after a successful `exportDocx` call, after the host `saveExport` callback (if any) has resolved.
|
|
843
|
+
|
|
844
|
+
```ts
|
|
845
|
+
{
|
|
846
|
+
type: "export_completed";
|
|
847
|
+
documentId: string;
|
|
848
|
+
result: ExportResult; // result.bytes, result.fileName, result.mimeType
|
|
849
|
+
}
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
#### `session_saved`
|
|
853
|
+
|
|
854
|
+
Fired after the host `saveSession` callback resolves.
|
|
855
|
+
|
|
856
|
+
```ts
|
|
857
|
+
{
|
|
858
|
+
type: "session_saved";
|
|
859
|
+
documentId: string;
|
|
860
|
+
sessionState: EditorSessionState;
|
|
861
|
+
savedAt: string;
|
|
862
|
+
isAutosave: boolean;
|
|
863
|
+
}
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
#### `snapshot_saved`
|
|
867
|
+
|
|
868
|
+
Fired after the datastore `saveSnapshot` callback resolves.
|
|
869
|
+
|
|
870
|
+
```ts
|
|
871
|
+
{
|
|
872
|
+
type: "snapshot_saved";
|
|
873
|
+
documentId: string;
|
|
874
|
+
snapshot: PersistedEditorSnapshot;
|
|
875
|
+
isAutosave: boolean;
|
|
876
|
+
}
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
#### `autosave_state`
|
|
880
|
+
|
|
881
|
+
Fired when the autosave lifecycle transitions.
|
|
882
|
+
|
|
883
|
+
```ts
|
|
884
|
+
{
|
|
885
|
+
type: "autosave_state";
|
|
886
|
+
documentId: string;
|
|
887
|
+
state: "idle" | "pending" | "saving" | "saved" | "error";
|
|
888
|
+
}
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
#### `warning_added` / `warning_cleared`
|
|
892
|
+
|
|
893
|
+
Non-fatal import or rendering warnings.
|
|
894
|
+
|
|
895
|
+
```ts
|
|
896
|
+
{ type: "warning_added"; documentId: string; warning: EditorWarning; }
|
|
897
|
+
{ type: "warning_cleared"; documentId: string; warningId: string; code: EditorWarningCode; }
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
#### `error`
|
|
901
|
+
|
|
902
|
+
Fatal editor error. The editor may be in an unrecoverable state after this event.
|
|
903
|
+
|
|
904
|
+
```ts
|
|
905
|
+
{
|
|
906
|
+
type: "error";
|
|
907
|
+
documentId: string;
|
|
908
|
+
error: EditorError; // error.code, error.message, error.isFatal
|
|
909
|
+
}
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
#### Workflow events
|
|
913
|
+
|
|
914
|
+
These events relate to the optional workflow overlay feature.
|
|
129
915
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
916
|
+
```ts
|
|
917
|
+
{ type: "workflow_overlay_changed"; documentId: string; snapshot: WorkflowScopeSnapshot; }
|
|
918
|
+
{ type: "workflow_active_work_item_changed"; documentId: string; activeWorkItemId: string | null; }
|
|
919
|
+
{ type: "command_blocked"; documentId: string; command: string; reasons: WorkflowBlockedCommandReason[]; }
|
|
920
|
+
```
|
|
133
921
|
|
|
134
922
|
---
|
|
135
923
|
|
|
136
|
-
|
|
924
|
+
### Key types
|
|
925
|
+
|
|
926
|
+
#### EditorAnchorProjection
|
|
137
927
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
928
|
+
Describes a position or range in the document. Returned by `selection.activeRange` and stored on comments/revisions.
|
|
929
|
+
|
|
930
|
+
```ts
|
|
931
|
+
type EditorAnchorProjection =
|
|
932
|
+
| { kind: "range"; from: number; to: number; assoc: { start: -1|1; end: -1|1 } }
|
|
933
|
+
| { kind: "node"; at: number; assoc: -1|1 }
|
|
934
|
+
| { kind: "detached"; lastKnownRange: { from: number; to: number };
|
|
935
|
+
reason: "deleted" | "invalidatedByStructureChange" | "importAmbiguity" };
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
A `"range"` anchor is required for `addComment` if the document contains tables or the selection spans multiple characters. The `from`/`to` positions are in the editor's internal runtime coordinate space — always capture them from `getRenderSnapshot().selection.activeRange`, never construct them manually.
|
|
939
|
+
|
|
940
|
+
#### DocumentMode
|
|
941
|
+
|
|
942
|
+
```ts
|
|
943
|
+
type DocumentMode = "editing" | "suggesting" | "viewing";
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
#### WorkspaceMode
|
|
947
|
+
|
|
948
|
+
```ts
|
|
949
|
+
type WorkspaceMode = "canvas" | "page";
|
|
950
|
+
```
|
|
141
951
|
|
|
142
952
|
---
|
|
143
953
|
|
|
144
|
-
|
|
954
|
+
### Common patterns
|
|
955
|
+
|
|
956
|
+
#### Full add-comment flow with custom UI
|
|
957
|
+
|
|
958
|
+
```tsx
|
|
959
|
+
function CommentButton({ editorRef }: { editorRef: React.RefObject<WordReviewEditorRef> }) {
|
|
960
|
+
const [draft, setDraft] = useState<{ anchor: EditorAnchorProjection; text: string } | null>(null);
|
|
961
|
+
|
|
962
|
+
function openDraft() {
|
|
963
|
+
// Capture anchor BEFORE the modal steals focus from the editor
|
|
964
|
+
const snapshot = editorRef.current?.getRenderSnapshot();
|
|
965
|
+
if (!snapshot) return;
|
|
966
|
+
const anchor = snapshot.selection.activeRange;
|
|
967
|
+
if (anchor.kind !== "range" || anchor.from === anchor.to) return;
|
|
968
|
+
setDraft({ anchor, text: "" });
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function submit() {
|
|
972
|
+
if (!draft) return;
|
|
973
|
+
editorRef.current?.addComment({ anchor: draft.anchor, body: draft.text });
|
|
974
|
+
setDraft(null);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
return (
|
|
978
|
+
<>
|
|
979
|
+
<button onClick={openDraft}>Comment</button>
|
|
980
|
+
{draft && (
|
|
981
|
+
<dialog open>
|
|
982
|
+
<textarea value={draft.text} onChange={(e) => setDraft({ ...draft, text: e.target.value })} />
|
|
983
|
+
<button onClick={submit}>Add</button>
|
|
984
|
+
<button onClick={() => setDraft(null)}>Cancel</button>
|
|
985
|
+
</dialog>
|
|
986
|
+
)}
|
|
987
|
+
</>
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
#### Listen for comment and review-change events
|
|
145
993
|
|
|
146
|
-
|
|
994
|
+
```tsx
|
|
995
|
+
<WordReviewEditor
|
|
996
|
+
onEvent={(event) => {
|
|
997
|
+
switch (event.type) {
|
|
998
|
+
case "comment_added":
|
|
999
|
+
console.log("comment added:", event.commentId, event.anchor);
|
|
1000
|
+
break;
|
|
1001
|
+
case "comment_resolved":
|
|
1002
|
+
console.log("comment resolved:", event.commentId);
|
|
1003
|
+
break;
|
|
1004
|
+
case "comments_changed":
|
|
1005
|
+
console.log("comments changed:", event.changedCommentIds);
|
|
1006
|
+
break;
|
|
1007
|
+
case "change_accepted":
|
|
1008
|
+
console.log("change accepted:", event.changeId);
|
|
1009
|
+
break;
|
|
1010
|
+
case "change_rejected":
|
|
1011
|
+
console.log("change rejected:", event.changeId);
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
}}
|
|
1015
|
+
/>
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
#### Resolve all open comments programmatically
|
|
1019
|
+
|
|
1020
|
+
```ts
|
|
1021
|
+
const { threads } = editorRef.current.getComments();
|
|
1022
|
+
for (const thread of threads) {
|
|
1023
|
+
if (thread.status === "open") {
|
|
1024
|
+
editorRef.current.resolveComment(thread.commentId);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
#### Accept or reject all actionable changes
|
|
1030
|
+
|
|
1031
|
+
```ts
|
|
1032
|
+
editorRef.current.acceptAllChanges();
|
|
1033
|
+
// or
|
|
1034
|
+
editorRef.current.rejectAllChanges();
|
|
1035
|
+
```
|