editor.doc.getHtml(...) | [`getHtml`](/document-api/reference/get-html) |
| editor.doc.markdownToFragment(...) | [`markdownToFragment`](/document-api/reference/markdown-to-fragment) |
| editor.doc.info(...) | [`info`](/document-api/reference/info) |
+| editor.doc.extract(...) | [`extract`](/document-api/reference/extract) |
| editor.doc.clearContent(...) | [`clearContent`](/document-api/reference/clear-content) |
| editor.doc.insert(...) | [`insert`](/document-api/reference/insert) |
| editor.doc.replace(...) | [`replace`](/document-api/reference/replace) |
diff --git a/apps/docs/document-api/reference/_generated-manifest.json b/apps/docs/document-api/reference/_generated-manifest.json
index 8887fe4a9e..b886c7b66d 100644
--- a/apps/docs/document-api/reference/_generated-manifest.json
+++ b/apps/docs/document-api/reference/_generated-manifest.json
@@ -130,6 +130,7 @@
"apps/docs/document-api/reference/diff/capture.mdx",
"apps/docs/document-api/reference/diff/compare.mdx",
"apps/docs/document-api/reference/diff/index.mdx",
+ "apps/docs/document-api/reference/extract.mdx",
"apps/docs/document-api/reference/fields/get.mdx",
"apps/docs/document-api/reference/fields/index.mdx",
"apps/docs/document-api/reference/fields/insert.mdx",
@@ -436,6 +437,7 @@
"getHtml",
"markdownToFragment",
"info",
+ "extract",
"clearContent",
"insert",
"replace",
@@ -1016,5 +1018,5 @@
}
],
"marker": "{/* GENERATED FILE: DO NOT EDIT. Regenerate via `pnpm run docapi:sync`. */}",
- "sourceHash": "b61fad6a3a330af8a57b78ded260c8d8918486c9829b50804227fbeb15e8bf53"
+ "sourceHash": "e74a36833ec8587b67447a79517de348cfc9b4bba1c564729c184f6d5464a018"
}
diff --git a/apps/docs/document-api/reference/capabilities/get.mdx b/apps/docs/document-api/reference/capabilities/get.mdx
index f64034b1be..d928604dd0 100644
--- a/apps/docs/document-api/reference/capabilities/get.mdx
+++ b/apps/docs/document-api/reference/capabilities/get.mdx
@@ -855,6 +855,11 @@ _No fields._
| `operations.diff.compare.dryRun` | boolean | yes | |
| `operations.diff.compare.reasons` | enum[] | no | |
| `operations.diff.compare.tracked` | boolean | yes | |
+| `operations.extract` | object | yes | |
+| `operations.extract.available` | boolean | yes | |
+| `operations.extract.dryRun` | boolean | yes | |
+| `operations.extract.reasons` | enum[] | no | |
+| `operations.extract.tracked` | boolean | yes | |
| `operations.fields.get` | object | yes | |
| `operations.fields.get.available` | boolean | yes | |
| `operations.fields.get.dryRun` | boolean | yes | |
@@ -3071,6 +3076,11 @@ _No fields._
"dryRun": false,
"tracked": false
},
+ "extract": {
+ "available": true,
+ "dryRun": false,
+ "tracked": false
+ },
"fields.get": {
"available": true,
"dryRun": false,
@@ -10179,6 +10189,41 @@ _No fields._
],
"type": "object"
},
+ "extract": {
+ "additionalProperties": false,
+ "properties": {
+ "available": {
+ "type": "boolean"
+ },
+ "dryRun": {
+ "type": "boolean"
+ },
+ "reasons": {
+ "items": {
+ "enum": [
+ "COMMAND_UNAVAILABLE",
+ "HELPER_UNAVAILABLE",
+ "OPERATION_UNAVAILABLE",
+ "TRACKED_MODE_UNAVAILABLE",
+ "DRY_RUN_UNAVAILABLE",
+ "NAMESPACE_UNAVAILABLE",
+ "STYLES_PART_MISSING",
+ "COLLABORATION_ACTIVE"
+ ]
+ },
+ "type": "array"
+ },
+ "tracked": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "available",
+ "tracked",
+ "dryRun"
+ ],
+ "type": "object"
+ },
"fields.get": {
"additionalProperties": false,
"properties": {
@@ -19570,6 +19615,7 @@ _No fields._
"getHtml",
"markdownToFragment",
"info",
+ "extract",
"clearContent",
"insert",
"replace",
diff --git a/apps/docs/document-api/reference/content-controls/create.mdx b/apps/docs/document-api/reference/content-controls/create.mdx
index 177cb4c016..620c897a9e 100644
--- a/apps/docs/document-api/reference/content-controls/create.mdx
+++ b/apps/docs/document-api/reference/content-controls/create.mdx
@@ -27,6 +27,10 @@ Returns a ContentControlMutationResult with the created content control target.
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `alias` | string | no | |
+| `at` | SelectionTarget | no | SelectionTarget |
+| `at.end` | SelectionPoint | no | SelectionPoint |
+| `at.kind` | `"selection"` | no | Constant: `"selection"` |
+| `at.start` | SelectionPoint | no | SelectionPoint |
| `content` | string | no | |
| `controlType` | string | no | |
| `kind` | enum | yes | `"block"`, `"inline"` |
@@ -120,6 +124,9 @@ Returns a ContentControlMutationResult with the created content control target.
"alias": {
"type": "string"
},
+ "at": {
+ "$ref": "#/$defs/SelectionTarget"
+ },
"content": {
"type": "string"
},
diff --git a/apps/docs/document-api/reference/core/index.mdx b/apps/docs/document-api/reference/core/index.mdx
index 19c242887f..6f4931ff98 100644
--- a/apps/docs/document-api/reference/core/index.mdx
+++ b/apps/docs/document-api/reference/core/index.mdx
@@ -21,6 +21,7 @@ Primary read and write operations.
| getHtml | `getHtml` | No | `idempotent` | No | No |
| markdownToFragment | `markdownToFragment` | No | `idempotent` | No | No |
| info | `info` | No | `idempotent` | No | No |
+| extract | `extract` | No | `idempotent` | No | No |
| clearContent | `clearContent` | Yes | `conditional` | No | No |
| insert | `insert` | Yes | `non-idempotent` | Yes | Yes |
| replace | `replace` | Yes | `conditional` | Yes | Yes |
diff --git a/apps/docs/document-api/reference/create/heading.mdx b/apps/docs/document-api/reference/create/heading.mdx
index 1d7f43d48c..06bbbdc051 100644
--- a/apps/docs/document-api/reference/create/heading.mdx
+++ b/apps/docs/document-api/reference/create/heading.mdx
@@ -99,7 +99,11 @@ Returns a CreateHeadingResult with the new heading block ID and address.
{
"entityId": "entity-789",
"entityType": "trackedChange",
- "kind": "entity"
+ "kind": "entity",
+ "story": {
+ "kind": "story",
+ "storyType": "body"
+ }
}
]
}
diff --git a/apps/docs/document-api/reference/create/paragraph.mdx b/apps/docs/document-api/reference/create/paragraph.mdx
index e2d1c4a43a..c115b81b33 100644
--- a/apps/docs/document-api/reference/create/paragraph.mdx
+++ b/apps/docs/document-api/reference/create/paragraph.mdx
@@ -97,7 +97,11 @@ Returns a CreateParagraphResult with the new paragraph block ID and address.
{
"entityId": "entity-789",
"entityType": "trackedChange",
- "kind": "entity"
+ "kind": "entity",
+ "story": {
+ "kind": "story",
+ "storyType": "body"
+ }
}
]
}
diff --git a/apps/docs/document-api/reference/extract.mdx b/apps/docs/document-api/reference/extract.mdx
new file mode 100644
index 0000000000..0eb276f66a
--- /dev/null
+++ b/apps/docs/document-api/reference/extract.mdx
@@ -0,0 +1,222 @@
+---
+title: extract
+sidebarTitle: extract
+description: Extract all document content with stable IDs for RAG pipelines. Returns blocks with full text, comments, and tracked changes — each with an ID compatible with scrollToElement().
+---
+
+{/* GENERATED FILE: DO NOT EDIT. Regenerate via `pnpm run docapi:sync`. */}
+
+## Summary
+
+Extract all document content with stable IDs for RAG pipelines. Returns blocks with full text, comments, and tracked changes — each with an ID compatible with scrollToElement().
+
+- Operation ID: `extract`
+- API member path: `editor.doc.extract(...)`
+- Mutates document: `no`
+- Idempotency: `idempotent`
+- Supports tracked mode: `no`
+- Supports dry run: `no`
+- Deterministic target resolution: `yes`
+
+## Expected result
+
+Returns an ExtractResult with blocks (nodeId, type, text, headingLevel), comments (entityId, text, anchoredText, blockId, status, author), tracked changes (entityId, type, excerpt, author, date), and revision.
+
+## Input fields
+
+_No fields._
+
+### Example request
+
+```json
+{}
+```
+
+## Output fields
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `blocks` | object[] | yes | |
+| `comments` | object[] | yes | |
+| `revision` | string | yes | |
+| `trackedChanges` | object[] | yes | |
+
+### Example response
+
+```json
+{
+ "blocks": [
+ {
+ "headingLevel": 1,
+ "nodeId": "node-def456",
+ "text": "Hello, world.",
+ "type": "example"
+ }
+ ],
+ "comments": [
+ {
+ "anchoredText": "example",
+ "entityId": "entity-789",
+ "status": "open",
+ "text": "Hello, world."
+ }
+ ],
+ "revision": "example",
+ "trackedChanges": [
+ {
+ "author": "Jane Doe",
+ "entityId": "entity-789",
+ "excerpt": "Sample excerpt...",
+ "type": "insert"
+ }
+ ]
+}
+```
+
+## Pre-apply throws
+
+- None
+
+## Non-applied failure codes
+
+- None
+
+## Raw schemas
+
+getHtml | editor.doc.getHtml(...) | Extract the document content as an HTML string. |
| markdownToFragment | editor.doc.markdownToFragment(...) | Convert a Markdown string into an SDM/1 structural fragment. |
| info | editor.doc.info(...) | Return document summary info including word, character, paragraph, heading, table, image, comment, tracked-change, SDT-field, list, and page counts, plus outline and capabilities. |
+| extract | editor.doc.extract(...) | Extract all document content with stable IDs for RAG pipelines. Returns blocks with full text, comments, and tracked changes — each with an ID compatible with scrollToElement(). |
| clearContent | editor.doc.clearContent(...) | Clear all document body content, leaving a single empty paragraph. |
| insert | editor.doc.insert(...) | Insert content into the document. Two input shapes: text-based (value + type) inserts inline content at a SelectionTarget or ref position within an existing block; structural SDFragment (content) inserts one or more blocks as siblings relative to a BlockNodeAddress target. When target/ref is omitted, content appends at the end of the document. Text mode supports text (default), markdown, and html content types via the `type` field. Structural mode uses `placement` (before/after/insideStart/insideEnd) to position relative to the target block. |
| replace | editor.doc.replace(...) | Replace content at a contiguous document selection. Text path accepts a SelectionTarget or ref plus replacement text. Structural path accepts a BlockNodeAddress (replaces whole block), SelectionTarget (expands to full covered block boundaries), or ref plus SDFragment content. |
diff --git a/apps/docs/document-api/reference/lists/insert.mdx b/apps/docs/document-api/reference/lists/insert.mdx
index ce184c103e..05294706fe 100644
--- a/apps/docs/document-api/reference/lists/insert.mdx
+++ b/apps/docs/document-api/reference/lists/insert.mdx
@@ -98,7 +98,11 @@ Returns a ListsInsertResult with the new list item address and block ID.
{
"entityId": "entity-789",
"entityType": "trackedChange",
- "kind": "entity"
+ "kind": "entity",
+ "story": {
+ "kind": "story",
+ "storyType": "body"
+ }
}
]
}
diff --git a/apps/docs/document-api/reference/track-changes/decide.mdx b/apps/docs/document-api/reference/track-changes/decide.mdx
index 5b0a3ba124..cfd98e37fb 100644
--- a/apps/docs/document-api/reference/track-changes/decide.mdx
+++ b/apps/docs/document-api/reference/track-changes/decide.mdx
@@ -35,7 +35,11 @@ Returns a Receipt confirming the decision was applied; reports NO_OP if the chan
{
"decision": "accept",
"target": {
- "id": "id-001"
+ "id": "id-001",
+ "story": {
+ "kind": "story",
+ "storyType": "body"
+ }
}
}
```
@@ -114,6 +118,9 @@ Returns a Receipt confirming the decision was applied; reports NO_OP if the chan
"properties": {
"id": {
"type": "string"
+ },
+ "story": {
+ "$ref": "#/$defs/StoryLocator"
}
},
"required": [
diff --git a/apps/docs/document-api/reference/track-changes/get.mdx b/apps/docs/document-api/reference/track-changes/get.mdx
index c57851388e..f3d9ab8a54 100644
--- a/apps/docs/document-api/reference/track-changes/get.mdx
+++ b/apps/docs/document-api/reference/track-changes/get.mdx
@@ -27,12 +27,17 @@ Returns a TrackChangeInfo object with the change type, author, date, affected co
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | |
+| `story` | StoryLocator | no | StoryLocator |
### Example request
```json
{
- "id": "id-001"
+ "id": "id-001",
+ "story": {
+ "kind": "story",
+ "storyType": "body"
+ }
}
```
@@ -44,6 +49,7 @@ Returns a TrackChangeInfo object with the change type, author, date, affected co
| `address.entityId` | string | yes | |
| `address.entityType` | `"trackedChange"` | yes | Constant: `"trackedChange"` |
| `address.kind` | `"entity"` | yes | Constant: `"entity"` |
+| `address.story` | StoryLocator | no | StoryLocator |
| `author` | string | no | |
| `authorEmail` | string | no | |
| `authorImage` | string | no | |
@@ -63,7 +69,11 @@ Returns a TrackChangeInfo object with the change type, author, date, affected co
"address": {
"entityId": "entity-789",
"entityType": "trackedChange",
- "kind": "entity"
+ "kind": "entity",
+ "story": {
+ "kind": "story",
+ "storyType": "body"
+ }
},
"author": "Jane Doe",
"id": "id-001",
@@ -92,6 +102,9 @@ Returns a TrackChangeInfo object with the change type, author, date, affected co
"properties": {
"id": {
"type": "string"
+ },
+ "story": {
+ "$ref": "#/$defs/StoryLocator"
}
},
"required": [
diff --git a/apps/docs/document-api/reference/track-changes/list.mdx b/apps/docs/document-api/reference/track-changes/list.mdx
index bcb7e86ada..6411a19b16 100644
--- a/apps/docs/document-api/reference/track-changes/list.mdx
+++ b/apps/docs/document-api/reference/track-changes/list.mdx
@@ -26,6 +26,7 @@ Returns a TrackChangesListResult with tracked change entries, total count, and r
| Field | Type | Required | Description |
| --- | --- | --- | --- |
+| `in` | StoryLocator \\| `"all"` | no | One of: StoryLocator, `"all"` |
| `limit` | integer | no | |
| `offset` | integer | no | |
| `type` | enum | no | `"insert"`, `"delete"`, `"format"` |
@@ -61,7 +62,11 @@ Returns a TrackChangesListResult with tracked change entries, total count, and r
"address": {
"entityId": "entity-789",
"entityType": "trackedChange",
- "kind": "entity"
+ "kind": "entity",
+ "story": {
+ "kind": "story",
+ "storyType": "body"
+ }
},
"author": "Jane Doe",
"handle": {
@@ -101,6 +106,17 @@ Returns a TrackChangesListResult with tracked change entries, total count, and r
{
"additionalProperties": false,
"properties": {
+ "in": {
+ "description": "Story scope. Omit for body only, pass a StoryLocator for a single story, or 'all' for body + every revision-capable non-body story.",
+ "oneOf": [
+ {
+ "$ref": "#/$defs/StoryLocator"
+ },
+ {
+ "const": "all"
+ }
+ ]
+ },
"limit": {
"description": "Maximum number of tracked changes to return.",
"type": "integer"
diff --git a/packages/document-api/src/README.md b/packages/document-api/src/README.md
index 7934baf06e..f1dc0b3bde 100644
--- a/packages/document-api/src/README.md
+++ b/packages/document-api/src/README.md
@@ -82,7 +82,7 @@ Deterministic outcomes:
- Missing tracked-change capabilities must fail with `CAPABILITY_UNAVAILABLE`.
- Text/format targets that cannot be resolved after remote edits must fail deterministically (`TARGET_NOT_FOUND` / `NO_OP`), never silently mutate the wrong range.
- Tracked entity IDs returned by mutation receipts (`insert` / `replace` / `delete`) and `create.paragraph.trackedChangeRefs` must match canonical IDs from `trackChanges.list`.
-- `trackChanges.get` / `accept` / `reject` accept canonical IDs only.
+- `trackChanges.get` / `trackChanges.decide` accept canonical tracked-change IDs. Include `story` when targeting a non-body change.
## Common Workflows
@@ -699,27 +699,27 @@ List all comments in the document. Optionally include resolved comments.
### `trackChanges.list`
-List tracked changes in the document. Supports filtering by `type` and pagination via `limit`/`offset`.
+List tracked changes in the document. Supports filtering by `type`, pagination via `limit`/`offset`, and story scoping via `in`.
-- **Input**: `TrackChangesListInput | undefined` (`{ limit?, offset?, type? }`)
+- **Input**: `TrackChangesListInput | undefined` (`{ limit?, offset?, type?, in?: StoryLocator | 'all' }`)
- **Output**: `TrackChangesListResult` (`{ items, total }`)
- **Mutates**: No
- **Idempotency**: idempotent
### `trackChanges.get`
-Retrieve full information for a single tracked change by its canonical ID. Throws `TARGET_NOT_FOUND` when the ID is invalid.
+Retrieve full information for a single tracked change by its canonical ID. Include `story` for non-body changes. Throws `TARGET_NOT_FOUND` when the ID is invalid.
-- **Input**: `TrackChangesGetInput` (`{ id }`)
+- **Input**: `TrackChangesGetInput` (`{ id, story? }`)
- **Output**: `TrackChangeInfo` (includes `wordRevisionIds` with raw imported Word OOXML `w:id` values when available)
- **Mutates**: No
- **Idempotency**: idempotent
### `trackChanges.decide`
-Accept or reject a tracked change by ID, or accept/reject all changes with `{ scope: 'all' }`.
+Accept or reject a tracked change by ID, or accept/reject all changes with `{ scope: 'all' }`. Include `story` when the change lives outside the body.
-- **Input**: `ReviewDecideInput` (`{ decision: 'accept' | 'reject', target: { id } | { scope: 'all' } }`)
+- **Input**: `ReviewDecideInput` (`{ decision: 'accept' | 'reject', target: { id, story? } | { scope: 'all' } }`)
- **Output**: `Receipt`
- **Mutates**: Yes
- **Idempotency**: conditional
diff --git a/packages/document-api/src/contract/schemas.ts b/packages/document-api/src/contract/schemas.ts
index a3d1c9c321..4e2e73d1ff 100644
--- a/packages/document-api/src/contract/schemas.ts
+++ b/packages/document-api/src/contract/schemas.ts
@@ -390,6 +390,7 @@ const SHARED_DEFS: RecordHello world
', + trackedChanges: { + visible: true, + mode: 'review', + enabled: true, + replacements: 'independent', + }, + }).editor as Editor, + ); + + const child = trackEditor( + createStoryEditor( + parent, + { + type: 'doc', + content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Header text' }] }], + }, + { + documentId: 'hf:part:rId9', + isHeaderOrFooter: true, + headless: true, + }, + ), + ); + + expect(child.options.trackedChanges).toEqual({ + visible: true, + mode: 'review', + enabled: true, + replacements: 'independent', + }); + + child.options.trackedChanges!.replacements = 'paired'; + expect(parent.options.trackedChanges?.replacements).toBe('independent'); + }); +}); diff --git a/packages/super-editor/src/editors/v1/core/story-editor-factory.ts b/packages/super-editor/src/editors/v1/core/story-editor-factory.ts index ffc7b8fe08..d271bbd31e 100644 --- a/packages/super-editor/src/editors/v1/core/story-editor-factory.ts +++ b/packages/super-editor/src/editors/v1/core/story-editor-factory.ts @@ -129,6 +129,9 @@ export function createStoryEditor( const inheritedExtensions = parentEditor.options.extensions?.length ? [...parentEditor.options.extensions] : undefined; + const inheritedTrackedChanges = parentEditor.options.trackedChanges + ? { ...parentEditor.options.trackedChanges } + : undefined; const StoryEditorClass = parentEditor.constructor as new (options: Partial