@ansonlai/docx-redline-js 0.2.1 → 0.4.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.
Files changed (86) hide show
  1. package/AGENTS.md +36 -10
  2. package/README.md +83 -6
  3. package/adapters/xml-adapter.js +73 -10
  4. package/core/list-targeting.js +3 -0
  5. package/core/paragraph-targeting.js +33 -7
  6. package/core/redline-validation.js +22 -0
  7. package/core/types.js +122 -27
  8. package/core/xml-query.js +3 -1
  9. package/dist/docx-redline-js.esm.js +1124 -545
  10. package/dist/docx-redline-js.esm.js.map +4 -4
  11. package/dist/docx-redline-js.esm.min.js +79 -78
  12. package/dist/docx-redline-js.esm.min.js.map +4 -4
  13. package/docs/TESTING.md +687 -0
  14. package/docs/VALIDATION.md +81 -2
  15. package/docs/WORD-MANUAL-REVIEW.md +138 -0
  16. package/docs/plans/2026-08-30-reliability-testing-improvements.md +488 -0
  17. package/docs/plans/2026-09-01-performance-and-complexity-reduction.md +210 -0
  18. package/docs/plans/{2026-03-01-release-0.1.4-design.md → completed/2026-03-01-release-0.1.4-design.md} +2 -0
  19. package/docs/plans/{2026-03-01-release-0.1.4.md → completed/2026-03-01-release-0.1.4.md} +5 -3
  20. package/docs/plans/{2026-05-31-architectural changes.md → completed/2026-05-31-architectural changes.md } +2 -0
  21. package/docs/plans/completed/2026-08-02-reliability-improvements.md +1155 -0
  22. package/docs/test-comparison-dashboard.html +95 -0
  23. package/docs/validation-reports/2026-08-30-phase-1-word-visual-preflight.md +22 -0
  24. package/docs/validation-reports/2026-08-30-phase-2-word-visual-preflight.md +24 -0
  25. package/docs/validation-reports/2026-08-30-phase-3-coverage.md +73 -0
  26. package/docs/validation-reports/2026-09-02-multilevel-bullets-visual-review.md +82 -0
  27. package/docs/validation-reports/2026-09-02-multimodal-visual-samples.md +114 -0
  28. package/docs/validation-reports/2026-09-02-visual-failures-preflight.md +79 -0
  29. package/engine/format-extraction.js +1 -1
  30. package/engine/formatting-removal.js +84 -99
  31. package/engine/oxml-engine.js +176 -83
  32. package/engine/reconstruction-mapper.js +276 -79
  33. package/engine/reconstruction-mode.js +20 -6
  34. package/engine/reconstruction-writer.js +117 -72
  35. package/engine/run-builders.js +3 -3
  36. package/engine/surgical-mode.js +3 -2
  37. package/engine/table-mode.js +27 -16
  38. package/index.d.ts +95 -3
  39. package/index.js +14 -13
  40. package/orchestration/list-structural-fallback.js +16 -39
  41. package/package.json +22 -4
  42. package/pipeline/diff-engine.js +174 -55
  43. package/pipeline/ingestion-export.js +39 -24
  44. package/pipeline/ingestion-paragraph.js +7 -5
  45. package/pipeline/list-generation.js +27 -18
  46. package/pipeline/patching.js +2 -3
  47. package/pipeline/pipeline.js +65 -36
  48. package/pipeline/serialization.js +13 -5
  49. package/scripts/build-test-dashboard.mjs +43 -0
  50. package/scripts/check-types.mjs +16 -24
  51. package/scripts/export-validation-fixtures.mjs +191 -45
  52. package/scripts/fetch-superdoc-corpus.mjs +61 -0
  53. package/scripts/generate-test-dashboard.mjs +199 -0
  54. package/scripts/inspect-visual-evidence.mjs +271 -0
  55. package/scripts/lib/minimal-zip.mjs +199 -18
  56. package/scripts/lib/word-coverage-catalogue.mjs +207 -0
  57. package/scripts/lib/word-coverage-metadata.mjs +93 -0
  58. package/scripts/lib/zip-reader.mjs +64 -0
  59. package/scripts/package-superdoc-word-fixtures.ps1 +64 -0
  60. package/scripts/prepare-corpus-word-visual-review.mjs +84 -0
  61. package/scripts/prepare-superdoc-word-corpus.mjs +284 -0
  62. package/scripts/prepare-word-review.mjs +77 -0
  63. package/scripts/prepare-word-visual-review.mjs +90 -0
  64. package/scripts/render-agenda-multilevel.mjs +70 -0
  65. package/scripts/render-case22.mjs +73 -0
  66. package/scripts/render-case40.ps1 +35 -0
  67. package/scripts/render-multilevel-bullet-images.py +58 -0
  68. package/scripts/render-multilevel-bullet-visual.ps1 +32 -0
  69. package/scripts/render-multilevel-cases.mjs +80 -0
  70. package/scripts/report-coverage-gaps.mjs +103 -0
  71. package/scripts/report-word-coverage.mjs +71 -0
  72. package/scripts/sample-multimodal-visual-check.mjs +221 -0
  73. package/scripts/test-multilevel-bullet-visual.mjs +187 -0
  74. package/scripts/word-com-corpus-suite.ps1 +43 -0
  75. package/scripts/word-com-corpus-visual-suite.ps1 +116 -0
  76. package/scripts/word-com-differential.ps1 +158 -16
  77. package/scripts/word-com-suite.ps1 +19 -0
  78. package/scripts/word-com-visual-suite.ps1 +132 -0
  79. package/services/comment-engine.js +51 -46
  80. package/services/comment-locator.js +0 -1
  81. package/services/comment-package.js +11 -10
  82. package/services/numbering-service.js +1 -1
  83. package/services/revision-comment-management.js +31 -10
  84. package/services/standalone-docx-plumbing.js +45 -34
  85. package/services/standalone-operation-runner.js +315 -75
  86. package/services/table-reconciliation.js +23 -11
@@ -0,0 +1,687 @@
1
+ # Testing Guide
2
+
3
+ This project uses several test lanes because no single oracle can prove that a
4
+ tracked-change document is correct. JavaScript assertions can verify exact XML
5
+ and return contracts; Microsoft Word can verify what the primary consumer
6
+ actually accepts and rejects; real documents expose structures that small
7
+ fixtures rarely contain.
8
+
9
+ ## Test lanes at a glance
10
+
11
+ | Lane | Command | What it proves | What it does not prove |
12
+ |---|---|---|---|
13
+ | Automated regression suite | `npm test` | API behavior, exact text invariants, OOXML structure, deterministic fuzz cases, and catalogue integrity | That desktop Word accepts the generated package |
14
+ | Isolation, types, and lint | `npm run test:isolation`, `npm run check:types`, `npm run lint` | Runtime boundaries, declaration alignment, and static repository rules | Document correctness |
15
+ | JavaScript coverage | `npm run test:coverage` | Which source lines, functions, and branches the automated suite executes | That an executed path is correct |
16
+ | Synthetic Word differential | `npm run test:word` | Word opens generated packages, sees revisions, and produces the intended text after Accept All and Reject All | The diversity of real-world packages |
17
+ | Word visual evidence | `npm run test:word:visual` | Word renders layout-sensitive fixtures in All Markup, Accept All, and Reject All views and writes a pending review manifest | That the rendered pages are visually correct until a reviewer inspects them |
18
+ | SuperDoc Word corpus | `npm run test:corpus:word` | The same Word differential on 60 scenarios drawn from 23 reviewed, pinned real English legal/administrative documents while untouched package parts remain byte-identical | Every possible DOCX producer or document type |
19
+ | SuperDoc visual evidence | `npm run test:corpus:word:visual` | Word renders 40 focused list, table, long-document, and page-header scenarios in All Markup, Accept All, and Reject All views | Human visual sign-off |
20
+ | Visual evidence inspection | `npm run test:visual:inspect` | Automated inspection of rendered Word PDFs across synthetic and SuperDoc suites (page counts, PDF integrity, anomaly detection) | Human visual sign-off |
21
+ | Visual failure regressions | `node tests/visual_failure_regression_tests.mjs` | Semantic OOXML guards against visual failures (formatting leaks, font resets, ghost bullets, table cell destruction) | Visual rendering proof in Word |
22
+ | Multimodal LLM visual spot check | On-demand / sampled | Evaluates rendered real-document pages with vision models for layout, table alignment, and typography regressions | Full-corpus automated coverage (intentionally decoupled and sampled due to cost/time) |
23
+ | XSD and LibreOffice | See `docs/VALIDATION.md` | Schema conformance and acceptance by a second consumer | Word-specific revision semantics |
24
+
25
+ ## Coverage matrix and test selection
26
+
27
+ Run the deterministic task-by-structure report before choosing the next Word
28
+ case:
29
+
30
+ ```powershell
31
+ npm run report:word:coverage
32
+ npm run report:word:coverage -- --json
33
+ npm run report:test:dashboard
34
+ ```
35
+
36
+ The dashboard command writes a self-contained interactive report to
37
+ `docs/test-comparison-dashboard.html`. It compares task/structure cells,
38
+ synthetic and real-document coverage, independent oracles, visual-render
39
+ eligibility, and planned high-priority gaps from the live catalogues. It also
40
+ embeds source, tracked, accepted, and rejected packages for every synthetic
41
+ fixture and, when the pinned corpus is downloaded, all 60 reviewed real legal
42
+ and administrative scenarios. Its `docx-preview` workbench supports arbitrary left/right states,
43
+ comparison presets, synchronized scrolling, revision metadata, expected text,
44
+ and local downloads; no fixture upload or file picker is required. Real cases
45
+ are labeled in the document selector and selected by default when available.
46
+ Use **Hide sidebar** beside the dashboard filters to remove the coverage-detail
47
+ column and expand the comparison workbench to the full browser width. The
48
+ button becomes **Show sidebar**, and the preference is stored locally so the
49
+ wide document view survives a reload.
50
+ Without the local corpus, generation remains offline-safe and embeds only the
51
+ synthetic previews.
52
+
53
+ The report combines all synthetic and reviewed SuperDoc scenarios. Synthetic
54
+ metadata is declared in `tests/fixtures/word-task-coverage.mjs`; SuperDoc
55
+ metadata is declared in `tests/corpus/superdoc-word-coverage.json`. Descriptive
56
+ `task`, `shape`, and `coverage` labels remain on the original cases as review
57
+ notes, while the matrix uses shared task, structure, and oracle vocabularies.
58
+
59
+ Catalogue tests reject unknown vocabulary labels, missing oracle or manual-review
60
+ metadata, duplicate identities, structural claims unsupported by fixture XML or
61
+ reviewed corpus labels, and uncovered high-priority cells without a recorded
62
+ plan or exclusion. The dispositions live in
63
+ `tests/fixtures/coverage-matrix-priorities.json` and require both a reason and a
64
+ dependency.
65
+
66
+ Choose new cases from uncovered high-priority cells first, then prefer a new
67
+ task/structure combination over another case in a dense cell. A higher count is
68
+ not itself a reason to add a fixture. Adding a catalogue case automatically
69
+ changes the report because it reads the live metadata.
70
+
71
+ ## Reliability improvement process used for this test expansion
72
+
73
+ The current corpus, visual runners, and comparison dashboard were built as one
74
+ feedback loop rather than as separate test features. The working sequence is:
75
+
76
+ 1. inventory the claimed behavior and existing test lanes;
77
+ 2. add realistic cases that expose weak structures;
78
+ 3. assert the smallest machine-checkable invariant at the engine level;
79
+ 4. package the result into the original real document without changing
80
+ unrelated parts;
81
+ 5. ask Word to accept and reject the revisions;
82
+ 6. render All Markup, Accept All, and Reject All views;
83
+ 7. inspect the actual pages at both contact-sheet and full-page scale; and
84
+ 8. turn every visual defect into a focused regression plus a corpus assertion.
85
+
86
+ This ordering matters. A valid `<w:ins>` or `<w:del>` tree does not establish
87
+ that the inserted run inherited the correct font, list indentation, hyperlink
88
+ wrapper, table-cell formatting, or header styling. Conversely, a good-looking
89
+ PDF does not prove that Reject All restores the exact source. Each stage answers
90
+ a different question, and a case is useful only when its intended claim is
91
+ explicit.
92
+
93
+ ### Start from coverage claims, not fixture count
94
+
95
+ Read the task-by-structure matrix and select a real document that adds a missing
96
+ or weak combination. The expansion deliberately favored bullets, nested lists,
97
+ table cells, headers, long documents, and atomic multi-change batches because
98
+ these structures cross more OOXML boundaries than a single plain paragraph.
99
+ Several scenarios reuse a pinned source when a new batch exercises a genuinely
100
+ different structure; this increases behavioral coverage without pretending
101
+ that another copy of the same document is a new source.
102
+
103
+ For long real documents, record structural minimums such as word count, table
104
+ count, list-paragraph count, section count, and related header/footer parts.
105
+ `scripts/prepare-superdoc-word-corpus.mjs` checks these declarations against the
106
+ source package. This prevents a scenario description such as “table-heavy” or
107
+ “multi-section” from silently drifting away from the actual fixture.
108
+
109
+ ### Use multi-change batches to exercise interaction effects
110
+
111
+ Single replacements remain valuable for isolating engine behavior, but real
112
+ editing sessions usually contain several changes. The larger corpus scenarios
113
+ therefore use `operations` to apply independent edits atomically across one
114
+ document. Targets are chosen from different list items or table cells when
115
+ possible. This catches failures caused by earlier replacements shifting later
116
+ anchors, revision-ID allocation across a batch, and formatting state leaking
117
+ between operations.
118
+
119
+ The batch runner must either produce all requested revisions or roll back the
120
+ document. Word then checks the complete accepted and rejected story, not merely
121
+ the first target. Untouched ZIP parts are hashed and compared with the pinned
122
+ source, so success cannot come from rebuilding or normalizing the rest of the
123
+ package.
124
+
125
+ ### Expand weak list and table cells with structural operations
126
+
127
+ The list/table expansion was driven directly by the task-by-structure matrix;
128
+ it did not add more plain replacement cases. The current catalogue contains 107
129
+ Word cases: 47 synthetic fixtures and 60 reviewed real-document scenarios. The
130
+ `List Change × List` cell now contains 26 cases; the
131
+ `Table Reconciliation × Table` cell contains 11 cases.
132
+
133
+ The added synthetic cases isolate one structural decision at a time:
134
+
135
+ - append one list item;
136
+ - append several adjacent items;
137
+ - add a nested child;
138
+ - insert into the middle of an explicit list range;
139
+ - preserve upper- and lower-Roman numbering;
140
+ - preserve upper-letter, parenthesized lower-letter, dash, and symbol bullets;
141
+ - update one table cell;
142
+ - insert a table row;
143
+ - delete a table row; and
144
+ - update several cells in one reconciliation.
145
+
146
+ The real-document cases repeat those claims against native numbering and table
147
+ markup from municipal notices, board agendas, a long zoning ordinance, PPG
148
+ minutes, action tables, and invoice tables. They include consecutive and nested
149
+ list insertions, a long-document definition insertion, single- and multi-cell
150
+ updates, row insertion/deletion, and invoice row changes. Reusing a reviewed,
151
+ hash-pinned source is intentional when the scenario exercises a different
152
+ operation; the unit of coverage is the behavioral claim, not the download.
153
+
154
+ These cases declare `operation.type` as `list-change` or
155
+ `table-reconciliation`. Their accepted/rejected expectations use explicit
156
+ contains/absent assertions when a Markdown table payload or a structural list
157
+ edit cannot be compared meaningfully to the raw Word source as one exact text
158
+ string. Structural expectations still verify real properties such as native
159
+ list paragraphs, tables, headers/footers, sections, and minimum document size.
160
+ The catalogue tests require these expectations and reject unknown operation or
161
+ coverage labels.
162
+
163
+ List-style cases do not infer their claim from visible text alone. Synthetic
164
+ fixtures declare the required `w:numFmt` values and use complete, schema-ordered
165
+ numbering parts: every `w:abstractNum` must precede the first concrete `w:num`
166
+ mapping. That ordering is regression-tested because Word otherwise opens the
167
+ package but silently substitutes its fallback bullet. Real scenarios declare a
168
+ `listStyleExpectation` with the source level, number format, and, where useful,
169
+ the exact `w:lvlText`. Corpus preparation resolves the target paragraph's
170
+ `w:numId` through `word/numbering.xml` and fails before editing if the pinned
171
+ source no longer has that native style. The current real cases exercise upper
172
+ and lower Roman, upper and lower letter, parenthesized lower letter, dash, and
173
+ symbol bullets across bylaws, agendas, healthcare minutes, and long council
174
+ minutes.
175
+
176
+ ### Diagnose visual failures in the generated OOXML first
177
+
178
+ When a page looks wrong, compare the relevant source and tracked
179
+ `word/document.xml` or related part before changing the renderer. Locate the
180
+ visible text, inspect its nearest `w:r`, `w:rPr`, wrapper, paragraph properties,
181
+ and neighboring runs, then compare those properties with the source. This
182
+ separates an engine defect from a Word, PDF, or `docx-preview` display issue.
183
+
184
+ Three defects illustrate the approach:
185
+
186
+ - The council header date replacement inherited `w:vertAlign="superscript"`
187
+ from the trailing ordinal suffix. The fix anchors replacement formatting at
188
+ the beginning of the deleted range, and the regression explicitly forbids
189
+ `vertAlign` on the inserted runs.
190
+ - The prospectus filing-date replacement crossed normal runs and a hyperlink.
191
+ A forward-only property lookup had already moved past the beginning of the
192
+ deletion when the insertion asked for its formatting, so the new run had no
193
+ `w:rPr`. Word and SuperDoc correctly fell back from the surrounding 10 pt to
194
+ the document default of 12 pt. The lookup now supports that deliberate
195
+ backward query. The focused regression requires `w:sz="20"` and
196
+ `w:szCs="20"`, and the real prospectus scenario requires those values on
197
+ every inserted text run.
198
+ - A wholly inserted list item originally tracked only its text run. Accept All
199
+ looked correct and text-only Reject All assertions passed, but Word retained
200
+ the untracked paragraph mark as an empty bullet or number. Inserted list
201
+ paragraphs now track both their text and paragraph mark. The regression
202
+ rejects the change and asserts the exact original paragraph count, text, and
203
+ numbering, while the real Word PDFs confirm that no ghost marker remains.
204
+
205
+ These assertions test the semantic cause rather than a screenshot pixel. The
206
+ visual render remains necessary to confirm that the corrected properties
207
+ produce the intended page. The automated test suite in
208
+ `tests/visual_failure_regression_tests.mjs` codifies these guards directly in
209
+ the regression test pipeline (`npm test`) to prevent regressions before DOCX
210
+ packages reach Word.
211
+
212
+ ### Regenerate packages; do not reason from stale outputs
213
+
214
+ Source downloads are content-addressed and may be reused. Generated tracked,
215
+ accepted, rejected, PDF, and dashboard artifacts are outputs and must be
216
+ rebuilt after an engine or scenario change. A `Verified cached ...` line from
217
+ the corpus fetcher means only that the pinned source `.docx` already matches its
218
+ recorded hash; it does not mean the edited package was reused.
219
+
220
+ Use this sequence for a corpus-affecting engine change:
221
+
222
+ ```powershell
223
+ npm test
224
+ npm run check:types
225
+ npm run lint
226
+ npm run test:isolation
227
+ npm run test:corpus:word
228
+ npm run test:corpus:word:visual
229
+ npm run report:test:dashboard
230
+ ```
231
+
232
+ For faster iteration, `prepare-superdoc-word-corpus.mjs` accepts
233
+ `--input-dir` and `--output-dir`, and the visual PowerShell runner accepts
234
+ `-Case <scenario-key>` plus custom fixture/output directories. Run the complete
235
+ lane before handoff even when a focused render passed.
236
+
237
+ ### Treat Word COM cleanup separately from render success
238
+
239
+ Word sometimes disconnects a COM document proxy after successfully exporting
240
+ a PDF and raises `RPC_E_DISCONNECTED` during `Document.Close()`. The visual
241
+ runner uses safe close/quit helpers: a disconnect during post-export cleanup is
242
+ reported but does not discard a valid, non-empty PDF; a disconnect during the
243
+ actual render causes Word to restart and the case to retry once. Other COM
244
+ exceptions still fail the run. This distinction avoids both false failures and
245
+ false passes.
246
+
247
+ Each visual invocation uses a process-specific fixture directory. That keeps a
248
+ Word process left behind by an earlier disconnect from locking or contaminating
249
+ the next set of generated DOCX files.
250
+
251
+ ### Review both the whole document and the changed page
252
+
253
+ For a long document, create low-resolution contact sheets for every page in all
254
+ three revision states and inspect them for pagination changes, blank pages,
255
+ clipping, table displacement, or large typography shifts. Then inspect every
256
+ changed page at full resolution. Contact sheets are good at document-wide
257
+ layout; they are not reliable for a 10 pt versus 12 pt difference or a leaked
258
+ superscript flag.
259
+
260
+ Record the exact scenario, Word version/build, revision view, page, observed
261
+ fact, and whether the judgment is certain. The visual manifest remains pending
262
+ until a reviewer makes that judgment; successful PDF export alone is not a
263
+ visual pass.
264
+
265
+ ### Keep the dashboard generated and test its controls
266
+
267
+ `scripts/generate-test-dashboard.mjs` owns the dashboard markup, styles, and
268
+ behavior. Do not hand-edit `docs/test-comparison-dashboard.html`; rebuild it
269
+ with `npm run report:test:dashboard`. The report embeds the four DOCX states so
270
+ it remains self-contained and can show the exact generated packages without a
271
+ file picker or server.
272
+
273
+ Dashboard changes require generator-level assertions in
274
+ `tests/test_dashboard_report_tests.mjs`. The sidebar-width control, for
275
+ example, is checked for its accessible relationship, full-width CSS state, and
276
+ persisted preference. DOCX package parsing remains covered separately by
277
+ `tests/docxjs_dashboard_rendering_tests.mjs`. When browser control is available,
278
+ also click the control in the generated page and verify that the sidebar is
279
+ hidden, both document panes widen, the label changes, and reloading preserves
280
+ the selected state.
281
+
282
+ ### Definition of done for a discovered real-document defect
283
+
284
+ A visual defect is complete only when all of the following are true:
285
+
286
+ - the source and faulty generated OOXML have been compared;
287
+ - the engine cause is understood rather than masked in CSS or the renderer;
288
+ - a minimal automated regression reproduces the structural boundary;
289
+ - Accept All yields the requested text and Reject All restores the source;
290
+ - the affected real corpus scenario has an appropriate structural or
291
+ formatting assertion;
292
+ - its source, tracked, accepted, and rejected packages have been regenerated;
293
+ - desktop Word differential passes;
294
+ - the changed page and the document-wide three-view render have been inspected;
295
+ - the self-contained comparison dashboard has been rebuilt; and
296
+ - the full automated, isolation, type, lint, and whitespace checks pass.
297
+
298
+ Prepare, but do not approve, a human review sample with:
299
+
300
+ ```powershell
301
+ npm run review:word:prepare -- --cycle=0
302
+ ```
303
+
304
+ The ignored `tmp/word-manual-review/review-manifest-cycle-0.json` selects all
305
+ catalogue families changed in the worktree, a rotating 20% synthetic sample,
306
+ and one legal plus one administrative SuperDoc case. Every All Markup, Accept
307
+ All, Reject All, and human sign-off field starts as `pending`; the helper cannot
308
+ turn them into passes. Increment `--cycle` between releases to rotate the
309
+ unchanged sample.
310
+
311
+ ## Automated JavaScript tests
312
+
313
+ Files matching `tests/*.mjs` are discovered by `scripts/run-tests.mjs`. Tests
314
+ use `assert/strict` and run as separate Node processes. Shared OOXML assertions
315
+ belong in `tests/helpers/ooxml-assertions.mjs`; XML-provider setup belongs in
316
+ `tests/setup-xml-provider.mjs`.
317
+
318
+ For function-level gap work, run:
319
+
320
+ ```powershell
321
+ npm run test:coverage
322
+ npm run coverage:gaps
323
+ npm run coverage:gaps -- --json
324
+ ```
325
+
326
+ The coverage command emits both the text summary and detailed Istanbul JSON.
327
+ The gap report includes only runtime production roots, lists each uncovered
328
+ function with its file and declaration line, assigns the Phase 3 P0/P1/P2
329
+ priority, and fails if a targeted file drops below the checked covered-function
330
+ or covered-branch baseline in `tests/coverage-data/phase3-baseline.json`. Because V8
331
+ discovers new branch sites when a formerly cold function first executes, review
332
+ covered counts and behavior assertions alongside percentages. Do not add ignore
333
+ annotations or call private code merely to improve a score; classify a retained
334
+ gap with a reachability or environment reason.
335
+
336
+ For an engine regression:
337
+
338
+ 1. Add the smallest fixed case that reproduces the bug and asserts the exact
339
+ result or structured error.
340
+ 2. Assert both sides of tracked changes: accepting must produce the requested
341
+ text and rejecting must restore the original text exactly.
342
+ 3. Assert structural invariants with `validateRedlineOoxml` and the shared OOXML
343
+ helpers. Do not use a whitespace-collapsing reader as the oracle.
344
+ 4. If engine behavior changed, add the same shape to the deterministic fuzz
345
+ corpus so nearby inputs are exercised too.
346
+ 5. Run `npm test`, isolation, types, and lint.
347
+
348
+ ## Synthetic Microsoft Word tests
349
+
350
+ The runtime library contains no COM or Word dependency. Word automation exists
351
+ only in Windows development scripts and is intentionally excluded from package
352
+ runtime paths.
353
+
354
+ The synthetic catalogue is `tests/fixtures/word-task-cases.mjs`. During
355
+ `npm run test:word`:
356
+
357
+ 1. `scripts/export-validation-fixtures.mjs` applies each operation, runs the
358
+ library's structural validator, checks any case-specific `requiredElements`,
359
+ and builds a minimal `.docx` under ignored `tmp/word-validation/` storage.
360
+ 2. Expected Accept All and Reject All text is derived from the test's edit
361
+ intent, not from the library's own revision-management functions.
362
+ 3. `scripts/word-com-differential.ps1` opens each fixture through desktop Word
363
+ without a repair dialog and confirms Word sees at least one revision.
364
+ 4. Word accepts every revision in one fresh document and rejects every revision
365
+ in another. Both resulting texts are compared with the intent-derived
366
+ expectations.
367
+
368
+ To add a synthetic Word case:
369
+
370
+ 1. Add a unique lowercase-hyphenated entry to `WORD_TASK_CASES` with
371
+ `category: 'legal'` or `'administrative'`, a distinct `task`, and `original`
372
+ and `modified` text.
373
+ 2. Use `sourceDocumentXml` when the case needs bookmarks, hyperlinks, tables,
374
+ content controls, prior revisions, or another deliberate structure.
375
+ 3. Add `requiredElements`, such as `{ bookmarkStart: 1, bookmarkEnd: 1 }`, when
376
+ retaining a structure is part of the claim. These are minimum namespace-aware
377
+ element counts checked before Word runs.
378
+ 4. Supply `expectedAcceptedText` and `expectedRejectedText` when the source is a
379
+ multi-paragraph document or Word exposes structural separators such as table
380
+ row boundaries. Exact comparison is the default.
381
+ 5. Run `node tests/word_task_catalog_tests.mjs`, then `npm run test:word` on a
382
+ Windows machine with desktop Word installed.
383
+
384
+ ### Related-part fixture schema
385
+
386
+ The script-only packager supports optional numbering plus comments, footnotes,
387
+ endnotes, headers, footers, and external hyperlinks. Add them to a catalogue
388
+ case through `packageParts`:
389
+
390
+ ```js
391
+ {
392
+ sourceDocumentXml,
393
+ packageParts: {
394
+ commentsXml,
395
+ footnotesXml,
396
+ endnotesXml,
397
+ headers: [{
398
+ partName: 'header1.xml',
399
+ relationshipId: 'rIdHeader1',
400
+ xml: headerXml
401
+ }],
402
+ footers: [{
403
+ partName: 'footer1.xml',
404
+ relationshipId: 'rIdFooter1',
405
+ xml: footerXml
406
+ }],
407
+ externalHyperlinks: [{
408
+ relationshipId: 'rIdPolicy',
409
+ target: 'https://example.com/policy'
410
+ }]
411
+ }
412
+ }
413
+ ```
414
+
415
+ `createCommentsPart`, `createNotesPart`, and `createHeaderFooterPart` in
416
+ `tests/fixtures/word-package-parts.mjs` provide small escaped constructors for
417
+ the common XML parts. Header/footer `partName` and `relationshipId` values have
418
+ deterministic defaults, but explicit values make the corresponding
419
+ `w:headerReference`, `w:footerReference`, or `w:hyperlink` easier to audit.
420
+
421
+ Before ZIP emission, the packager rejects malformed XML, duplicate relationship
422
+ IDs or part names, missing relationship targets, undefined comment/note IDs,
423
+ and note parts without separator IDs `-1` and `0`. It generates the required
424
+ content-type overrides and document relationships. Every supplied related part
425
+ is compared byte-for-byte with the packaged entry, recorded as SHA-256 in the
426
+ case sidecar, and rechecked by the Word differential before opening the DOCX.
427
+ These helpers remain under `scripts/` and `tests/`; none are shipped through the
428
+ runtime entry point.
429
+
430
+ ### What the automated Word differential proves
431
+
432
+ The Word process is real desktop Microsoft Word, but it is driven invisibly
433
+ through COM. For each fixture, the script:
434
+
435
+ 1. starts `Word.Application` with alerts and the window disabled;
436
+ 2. opens the generated package with `OpenNoRepairDialog`;
437
+ 3. fails if Word cannot open it or sees zero tracked revisions;
438
+ 4. accepts all revisions and reads `Document.Content.Text`;
439
+ 5. closes without saving, reopens the untouched fixture, rejects all revisions,
440
+ and reads the text again; and
441
+ 6. compares both results with expectations that were not calculated by this
442
+ library's own accept/reject implementation.
443
+
444
+ Exact comparison is the default. The harness removes Word's terminal paragraph
445
+ mark, normalizes CR/LF representation, and removes the characters Word exposes
446
+ as table-cell and footnote/endnote reference boundaries; case-specific normalized comparison requires an
447
+ explicit reason. Documents are never saved by the automated differential.
448
+
449
+ For synthetic fixtures, expectations come directly from `original`, `modified`,
450
+ and any explicit full-document expectations in the case. For SuperDoc fixtures,
451
+ Word first reads the declared story from the original pinned source document;
452
+ every target in a single or multi-change scenario must occur exactly once, and
453
+ the accepted expectation is formed by applying those replacements to Word's own
454
+ source text. Header scenarios use Word's header stories rather than body text.
455
+ The corpus packager hashes every package part other than the one intentionally
456
+ replaced by the scenario.
457
+
458
+ This proves that Word can consume the package, recognizes the revision markup,
459
+ and resolves Accept All and Reject All to the intended text. It does **not**
460
+ prove that the document looks right on the page. `Content.Text` cannot detect
461
+ bad pagination, awkward revision balloons, shifted table widths, broken tab
462
+ alignment, font substitution, changed list indentation, clipped headers,
463
+ visually stale fields, or a comment/footnote marker that is technically present
464
+ but poorly placed.
465
+
466
+ ### Human Word visual review
467
+
468
+ Human visual review complements—not replaces—the automated differential. Use
469
+ the checklist and report template in
470
+ [`WORD-MANUAL-REVIEW.md`](./WORD-MANUAL-REVIEW.md).
471
+
472
+ Review is required for:
473
+
474
+ - every new or materially changed synthetic Word case;
475
+ - the first case for a new structure, operation type, or related package part;
476
+ - any engine change affecting reconstruction, formatting, lists, tables,
477
+ fields, tabs/breaks, comments, notes, headers/footers, or revision metadata;
478
+ - any case that needs normalized rather than exact text comparison; and
479
+ - any automated Word failure whose cause is not immediately textual.
480
+
481
+ Before a release, review all new/changed cases plus a rotating sample of at
482
+ least 20% of the unchanged synthetic catalogue. The sample must include legal
483
+ and administrative content and at least one list, table, formatted-run, and
484
+ structural-anchor case. Also review at least one legal and one administrative
485
+ SuperDoc result. Rotate the sample so every retained synthetic case receives a
486
+ human review over five release cycles. A major release or a change to package
487
+ assembly requires a full visual sweep of affected structure families.
488
+
489
+ The reviewer inspects three states in Word: tracked changes with **All Markup**,
490
+ the result after **Accept All**, and a fresh copy after **Reject All**. Record
491
+ the reviewer, date, Word version/build, cases selected, pass/fail result, and
492
+ notes. A visual failure becomes a regression case or a documented harness gap;
493
+ do not waive it merely because the COM text differential passed.
494
+
495
+ ### AI-assisted Word visual preflight
496
+
497
+ Generate repeatable three-view PDF evidence for every layout-sensitive
498
+ synthetic fixture with:
499
+
500
+ ```powershell
501
+ npm run test:word:visual
502
+ ```
503
+
504
+ The command writes PDFs and `manifest.json` under ignored
505
+ `tmp/word-visual-review/rendered/` storage. It verifies that Word opened each
506
+ fixture and produced a non-empty, paginated rendering, but leaves the manifest
507
+ certification and every visual judgment pending. To render only named cases,
508
+ invoke `scripts/word-com-visual-suite.ps1 -Case case-one,case-two` directly.
509
+
510
+ An AI agent with Windows computer control may also open the generated fixtures
511
+ in the installed desktop Word application, switch among All Markup, Accept All,
512
+ and Reject All views, capture screenshots, and inspect them for visible
513
+ regressions. This is a useful intermediate oracle because it exercises the same
514
+ real UI a person sees rather than only `Document.Content.Text`.
515
+
516
+ Run an AI visual preflight:
517
+
518
+ - after a phase that changes reconstruction, formatting, list/table behavior,
519
+ package assembly, or revision display;
520
+ - after every five to ten new Word fixtures;
521
+ - for all newly introduced structure families;
522
+ - when automated Word passes but the XML change is unusually broad; and
523
+ - before asking a human to perform the release sample, so obvious failures are
524
+ found first.
525
+
526
+ For the focused real-document additions, run:
527
+
528
+ ```powershell
529
+ npm run test:corpus:word:visual
530
+ ```
531
+
532
+ This first runs the exact Word differential, then writes 120 PDFs and a pending
533
+ manifest under ignored `tmp/superdoc-word-visual-review/rendered/` storage: three
534
+ views for 22 list-focused cases, 16 table-focused cases, and two page-header
535
+ cases. The long-document set includes 6,000+ word council minutes,
536
+ an 8,000+ word zoning resolution, and a 59,000+ word prospectus with 180 tables.
537
+ Each invocation packages its fixtures in a process-specific directory so
538
+ a Word process left behind by an RPC disconnect cannot lock the next run's
539
+ inputs. The runner ignores `RPC_E_DISCONNECTED` during post-export cleanup and
540
+ restarts Word once when a disconnect interrupts an actual render. After Word
541
+ finishes, the command rebuilds `docs/test-comparison-dashboard.html` from that
542
+ exact process-specific fixture directory. The `Verified cached ...` messages
543
+ refer only to the pinned source-document downloads; edited comparison DOCX
544
+ files are regenerated on every run.
545
+
546
+ The AI should use the same selection rules and checklist as a human, inspect at
547
+ least the changed cases plus representative legal and administrative samples,
548
+ and save screenshots under ignored `tmp/word-manual-review/<date>/` storage.
549
+ Its report must identify itself as **AI visual preflight**, record the Word
550
+ version/build when available, list the exact cases and views inspected, and
551
+ separate observed facts from uncertain visual judgments.
552
+
553
+ AI visual review is advisory. It can catch missing or misplaced revisions,
554
+ unexpected whole-paragraph markup, obvious pagination shifts, broken tables,
555
+ lost indentation, clipped headers, and visibly misplaced anchors. It may miss
556
+ subtle font metrics, accessibility issues, field behavior that requires domain
557
+ knowledge, or a legally meaningful formatting distinction. An AI pass does not
558
+ satisfy the human release sign-off, and sensitive/private documents must not be
559
+ opened for AI review without explicit authorization.
560
+
561
+ ### Multimodal LLM visual inspection (real-document spot checks)
562
+
563
+ Evaluating subtle visual defects—such as shifted table borders, unnatural run
564
+ breaks, font substitution, clipping headers, or awkward revision balloons—requires
565
+ interpreting rendered document pages visually rather than relying solely on
566
+ string assertions or raw XML checks. A multimodal Large Language Model (MLLM)
567
+ with vision capabilities can review rendered page images across the three
568
+ revision views (`allMarkup`, `acceptAll`, and `rejectAll`) and detect regressions
569
+ that are invisible to plain-text diffs.
570
+
571
+ #### Why multimodal inspection is kept separate
572
+
573
+ Passing high-resolution, multi-page document images through a vision-capable LLM
574
+ is **expensive, computationally intensive, and time-consuming**:
575
+
576
+ - Each document scenario produces three full-document rendering views (often 1
577
+ to 20+ pages per view).
578
+ - Sending dozens of high-resolution page images consumes significant token
579
+ budgets and model context.
580
+ - Latency per case is orders of magnitude higher than programmatic XML parsing
581
+ or COM automation.
582
+
583
+ For these reasons, multimodal visual inspection **forms a separate check from the
584
+ rest of the test suite**. It is never executed on every commit, local test run, or
585
+ full batch pipeline.
586
+
587
+ #### Sampling strategy: Random real-document spot checks
588
+
589
+ Instead of exhaustive or continuous execution, multimodal visual inspection is
590
+ conducted as a **random spot check of real documents here and there only**:
591
+
592
+ 1. **Focus on authentic real documents:** Spot checks prioritize the
593
+ [SuperDoc real-document corpus](#superdoc-real-document-word-tests) rather than
594
+ simple synthetic fixtures. Real documents contain organic complexities—such
595
+ as multi-column headers, nested bullet styles, non-uniform table borders, and
596
+ mixed-font legal numbering—where visual layout errors are most likely to hide.
597
+ 2. **Small random sampling:** A typical visual spot-check run selects a random
598
+ sample of **2 to 5 real document scenarios** (for example, one administrative
599
+ board agenda, one municipal minutes multi-table document, and one complex
600
+ zoning ordinance).
601
+ 3. **Execution cadence:** Spot checks are run on-demand:
602
+ - periodically during active engine refactoring;
603
+ - when introducing major changes to run splitting, table reconciliation, or
604
+ list reconstruction; or
605
+ - as an advisory audit prior to a release cycle.
606
+
607
+ #### Multimodal inspection workflow
608
+
609
+ When performing a multimodal visual spot check:
610
+
611
+ 1. **Render target pages to images:** Render the selected scenario in Word to
612
+ PDFs using `scripts/word-com-corpus-visual-suite.ps1 -Case <scenario-name>`,
613
+ then convert the relevant pages (or contact sheets) to PNG images using the
614
+ visual inspection helper:
615
+ ```powershell
616
+ node scripts/inspect-visual-evidence.mjs --contact-sheets
617
+ ```
618
+ 2. **Submit to the multimodal model:** Provide the vision model with the side-by-side
619
+ page renders of `allMarkup`, `acceptAll`, and `rejectAll` along with the target
620
+ diff and context.
621
+ 3. **Targeted visual checklist:** Prompt the model to evaluate specific layout risks:
622
+ - **Table cell integrity:** Did column widths shift? Are table borders intact?
623
+ Did multi-line cell text collapse or overlap?
624
+ - **Typography & Font metrics:** Did any run revert to default font size (e.g.
625
+ dropping from 14pt Georgia to 12pt Normal)? Did bold, italic, or underline
626
+ bleed into neighboring text?
627
+ - **List alignment:** Are bullet indents consistent with existing items? Are
628
+ there ghost markers or blank list rows?
629
+ - **Page apparatus:** Are headers, footers, and page numbers in their expected
630
+ margins without clipping or wrapping anomalies?
631
+ 4. **Triaging findings:** If the multimodal model flags a visual abnormality,
632
+ treat it as an advisory signal:
633
+ - Verify the defect manually in Microsoft Word.
634
+ - Trace the visual defect to its underlying OOXML cause (per the
635
+ [visual failure diagnosis guidelines](#diagnose-visual-failures-in-the-generated-ooxml-first)).
636
+ - Add a minimal semantic regression test in
637
+ `tests/visual_failure_regression_tests.mjs` to ensure the issue is permanently
638
+ prevented in the automated suite (`npm test`).
639
+
640
+ ## SuperDoc real-document Word tests
641
+
642
+ The corpus lane references selected documents from SuperDoc's
643
+ [docx-corpus](https://docxcorp.us/) under ODC-By 1.0. Source documents are never
644
+ committed. `tests/corpus/superdoc-english-legal-administrative.json` pins each
645
+ reviewed source and observed SHA-256; `tests/corpus/superdoc-word-scenarios.json`
646
+ defines named deterministic scenarios and records the structural coverage they
647
+ add. A source may support multiple scenarios when each one adds a distinct
648
+ behavioral claim.
649
+
650
+ To add a corpus case:
651
+
652
+ 1. Select an English legal or administrative document that adds a structure or
653
+ task not already represented. Review its content and record why it is
654
+ suitable.
655
+ 2. Add an explicit pinned reference and observed hash to the manifest. Do not
656
+ use a floating or bulk corpus download.
657
+ 3. Fetch only that reference with
658
+ `npm run corpus:fetch:superdoc -- --id <pinned-id>`.
659
+ 4. Inspect the document in Word and choose a target that occurs exactly once in
660
+ Word's source text.
661
+ 5. Add the deterministic operation, shape, coverage labels, and review note to
662
+ `superdoc-word-scenarios.json`. Use `key` plus `sourceId` when adding another
663
+ scenario for an already pinned source. Use `operations` for a multi-change
664
+ atomic batch, or `part: "word/header<N>.xml"` for a page-header revision.
665
+ 6. Run `npm test` for manifest/catalogue checks and `npm run test:corpus:word`
666
+ for package hashing plus the Word Accept All/Reject All differential.
667
+
668
+ The corpus packager starts from the original `.docx`, replaces only the declared
669
+ revision part (`word/document.xml` by default, or a named header part), and
670
+ verifies every untouched ZIP part byte-for-byte before Word opens the result.
671
+ The Word oracle derives multi-change expectations by applying each independently
672
+ declared replacement to Word's source text and reads header-story text separately
673
+ for header cases.
674
+
675
+ ## Choosing where a new test belongs
676
+
677
+ - A fixed bug always gets an automated regression.
678
+ - A broad engine rule also gets deterministic fuzz coverage.
679
+ - A claim about what Word renders or accepts gets a synthetic Word case.
680
+ - A claim involving package complexity or realistic authoring structures gets
681
+ a reviewed corpus case.
682
+ - A structural XML rule should be checked by runtime validation and, where
683
+ practical, the ECMA-376 XSD lane.
684
+
685
+ Keep expectations independent of the code under test. In particular, never use
686
+ this library's Accept/Reject helpers to calculate the expected result for the
687
+ Word differential.