npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sqren/docx-react-component

v1.0.190-a

Published

Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.

Downloads

1,303

Readme


title: Beyond Work Components summary: Backoffice work components landing page and primary router into consumer, wrapper, agent, and maintainer documentation. audience: consumer, wrapper, agent, maintainer stability: main-path docRole: main-path canonical: true

Beyond Work Components

CI

This repository builds backoffice work components for the Beyond Work runtime — embeddable, composable modules that handle document editing, review workflows, data processing, and task automation inside enterprise workspaces.

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.

Use This README For

  • confirming what is shipped today vs coming
  • installing the package
  • finding the right documentation lane quickly

Do not use this README as the full package contract. The canonical consumer path starts in docs/README.md, docs/reference/public-api.md (V1 + V2), docs/reference/public-api-3.md (V3 additive surface), and docs/reference/integration-guide.md.

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. Read it before designing cross-cutting changes. The technical wiki under 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.

Components

Document Editor (@beyondwork/docx-react-component)

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.

Shipped capabilities:

  • Full tracked-change and comment support (add, resolve, accept, reject) — including Lane 7b X4.a/X4.b container + range revision markers and move-promotion
  • Suggesting mode where every edit automatically becomes a tracked change
  • Round-trip OOXML preservation — open, edit, export, reopen in Word without repair prompts; preserve-first unknown handling (Lanes 7b/7c closed)
  • Workflow overlays with scope-based editing constraints, marker-backed scope identity, and three-way scope-source discrimination (marker-backed / overlay-only / detached)
  • Runtime-backed read models for document structure, review state, selection, compatibility, and fields
  • Document comparison with LCS-based diffing and redline export
  • Legal document analysis (bookmarks, cross-references, defined terms, signature blocks)
  • Real-time collaboration via Yjs — Lane 4 Phase C shipped (R1 remote-replay coalescing, R3 idle-priority abort signal, pairRuntimes migration)
  • Layout engine proven at 97.77% (615/629) external layout-structure truth and 96.34% (606/629) rendered-word geometry over the private contract corpus (see services/truth-baseline/)
  • Charts v1 — native SVG renderer for 7 chart families with progressive time-slicing (Lane 5 Stages 0–6 shipped)
  • Debug substrate Phase 0 + Phase 1 — runtime projector + 12-channel TelemetryBus + DebugInspectorSnapshot + UxResponse event stream
  • 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

Human-AI parity in practice:

  • 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
  • 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
  • The human exports via toolbar; the agent calls exportDocx() (V1) or api.runtime.document.export() (V3) after the same compatibility check
  • Workflow overlays constrain both humans and agents identically — getInteractionGuardSnapshot() / api.runtime.workflow.getGuard() is the single source of posture truth
  • The AI action policy module gates 37 discrete agent operations with risk classification and context validation
  • 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

Workblocks

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.

Categories:

  • Document processing — invoice extraction, PDF template OCR, bulk processing, email-to-action
  • AI agents — explore, flow debugging, data querying, task launching, authoring
  • Analytics — execution history, company insights, timeline queries
  • Learning — human correction capture, learning set curation, embedding management
  • Infrastructure — registry, health checks, credential management (9 types)

Human-AI parity in practice:

  • Users create workblocks through VS Code Studio with prompts and visual editing; authoring agents create them through the same manifest schema and file tools
  • Users validate invoice fields in a React form; agents validate the same fields through typed task inputs/outputs
  • Both humans and agents operate within the same workblock execution engine — same task graph, same data flow, same error handling

The Beyond Work Runtime

These components plug into a four-layer platform:

User-Facing (Layer 4)
  neui (Next.js 15), bport (BFF), MCP server, VS Code extension
       |
Workblocks (Layer 3)
  20+ composable task modules: agents, data processing, credentials
       |
Platform Services (Layer 2)
  bworker (Go), modelproxy (LiteLLM), registry, pythonworker, Temporal
       |
AWS Infrastructure (Layer 1)
  EKS, Crossplane, Terraform, PostgreSQL, Redis, Qdrant, S3, SQS

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.

Shipped vs Coming

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 §"Migration Plan" (Phases 0–7).

| Area | Shipped today | Coming | |---|---|---| | Package runtime | docx — the only shipped runtime contract | xlsx planned; pdf future work outside current package boundary | | 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) | | 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) | | Layout / geometry | External truth baseline proven at 97.77% layout / 96.34% rendered-word on 629 private corpus docs | First-class runtime-owned geometry contract for caret/hit-test/replacement envelopes | | 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/** | | External oracles | OpenXML SDK validator MCP (round-trip proof); LibreOffice-backed services/truth-baseline for layout + geometry; visual-smoke MCP for private contract templates | Phase F full oracle integration: stage-token lineage, alignment keys, known-divergence register, deterministic environment | | 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 |

The shipped primitives are correct for their respective layers; the target architecture widens the middle and tops the stack, without replacing anything below.

Install

pnpm add @beyondwork/docx-react-component react react-dom tailwindcss \
  prosemirror-commands prosemirror-keymap prosemirror-model \
  prosemirror-state prosemirror-tables prosemirror-transform prosemirror-view

Current packaging truth:

  • the package is ESM-only
  • exports point at shipped TypeScript source entry points
  • types and subpath types entries resolve to the shipped source-backed TypeScript contracts
  • consumers need a bundler or runtime that can resolve .ts and .tsx ESM imports
  • consumers should import @beyondwork/docx-react-component/ui-tailwind/theme/editor-theme.css once and provide a Tailwind v4 CSS pipeline for it
  • package and source identifiers remain docx-first until a deliberate rename lands

Shipped Product

The primary shipped surface is:

import { WordReviewEditor } from "@beyondwork/docx-react-component";

<WordReviewEditor />

WordReviewEditor remains uncontrolled by default:

  • host passes initialDocx, initialSessionState, or initialSnapshot, or provides hostAdapter.load() / datastore.load()
  • runtime owns the live working session
  • host receives events, warnings, errors, session state, compatibility snapshots, and exported artifacts

Persistence direction:

  • EditorSessionState is the canonical host-facing live-session contract
  • EditorHostAdapter is the preferred persistence boundary for load, saveSession, saveExport, and telemetry
  • EditorDatastoreAdapter remains the legacy snapshot bridge for hosts that still persist PersistedEditorSnapshot

Snapshot/export note:

  • 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
  • legacy snapshots without that provenance still load, but .docx export is intentionally blocked rather than falling back to a lossy minimal package

The current shipped ESM exports include:

  • stable default entrypoints:
    • @beyondwork/docx-react-component
    • @beyondwork/docx-react-component/public-types
    • @beyondwork/docx-react-component/ui-tailwind
    • @beyondwork/docx-react-component/legal
    • @beyondwork/docx-react-component/compare
    • @beyondwork/docx-react-component/ui-tailwind/theme/editor-theme.css
  • V3 public API (additive, Phase P' 2026-04-21):
    • @beyondwork/docx-react-component/api/v3createApiV3(runtime) + metadata primitives + UxResponse
  • advanced-supported subpaths for wrapper teams and package-adjacent integrations:
    • @beyondwork/docx-react-component/runtime/document-runtime
    • @beyondwork/docx-react-component/io/docx-session
    • @beyondwork/docx-react-component/core/commands/*
    • @beyondwork/docx-react-component/core/selection/mapping
    • @beyondwork/docx-react-component/core/state/editor-state
    • @beyondwork/docx-react-component/ui-tailwind/editor-surface/search-plugin
  • alias subpaths:
    • @beyondwork/docx-react-component/tailwind
    • @beyondwork/docx-react-component/api/public-types

Use docs/reference/public-api.md + docs/reference/public-api.manifest.json for the V1/V2 contract inventory, and 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).

Product Contract

For every component this repo ships, the standard is:

Open -> edit -> save -> reopen in the host application without damage.

For the current shipped docx implementation, that specifically means:

  • open in recent Microsoft Word without repair prompts
  • preserve supported content and review structures
  • preserve unsupported but preservable OOXML
  • remain editable in Word after export

For the normative API and host contract, use:

Documentation Map

Start Here

Main Consumer Path

Architecture & Mental Model

  • docs/architecture/00-overview.md — target end-state: 11-layer stack, Runtime/AI/UI API split, semantic-scope compiler, Phase 0–7 migration plan
  • 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/wiki/future-architecture.md — forward-looking design anchors + proof status

Wrapper And Agent Path

Wrapper and agent docs stay on main, but they are downstream integration guidance rather than the canonical package contract.

Current integration honesty:

  • the shipped React host surface is still WordReviewEditor
  • showReviewPanel={false} only hides the bundled review rail
  • a distinct public chromeless React surface is not yet shipped
  • V3 is additive — React editor mounts keep using V1/V2; V3 is for stateless services, out-of-process agents, workblocks, and debug consumers
  • 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

Maintainer Path

Private contract corpus assets are maintainer/operator inputs for agreement-heavy validation and wrapper or agent benchmarking. They should remain local or in controlled artifact storage rather than being advertised as part of the package contract.

Plans and audits

docs/plans/ now keeps only the maintainer-facing artifacts that are still in scope for current audit/debug work:

  • chrome-composition-audit.md
  • chrome-component-index.json
  • ux-inventory.md
  • scope-audit.md + scope-audit-classification.json
  • debug-tooling-v2-architecture.md
  • doc-debug-stack.md
  • doc-debug-test-protocol.md

Closed lane and one-off execution plans now live under docs/plans/archived/. The gap-analysis/ and v2/ subdirectories remain only where current docs or generators still depend on them. (The former lane-prompts subdirectory was deleted on 2026-04-21; its content was consolidated into docs/wiki/shipped-history.md.)

Technical Wiki

docs/wiki/ is the feature-by-feature technical layer (40+ topics covering OOXML, ProseMirror, runtime, debug, and platform). Enter via docs/README.md. Highlights:

  • api-v1 · api-v2 · api-v3 — the three public API generations, delineated
  • runtime-truth — the canonical-truth narrative every agent should internalize
  • debugging + doc-debug-service-guide — runtime debug substrate + HTTPS debug service
  • chrome-composition + chrome-composition-backlog — UX composition audit split: shipped vs outstanding
  • testing-and-fixtures — F01–F52 corpus + private large-document corpus + truth-baseline numbers + MCP tooling map

Agent Integration

Agents consume the editor through a consistent pipeline. Two entry shapes, same pipeline:

V1/V2 ref (React editor mounts):

inspect --> locate --> mutate --> validate --> export

V3 createApiV3(runtime) (stateless services, workblocks, out-of-process agents):

inspect --> listScopes --> getScopeBundle --> propose --> validate --> apply --> export
                                              └── mock today (Phase 4/5) ──┘

Beyond Work platform agents extend the V1/V2 pipeline with propose/approve gates:

inspect --> locate --> propose --> approve --> mutate --> validate --> export

The package exposes purpose-built read models — not a monolithic state dump. V1 getters and V3 equivalents dispatch through the same runtime:

| Read model | V1 getter | V3 equivalent | Purpose | |---|---|---|---| | Document surface | getRenderSnapshot() | (composed via runtime.content.*) | Structure, blocks, text, selection | | Comments | getComments() | api.runtime.review.getComments() (partial) | Thread state, resolution, anchors | | Tracked changes | getTrackedChanges() | api.runtime.review.getChanges() (partial) | Revision inventory, actionability | | Workflow scope | getWorkflowScopeSnapshot() | api.runtime.workflow.queryScopes() (live) | Scope boundaries, work items, mode | | Interaction guard | getInteractionGuardSnapshot() | api.runtime.workflow.getGuard() (live) | Effective mode, blocked reasons | | Compatibility | getCompatibilityReport() | api.runtime.document.validate() (live-with-adapter) | Export risk assessment | | Warnings | getWarnings() | (bundled into validate()) | Active fidelity or mutation warnings | | Layout | ref.layout.getPage(...) | api.runtime.layout.getPage(...) (live-with-adapter) | Page/block layout reads | | Debug snapshot | runtime.debug.getSnapshot() | same (shared projector) | Canonical/workflow/review/scope/layout one-shot JSON |

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.

Packaging And Release

  • .github/workflows/publish.yml publishes on v* tags after verifying the tag matches package.json
  • pnpm pack --dry-run is the baseline package proof
  • npm provenance is enabled in publishConfig and in the publish workflow invocation
  • the published package currently ships source ESM entry points plus TypeScript source-backed types exports
  • the Microsoft Open XML SDK remains CI/internal-service only, never part of the shipped browser runtime

Contribution Rules

  • respect the runtime-owned state model
  • do not bypass commands and transactions
  • do not treat the DOM as canonical state
  • do not silently drop unknown OOXML
  • keep docs honest about shipped versus planned behavior
  • add or extend fixtures for compatibility-critical changes
  • bash scripts/validate-fixtures.sh now uses the Railway validator service and requires OPENXML_VALIDATOR_AUTH_TOKEN when hitting the public domain
  • node scripts/validate-docs-navigation.mjs enforces the core docs catalog, required indexes, and frontmatter contract

Guiding Principle

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.

Using the package

WordReviewEditor

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.

Installation

import {
  WordReviewEditor,
  type WordReviewEditorRef,
  type WordReviewEditorProps,
  type WordReviewEditorEvent,
} from "@beyondwork/docx-react-component";

Basic mount

import { useRef } from "react";
import { WordReviewEditor, type WordReviewEditorRef } from "@beyondwork/docx-react-component";

export function MyEditor({ docxBytes }: { docxBytes: Uint8Array }) {
  const editorRef = useRef<WordReviewEditorRef>(null);

  return (
    <WordReviewEditor
      ref={editorRef}
      documentId="doc-001"
      currentUser={{ userId: "u1", displayName: "Alice" }}
      initialDocx={docxBytes}
      onEvent={(event) => console.log(event)}
    />
  );
}

Props reference

| Prop | Type | Description | |---|---|---| | documentId | string | Required. Stable identifier for this document. | | currentUser | EditorUser | Required. The user performing edits and adding comments. | | initialDocx | Uint8Array \| ArrayBuffer | Raw .docx bytes to load on first mount. | | initialSessionState | EditorSessionState | Previously saved session state to restore. | | initialSnapshot | PersistedEditorSnapshot | Previously saved snapshot to restore. | | externalDocSource | ExternalDocumentSource | Alternative source with explicit kind ("docx", "session", "snapshot"). | | readOnly | boolean | When true, all editing commands are disabled. | | reviewMode | "editing" \| "review" | Shell layout hint — affects toolbar/panel arrangement but not editing authority. | | markupDisplay | "clean" \| "simple" \| "all" | Controls tracked-change visibility. | | showReviewPanel | boolean | Shows or hides the right-side comment and tracked-change panel. | | autosave | AutosaveConfig | Enables automatic saving. | | hostAdapter | EditorHostAdapter | Callbacks for load, saveSession, saveExport. | | datastore | EditorDatastoreAdapter | Alternative persistence adapter with load, saveSnapshot. | | onEvent | (event: WordReviewEditorEvent) => void | Unified event handler (see Events). | | onWarning | (warning: EditorWarning) => void | Fired for non-fatal warnings. | | onError | (error: EditorError) => void | Fired for fatal errors. |

EditorUser

interface EditorUser {
  userId: string;
  displayName: string;
  email?: string;
  avatarUrl?: string;
}

AutosaveConfig

interface AutosaveConfig {
  enabled?: boolean;
  debounceMs?: number; // default: 2000
}

Show / hide UI regions

Review panel

The right-side panel lists comment threads and tracked changes.

<WordReviewEditor showReviewPanel={false} ... />  // hide panel
<WordReviewEditor showReviewPanel={true}  ... />  // show panel (default)

Tracked-change display mode

markupDisplay controls how tracked changes appear in the document body.

| Value | Behaviour | |---|---| | "clean" | Show the accepted version — insertions visible, deletions hidden. | | "simple" | Show a simplified view of changes without inline markup. | | "all" | Show all insertion and deletion marks inline (Word's "Show Markup" mode). |

<WordReviewEditor markupDisplay="clean" ... />

You can also change the display mode at runtime:

// no ref method for markupDisplay — pass as a prop; React re-renders propagate the change.

Document mode

DocumentMode controls editing authority, not just appearance.

| Mode | Effect | |---|---| | "editing" | Edits are applied directly (no tracking). | | "suggesting" | Every edit is automatically wrapped in a tracked change. | | "viewing" | Document is read-only regardless of the readOnly prop. |

Set via ref:

editorRef.current.setDocumentMode("suggesting");

Or pass reviewMode="review" as a prop to start in a review-friendly shell layout (the component internally maps this to "suggesting" document mode).

Read-only mode

<WordReviewEditor readOnly={true} ... />

All editing, commenting, and tracked-change commands are blocked. The toolbar is still rendered but all buttons are disabled.

Workspace layout

editorRef.current.setWorkspaceMode("canvas"); // continuous scroll
editorRef.current.setWorkspaceMode("page");   // paginated view

Imperative ref

Obtain the ref via useRef<WordReviewEditorRef>():

const editorRef = useRef<WordReviewEditorRef>(null);
<WordReviewEditor ref={editorRef} ... />

// then:
editorRef.current?.addComment({ body: "Needs revision" });

Comment operations

Add a comment

addComment(params: AddCommentParams): string
// returns the new commentId
interface AddCommentParams {
  anchor?: EditorAnchorProjection; // defaults to the current selection
  body?: string;
  authorId?: string;               // defaults to currentUser.userId
}

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.

// 1. Capture anchor while text is still selected
const snapshot = editorRef.current.getRenderSnapshot();
const anchor = snapshot.selection.activeRange;

// 2. Open your draft UI, let user type a message...
// 3. On submit:
const commentId = editorRef.current.addComment({ anchor, body: draftText });

Resolve a comment

editorRef.current.resolveComment(commentId);

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.

Reopen a resolved comment

editorRef.current.reopenComment(commentId);

Delete a comment permanently

editorRef.current.deleteComment(commentId);

Removes the comment entirely. Use this to clean up failed or unwanted drafts.

Add a reply to an existing thread

editorRef.current.addCommentReply(commentId, "Reply text here");

Edit a comment body

editorRef.current.editCommentBody(commentId, "Updated text");

Scroll to a comment

editorRef.current.scrollToComment(commentId);

Open (focus) a comment in the sidebar

editorRef.current.openComment(commentId);

Get all comments

const sidebar: CommentSidebarSnapshot = editorRef.current.getComments();
interface CommentSidebarSnapshot {
  activeCommentId?: string;
  openCommentIds: string[];
  resolvedCommentIds: string[];
  detachedCommentIds: string[];
  totalCount: number;
  threads: CommentSidebarThreadSnapshot[];
}

interface CommentSidebarThreadSnapshot {
  commentId: string;
  status: "open" | "resolved" | "detached";
  anchor: EditorAnchorProjection;
  excerpt: string;               // the anchored text snippet
  entries: CommentSidebarThreadEntrySnapshot[];
  entryCount: number;
  createdAt: string;             // ISO 8601
  createdBy: string;             // userId
  resolvedAt?: string;
  resolvedBy?: string;
}

Detached comments

A comment becomes detached when the text it was anchored to is deleted. Detached comments:

  • Still appear in sidebar.detachedCommentIds and have status: "detached".
  • Have anchor.kind === "detached" with a lastKnownRange and a reason ("deleted", "invalidatedByStructureChange", or "importAmbiguity").
  • Do not block DOCX export.
  • Can be resolved, reopened, or deleted via the same methods above.

Tracked-change operations

Get all tracked changes

const changes: TrackedChangesSnapshot = editorRef.current.getTrackedChanges();
interface TrackedChangesSnapshot {
  pendingChangeIds: string[];
  acceptedChangeIds: string[];
  rejectedChangeIds: string[];
  detachedChangeIds: string[];
  actionableChangeIds: string[];
  preserveOnlyChangeIds: string[];
  totalCount: number;
  revisions: TrackedChangeEntrySnapshot[];
}

interface TrackedChangeEntrySnapshot {
  revisionId: string;
  kind: "insertion" | "deletion" | "formatting" | "move" | "property-change";
  status: "active" | "accepted" | "rejected" | "detached";
  actionability: "actionable" | "preserve-only";
  canAccept: boolean;
  canReject: boolean;
  anchor: EditorAnchorProjection;
  anchorLabel: string;
  excerpt?: string;
  detail?: string;
  authorId: string;
  createdAt: string;
}

preserve-only revisions (formatting, move) can be displayed but cannot be individually accepted or rejected through the API.

Accept a single change

editorRef.current.acceptChange(revisionId);

Reject a single change

editorRef.current.rejectChange(revisionId);

Accept all pending changes

editorRef.current.acceptAllChanges();

Reject all pending changes

editorRef.current.rejectAllChanges();

Scroll to a tracked change

editorRef.current.scrollToRevision(revisionId);

Export

const result = await editorRef.current.exportDocx({ fileName: "output.docx" });
// result.bytes is the Uint8Array of the .docx file

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.


Events

All events are dispatched through the single onEvent prop. The type discriminator narrows the payload.

<WordReviewEditor
  onEvent={(event) => {
    if (event.type === "comment_added") {
      console.log("New comment:", event.commentId);
    }
  }}
/>

ready

Fired once after the document finishes loading and the editor is interactive.

{
  type: "ready";
  documentId: string;
  sessionId: string;
  source: "docx" | "session" | "snapshot";
  stats: DocumentStats;          // storyLength, commentCount, revisionCount
  compatibility: CompatibilityReport;
  comments: CommentSidebarSnapshot;
  trackedChanges: TrackedChangesSnapshot;
}

comment_added

Fired when a new comment thread is created (via addComment or the toolbar).

{
  type: "comment_added";
  documentId: string;
  commentId: string;
  anchor: EditorAnchorProjection;
}

comment_resolved

Fired when a comment is resolved (via resolveComment or the sidebar).

{
  type: "comment_resolved";
  documentId: string;
  commentId: string;
}

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.

comments_changed

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().

{
  type: "comments_changed";
  documentId: string;
  changedCommentIds: string[]; // always non-empty
}

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.

change_accepted

Fired when a tracked change is accepted.

{
  type: "change_accepted";
  documentId: string;
  changeId: string;
}

change_rejected

Fired when a tracked change is rejected.

{
  type: "change_rejected";
  documentId: string;
  changeId: string;
}

selection_changed

Fired whenever the editor cursor or selection changes.

{
  type: "selection_changed";
  documentId: string;
  selection: SelectionSnapshot;
}

interface SelectionSnapshot {
  anchor: number;
  head: number;
  isCollapsed: boolean;
  activeRange: EditorAnchorProjection;
  storyTarget?: EditorStoryTarget;
}

Use selection.activeRange as the anchor argument to addComment — but capture it before opening any modal UI.

dirty_changed

Fired when the document transitions between clean and dirty (unsaved) states.

{
  type: "dirty_changed";
  documentId: string;
  isDirty: boolean;
}

story_changed

Fired when the user navigates between document stories (e.g. main body -> header/footer).

{
  type: "story_changed";
  documentId: string;
  activeStory: EditorStoryTarget;
}

export_completed

Fired after a successful exportDocx call, after the host saveExport callback (if any) has resolved.

{
  type: "export_completed";
  documentId: string;
  result: ExportResult;   // result.bytes, result.fileName, result.mimeType
}

session_saved

Fired after the host saveSession callback resolves.

{
  type: "session_saved";
  documentId: string;
  sessionState: EditorSessionState;
  savedAt: string;
  isAutosave: boolean;
}

snapshot_saved

Fired after the datastore saveSnapshot callback resolves.

{
  type: "snapshot_saved";
  documentId: string;
  snapshot: PersistedEditorSnapshot;
  isAutosave: boolean;
}

autosave_state

Fired when the autosave lifecycle transitions.

{
  type: "autosave_state";
  documentId: string;
  state: "idle" | "pending" | "saving" | "saved" | "error";
}

warning_added / warning_cleared

Non-fatal import or rendering warnings.

{ type: "warning_added";   documentId: string; warning: EditorWarning; }
{ type: "warning_cleared"; documentId: string; warningId: string; code: EditorWarningCode; }

error

Fatal editor error. The editor may be in an unrecoverable state after this event.

{
  type: "error";
  documentId: string;
  error: EditorError;    // error.code, error.message, error.isFatal
}

Workflow events

These events relate to the optional workflow overlay feature.

{ type: "workflow_overlay_changed";          documentId: string; snapshot: WorkflowScopeSnapshot; }
{ type: "workflow_active_work_item_changed"; documentId: string; activeWorkItemId: string | null; }
{ type: "command_blocked";                   documentId: string; command: string; reasons: WorkflowBlockedCommandReason[]; }

Key types

EditorAnchorProjection

Describes a position or range in the document. Returned by selection.activeRange and stored on comments/revisions.

type EditorAnchorProjection =
  | { kind: "range";    from: number; to: number; assoc: { start: -1|1; end: -1|1 } }
  | { kind: "node";     at: number;  assoc: -1|1 }
  | { kind: "detached"; lastKnownRange: { from: number; to: number };
      reason: "deleted" | "invalidatedByStructureChange" | "importAmbiguity" };

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.

DocumentMode

type DocumentMode = "editing" | "suggesting" | "viewing";

WorkspaceMode

type WorkspaceMode = "canvas" | "page";

Common patterns

Full add-comment flow with custom UI

function CommentButton({ editorRef }: { editorRef: React.RefObject<WordReviewEditorRef> }) {
  const [draft, setDraft] = useState<{ anchor: EditorAnchorProjection; text: string } | null>(null);

  function openDraft() {
    // Capture anchor BEFORE the modal steals focus from the editor
    const snapshot = editorRef.current?.getRenderSnapshot();
    if (!snapshot) return;
    const anchor = snapshot.selection.activeRange;
    if (anchor.kind !== "range" || anchor.from === anchor.to) return;
    setDraft({ anchor, text: "" });
  }

  function submit() {
    if (!draft) return;
    editorRef.current?.addComment({ anchor: draft.anchor, body: draft.text });
    setDraft(null);
  }

  return (
    <>
      <button onClick={openDraft}>Comment</button>
      {draft && (
        <dialog open>
          <textarea value={draft.text} onChange={(e) => setDraft({ ...draft, text: e.target.value })} />
          <button onClick={submit}>Add</button>
          <button onClick={() => setDraft(null)}>Cancel</button>
        </dialog>
      )}
    </>
  );
}

Listen for comment and review-change events

<WordReviewEditor
  onEvent={(event) => {
    switch (event.type) {
      case "comment_added":
        console.log("comment added:", event.commentId, event.anchor);
        break;
      case "comment_resolved":
        console.log("comment resolved:", event.commentId);
        break;
      case "comments_changed":
        console.log("comments changed:", event.changedCommentIds);
        break;
      case "change_accepted":
        console.log("change accepted:", event.changeId);
        break;
      case "change_rejected":
        console.log("change rejected:", event.changeId);
        break;
    }
  }}
/>

Resolve all open comments programmatically

const { threads } = editorRef.current.getComments();
for (const thread of threads) {
  if (thread.status === "open") {
    editorRef.current.resolveComment(thread.commentId);
  }
}

Accept or reject all actionable changes

editorRef.current.acceptAllChanges();
// or
editorRef.current.rejectAllChanges();