@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.
Files changed (100) hide show
  1. package/AGENTS.md +589 -287
  2. package/ARCHITECTURE.md +215 -9
  3. package/CHANGELOG.md +319 -0
  4. package/README.md +604 -360
  5. package/adapters/config.js +45 -43
  6. package/bin/docx-redline.js +3 -0
  7. package/core/list-targeting.js +101 -110
  8. package/core/paragraph-targeting.js +501 -61
  9. package/core/paragraph-text.js +209 -0
  10. package/core/revision-cloning.js +38 -0
  11. package/core/types.js +64 -10
  12. package/core/word-xml.js +43 -15
  13. package/dist/docx-redline-js.esm.js +2849 -466
  14. package/dist/docx-redline-js.esm.js.map +4 -4
  15. package/dist/docx-redline-js.esm.min.js +87 -76
  16. package/dist/docx-redline-js.esm.min.js.map +4 -4
  17. package/docs/TESTING.md +342 -23
  18. package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +1669 -0
  19. package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +669 -0
  20. package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +427 -0
  21. package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +519 -0
  22. package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +69 -0
  23. package/docs/plans/completed/structural-revision-capability-matrix.md +115 -0
  24. package/docs/schemas/document-operations.schema.json +109 -0
  25. package/docs/test-comparison-dashboard.html +4250 -7
  26. package/engine/formatting-removal.js +11 -2
  27. package/engine/oxml-engine.js +491 -336
  28. package/engine/reconstruction-mode.js +15 -14
  29. package/engine/reconstruction-writer.js +247 -142
  30. package/engine/route-selection.js +35 -0
  31. package/engine/rpr-helpers.js +334 -35
  32. package/engine/run-builders.js +239 -196
  33. package/engine/surgical-diff-application.js +222 -37
  34. package/engine/surgical-mode.js +134 -6
  35. package/engine/surgical-spans.js +52 -1
  36. package/engine/table-cell-context.js +3 -6
  37. package/engine/table-mode.js +1 -1
  38. package/index.d.ts +234 -6
  39. package/index.js +24 -1
  40. package/node/cli.js +317 -0
  41. package/node/docx-document.js +302 -0
  42. package/node/index.d.ts +31 -0
  43. package/node/index.js +2 -0
  44. package/node/zip-archive.js +52 -0
  45. package/orchestration/list-markdown.js +10 -16
  46. package/orchestration/list-parsing.js +7 -12
  47. package/orchestration/list-structural-fallback.js +21 -10
  48. package/package.json +24 -3
  49. package/pipeline/content-analysis.js +12 -17
  50. package/pipeline/ingestion-export.js +3 -31
  51. package/pipeline/ingestion-paragraph.js +10 -5
  52. package/pipeline/list-generation.js +150 -55
  53. package/pipeline/list-markers.js +70 -3
  54. package/pipeline/serialization.js +4 -2
  55. package/pipeline/structured-content.js +160 -0
  56. package/scripts/apply_changes.mjs +27 -0
  57. package/scripts/benchmark-operation-session.mjs +137 -0
  58. package/scripts/benchmark-targeting-browser.html +74 -0
  59. package/scripts/benchmark-targeting-hot-paths.mjs +67 -0
  60. package/scripts/benchmark-test-runner.mjs +59 -0
  61. package/scripts/build-test-dashboard.mjs +23 -0
  62. package/scripts/export-lane1-fixtures.mjs +380 -0
  63. package/scripts/export-reredline-stress-fixtures.mjs +317 -0
  64. package/scripts/export-validation-fixtures.mjs +1 -1
  65. package/scripts/extract_text.mjs +7 -0
  66. package/scripts/generate-paragraph-boundary-fixtures.ps1 +215 -0
  67. package/scripts/generate-test-dashboard.mjs +362 -11
  68. package/scripts/lib/word-coverage-catalogue.mjs +6 -2
  69. package/scripts/profile-route-selection.mjs +19 -0
  70. package/scripts/render-agenda-multilevel.mjs +0 -5
  71. package/scripts/render-multilevel-cases.mjs +0 -1
  72. package/scripts/run-tests.mjs +107 -35
  73. package/scripts/word-com-corpus-suite.ps1 +3 -0
  74. package/scripts/word-com-differential.ps1 +64 -4
  75. package/scripts/word-com-suite.ps1 +3 -0
  76. package/services/batch-operation-orchestrator.js +494 -0
  77. package/services/capture-engine.js +226 -0
  78. package/services/comment-builders.js +23 -6
  79. package/services/comment-engine.js +108 -47
  80. package/services/comment-locator.js +187 -82
  81. package/services/comment-replies.js +95 -0
  82. package/services/document-inspection.js +258 -0
  83. package/services/document-operation-applier.js +372 -0
  84. package/services/document-operation-contract.js +323 -0
  85. package/services/document-operation-mutations.js +1733 -0
  86. package/services/document-operation-session.js +258 -0
  87. package/services/numbering-service.js +14 -5
  88. package/services/operation-heuristics.js +173 -0
  89. package/services/operation-preflight.js +366 -0
  90. package/services/receipt-collector.js +288 -0
  91. package/services/revision-comment-management.js +37 -5
  92. package/services/revision-token.js +290 -0
  93. package/services/standalone-docx-plumbing.js +123 -8
  94. package/services/standalone-operation-runner.d.ts +296 -0
  95. package/services/standalone-operation-runner.js +10 -1455
  96. package/services/table-reconciliation.js +15 -6
  97. package/docs/VALIDATION.md +0 -183
  98. package/docs/WORD-MANUAL-REVIEW.md +0 -138
  99. package/docs/plans/2026-09-01-performance-and-complexity-reduction.md +0 -210
  100. /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 { generateRedlines = false, author = 'AI', revisionIdAllocator = null } = options;
23
- const tableInsertMeta = generateRedlines ? createRevisionMetadata(author, revisionIdAllocator) : null;
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
- // Row properties
84
- const trPr = '<w:trPr/>';
85
- rowsXml += `<w:tr>${trPr}${cellsXml}</w:tr>`;
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
@@ -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.