@ansonlai/docx-redline-js 0.5.4 → 0.6.0
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/AGENTS.md +78 -697
- package/ARCHITECTURE.md +13 -1
- package/CHANGELOG.md +5 -0
- package/README.md +140 -30
- package/core/paragraph-targeting.js +14 -2
- package/dist/docx-redline-js.esm.js +113 -33
- package/dist/docx-redline-js.esm.js.map +3 -3
- package/dist/docx-redline-js.esm.min.js +75 -75
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/docs/AGENT_FAST_START.md +59 -0
- package/docs/AGENT_KNOWLEDGE_BASE.md +868 -0
- package/docs/TESTING.md +20 -1
- package/docs/schemas/document-operations.schema.json +5 -1
- package/docs/validation-reports/2026-09-12-agent-protocol-rollout.md +82 -0
- package/engine/oxml-engine.js +80 -13
- package/engine/run-builders.js +5 -15
- package/index.d.ts +17 -1
- package/node/cli.js +111 -27
- package/node/docx-document.js +120 -69
- package/node/index.d.ts +6 -2
- package/package.json +10 -3
- package/scripts/generate-cross-author-slicing-fixtures.ps1 +25 -25
- package/services/batch-operation-orchestrator.js +215 -120
- package/services/document-inspection.js +5 -3
- package/services/document-operation-applier.js +52 -34
- package/services/document-operation-contract.js +10 -6
- package/services/document-operation-mutations.js +51 -5
- package/services/document-operation-session.js +4 -0
- package/services/error-recovery.js +174 -0
- package/services/operation-batch-compiler.js +394 -0
- package/services/operation-preflight.js +91 -72
- package/services/standalone-operation-runner.d.ts +17 -1
- package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +0 -1669
- package/docs/plans/2026-09-08-cross-author-revision-slicing.md +0 -1399
- package/docs/plans/completed/2026-03-01-release-0.1.4-design.md +0 -33
- package/docs/plans/completed/2026-03-01-release-0.1.4.md +0 -110
- package/docs/plans/completed/2026-05-31-architectural changes.md +0 -593
- package/docs/plans/completed/2026-08-02-reliability-improvements.md +0 -1155
- package/docs/plans/completed/2026-08-30-reliability-testing-improvements.md +0 -488
- package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +0 -669
- package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +0 -427
- package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +0 -519
- package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +0 -69
- package/docs/plans/completed/structural-revision-capability-matrix.md +0 -115
- package/docs/test-comparison-dashboard.html +0 -4338
- package/docs/validation-reports/2026-08-30-phase-1-word-visual-preflight.md +0 -22
- package/docs/validation-reports/2026-08-30-phase-2-word-visual-preflight.md +0 -24
- package/docs/validation-reports/2026-08-30-phase-3-coverage.md +0 -73
- package/docs/validation-reports/2026-09-02-multilevel-bullets-visual-review.md +0 -82
- package/docs/validation-reports/2026-09-02-multimodal-visual-samples.md +0 -114
- package/docs/validation-reports/2026-09-02-visual-failures-preflight.md +0 -79
package/ARCHITECTURE.md
CHANGED
|
@@ -69,7 +69,13 @@ No Word add-in entrypoints or host-specific integration layers are part of this
|
|
|
69
69
|
|
|
70
70
|
## Entry Points
|
|
71
71
|
|
|
72
|
-
- `index.js
|
|
72
|
+
- `index.js`: primary host-independent exports for OOXML reconciliation.
|
|
73
|
+
- `services/standalone-operation-runner.js`: stable complete-document XML
|
|
74
|
+
operations, exported as `@ansonlai/docx-redline-js/standalone-runner`.
|
|
75
|
+
- `node/index.js`: Node-only complete-DOCX buffer facade, exported as
|
|
76
|
+
`@ansonlai/docx-redline-js/node`.
|
|
77
|
+
- `bin/docx-redline.js`: CLI launcher; command implementation lives in
|
|
78
|
+
`node/cli.js` and shared operation behavior lives in `services/`.
|
|
73
79
|
|
|
74
80
|
## Module Responsibilities
|
|
75
81
|
|
|
@@ -334,6 +340,12 @@ still be re-exported from `index.js`.
|
|
|
334
340
|
- Operation-level authors override the batch author. Runtime results expose
|
|
335
341
|
`authorUsed`, `authorsUsed`, `operationType`, `resolvedBy`, and resolved target
|
|
336
342
|
metadata so integrations can audit what the engine actually selected.
|
|
343
|
+
- Target fingerprints are revision-view scoped. Inspection returns a
|
|
344
|
+
`revisionView` beside every paragraph and computes its fingerprint from the
|
|
345
|
+
same text/view pair. Restore normalization defaults omitted target and range
|
|
346
|
+
endpoint views to `rejected`; all other operations default to `accepted`.
|
|
347
|
+
Explicit views remain authoritative, and cross-view text/fingerprint mistakes
|
|
348
|
+
return an actionable mismatch hint rather than silently weakening targeting.
|
|
337
349
|
- A normalized target match does not become an edit coordinate system for a
|
|
338
350
|
text-bearing mutation. Mutation uses canonical accepted-view source text, and `resolvedTarget.targetTextMatch`
|
|
339
351
|
records `exact`, `space_equivalent`, or `normalized` selection plus bounded
|
package/CHANGELOG.md
CHANGED
|
@@ -9,9 +9,14 @@
|
|
|
9
9
|
- **Exact mismatch diagnostics**: `PATCH_ROUNDTRIP_MISMATCH` now includes the expected and actual code points at the first mismatch. Document-operation results also report bounded `targetTextMatch` diagnostics when target selection used equivalent whitespace.
|
|
10
10
|
- **Baseline-delta validation (WP09c)**: Restoration, operation, and package validation now compare issue multisets against the source. Unchanged legacy defects remain visible without blocking safe work, while any added occurrence or mutation-envelope error fails closed with `GENERATED_OOXML_INVALID` before an operation is reported as applied.
|
|
11
11
|
- **Revision identity sanitation (WP09d)**: New paragraph/list builders inherit effective `pPr`/`rPr` formatting without cloning historical revision descendants. Operation-level whole-document validation catches duplicate revision IDs and rolls back the operation savepoint before committing its receipt.
|
|
12
|
+
- **View-consistent restore targeting**: `restore` targets now default to the rejected view, where a wholly deleted paragraph's source text exists. Rejected-view inspection now computes fingerprints from rejected-view text and reports the view beside each paragraph. Explicit cross-view text or fingerprint mistakes retain strict refusal while returning an actionable view hint.
|
|
12
13
|
|
|
13
14
|
### New Features
|
|
14
15
|
|
|
16
|
+
- **Batch-start source binding (WP-04)**: Strong document-operation targets are compiled once against the immutable source DOM and carried through structural index/fingerprint drift with rollback-aware session identities. Independent edits no longer need manual bottom-up sorting; same-source writes, incompatible revision/format combinations, and unsafe capture fan-out fail before mutation with causal conflict codes. Unique references to paragraph text created elsewhere in the same batch become explicit internal capture dependencies.
|
|
17
|
+
- **Machine-actionable recovery (WP-05)**: Operation, package-facade, example-session, and compact CLI failures now carry recovery envelope version 1 with stable stage/category/action metadata, bounded corrective context, and authorization flags. Failed and partial mutation results include an explicit original-vs-output retry plan and operation indexes. `apply --require-complete` exits with code 3 for partial work while preserving legacy behavior without the flag.
|
|
18
|
+
- **Compact shell-agent protocol (WP-06)**: CLI contract version 5 adds `operations-stdin` and `agent-profile-v1`. `--operations -` accepts UTF-8 operation arrays/envelopes without a temporary operations file, while explicit `--profile agent` combines atomic rollback and complete-success exit behavior and reports its effective settings. `AGENTS.md` is now a 527-word routing card, and the 349-word `docs/AGENT_FAST_START.md` carries the ordinary edit contract. Ambient project configuration was deliberately deferred because it would save only the profile flag while adding hidden state.
|
|
19
|
+
- **Agent rollout audit (WP-07)**: The observational benchmark now compares canonical Node, legacy file-based CLI, compact stdin/profile CLI, and the development session example while verifying accepted/rejected text, comment preservation, independent batch permutations, recovery behavior, and cross-author attribution. The checked run records 82.75% fewer ordinary instruction words, one fewer shell tool turn (33.33%), and 82.22–89.96% smaller localized session requests than canonical Node envelopes without claiming unmeasured provider/model latency.
|
|
15
20
|
- **Explicit paragraph restoration (`type: 'restore'`)**: Restores or counterproposes another reviewer's pending whole-paragraph deletion as a separately tracked sibling paragraph. The source deletion remains untouched; the restored paragraph receives its own inserted paragraph mark, content insertion, sanitized paragraph properties, and fresh `w14:paraId`. Single paragraphs and contiguous ranges are supported, with full Accept/Reject lifecycle verification and structured refusals at unsafe table-row, move, section-break, and terminal-paragraph boundaries.
|
|
16
21
|
- **Compact mutation CLI contract (WP09b)**: CLI contract version 3 removes `documentXml`, OOXML/package artifacts, full inspection data, and full validation issue arrays from normal `apply`, `accept`, `reject`, and `delete-comments` stdout. Mutation responses retain actionable errors, per-operation receipts, output durability fields, code/count validation summaries, and a derived `completion` flag that cannot report success for failed, partial, or unwritten work.
|
|
17
22
|
- **Word-native deleted-section editing (WP09e)**: An explicit rejected-view `insert` operation can split a foreign deletion at an exact anchor-relative offset into sibling `del(A) / ins(B) / del(A)` carriers. Contiguous paragraph restorations now follow their untouched deleted source block, matching Microsoft Word's ordering. Ambiguous anchors and unsupported structural split boundaries remain fail-closed.
|
package/README.md
CHANGED
|
@@ -20,13 +20,45 @@ Converts AI-generated or programmatic text/markdown edits into valid Office Open
|
|
|
20
20
|
- Zero host dependencies: works in Node.js, browsers, Deno, and similar JS runtimes with DOM parsing support
|
|
21
21
|
## Documentation Index
|
|
22
22
|
|
|
23
|
-
| Document | Description |
|
|
24
|
-
|---|---|
|
|
25
|
-
| **[README.md](./README.md)** | Library overview, installation, quick start, and public API reference |
|
|
26
|
-
| **[
|
|
23
|
+
| Document | Description |
|
|
24
|
+
|---|---|
|
|
25
|
+
| **[README.md](./README.md)** | Library overview, installation, quick start, and public API reference |
|
|
26
|
+
| **[docs/AGENT_FAST_START.md](./docs/AGENT_FAST_START.md)** | Compact ordinary-edit protocol for structured tools and shell-only agents |
|
|
27
|
+
| **[AGENTS.md](./AGENTS.md)** | Short repository launch card for task routing and contributor verification |
|
|
28
|
+
| **[docs/AGENT_KNOWLEDGE_BASE.md](./docs/AGENT_KNOWLEDGE_BASE.md)** | Full agent reference, CLI workflow, operation examples, error recovery, options, and gotchas |
|
|
27
29
|
| **[ARCHITECTURE.md](./ARCHITECTURE.md)** | Contributor architecture, module responsibilities, end-to-end data flow, and contracts |
|
|
28
30
|
| **[docs/TESTING.md](./docs/TESTING.md)** | Complete testing guide, test lanes, independent oracle validation, and Word visual review checklist |
|
|
29
|
-
| **[CHANGELOG.md](./CHANGELOG.md)** | Release history, breaking changes, and migration notes |
|
|
31
|
+
| **[CHANGELOG.md](./CHANGELOG.md)** | Release history, breaking changes, and migration notes |
|
|
32
|
+
|
|
33
|
+
## Repository Layout
|
|
34
|
+
|
|
35
|
+
The package exposes three levels of API:
|
|
36
|
+
|
|
37
|
+
| Level | Entry point | Use it for |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| Host-independent OOXML API | `index.js` | Paragraph/range transforms and exported OOXML utilities in browsers, Node.js, or another DOM-capable runtime |
|
|
40
|
+
| Standalone document XML runner | `services/standalone-operation-runner.js` | Applying operations to a complete `word/document.xml` string |
|
|
41
|
+
| Node/DOCX API and CLI | `node/index.js`, `bin/docx-redline.js` | Reading, changing, validating, and writing complete `.docx` ZIP packages |
|
|
42
|
+
|
|
43
|
+
Implementation folders have distinct roles: `core/` holds shared OOXML and
|
|
44
|
+
targeting primitives; `pipeline/` handles ingestion, diffing, markdown, lists,
|
|
45
|
+
and serialization; `engine/` performs reconciliation; `services/` coordinates
|
|
46
|
+
document operations and package artifacts; `node/` contains Node-only ZIP and
|
|
47
|
+
whole-document code. Tests are directly runnable `tests/*.mjs` files, while
|
|
48
|
+
`tests/helpers/` and `tests/fixtures/` contain support code and data.
|
|
49
|
+
|
|
50
|
+
Contributors and coding agents should start with the routing table in
|
|
51
|
+
[AGENTS.md](./AGENTS.md#pick-the-route) before exploring the tree. The full
|
|
52
|
+
dependency and ownership map is in [ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
53
|
+
|
|
54
|
+
For document-operation JSON, choose operations by the desired output structure,
|
|
55
|
+
not by the everyday meaning of the type name. `redline` and `replace` provide
|
|
56
|
+
ordinary text replacement; `list-change` and `table-reconciliation` provide
|
|
57
|
+
structural intent; and ordinary `insert` is a compatibility alias of the
|
|
58
|
+
redline path unless it includes a rejected-view target and anchor. In every
|
|
59
|
+
ordinary text-bearing operation, `modified` is the complete desired content for
|
|
60
|
+
the target. See the [operation model](./docs/AGENT_KNOWLEDGE_BASE.md#operation-model-choose-by-output-shape)
|
|
61
|
+
and the [JSON schema](./docs/schemas/document-operations.schema.json).
|
|
30
62
|
|
|
31
63
|
## Install
|
|
32
64
|
|
|
@@ -128,15 +160,56 @@ const result = await document.applyOperations(operations, {
|
|
|
128
160
|
const outputBuffer = result.toBuffer();
|
|
129
161
|
```
|
|
130
162
|
|
|
131
|
-
The Node facade performs edits, artifact merges, package wiring, validation,
|
|
132
|
-
and commit as one transaction. It defaults to strict targets and returns the
|
|
133
|
-
untouched input with `written: false` on atomic failure. It is isolated from
|
|
134
|
-
the root/browser dependency graph.
|
|
135
|
-
|
|
136
|
-
Install `@xmldom/xmldom` alongside the package when using the Node facade or
|
|
137
|
-
CLI; it remains an optional peer so browser consumers do not install a DOM shim.
|
|
138
|
-
|
|
139
|
-
|
|
163
|
+
The Node facade performs edits, artifact merges, package wiring, validation,
|
|
164
|
+
and commit as one transaction. It defaults to strict targets and returns the
|
|
165
|
+
untouched input with `written: false` on atomic failure. It is isolated from
|
|
166
|
+
the root/browser dependency graph.
|
|
167
|
+
|
|
168
|
+
Install `@xmldom/xmldom` alongside the package when using the Node facade or
|
|
169
|
+
CLI; it remains an optional peer so browser consumers do not install a DOM shim.
|
|
170
|
+
|
|
171
|
+
#### Example agent session wrapper (development only)
|
|
172
|
+
|
|
173
|
+
[`examples/agent-session-wrapper.mjs`](./examples/agent-session-wrapper.mjs)
|
|
174
|
+
demonstrates how a custom agent harness can keep one `DocxDocument` open, return
|
|
175
|
+
short revision-bound target handles, apply safe defaults once, and translate a
|
|
176
|
+
narrow edit request into canonical document operations:
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
import { readFile } from 'node:fs/promises';
|
|
180
|
+
import { createExampleAgentSession } from './examples/agent-session-wrapper.mjs';
|
|
181
|
+
|
|
182
|
+
const session = createExampleAgentSession(await readFile('contract.docx'), {
|
|
183
|
+
profile: { author: 'Editor' }
|
|
184
|
+
});
|
|
185
|
+
const inspection = session.inspect({ search: 'termination', around: 2 });
|
|
186
|
+
const clause = inspection.targets.find(target => target.role === 'match');
|
|
187
|
+
const result = await session.applyEdits([{
|
|
188
|
+
target: clause.handle,
|
|
189
|
+
replacements: [{
|
|
190
|
+
find: 'The Company may terminate',
|
|
191
|
+
replace: 'Either party may terminate'
|
|
192
|
+
}]
|
|
193
|
+
}]);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
This file is a testable sample, not a package export or supported alternate
|
|
197
|
+
mutation engine. It delegates to `@ansonlai/docx-redline-js/node`, is excluded
|
|
198
|
+
from the published package files, and is intended to help MCP servers, Claude
|
|
199
|
+
skills, OpenCode tools, and other custom harnesses design thin integrations.
|
|
200
|
+
The sample binds each handle to the inspected package revision and view, returns
|
|
201
|
+
new handles after successful mutations, and expands localized exact replacements
|
|
202
|
+
into complete desired paragraph text before delegating to the canonical redline
|
|
203
|
+
operation. Duplicate matches require an explicit `occurrence`; missing,
|
|
204
|
+
ambiguous, overlapping, and conflicting patches fail before document mutation.
|
|
205
|
+
From a source checkout, run `npm run benchmark:agent` to compare its native
|
|
206
|
+
execution and serialized request size with a canonical stateless Node workflow.
|
|
207
|
+
The example and its benchmark are excluded from the published package. The
|
|
208
|
+
benchmark explicitly does not claim to measure LLM reasoning or provider/tool
|
|
209
|
+
latency. Checked comparative results are in the
|
|
210
|
+
[agent protocol rollout audit](./docs/validation-reports/2026-09-12-agent-protocol-rollout.md).
|
|
211
|
+
|
|
212
|
+
### Agent CLI
|
|
140
213
|
|
|
141
214
|
```bash
|
|
142
215
|
docx-redline extract contract.docx --range 10:30
|
|
@@ -155,21 +228,32 @@ docx-redline apply contract.docx --target "Typo fix" --modified "Fixed typo" --n
|
|
|
155
228
|
# Cross-author edit inside another reviewer's pending insertion
|
|
156
229
|
docx-redline apply contract.docx --target "Another author's clause" --modified "Revised clause" --existing-revisions slice-cross-author --output reviewed.docx
|
|
157
230
|
|
|
158
|
-
# High-assurance atomic batch
|
|
159
|
-
docx-redline apply contract.docx --operations operations.json --atomic --output reviewed.docx
|
|
231
|
+
# High-assurance atomic batch with nonzero exit on any incomplete result
|
|
232
|
+
docx-redline apply contract.docx --operations operations.json --atomic --require-complete --output reviewed.docx
|
|
233
|
+
|
|
234
|
+
# Agent shell path: JSON is emitted by a serializer, not interpolated by the shell
|
|
235
|
+
node emit-operations.mjs | docx-redline apply contract.docx --operations - --profile agent --output reviewed.docx
|
|
160
236
|
```
|
|
161
237
|
|
|
162
238
|
All commands emit JSON on stdout. `apply` defaults:
|
|
163
239
|
- **Author**: Defaults to `'AI Redliner'` (or `DOCX_REDLINE_AUTHOR` environment variable).
|
|
164
240
|
- **Output overwrite**: Destination files provided via `--output` overwrite by default. Pass `--no-overwrite` or `--no-clobber` to safeguard existing destination files. The source input is never overwritten unless `--in-place` is specified.
|
|
165
241
|
- **Existing revisions**: Defaults to `'merge-same-author'`. Pass `--existing-revisions slice-cross-author` to edit inside another reviewer's pending insertions with native carrier slicing.
|
|
166
|
-
- **Transactionality**: Defaults to `atomic: false` (applies valid operations and reports any failures). Pass `--atomic` for all-or-nothing rollback on any operation error.
|
|
242
|
+
- **Transactionality**: Defaults to `atomic: false` (applies valid operations and reports any failures). Pass `--atomic` for all-or-nothing rollback on any operation error.
|
|
243
|
+
- **Complete-success exit**: Pass `--require-complete` when a progressive `partial` result must exit with code `3`; errors exit with code `2`. Without the flag, partial results retain the legacy zero exit code, so always inspect `completion`.
|
|
244
|
+
- **Agent profile**: `--profile agent` explicitly enables atomic rollback and complete-success exit behavior while retaining strict targeting, validation, tracked changes, and `merge-same-author`. It reports the resolved `effectiveOptions`; explicit flags take precedence.
|
|
245
|
+
- **Operations from stdin**: `--operations -` reads the same array or `{ operations, expectedRevision }` envelope accepted from a file. Feed it from a JSON serializer or structured process API, not shell-interpolated legal text.
|
|
167
246
|
- **Tracked changes**: Defaults to `generateRedlines: true`. Pass `--no-redlines` when clean direct text edits are desired.
|
|
168
247
|
- **Inline edits**: Use `--target <text>` with `--modified <text>` or `--comment <text>` for quick one-liners without creating a JSON file.
|
|
169
248
|
- **Compact mutation results**: `apply`, `accept`, `reject`, and `delete-comments` omit full OOXML/package payloads and inspection text from stdout. They report `written`, `outputPath`, per-operation results and receipts, compact validation counts, and a derived `completion` boolean. Use `validate` when full issue arrays are needed.
|
|
249
|
+
|
|
250
|
+
`docx-redline version` reports contract version 5 and the additive
|
|
251
|
+
`batch-start-source-binding`, `recovery-envelope-v1`, and
|
|
252
|
+
`require-complete-exit`, `operations-stdin`, and `agent-profile-v1`
|
|
253
|
+
capabilities. Wrappers should negotiate only the capabilities they use.
|
|
170
254
|
|
|
171
|
-
See
|
|
172
|
-
[operation JSON Schema](docs/schemas/document-operations.schema.json).
|
|
255
|
+
See the [compact agent fast start](./docs/AGENT_FAST_START.md) and the
|
|
256
|
+
[operation JSON Schema](docs/schemas/document-operations.schema.json).
|
|
173
257
|
|
|
174
258
|
### Configuration (call once at startup)
|
|
175
259
|
|
|
@@ -203,7 +287,7 @@ Common result fields:
|
|
|
203
287
|
| Field | Purpose |
|
|
204
288
|
|-------|---------|
|
|
205
289
|
| `status` | Operation status: `'ok'`, `'partial'`, `'no-op'`, or `'error'`. |
|
|
206
|
-
| `error` | Present on failure;
|
|
290
|
+
| `error` | Present on failure; retains a stable `code` and adds recovery envelope version, stage, category, bounded context, and a machine-readable recovery action. |
|
|
207
291
|
| `written` | CLI/facade boolean indicating whether the output file was successfully written to disk. |
|
|
208
292
|
| `completion` | CLI-only boolean that is `true` only when a destination was written, top-level status is neither error nor partial, and no operation result failed. |
|
|
209
293
|
| `rolledBack` | Present and `true` when an atomic batch encountered an error and rolled back all changes. |
|
|
@@ -369,7 +453,17 @@ import {
|
|
|
369
453
|
import { getParagraphText } from '@ansonlai/docx-redline-js/core/paragraph-targeting.js';
|
|
370
454
|
```
|
|
371
455
|
|
|
372
|
-
Use `applyOperationsToDocumentXml(...)` for mixed batches. It stably runs comments before text-changing operations so replacements cannot invalidate their original anchors. Other operation types retain their relative order. Batch results retain each operation's original 1-based index and expose the actual `executionOrder`.
|
|
456
|
+
Use `applyOperationsToDocumentXml(...)` for mixed batches. It stably runs comments before text-changing operations so replacements cannot invalidate their original anchors. Other operation types retain their relative order. Batch results retain each operation's original 1-based index and expose the actual `executionOrder`.
|
|
457
|
+
|
|
458
|
+
Before mutation, the runner resolves strong source descriptors against the
|
|
459
|
+
immutable batch-start document and binds them to session-local source
|
|
460
|
+
identities. Independent edits therefore do not need to be manually sorted when
|
|
461
|
+
an earlier structural rewrite changes later paragraph indexes or fingerprints.
|
|
462
|
+
Targets that deliberately refer to uniquely created paragraph text are compiled
|
|
463
|
+
into an internal capture dependency. True overlap is not guessed: incompatible
|
|
464
|
+
writes to one source fail before mutation with `OVERLAPPING_SOURCE_TARGETS` or
|
|
465
|
+
`REVISION_ORDER_CONFLICT`, and mutating capture fan-out without distinct
|
|
466
|
+
selectors fails with `CAPTURE_FANOUT_CONFLICT`.
|
|
373
467
|
|
|
374
468
|
Threaded replies use a comment operation with no body target:
|
|
375
469
|
|
|
@@ -392,12 +486,23 @@ serialization. Accuracy remains the controlling constraint: each operation has
|
|
|
392
486
|
an internal savepoint so an error or no-op cannot leak a partial edit or consumed
|
|
393
487
|
revision ID into later operations.
|
|
394
488
|
|
|
395
|
-
Batches are
|
|
396
|
-
|
|
397
|
-
`
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
`{ continueOnError: false }`
|
|
489
|
+
Batches are progressive by default (`atomic: false`): valid operations commit
|
|
490
|
+
while failed operations remain unapplied and are reported in `results`. Pass
|
|
491
|
+
`{ atomic: true }` when any operation error must return the original
|
|
492
|
+
`documentXml`, `hasChanges: false`, empty package artifacts, and
|
|
493
|
+
`rolledBack: true`. The default `continueOnError: true` still attempts the full
|
|
494
|
+
batch so `results` describes every operation; use `{ continueOnError: false }`
|
|
495
|
+
to stop after the first error.
|
|
496
|
+
|
|
497
|
+
Every failed or partial mutation includes `retryPlan`. Its `base` is `original`
|
|
498
|
+
after rollback/no commit and `output` after a progressive partial commit;
|
|
499
|
+
`committedIndexes`, `failedIndexes`, and `unattemptedIndexes` identify the safe
|
|
500
|
+
replay scope. Errors use recovery envelope version 1 and always report
|
|
501
|
+
`sameArgumentsSafe: false`. Follow `error.recovery.action`; do not infer a retry
|
|
502
|
+
from prose. Authorization-sensitive actions, such as resolving comments, are
|
|
503
|
+
marked with `requiresUserAuthorization: true`. For `EXISTING_REVISIONS`, the
|
|
504
|
+
non-normalizing surgical recommendation is `slice-cross-author`; accepting or
|
|
505
|
+
rejecting prior revisions still requires explicit authority.
|
|
401
506
|
|
|
402
507
|
Comment anchors use exact matching first, then a unique ASCII-space/NBSP
|
|
403
508
|
equivalent match that preserves source offsets and text. Missing anchors return
|
|
@@ -541,7 +646,8 @@ const restoration = await applyOperationToDocumentXml(
|
|
|
541
646
|
documentXml,
|
|
542
647
|
{
|
|
543
648
|
type: 'restore',
|
|
544
|
-
|
|
649
|
+
// restore targets default to the rejected view, where deleted text exists
|
|
650
|
+
target: { paragraphId: '1A2B3C4D', exactText: 'Original deleted paragraph text.' },
|
|
545
651
|
modified: 'Restored or adjusted paragraph text.'
|
|
546
652
|
},
|
|
547
653
|
'Editor'
|
|
@@ -551,6 +657,8 @@ const restoration = await applyOperationToDocumentXml(
|
|
|
551
657
|
// restoration follows the complete deleted source block. Unchanged legacy
|
|
552
658
|
// validation defects are retained as baseline issues; newly generated errors
|
|
553
659
|
// fail closed before commit.
|
|
660
|
+
// inspect/extract descriptors report their revisionView, and each fingerprint
|
|
661
|
+
// is computed from the same view as exactText. Keep those fields together.
|
|
554
662
|
|
|
555
663
|
// To insert run-level text at a location visible only in the rejected view,
|
|
556
664
|
// provide explicit rejected-view intent and an exact anchor-relative offset.
|
|
@@ -650,8 +758,10 @@ invariant with a fresh seed. See [Release validation in docs/TESTING.md](./docs/
|
|
|
650
758
|
|
|
651
759
|
## Architecture & Contributing
|
|
652
760
|
|
|
653
|
-
- **[ARCHITECTURE.md](./ARCHITECTURE.md)**: Detailed module layout, end-to-end reconciliation flow, and contributor fast orientation.
|
|
654
|
-
- **[AGENTS.md](./AGENTS.md)**:
|
|
761
|
+
- **[ARCHITECTURE.md](./ARCHITECTURE.md)**: Detailed module layout, end-to-end reconciliation flow, and contributor fast orientation.
|
|
762
|
+
- **[AGENTS.md](./AGENTS.md)**: Fast-start routing and operational guardrails for AI coding agents.
|
|
763
|
+
- **[docs/AGENT_FAST_START.md](./docs/AGENT_FAST_START.md)**: Minimal ordinary-edit contract for agent integrations.
|
|
764
|
+
- **[docs/AGENT_KNOWLEDGE_BASE.md](./docs/AGENT_KNOWLEDGE_BASE.md)**: Full agent reference for APIs, operations, CLI automation, recovery, and gotchas.
|
|
655
765
|
- **[docs/TESTING.md](./docs/TESTING.md)**: Comprehensive testing model, test lanes, independent oracle checks, and visual review checklist.
|
|
656
766
|
- **[CHANGELOG.md](./CHANGELOG.md)**: Version history, migration guides, and deprecation schedules.
|
|
657
767
|
|
|
@@ -424,9 +424,14 @@ export function resolveTargetParagraph(xmlDoc, options = {}) {
|
|
|
424
424
|
);
|
|
425
425
|
}
|
|
426
426
|
if (descriptor.fingerprint && descriptor.fingerprint !== actualFingerprint) {
|
|
427
|
+
const alternateView = revisionView === 'rejected' ? 'accepted' : 'rejected';
|
|
428
|
+
const alternateFingerprint = createParagraphFingerprint(byId, { revisionView: alternateView });
|
|
429
|
+
const viewHint = descriptor.fingerprint === alternateFingerprint
|
|
430
|
+
? ` The supplied fingerprint matches the ${alternateView} view; set target.revisionView to "${alternateView}" or use a fingerprint extracted from the ${revisionView} view.`
|
|
431
|
+
: '';
|
|
427
432
|
throw createTargetError(
|
|
428
433
|
'TARGET_FINGERPRINT_MISMATCH',
|
|
429
|
-
`Target paragraphId "${descriptor.paragraphId}" no longer matches its source fingerprint
|
|
434
|
+
`Target paragraphId "${descriptor.paragraphId}" no longer matches its source fingerprint.${viewHint}`,
|
|
430
435
|
cachedEntry ? [serializeTargetCandidate(cachedEntry)] : null
|
|
431
436
|
);
|
|
432
437
|
}
|
|
@@ -438,9 +443,16 @@ export function resolveTargetParagraph(xmlDoc, options = {}) {
|
|
|
438
443
|
);
|
|
439
444
|
}
|
|
440
445
|
if (cleanTargetText && actualText !== normalizeWhitespaceForTargeting(cleanTargetText)) {
|
|
446
|
+
const alternateView = revisionView === 'rejected' ? 'accepted' : 'rejected';
|
|
447
|
+
const alternateText = normalizeWhitespaceForTargeting(
|
|
448
|
+
extractCanonicalParagraphText(byId, { revisionView: alternateView })
|
|
449
|
+
);
|
|
450
|
+
const viewHint = alternateText === normalizeWhitespaceForTargeting(cleanTargetText)
|
|
451
|
+
? ` The supplied text matches the ${alternateView} view; set target.revisionView to "${alternateView}".`
|
|
452
|
+
: '';
|
|
441
453
|
throw createTargetError(
|
|
442
454
|
'TARGET_TEXT_MISMATCH',
|
|
443
|
-
`Target paragraphId "${descriptor.paragraphId}" no longer matches the supplied text
|
|
455
|
+
`Target paragraphId "${descriptor.paragraphId}" no longer matches the supplied text.${viewHint}`,
|
|
444
456
|
cachedEntry ? [serializeTargetCandidate(cachedEntry)] : null
|
|
445
457
|
);
|
|
446
458
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ansonlai/docx-redline-js v0.
|
|
1
|
+
// @ansonlai/docx-redline-js v0.6.0 — https://github.com/AnsonLai/docx-redline-js
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -4890,6 +4890,47 @@ function extractFormattingFromOoxml(xmlDoc) {
|
|
|
4890
4890
|
return { existingFormatHints, textSpans, paragraphs };
|
|
4891
4891
|
}
|
|
4892
4892
|
|
|
4893
|
+
// core/revision-cloning.js
|
|
4894
|
+
var REVISION_HISTORY_ELEMENTS = /* @__PURE__ */ new Set([
|
|
4895
|
+
"ins",
|
|
4896
|
+
"del",
|
|
4897
|
+
"moveFrom",
|
|
4898
|
+
"moveTo",
|
|
4899
|
+
"pPrChange",
|
|
4900
|
+
"rPrChange",
|
|
4901
|
+
"tblPrChange",
|
|
4902
|
+
"trPrChange",
|
|
4903
|
+
"tcPrChange",
|
|
4904
|
+
"sectPrChange"
|
|
4905
|
+
]);
|
|
4906
|
+
function clonePropertiesWithoutRevisionHistory(root) {
|
|
4907
|
+
if (!root) return null;
|
|
4908
|
+
const clone = root.cloneNode(true);
|
|
4909
|
+
const candidates = [clone, ...Array.from(clone.getElementsByTagName?.("*") || [])];
|
|
4910
|
+
for (const node of candidates.reverse()) {
|
|
4911
|
+
if (!REVISION_HISTORY_ELEMENTS.has(String(node.localName || node.nodeName || "").replace(/^.*:/, ""))) continue;
|
|
4912
|
+
node.parentNode?.removeChild(node);
|
|
4913
|
+
}
|
|
4914
|
+
return clone;
|
|
4915
|
+
}
|
|
4916
|
+
function refreshRunPropertyChangeIds(root, allocator = null) {
|
|
4917
|
+
if (!root) return root;
|
|
4918
|
+
const xmlDoc = root.nodeType === 9 ? root : root.ownerDocument;
|
|
4919
|
+
const resolvedAllocator = allocator instanceof RevisionIdAllocator ? allocator : getRevisionIdAllocatorForDocument(xmlDoc) || createRevisionIdAllocator(xmlDoc);
|
|
4920
|
+
const candidates = [root, ...Array.from(root.getElementsByTagName?.("*") || [])];
|
|
4921
|
+
for (const node of candidates) {
|
|
4922
|
+
if (!isWordElement(node, "rPrChange")) continue;
|
|
4923
|
+
const nextId = String(resolvedAllocator.next());
|
|
4924
|
+
if (typeof node.setAttributeNS === "function") {
|
|
4925
|
+
node.setAttributeNS(NS_W, "w:id", nextId);
|
|
4926
|
+
} else {
|
|
4927
|
+
node.setAttribute("w:id", nextId);
|
|
4928
|
+
}
|
|
4929
|
+
resolvedAllocator._receiptCollector?.recordRevision(Number(nextId), "rPrChange");
|
|
4930
|
+
}
|
|
4931
|
+
return root;
|
|
4932
|
+
}
|
|
4933
|
+
|
|
4893
4934
|
// engine/run-builders.js
|
|
4894
4935
|
function createTrackChange(xmlDoc, type, run, author, revisionMetadata = null) {
|
|
4895
4936
|
const wrapper = createWordElement(xmlDoc, type === "ins" ? "w:ins" : "w:del");
|
|
@@ -5078,13 +5119,8 @@ function snapshotAndAttachRPrChange(xmlDoc, rPr, author, dateStr, sourceNode) {
|
|
|
5078
5119
|
rPrChange.setAttribute("w:id", String(metadata.id));
|
|
5079
5120
|
rPrChange.setAttribute("w:author", metadata.author);
|
|
5080
5121
|
rPrChange.setAttribute("w:date", dateStr || metadata.date);
|
|
5081
|
-
const previousRPr = createWordElement(xmlDoc, "w:rPr");
|
|
5082
5122
|
const source = sourceNode || rPr;
|
|
5083
|
-
|
|
5084
|
-
if (child.nodeName !== "w:rPrChange") {
|
|
5085
|
-
previousRPr.appendChild(child.cloneNode(true));
|
|
5086
|
-
}
|
|
5087
|
-
});
|
|
5123
|
+
const previousRPr = clonePropertiesWithoutRevisionHistory(source) || createWordElement(xmlDoc, "w:rPr");
|
|
5088
5124
|
rPrChange.appendChild(previousRPr);
|
|
5089
5125
|
const existing = getFirstElementByTag(rPr, "w:rPrChange");
|
|
5090
5126
|
if (existing) {
|
|
@@ -5894,25 +5930,6 @@ function describeInsertionBoundary(spanIndex, pos, fallbackParagraph = null) {
|
|
|
5894
5930
|
};
|
|
5895
5931
|
}
|
|
5896
5932
|
|
|
5897
|
-
// core/revision-cloning.js
|
|
5898
|
-
function refreshRunPropertyChangeIds(root, allocator = null) {
|
|
5899
|
-
if (!root) return root;
|
|
5900
|
-
const xmlDoc = root.nodeType === 9 ? root : root.ownerDocument;
|
|
5901
|
-
const resolvedAllocator = allocator instanceof RevisionIdAllocator ? allocator : getRevisionIdAllocatorForDocument(xmlDoc) || createRevisionIdAllocator(xmlDoc);
|
|
5902
|
-
const candidates = [root, ...Array.from(root.getElementsByTagName?.("*") || [])];
|
|
5903
|
-
for (const node of candidates) {
|
|
5904
|
-
if (!isWordElement(node, "rPrChange")) continue;
|
|
5905
|
-
const nextId = String(resolvedAllocator.next());
|
|
5906
|
-
if (typeof node.setAttributeNS === "function") {
|
|
5907
|
-
node.setAttributeNS(NS_W, "w:id", nextId);
|
|
5908
|
-
} else {
|
|
5909
|
-
node.setAttribute("w:id", nextId);
|
|
5910
|
-
}
|
|
5911
|
-
resolvedAllocator._receiptCollector?.recordRevision(Number(nextId), "rPrChange");
|
|
5912
|
-
}
|
|
5913
|
-
return root;
|
|
5914
|
-
}
|
|
5915
|
-
|
|
5916
5933
|
// engine/surgical-run-splitting.js
|
|
5917
5934
|
var TRACK_CHANGE_CARRIERS = /* @__PURE__ */ new Set(["ins", "del"]);
|
|
5918
5935
|
function getRunContentPieces(runElement) {
|
|
@@ -8415,6 +8432,51 @@ function getCommentIdsInOoxml(node) {
|
|
|
8415
8432
|
}
|
|
8416
8433
|
return [...ids].sort((a, b) => Number(a) - Number(b) || a.localeCompare(b));
|
|
8417
8434
|
}
|
|
8435
|
+
function directWordChild(node, localName2) {
|
|
8436
|
+
return Array.from(node?.childNodes || []).find((child) => child?.nodeType === 1 && child.namespaceURI === NS_W && child.localName === localName2) || null;
|
|
8437
|
+
}
|
|
8438
|
+
function insertedParagraphMarkMetadata(paragraph, author) {
|
|
8439
|
+
const marker = directWordChild(directWordChild(directWordChild(paragraph, "pPr"), "rPr"), "ins");
|
|
8440
|
+
if (!marker) return null;
|
|
8441
|
+
const markerAuthor = marker.getAttribute("w:author") || marker.getAttributeNS(NS_W, "author") || "";
|
|
8442
|
+
if (markerAuthor.trim().toLowerCase() !== String(author || "").trim().toLowerCase()) return null;
|
|
8443
|
+
return {
|
|
8444
|
+
id: marker.getAttribute("w:id") || marker.getAttributeNS(NS_W, "id") || "",
|
|
8445
|
+
author: markerAuthor,
|
|
8446
|
+
date: marker.getAttribute("w:date") || marker.getAttributeNS(NS_W, "date") || ""
|
|
8447
|
+
};
|
|
8448
|
+
}
|
|
8449
|
+
function emptyParagraphBaseline(paragraph, serializer) {
|
|
8450
|
+
const clone = paragraph.cloneNode(false);
|
|
8451
|
+
const pPr = directWordChild(paragraph, "pPr");
|
|
8452
|
+
if (pPr) clone.appendChild(clonePropertiesWithoutRevisionHistory(pPr));
|
|
8453
|
+
return serializer.serializeToString(clone);
|
|
8454
|
+
}
|
|
8455
|
+
function restoreInsertedParagraphMark(oxml, metadata) {
|
|
8456
|
+
if (!metadata || typeof oxml !== "string" || !oxml.trim()) return oxml;
|
|
8457
|
+
const parsed = parseOoxmlSafe(oxml, "text/xml");
|
|
8458
|
+
if (!parsed.doc || parsed.error) return oxml;
|
|
8459
|
+
const paragraph = parsed.doc.documentElement?.localName === "p" ? parsed.doc.documentElement : getDocumentParagraphs(parsed.doc)[0];
|
|
8460
|
+
if (!paragraph) return oxml;
|
|
8461
|
+
let pPr = directWordChild(paragraph, "pPr");
|
|
8462
|
+
if (!pPr) {
|
|
8463
|
+
pPr = createWordElement(parsed.doc, "w:pPr");
|
|
8464
|
+
paragraph.insertBefore(pPr, paragraph.firstChild);
|
|
8465
|
+
}
|
|
8466
|
+
let rPr = directWordChild(pPr, "rPr");
|
|
8467
|
+
if (!rPr) {
|
|
8468
|
+
rPr = createWordElement(parsed.doc, "w:rPr");
|
|
8469
|
+
pPr.appendChild(rPr);
|
|
8470
|
+
}
|
|
8471
|
+
if (!directWordChild(rPr, "ins")) {
|
|
8472
|
+
const marker = createWordElement(parsed.doc, "w:ins");
|
|
8473
|
+
if (metadata.id) marker.setAttribute("w:id", metadata.id);
|
|
8474
|
+
marker.setAttribute("w:author", metadata.author);
|
|
8475
|
+
if (metadata.date) marker.setAttribute("w:date", metadata.date);
|
|
8476
|
+
rPr.appendChild(marker);
|
|
8477
|
+
}
|
|
8478
|
+
return serializeXml(parsed.doc);
|
|
8479
|
+
}
|
|
8418
8480
|
async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}) {
|
|
8419
8481
|
const inputOoxml = oxml;
|
|
8420
8482
|
let workingOoxml = oxml;
|
|
@@ -8426,6 +8488,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
8426
8488
|
let parseWarnings = [];
|
|
8427
8489
|
const operationWarnings = [];
|
|
8428
8490
|
let normalizedExistingRevisions = false;
|
|
8491
|
+
let preservedInsertedParagraphMark = null;
|
|
8429
8492
|
const existingRevisionsPolicy = options.existingRevisions || "merge-same-author";
|
|
8430
8493
|
const keepNormalizedNoOp = existingRevisionsPolicy === "accept-all-first-keep-normalized";
|
|
8431
8494
|
const finalize = (result) => {
|
|
@@ -8456,6 +8519,9 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
8456
8519
|
if (!withStatus.status) {
|
|
8457
8520
|
withStatus.status = withStatus.hasChanges ? "ok" : "no-op";
|
|
8458
8521
|
}
|
|
8522
|
+
if (preservedInsertedParagraphMark && withStatus.hasChanges && typeof withStatus.oxml === "string") {
|
|
8523
|
+
withStatus.oxml = restoreInsertedParagraphMark(withStatus.oxml, preservedInsertedParagraphMark);
|
|
8524
|
+
}
|
|
8459
8525
|
return withOoxmlSourceType(withStatus);
|
|
8460
8526
|
};
|
|
8461
8527
|
const finalizeUnchanged = () => {
|
|
@@ -8521,9 +8587,11 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
8521
8587
|
});
|
|
8522
8588
|
}
|
|
8523
8589
|
log("[OxmlEngine] Existing revisions from same author detected; rejecting previous changes to merge against baseline");
|
|
8590
|
+
const soleParagraph = inputParagraphs.length === 1 ? inputParagraphs[0] : null;
|
|
8591
|
+
preservedInsertedParagraphMark = insertedParagraphMarkMetadata(soleParagraph, author);
|
|
8524
8592
|
const rejected = rejectTrackedChangesInOoxml(inputOoxml, { author });
|
|
8525
8593
|
if (rejected.status === "error") return finalize(rejected);
|
|
8526
|
-
workingOoxml = rejected.oxml;
|
|
8594
|
+
workingOoxml = preservedInsertedParagraphMark && !String(rejected.oxml || "").trim() ? emptyParagraphBaseline(soleParagraph, serializer) : rejected.oxml;
|
|
8527
8595
|
normalizedExistingRevisions = true;
|
|
8528
8596
|
const rejectedParsed = parseOoxmlSafe(workingOoxml, "text/xml");
|
|
8529
8597
|
parseWarnings.push(...rejectedParsed.warnings);
|
|
@@ -8565,7 +8633,9 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
8565
8633
|
status: "error",
|
|
8566
8634
|
error: {
|
|
8567
8635
|
code: "EXISTING_REVISIONS",
|
|
8568
|
-
message: `Input OOXML contains tracked changes from another author (${authors.length ? authors.join(", ") : "unattributed"}).
|
|
8636
|
+
message: `Input OOXML contains tracked changes from another author (${authors.length ? authors.join(", ") : "unattributed"}). Use existingRevisions: "slice-cross-author" for a surgical edit that preserves reviewer history; accepting or rejecting revisions requires separate authorization.`,
|
|
8637
|
+
revisionAuthors: authors,
|
|
8638
|
+
currentPolicy: existingRevisionsPolicy
|
|
8569
8639
|
}
|
|
8570
8640
|
});
|
|
8571
8641
|
} else {
|
|
@@ -9173,9 +9243,12 @@ function resolveTargetParagraph(xmlDoc, options = {}) {
|
|
|
9173
9243
|
);
|
|
9174
9244
|
}
|
|
9175
9245
|
if (descriptor.fingerprint && descriptor.fingerprint !== actualFingerprint) {
|
|
9246
|
+
const alternateView = revisionView === "rejected" ? "accepted" : "rejected";
|
|
9247
|
+
const alternateFingerprint = createParagraphFingerprint(byId, { revisionView: alternateView });
|
|
9248
|
+
const viewHint = descriptor.fingerprint === alternateFingerprint ? ` The supplied fingerprint matches the ${alternateView} view; set target.revisionView to "${alternateView}" or use a fingerprint extracted from the ${revisionView} view.` : "";
|
|
9176
9249
|
throw createTargetError(
|
|
9177
9250
|
"TARGET_FINGERPRINT_MISMATCH",
|
|
9178
|
-
`Target paragraphId "${descriptor.paragraphId}" no longer matches its source fingerprint
|
|
9251
|
+
`Target paragraphId "${descriptor.paragraphId}" no longer matches its source fingerprint.${viewHint}`,
|
|
9179
9252
|
cachedEntry ? [serializeTargetCandidate(cachedEntry)] : null
|
|
9180
9253
|
);
|
|
9181
9254
|
}
|
|
@@ -9187,9 +9260,14 @@ function resolveTargetParagraph(xmlDoc, options = {}) {
|
|
|
9187
9260
|
);
|
|
9188
9261
|
}
|
|
9189
9262
|
if (cleanTargetText && actualText !== normalizeWhitespaceForTargeting(cleanTargetText)) {
|
|
9263
|
+
const alternateView = revisionView === "rejected" ? "accepted" : "rejected";
|
|
9264
|
+
const alternateText = normalizeWhitespaceForTargeting(
|
|
9265
|
+
extractCanonicalParagraphText(byId, { revisionView: alternateView })
|
|
9266
|
+
);
|
|
9267
|
+
const viewHint = alternateText === normalizeWhitespaceForTargeting(cleanTargetText) ? ` The supplied text matches the ${alternateView} view; set target.revisionView to "${alternateView}".` : "";
|
|
9190
9268
|
throw createTargetError(
|
|
9191
9269
|
"TARGET_TEXT_MISMATCH",
|
|
9192
|
-
`Target paragraphId "${descriptor.paragraphId}" no longer matches the supplied text
|
|
9270
|
+
`Target paragraphId "${descriptor.paragraphId}" no longer matches the supplied text.${viewHint}`,
|
|
9193
9271
|
cachedEntry ? [serializeTargetCandidate(cachedEntry)] : null
|
|
9194
9272
|
);
|
|
9195
9273
|
}
|
|
@@ -11403,9 +11481,10 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
11403
11481
|
const resolveNumbering = createNumberingResolver(numberingPart.doc);
|
|
11404
11482
|
let nearestHeading = null;
|
|
11405
11483
|
const paragraphNodes = getDocumentParagraphNodes(documentPart.doc);
|
|
11406
|
-
const
|
|
11484
|
+
const revisionView = options.revisionView === "rejected" ? "rejected" : "accepted";
|
|
11485
|
+
const commentAnchors = collectDocumentCommentAnchors(paragraphNodes, revisionView);
|
|
11407
11486
|
let paragraphs = paragraphNodes.map((paragraph, zeroIndex) => {
|
|
11408
|
-
const text = extractCanonicalParagraphText(paragraph, { revisionView
|
|
11487
|
+
const text = extractCanonicalParagraphText(paragraph, { revisionView });
|
|
11409
11488
|
const level = headingLevel(paragraph);
|
|
11410
11489
|
if (level) nearestHeading = { level, text };
|
|
11411
11490
|
const ids = [...new Set([...descendants(paragraph, "commentRangeStart"), ...descendants(paragraph, "commentReference")].map((node) => attr2(node, "id")).filter(Boolean))];
|
|
@@ -11422,7 +11501,8 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
11422
11501
|
index,
|
|
11423
11502
|
ref: `P${index}`,
|
|
11424
11503
|
paragraphId: getParagraphId2(paragraph),
|
|
11425
|
-
fingerprint: createParagraphFingerprint(paragraph),
|
|
11504
|
+
fingerprint: createParagraphFingerprint(paragraph, { text, index, revisionView }),
|
|
11505
|
+
revisionView,
|
|
11426
11506
|
text,
|
|
11427
11507
|
exactText: text,
|
|
11428
11508
|
excerpt: text.slice(0, options.excerptLength || 120),
|