@ansonlai/docx-redline-js 0.4.0 → 0.5.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 +589 -287
- package/ARCHITECTURE.md +215 -9
- package/CHANGELOG.md +319 -0
- package/README.md +604 -360
- package/adapters/config.js +45 -43
- package/bin/docx-redline.js +3 -0
- package/core/list-targeting.js +101 -110
- package/core/paragraph-targeting.js +501 -61
- package/core/paragraph-text.js +209 -0
- package/core/revision-cloning.js +38 -0
- package/core/types.js +64 -10
- package/core/word-xml.js +43 -15
- package/dist/docx-redline-js.esm.js +2849 -466
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +87 -76
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/docs/TESTING.md +342 -23
- package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +1669 -0
- package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +669 -0
- package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +427 -0
- package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +519 -0
- package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +69 -0
- package/docs/plans/completed/structural-revision-capability-matrix.md +115 -0
- package/docs/schemas/document-operations.schema.json +109 -0
- package/docs/test-comparison-dashboard.html +4250 -7
- package/engine/formatting-removal.js +11 -2
- package/engine/oxml-engine.js +491 -336
- package/engine/reconstruction-mode.js +15 -14
- package/engine/reconstruction-writer.js +247 -142
- package/engine/route-selection.js +35 -0
- package/engine/rpr-helpers.js +334 -35
- package/engine/run-builders.js +239 -196
- package/engine/surgical-diff-application.js +222 -37
- package/engine/surgical-mode.js +134 -6
- package/engine/surgical-spans.js +52 -1
- package/engine/table-cell-context.js +3 -6
- package/engine/table-mode.js +1 -1
- package/index.d.ts +234 -6
- package/index.js +24 -1
- package/node/cli.js +317 -0
- package/node/docx-document.js +302 -0
- package/node/index.d.ts +31 -0
- package/node/index.js +2 -0
- package/node/zip-archive.js +52 -0
- package/orchestration/list-markdown.js +10 -16
- package/orchestration/list-parsing.js +7 -12
- package/orchestration/list-structural-fallback.js +21 -10
- package/package.json +24 -3
- package/pipeline/content-analysis.js +12 -17
- package/pipeline/ingestion-export.js +3 -31
- package/pipeline/ingestion-paragraph.js +10 -5
- package/pipeline/list-generation.js +150 -55
- package/pipeline/list-markers.js +70 -3
- package/pipeline/serialization.js +4 -2
- package/pipeline/structured-content.js +160 -0
- package/scripts/apply_changes.mjs +27 -0
- package/scripts/benchmark-operation-session.mjs +137 -0
- package/scripts/benchmark-targeting-browser.html +74 -0
- package/scripts/benchmark-targeting-hot-paths.mjs +67 -0
- package/scripts/benchmark-test-runner.mjs +59 -0
- package/scripts/build-test-dashboard.mjs +23 -0
- package/scripts/export-lane1-fixtures.mjs +380 -0
- package/scripts/export-reredline-stress-fixtures.mjs +317 -0
- package/scripts/export-validation-fixtures.mjs +1 -1
- package/scripts/extract_text.mjs +7 -0
- package/scripts/generate-paragraph-boundary-fixtures.ps1 +215 -0
- package/scripts/generate-test-dashboard.mjs +362 -11
- package/scripts/lib/word-coverage-catalogue.mjs +6 -2
- package/scripts/profile-route-selection.mjs +19 -0
- package/scripts/render-agenda-multilevel.mjs +0 -5
- package/scripts/render-multilevel-cases.mjs +0 -1
- package/scripts/run-tests.mjs +107 -35
- package/scripts/word-com-corpus-suite.ps1 +3 -0
- package/scripts/word-com-differential.ps1 +64 -4
- package/scripts/word-com-suite.ps1 +3 -0
- package/services/batch-operation-orchestrator.js +494 -0
- package/services/capture-engine.js +226 -0
- package/services/comment-builders.js +23 -6
- package/services/comment-engine.js +108 -47
- package/services/comment-locator.js +187 -82
- package/services/comment-replies.js +95 -0
- package/services/document-inspection.js +258 -0
- package/services/document-operation-applier.js +372 -0
- package/services/document-operation-contract.js +323 -0
- package/services/document-operation-mutations.js +1733 -0
- package/services/document-operation-session.js +258 -0
- package/services/numbering-service.js +14 -5
- package/services/operation-heuristics.js +173 -0
- package/services/operation-preflight.js +366 -0
- package/services/receipt-collector.js +288 -0
- package/services/revision-comment-management.js +37 -5
- package/services/revision-token.js +290 -0
- package/services/standalone-docx-plumbing.js +123 -8
- package/services/standalone-operation-runner.d.ts +296 -0
- package/services/standalone-operation-runner.js +10 -1455
- package/services/table-reconciliation.js +15 -6
- package/docs/VALIDATION.md +0 -183
- package/docs/WORD-MANUAL-REVIEW.md +0 -138
- package/docs/plans/2026-09-01-performance-and-complexity-reduction.md +0 -210
- /package/docs/plans/{2026-08-30-reliability-testing-improvements.md → completed/2026-08-30-reliability-testing-improvements.md} +0 -0
|
@@ -19,8 +19,13 @@ import { preprocessMarkdown } from '../pipeline/markdown-processor.js';
|
|
|
19
19
|
* @returns {string} Complete w:tbl OOXML
|
|
20
20
|
*/
|
|
21
21
|
export function generateTableOoxml(tableData, options = {}) {
|
|
22
|
-
const {
|
|
23
|
-
|
|
22
|
+
const {
|
|
23
|
+
generateRedlines = false,
|
|
24
|
+
author = 'AI',
|
|
25
|
+
revisionIdAllocator = null,
|
|
26
|
+
trackAsBlock = false
|
|
27
|
+
} = options;
|
|
28
|
+
const tableInsertMeta = generateRedlines && trackAsBlock ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
24
29
|
|
|
25
30
|
// Determine number of columns
|
|
26
31
|
const numCols = tableData.headers?.length || (tableData.rows?.[0]?.length || 1);
|
|
@@ -60,7 +65,7 @@ export function generateTableOoxml(tableData, options = {}) {
|
|
|
60
65
|
|
|
61
66
|
// Build run model for the cell
|
|
62
67
|
const runModel = [{
|
|
63
|
-
kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
|
|
68
|
+
kind: generateRedlines && !trackAsBlock ? RunKind.INSERTION : RunKind.TEXT,
|
|
64
69
|
text: cleanText,
|
|
65
70
|
rPrXml: isHeaderRow ? '<w:rPr><w:b/></w:rPr>' : '',
|
|
66
71
|
author,
|
|
@@ -80,9 +85,13 @@ export function generateTableOoxml(tableData, options = {}) {
|
|
|
80
85
|
cellsXml += `<w:tc>${tcPr}${runsOoxml}</w:tc>`;
|
|
81
86
|
}
|
|
82
87
|
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
// Keep each logical record intact across page boundaries. When a
|
|
89
|
+
// Markdown header is present, also mark it as a repeating Word table
|
|
90
|
+
// header so continuation pages retain their column labels.
|
|
91
|
+
const trPr = isHeaderRow
|
|
92
|
+
? '<w:trPr><w:tblHeader/><w:cantSplit/></w:trPr>'
|
|
93
|
+
: '<w:trPr><w:cantSplit/></w:trPr>';
|
|
94
|
+
rowsXml += `<w:tr>${trPr}${cellsXml}</w:tr>`;
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
// Build the table
|
package/docs/VALIDATION.md
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
# Validation
|
|
2
|
-
|
|
3
|
-
This document is the release-validation reference. For the testing model and
|
|
4
|
-
step-by-step instructions for adding cases, see [TESTING.md](./TESTING.md).
|
|
5
|
-
|
|
6
|
-
This package works on OOXML strings and intentionally leaves `.docx` zip
|
|
7
|
-
packaging to consumers (release *tooling* assembles minimal `.docx` fixtures
|
|
8
|
-
with a script-local zip writer; the published library still has no zip
|
|
9
|
-
dependency).
|
|
10
|
-
|
|
11
|
-
The test suite verifies the accept/reject round-trip invariant using the
|
|
12
|
-
library's own transforms. Because a shared misconception between the
|
|
13
|
-
generator and the resolver would pass those tests silently, release
|
|
14
|
-
validation adds **independent oracles**: Microsoft Word, LibreOffice, and
|
|
15
|
-
the ECMA-376 schemas.
|
|
16
|
-
|
|
17
|
-
## Automated Checks (every `npm test`)
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npm test # includes tests/roundtrip_fuzz_tests.mjs (seeded, deterministic)
|
|
21
|
-
npm run test:isolation
|
|
22
|
-
npm run check:types
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
The fuzz harness generates random paragraph structures and edits, then
|
|
26
|
-
asserts the round-trip invariant plus `validateRedlineOoxml` on each case.
|
|
27
|
-
Tune or reproduce with:
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
FUZZ_SEED=<seed> FUZZ_ITERATIONS=<n> node tests/roundtrip_fuzz_tests.mjs
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
A failing case prints its exact reproduction command.
|
|
34
|
-
|
|
35
|
-
## Runtime Guardrail
|
|
36
|
-
|
|
37
|
-
`validateRedlineOoxml(oxml)` (exported from `index.js`) runs the structural
|
|
38
|
-
invariants at runtime and returns `{ valid, issues }`. Downstream packagers
|
|
39
|
-
should call it before writing engine output into `word/document.xml`.
|
|
40
|
-
|
|
41
|
-
## Export Fixtures
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
node scripts/export-validation-fixtures.mjs
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Writes to `tmp/validation-docx/`, per case:
|
|
48
|
-
|
|
49
|
-
- `<name>.document.xml` — generated `word/document.xml` payload
|
|
50
|
-
- `<name>.docx` — minimal assembled package
|
|
51
|
-
- `<name>.expected.json` — expected accept-all / reject-all plain text,
|
|
52
|
-
derived from edit *intent* (not from this library's transforms), so
|
|
53
|
-
external consumers act as independent oracles
|
|
54
|
-
|
|
55
|
-
## Word Differential Check (Windows, desktop Word)
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
npm run test:word
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
For each fixture, desktop Word opens the `.docx`, confirms revisions are
|
|
62
|
-
visible, runs **AcceptAllRevisions**, and compares the document text to the
|
|
63
|
-
expected modified text; then reopens and runs **RejectAllRevisions** and
|
|
64
|
-
compares to the original text. This is the strongest check available: Word
|
|
65
|
-
itself resolves the revisions this library generated.
|
|
66
|
-
|
|
67
|
-
`npm run test:word` exports the current English legal/administrative task
|
|
68
|
-
catalogue to `tmp/word-validation/` before running the differential. Expected
|
|
69
|
-
text is compared exactly by default; only Word's paragraph terminators are
|
|
70
|
-
normalized. The 33-case catalogue includes reliability regressions for literal
|
|
71
|
-
dollar/escape content, inline assistant-like text, leading whitespace,
|
|
72
|
-
multi-paragraph replacement, preserving prior revisions on no-op, and atomic
|
|
73
|
-
batch rollback after a later target failure. It also verifies that a document
|
|
74
|
-
with a near-limit prior revision ID produces safe low-range IDs, and exercises
|
|
75
|
-
bookmark/hyperlink adjacency, mixed formatted runs, content controls, table
|
|
76
|
-
cells, structural tabs, locked complex fields, comments, footnotes/endnotes,
|
|
77
|
-
headers/footers, and external hyperlinks with explicit structural-preservation
|
|
78
|
-
assertions. The
|
|
79
|
-
lower-level `npm run smoke:word:diff` remains available for an
|
|
80
|
-
already-exported fixture directory.
|
|
81
|
-
|
|
82
|
-
The older `npm run smoke:word -- path/to/file.docx` open-only smoke check
|
|
83
|
-
remains available for ad-hoc files.
|
|
84
|
-
|
|
85
|
-
## Schema Validation (ECMA-376 transitional XSD)
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
node scripts/export-validation-fixtures.mjs
|
|
89
|
-
bash scripts/validate-fixtures-xsd.sh
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Downloads (and caches in `.cache/ooxml-schemas/`) the transitional
|
|
93
|
-
wordprocessingml schemas from ECMA-376 Part 4, patches the `xml:` namespace
|
|
94
|
-
import to resolve offline, and validates every `*.document.xml` fixture with
|
|
95
|
-
`xmllint`. Requires `curl`, `unzip`, and `xmllint` (`libxml2-utils` on
|
|
96
|
-
Debian/Ubuntu; available on Windows via conda/msys).
|
|
97
|
-
|
|
98
|
-
## LibreOffice Consumer Check
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
cd tmp/validation-docx
|
|
102
|
-
soffice --headless --convert-to pdf --outdir converted *.docx
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
A second independent OOXML consumer parsing the fixtures without error.
|
|
106
|
-
|
|
107
|
-
## Continuous Validation
|
|
108
|
-
|
|
109
|
-
`.github/workflows/validation.yml` runs nightly (and on demand via
|
|
110
|
-
`workflow_dispatch`):
|
|
111
|
-
|
|
112
|
-
1. **xsd-schema** — exports fixtures and validates them against the
|
|
113
|
-
ECMA-376 transitional `wml.xsd`.
|
|
114
|
-
2. **libreoffice** — exports fixtures and converts them with headless
|
|
115
|
-
LibreOffice.
|
|
116
|
-
3. **fuzz-extended** — 20,000 fuzz round-trip cases with a date-derived
|
|
117
|
-
seed, so every night explores new inputs. A failure log includes the
|
|
118
|
-
exact `FUZZ_SEED` reproduction command.
|
|
119
|
-
|
|
120
|
-
The Word differential check stays manual because it requires desktop Word;
|
|
121
|
-
run it before tagging a release.
|
|
122
|
-
|
|
123
|
-
## JavaScript Coverage Baseline
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
npm run test:coverage
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
The command runs the complete JavaScript test suite under c8 and prints a
|
|
130
|
-
per-file report. Coverage answers “which implementation paths did the automated
|
|
131
|
-
JavaScript tests execute?” It does not prove that executed paths are correct,
|
|
132
|
-
that generated OOXML opens in Word, or that the real-document corpus is broad
|
|
133
|
-
enough. Treat it as a map for finding thinly tested code; Word, schema,
|
|
134
|
-
LibreOffice, fuzz, and corpus checks provide different evidence.
|
|
135
|
-
|
|
136
|
-
The initial Phase 7 baseline recorded on 2026-08-29 was:
|
|
137
|
-
|
|
138
|
-
| Metric | Coverage |
|
|
139
|
-
|---|---:|
|
|
140
|
-
| Lines / statements | 79.57% |
|
|
141
|
-
| Functions | 80.07% |
|
|
142
|
-
| Branches | 69.22% |
|
|
143
|
-
|
|
144
|
-
This is a visibility baseline, not a CI threshold. Notable opportunities from
|
|
145
|
-
the baseline are `services/numbering-helpers.js` (23.55% lines),
|
|
146
|
-
`orchestration/route-plan.js` (23.75%), and
|
|
147
|
-
`orchestration/list-structural-fallback.js` (57.19%).
|
|
148
|
-
|
|
149
|
-
The post-Phase-5 snapshot recorded on 2026-08-30 is 79.96% lines/statements,
|
|
150
|
-
80.95% functions, and 69.52% branches. Preserve both snapshots so changes are
|
|
151
|
-
visible over time rather than presenting coverage as a pass/fail quality score.
|
|
152
|
-
|
|
153
|
-
## Pinned SuperDoc Corpus References
|
|
154
|
-
|
|
155
|
-
The real-document lane uses explicitly selected references from
|
|
156
|
-
[SuperDoc's docx-corpus](https://docxcorp.us/) (ODC-By 1.0). It does not consume
|
|
157
|
-
a floating or bulk manifest. The reviewed reference set contains 10 English
|
|
158
|
-
legal and 10 English administrative documents. Run the complete local lane on
|
|
159
|
-
Windows with desktop Word installed:
|
|
160
|
-
|
|
161
|
-
```bash
|
|
162
|
-
npm run test:corpus:word
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
To fetch only particular reviewed sources:
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
npm run corpus:fetch:superdoc -- --id <pinned-sha256>
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Valid IDs and provenance are recorded in
|
|
172
|
-
`tests/corpus/superdoc-english-legal-administrative.json`. The fetcher refuses
|
|
173
|
-
unknown IDs, verifies downloaded bytes against a separately pinned observed
|
|
174
|
-
SHA-256, and writes `.docx` plus attribution metadata under
|
|
175
|
-
`tmp/superdoc-corpus/`. The separate digest is intentional: on 2026-08-29 the
|
|
176
|
-
service returned valid DOCX bytes that did not hash to its advertised corpus
|
|
177
|
-
IDs. Deterministic reviewed operations live in
|
|
178
|
-
`tests/corpus/superdoc-word-scenarios.json`.
|
|
179
|
-
|
|
180
|
-
The corpus suite replaces only `word/document.xml`, proves that the uncompressed
|
|
181
|
-
bytes of every untouched package part remain identical, opens each result in
|
|
182
|
-
Word without a repair dialog, and checks both Accept All and Reject All. Do not
|
|
183
|
-
commit downloaded documents.
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
# Microsoft Word Manual Review
|
|
2
|
-
|
|
3
|
-
This checklist is the human visual companion to `npm run test:word` and
|
|
4
|
-
`npm run test:corpus:word`. The automated differential proves Word revision
|
|
5
|
-
semantics by comparing text after Accept All and Reject All. This review checks
|
|
6
|
-
the layout and interaction details that `Document.Content.Text` cannot see.
|
|
7
|
-
|
|
8
|
-
An AI agent may use this same checklist as a visual preflight by controlling the
|
|
9
|
-
local Word UI and inspecting screenshots. Mark that report **AI visual
|
|
10
|
-
preflight**; it helps select and triage cases but does not replace the human
|
|
11
|
-
release sign-off described below.
|
|
12
|
-
|
|
13
|
-
## Prepare the review set
|
|
14
|
-
|
|
15
|
-
Run the automated lane first:
|
|
16
|
-
|
|
17
|
-
```powershell
|
|
18
|
-
npm run test:word
|
|
19
|
-
npm run test:corpus:word
|
|
20
|
-
npm run report:word:coverage
|
|
21
|
-
npm run review:word:prepare -- --cycle=0
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
The last command writes a pending review manifest under ignored
|
|
25
|
-
`tmp/word-manual-review/` containing changed catalogue families, the rotating
|
|
26
|
-
20% synthetic release sample, and legal/administrative corpus representatives.
|
|
27
|
-
It only prepares the selection: it never records a visual pass or human
|
|
28
|
-
sign-off. Use a new cycle number for each release rotation.
|
|
29
|
-
|
|
30
|
-
Synthetic documents are generated under `tmp/word-validation/`; reviewed
|
|
31
|
-
SuperDoc results are under `tmp/superdoc-word-fixtures/`. Do not commit generated
|
|
32
|
-
or downloaded `.docx` files.
|
|
33
|
-
|
|
34
|
-
Select:
|
|
35
|
-
|
|
36
|
-
- every new or changed case;
|
|
37
|
-
- every case required by the triggers in `docs/TESTING.md`;
|
|
38
|
-
- at least 20% of unchanged synthetic cases, rotating from the prior release;
|
|
39
|
-
- at least one legal and one administrative SuperDoc result; and
|
|
40
|
-
- representative list, table, formatted-run, and anchor/field/content-control
|
|
41
|
-
structures.
|
|
42
|
-
|
|
43
|
-
Record the installed Word version and build from **File → Account → About
|
|
44
|
-
Word**. Differences in rendering can be version-specific.
|
|
45
|
-
|
|
46
|
-
## Configure Word
|
|
47
|
-
|
|
48
|
-
For the tracked-change inspection:
|
|
49
|
-
|
|
50
|
-
1. Open the generated fixture directly in desktop Word.
|
|
51
|
-
2. On **Review**, select **All Markup**.
|
|
52
|
-
3. Under **Show Markup**, enable insertions/deletions, formatting, comments, and
|
|
53
|
-
all reviewers relevant to the case.
|
|
54
|
-
4. Use the expected balloons/inline display for the document and enable
|
|
55
|
-
paragraph marks when checking whitespace, tabs, breaks, lists, and empty
|
|
56
|
-
paragraphs.
|
|
57
|
-
5. Do not overwrite the generated fixture. Work on disposable copies if a
|
|
58
|
-
saved accepted or rejected view is useful.
|
|
59
|
-
|
|
60
|
-
## Inspect each case
|
|
61
|
-
|
|
62
|
-
### Tracked-change view
|
|
63
|
-
|
|
64
|
-
- The document opens without a repair, conversion, or unreadable-content prompt.
|
|
65
|
-
- Word shows the expected revision count and author attribution.
|
|
66
|
-
- Insertions and deletions are anchored at the intended words or paragraph
|
|
67
|
-
marks; a small edit has not become an unexplained whole-paragraph rewrite.
|
|
68
|
-
- Untargeted text and surrounding revisions remain unchanged.
|
|
69
|
-
- Existing bold, italic, underline, highlighting, fonts, styles, and language
|
|
70
|
-
settings remain visually consistent.
|
|
71
|
-
- Spaces, tabs, manual breaks, paragraph spacing, indentation, and alignment
|
|
72
|
-
look intentional with formatting marks visible.
|
|
73
|
-
- Lists retain numbering, levels, continuation, indentation, and marker style.
|
|
74
|
-
- Tables retain widths, borders, merged cells, row heights, and alignment.
|
|
75
|
-
- Bookmarks, hyperlinks, fields, content controls, comments, and note references
|
|
76
|
-
remain in the correct visible location and still behave when activated.
|
|
77
|
-
- Headers, footers, section boundaries, page breaks, and pagination remain
|
|
78
|
-
stable around the edit.
|
|
79
|
-
- Revision balloons and comment balloons point to the correct content and do
|
|
80
|
-
not obscure or displace unrelated layout unexpectedly.
|
|
81
|
-
|
|
82
|
-
### Accept All view
|
|
83
|
-
|
|
84
|
-
On a disposable copy, choose **Accept All Changes** and verify:
|
|
85
|
-
|
|
86
|
-
- the resulting visible text expresses the intended edit;
|
|
87
|
-
- no deletion residue, empty revision wrapper, unexpected blank line, or stale
|
|
88
|
-
formatting remains;
|
|
89
|
-
- lists, tables, fields, links, comments, notes, and page layout still work; and
|
|
90
|
-
- untargeted content is visually unchanged.
|
|
91
|
-
|
|
92
|
-
Close the copy without replacing the generated fixture.
|
|
93
|
-
|
|
94
|
-
### Reject All view
|
|
95
|
-
|
|
96
|
-
Reopen a fresh copy, choose **Reject All Changes**, and verify:
|
|
97
|
-
|
|
98
|
-
- the original visible text and formatting are restored;
|
|
99
|
-
- original list numbering, table layout, fields, anchors, and pagination return;
|
|
100
|
-
and
|
|
101
|
-
- no content introduced by the edit remains.
|
|
102
|
-
|
|
103
|
-
## Record the result
|
|
104
|
-
|
|
105
|
-
Keep the review report with the release-validation artifacts. Screenshots may be
|
|
106
|
-
stored under ignored `tmp/word-manual-review/<date>/` when useful, but do not
|
|
107
|
-
commit corpus document images or document contents without checking their
|
|
108
|
-
rights and sensitivity.
|
|
109
|
-
|
|
110
|
-
Suggested report:
|
|
111
|
-
|
|
112
|
-
```markdown
|
|
113
|
-
# Word visual review — <release/date>
|
|
114
|
-
|
|
115
|
-
- Reviewer:
|
|
116
|
-
- Review date:
|
|
117
|
-
- Word version/build:
|
|
118
|
-
- Automated synthetic result:
|
|
119
|
-
- Automated corpus result:
|
|
120
|
-
- Review type: Human sign-off | AI visual preflight
|
|
121
|
-
|
|
122
|
-
| Case | Why selected | All Markup | Accept All | Reject All | Result | Notes |
|
|
123
|
-
|---|---|---|---|---|---|---|
|
|
124
|
-
| example-case | New table structure | Pass | Pass | Pass | Pass | No layout shift |
|
|
125
|
-
|
|
126
|
-
## Failures or follow-ups
|
|
127
|
-
|
|
128
|
-
- None.
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
A **Pass** requires all three views to pass. Record **Fail** if the rendering or
|
|
132
|
-
interaction is wrong even when automated text comparison passes. Turn a failure
|
|
133
|
-
into a fixed regression case when possible; otherwise record the exact harness
|
|
134
|
-
or Word-version limitation in the active reliability plan.
|
|
135
|
-
|
|
136
|
-
For an AI preflight, also record screenshot paths and confidence/uncertainty in
|
|
137
|
-
the notes. A human reviewer should revisit every AI failure or uncertain result
|
|
138
|
-
and must still complete the release sample independently.
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
# Performance and Complexity Reduction Plan
|
|
2
|
-
|
|
3
|
-
**Status:** Proposed
|
|
4
|
-
**Date:** 2026-09-01
|
|
5
|
-
|
|
6
|
-
This plan outlines opportunities to reduce architectural complexity, eliminate redundant diffing engines, and resolve critical performance bottlenecks across `@ansonlai/docx-redline-js`.
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## 1. Context & Motivation
|
|
11
|
-
|
|
12
|
-
Following the completion of reliability improvements (Rounds 1–3), the library has achieved strong correctness invariants (38/38 test suites passing, Word COM differential parity, and round-trip invariant checks).
|
|
13
|
-
|
|
14
|
-
However, feature additions over time (tables, lists, comments, formatting removal, and batch operation orchestration) have introduced structural debt:
|
|
15
|
-
1. **Three competing diff-and-patch paradigms**: [Reconstruction Mode](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/engine/reconstruction-mode.js), [Surgical Mode](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/engine/surgical-mode.js), and [Pipeline Mode](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/pipeline/pipeline.js) each maintain distinct run models and patching strategies.
|
|
16
|
-
2. **Monolithic Coordination**: [`services/standalone-operation-runner.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/services/standalone-operation-runner.js) has expanded to 1,451 lines (63 KB), entangling batch scheduling, atomic rollback, DOM mutation, range resolution, and ad-hoc heuristics.
|
|
17
|
-
3. **Parse $\leftrightarrow$ Serialize Thrashing in Batches**: In multi-operation document turns, full-document XML is repeatedly parsed and serialized for each operation ($O(N \times \text{document size})$), consuming seconds of CPU time.
|
|
18
|
-
4. **Scattered Domain Logic**: List markers, list parsing, numbering fallback, and list targeting are fragmented across 6+ separate files.
|
|
19
|
-
5. **Hot Path Inefficiencies**: Linear scans in target detection, universal element traversal (`getElementsByTagName('*')`) during revision ID seeding, and excessive heap allocations (`Array.from`) in tree-traversal loops.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## 2. Guiding Principles
|
|
24
|
-
|
|
25
|
-
This plan strictly follows the project's core philosophy:
|
|
26
|
-
> **"Readability and Order > Speed and Complex Interconnections"**
|
|
27
|
-
> This project prioritizes maintainability and clarity over clever, hyper-optimized code. Even if a solution is slightly less performant but significantly easier to read, choose the readable one (within reason).
|
|
28
|
-
|
|
29
|
-
- **SOLID & DRY**: Each module should have one clear responsibility. Avoid maintaining duplicate diff/patch representations.
|
|
30
|
-
- **KISS**: Favor simple, direct DOM operations over multi-layered abstractions.
|
|
31
|
-
- **Strict Backward Compatibility**: Public APIs exported from [`index.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/index.js) and the package `exports` map must remain intact. All structural invariants verified by [`validateRedlineOoxml`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/core/redline-validation.js) and the round-trip tests must continue to pass.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## 3. Baseline Metrics
|
|
36
|
-
|
|
37
|
-
As of 2026-09-01:
|
|
38
|
-
- `npm test`: 38/38 test files pass (~6.0s execution time due to sequential process spawning).
|
|
39
|
-
- Core package exports remain centralized in [`index.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/index.js).
|
|
40
|
-
- For an $N$-operation batch on `word/document.xml`, the runner currently performs up to $3N$ full-document XML parses and $N$ full-document XML serializations using `@xmldom/xmldom`.
|
|
41
|
-
- Four divergent text-extraction functions exist across targeting, engine, and ingestion modules.
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
## 4. Production API Compatibility
|
|
46
|
-
|
|
47
|
-
| Phase | Expected Impact |
|
|
48
|
-
|---|---|
|
|
49
|
-
| **Phase 1: In-Memory Batch DOM & Hot Path Optimization** | **Non-breaking.** Pure internal performance optimization for [`applyOperationsToDocumentXml`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/services/standalone-operation-runner.js#L1356), target searches, and revision ID allocation. Output OOXML remains identical. |
|
|
50
|
-
| **Phase 2: Decomposing `standalone-operation-runner.js`** | **Non-breaking.** Public exports preserved through [`index.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/index.js) and package exports; internal subroutines refactored into focused single-responsibility modules. |
|
|
51
|
-
| **Phase 3: Diff Engine Consolidation & Pipeline Retirement** | **Non-breaking.** Unifies paragraph and list generation onto Reconstruction mode; deprecates/retires redundant AST patching in [`pipeline/pipeline.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/pipeline/pipeline.js). |
|
|
52
|
-
| **Phase 4: Consolidating Lists & Canonical Text Extraction** | **Non-breaking.** Centralizes list markers/heuristics into `services/list-service.js` and creates an authoritative canonical text extractor in [`core/word-xml.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/core/word-xml.js). |
|
|
53
|
-
| **Phase 5: Developer Ergonomics & Fast Test Runner** | **Development-only.** In-process test execution cuts test suite run time from ~6.0s down to <1.0s. |
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## Phase 1 — In-Memory Batch DOM & Hot Path Optimization
|
|
58
|
-
|
|
59
|
-
### Problem
|
|
60
|
-
1. In [`applyOperationsToDocumentXml`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/services/standalone-operation-runner.js#L1356-L1449), every scheduled operation causes a full serialize-and-reparse cycle:
|
|
61
|
-
- `parseOoxmlSafe(documentXml)` parses the document.
|
|
62
|
-
- `applyOperationToDocumentXml` parses it again.
|
|
63
|
-
- `applyToParagraphByExactText` parses it a third time.
|
|
64
|
-
- The scoped paragraph is serialized to a string, passed to `applyRedlineToOxml`, and parsed again.
|
|
65
|
-
- The result is serialized to a string, parsed again by `extractReplacementNodesFromOoxml`, imported back into `xmlDoc`, and the **entire document** is serialized back to string.
|
|
66
|
-
- In `@xmldom/xmldom` (pure JS DOM implementation), this creates massive CPU overhead for multi-operation turns.
|
|
67
|
-
2. In [`oxml-engine.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/engine/oxml-engine.js#L183-L198), target detection uses an $O(\text{lines} \times \text{paragraphs} \times \text{spans})$ nested loop (`textSpans.filter(...)` inside `paragraphs.some(...)` for every line).
|
|
68
|
-
3. In [`RevisionIdAllocator.prototype.seed`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/core/types.js#L203-L222), `getElementsByTagName('*')` copies every single element in the document into an array to find revision IDs, even though only 9 revision tags can carry `w:id`.
|
|
69
|
-
|
|
70
|
-
### Implementation Steps
|
|
71
|
-
1. **In-Memory Batch Execution**:
|
|
72
|
-
- Parse `documentXml` once at the beginning of `applyOperationsToDocumentXml`.
|
|
73
|
-
- Maintain a live `xmlDoc` reference across operations.
|
|
74
|
-
- Apply operations directly to `xmlDoc` without intermediate full-document string serialization.
|
|
75
|
-
- Serialize `xmlDoc` once at the end.
|
|
76
|
-
- Retain the initial `documentXml` string for atomic rollback; if any operation fails when `atomic: true`, simply return the untouched original string.
|
|
77
|
-
2. **Pre-Group Spans in Target Check**:
|
|
78
|
-
- In `oxml-engine.js`, pre-aggregate text by paragraph into a `Map<Element, string>` in a single $O(\text{spans})$ pass (or reuse [`buildParagraphInfos`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/engine/format-paragraph-targeting.js#L27)), eliminating the quadratic scan.
|
|
79
|
-
3. **Targeted Revision Tag Query**:
|
|
80
|
-
- In `RevisionIdAllocator.seed`, replace `getElementsByTagName('*')` with targeted queries for known revision element names (`w:ins`, `w:del`, `w:moveFrom`, `w:moveTo`, `w:rPrChange`, `w:pPrChange`, `w:cellIns`, `w:cellDel`, `w:comment`).
|
|
81
|
-
4. **Common Prefix/Suffix Diff Short-Circuit**:
|
|
82
|
-
- In [`diff-engine.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/pipeline/diff-engine.js#L206), add a fast character-level common prefix/suffix trim before DMP tokenization to short-circuit edits that only touch a few words in a large paragraph.
|
|
83
|
-
|
|
84
|
-
### Acceptance
|
|
85
|
-
- Multi-operation batch benchmarks demonstrate a **5x–15x speedup** on documents with 10+ operations.
|
|
86
|
-
- All 38 existing test suites pass with zero regressions.
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## Phase 2 — Modularizing `standalone-operation-runner.js`
|
|
91
|
-
|
|
92
|
-
### Problem
|
|
93
|
-
[`standalone-operation-runner.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/services/standalone-operation-runner.js) is 1,451 lines long. It acts as an orchestrator, batch scheduler, DOM updater, and heuristic router all at once, making navigation and maintenance difficult.
|
|
94
|
-
|
|
95
|
-
### Implementation Steps
|
|
96
|
-
1. Extract batch scheduling, priority sorting, dependency tracking, and atomic rollback into `services/batch-operation-orchestrator.js`:
|
|
97
|
-
- `applyOperationsToDocumentXml`
|
|
98
|
-
- `operationTargetPriority`
|
|
99
|
-
- Context cloning and commit helpers (`cloneBatchRuntimeContext`, `commitBatchRuntimeContext`)
|
|
100
|
-
2. Extract single-operation execution and target paragraph scoping into `services/document-operation-applier.js`:
|
|
101
|
-
- `applyOperationToDocumentXml`
|
|
102
|
-
- `applyToParagraphByExactText`
|
|
103
|
-
- `applyHighlightToParagraphByExactText`
|
|
104
|
-
- `applyCommentToParagraphByExactText`
|
|
105
|
-
3. Extract ad-hoc structural heuristics into `services/operation-heuristics.js`:
|
|
106
|
-
- Adjacency insertion heuristics (`deriveSingleParagraphListAdjacencyInsertion`, `deriveSingleParagraphPlainAdjacencyInsertion`, `buildInsertedListParagraph`, `buildInsertedPlainParagraph`)
|
|
107
|
-
- Insertion-only heuristics (`planListInsertionOnlyEdit`, `buildExplicitRangeInsertionEntries`, `applyExplicitRangeListInsertions`)
|
|
108
|
-
- Scope expansion (`synthesizeExpandedListScopeEdit`, `synthesizeTableMarkdownFromMultilineCellEdit`)
|
|
109
|
-
4. Keep [`services/standalone-operation-runner.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/services/standalone-operation-runner.js) as a clean façade re-exporting the public contract.
|
|
110
|
-
|
|
111
|
-
### Acceptance
|
|
112
|
-
- No single file exceeds 500 lines.
|
|
113
|
-
- Each module has a single, testable responsibility.
|
|
114
|
-
- Existing tests in `tests/standalone_operation_runner_tests.mjs` pass without modification.
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## Phase 3 — Diff Engine Consolidation & Pipeline Retirement
|
|
119
|
-
|
|
120
|
-
### Problem
|
|
121
|
-
The codebase currently supports three reconciliation execution engines:
|
|
122
|
-
- **Reconstruction Mode**: Maps characters to runs, properties, and sentinels; rebuilds OOXML structure. (Primary, actively maintained, tab/field safe).
|
|
123
|
-
- **Surgical Mode**: In-place mutation of runs in existing DOM. (Required for tables).
|
|
124
|
-
- **Pipeline Mode** ([`pipeline/pipeline.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/pipeline/pipeline.js)): Converts OOXML to an AST (`runModel`), splits runs at diff boundaries, and patches via string templates. It is now only invoked when `isTargetList` is true.
|
|
125
|
-
|
|
126
|
-
Maintaining three diffing and patching engines increases maintenance burden and means bug fixes (such as `w:tab`, complex fields, or namespace handling) must be replicated across separate systems.
|
|
127
|
-
|
|
128
|
-
### Implementation Steps
|
|
129
|
-
1. Enhance Reconstruction Mode to handle list structural conversions natively:
|
|
130
|
-
- When marker-prefixed list text is detected, emit Word list properties (`w:numPr`) directly during reconstruction writing.
|
|
131
|
-
2. Route `isTargetList` operations in [`oxml-engine.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/engine/oxml-engine.js#L371) through Reconstruction Mode instead of `ReconciliationPipeline`.
|
|
132
|
-
3. Deprecate and remove redundant AST patching modules:
|
|
133
|
-
- [`pipeline/patching.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/pipeline/patching.js)
|
|
134
|
-
- [`pipeline/serialization.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/pipeline/serialization.js) (retaining only document fragment wrapping helpers)
|
|
135
|
-
- [`pipeline/pipeline.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/pipeline/pipeline.js)
|
|
136
|
-
4. Standardize on **Reconstruction Mode** for paragraphs/lists and **Surgical Mode** for tables.
|
|
137
|
-
|
|
138
|
-
### Acceptance
|
|
139
|
-
- ~3,000 lines of redundant code removed.
|
|
140
|
-
- All list generation and list fallback tests (`tests/list_tests.mjs`, `tests/phase3_list_structural_fallback_tests.mjs`) pass with equal or better fidelity.
|
|
141
|
-
- Output validity confirmed via `validateRedlineOoxml`.
|
|
142
|
-
|
|
143
|
-
---
|
|
144
|
-
|
|
145
|
-
## Phase 4 — Domain Consolidation: Lists & Canonical Text Extraction
|
|
146
|
-
|
|
147
|
-
### Problem
|
|
148
|
-
1. List logic is currently dispersed across 6+ separate files:
|
|
149
|
-
- [`pipeline/list-generation.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/pipeline/list-generation.js)
|
|
150
|
-
- [`pipeline/list-markers.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/pipeline/list-markers.js)
|
|
151
|
-
- [`orchestration/list-parsing.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/orchestration/list-parsing.js)
|
|
152
|
-
- [`orchestration/list-markdown.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/orchestration/list-markdown.js)
|
|
153
|
-
- [`orchestration/list-structural-fallback.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/orchestration/list-structural-fallback.js)
|
|
154
|
-
- [`core/list-targeting.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/core/list-targeting.js)
|
|
155
|
-
2. Four distinct visible-text extraction functions exist across targeting, engine, and ingestion modules, creating risk of subtle behavioral differences around `w:tab`, `w:br`, `w:noBreakHyphen`, and `w:del`.
|
|
156
|
-
3. Frequent `Array.from(nodeList)` calls inside recursive DOM traversals generate heavy GC pressure.
|
|
157
|
-
|
|
158
|
-
### Implementation Steps
|
|
159
|
-
1. **Canonical Text Extractor**:
|
|
160
|
-
- Establish a single, authoritative `getParagraphVisibleText(pElement)` in [`core/word-xml.js`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/core/word-xml.js).
|
|
161
|
-
- Standardize handling for `w:t`, `w:tab` (`\t`), `w:br`/`w:cr` (`\n`), `w:noBreakHyphen` (`\u2011`), and exclusion of `w:delText` / deleted runs.
|
|
162
|
-
- Refactor `paragraph-targeting.js`, `oxml-engine.js`, and `ingestion-paragraph.js` to use this canonical helper.
|
|
163
|
-
2. **Consolidate List Domain Modules**:
|
|
164
|
-
- Combine list marker matching and markdown list parsing into `services/list-service.js`.
|
|
165
|
-
- Simplify fallback orchestration so single-line structural fallback shares the same list definitions as multi-line lists.
|
|
166
|
-
3. **Reduce DOM Traversal Allocations**:
|
|
167
|
-
- Replace `Array.from(node.childNodes)` in hot traversal loops with pointer iteration (`for (let child = node.firstChild; child; child = child.nextSibling)`).
|
|
168
|
-
- Use `node.getAttributeNS(NS_W, 'author')` directly instead of iterating through `Array.from(node.attributes)`.
|
|
169
|
-
|
|
170
|
-
### Acceptance
|
|
171
|
-
- Single source of truth for paragraph text extraction across all modules.
|
|
172
|
-
- List parsing and numbering fallback unified under a cohesive domain service.
|
|
173
|
-
- Reduced memory churn during large document traversals.
|
|
174
|
-
|
|
175
|
-
---
|
|
176
|
-
|
|
177
|
-
## Phase 5 — Developer Ergonomics & Fast Test Runner
|
|
178
|
-
|
|
179
|
-
### Problem
|
|
180
|
-
[`scripts/run-tests.mjs`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/scripts/run-tests.mjs#L21) currently runs 38 test files by invoking `execSync('node "${filePath}"')` sequentially. On Windows, spawning 38 separate Node.js processes accounts for ~5 seconds of the 6-second test run.
|
|
181
|
-
|
|
182
|
-
### Implementation Steps
|
|
183
|
-
1. Update `scripts/run-tests.mjs` to dynamically import and execute test files in-process (`await import(filePath)`), or leverage Node's native test runner (`node --test tests/*.mjs`).
|
|
184
|
-
2. Retain process isolation checks in `npm run test:isolation`.
|
|
185
|
-
|
|
186
|
-
### Acceptance
|
|
187
|
-
- `npm test` execution time reduced from ~6.0s down to <1.0s.
|
|
188
|
-
- Contributor iteration speed significantly improved.
|
|
189
|
-
|
|
190
|
-
---
|
|
191
|
-
|
|
192
|
-
## 5. Execution Roadmap & Prioritization
|
|
193
|
-
|
|
194
|
-
```mermaid
|
|
195
|
-
graph TD
|
|
196
|
-
P1[Phase 1: In-Memory Batch DOM & Hot Paths] --> P2[Phase 2: Modularize standalone-operation-runner]
|
|
197
|
-
P1 --> P5[Phase 5: Fast Test Runner]
|
|
198
|
-
P2 --> P3[Phase 3: Unify Diff Engines & Retire Pipeline]
|
|
199
|
-
P3 --> P4[Phase 4: List & Text Extraction Consolidation]
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
1. **Sprint 1 (Immediate Performance Wins)**:
|
|
203
|
-
- Phase 1 (In-memory DOM batch execution + target search optimization + revision allocator query).
|
|
204
|
-
- Phase 5 (In-process test runner).
|
|
205
|
-
2. **Sprint 2 (Architecture Cleanliness & Maintainability)**:
|
|
206
|
-
- Phase 2 (Decompose `standalone-operation-runner.js`).
|
|
207
|
-
- Phase 4 (Canonical visible text extractor).
|
|
208
|
-
3. **Sprint 3 (Structural Unification)**:
|
|
209
|
-
- Phase 3 (Retire legacy pipeline; unify on Reconstruction & Surgical modes).
|
|
210
|
-
- Complete list logic consolidation.
|