@ansonlai/docx-redline-js 0.8.4 → 0.10.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/ARCHITECTURE.md +679 -460
- package/CHANGELOG.md +208 -1
- package/README.md +152 -16
- package/core/current-properties.js +136 -0
- package/core/paragraph-text.js +211 -209
- package/core/types.js +9 -3
- package/core/word-revision-semantics.js +256 -0
- package/core/word-xml.js +160 -118
- package/dist/docx-redline-js.esm.js +4338 -796
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +111 -108
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/dist/docx-redline.bundle.cjs +4463 -921
- package/dist/docx-redline.bundle.cjs.map +4 -4
- package/dist/docx-redline.bundle.js +4463 -921
- package/dist/docx-redline.bundle.js.map +4 -4
- package/docs/AGENT_FAST_START.md +2 -0
- package/docs/AGENT_KNOWLEDGE_BASE.md +49 -7
- package/docs/SKILL_AUTHORING.md +169 -150
- package/docs/TESTING.md +1146 -1059
- package/docs/schemas/document-operations.schema.json +55 -2
- package/document/docx-document.js +1022 -1010
- package/engine/oxml-engine.js +21 -22
- package/engine/paragraph-mark-revisions.js +292 -0
- package/engine/reconstruction-mapper.js +312 -311
- package/engine/reconstruction-writer.js +67 -10
- package/engine/rpr-helpers.js +42 -0
- package/engine/run-builders.js +0 -73
- package/engine/surgical-diff-application.js +604 -605
- package/engine/table-mode.js +63 -63
- package/index.d.ts +53 -2
- package/index.js +13 -2
- package/node/cli-help.js +1 -0
- package/node/cli.js +21 -3
- package/orchestration/list-structural-fallback.js +67 -9
- package/package.json +1 -1
- package/pipeline/diff-engine.js +130 -1
- package/pipeline/ingestion-export.js +227 -243
- package/pipeline/ingestion-paragraph.js +93 -98
- package/pipeline/list-generation.js +415 -419
- package/pipeline/markdown-processor.js +5 -2
- package/pipeline/pipeline.js +206 -204
- package/scripts/export-validation-fixtures.mjs +19 -6
- package/scripts/generate-word-revision-semantics-matrix.mjs +134 -0
- package/scripts/lib/word-coverage-catalogue.mjs +7 -2
- package/scripts/render-multilevel-bullet-visual.ps1 +2 -2
- package/scripts/word-com-corpus-visual-suite.ps1 +2 -2
- package/scripts/word-com-differential.ps1 +443 -335
- package/scripts/word-com-visual-suite.ps1 +2 -2
- package/scripts/word-revision-semantics-matrix.ps1 +108 -0
- package/services/batch-operation-orchestrator.js +23 -3
- package/services/capture-engine.js +228 -226
- package/services/comment-replies.js +1 -1
- package/services/document-operation-applier.js +214 -0
- package/services/document-operation-contract.js +76 -0
- package/services/document-operation-mutations.js +688 -51
- package/services/error-recovery.js +17 -0
- package/services/inspection-comparison.js +82 -0
- package/services/list-conversion-diagnostics.js +296 -0
- package/services/list-format-change.js +586 -0
- package/services/list-format-mutation.js +416 -0
- package/services/list-format.js +305 -0
- package/services/numbering-allocation.js +642 -0
- package/services/numbering-helpers.js +354 -524
- package/services/operation-batch-compiler.js +183 -11
- package/services/operation-preflight.js +43 -33
- package/services/receipt-collector.js +288 -288
- package/services/revision-comment-management.js +38 -5
- package/services/revision-fidelity-verifier.js +484 -0
- package/services/standalone-docx-plumbing.js +627 -631
- package/services/standalone-operation-runner.d.ts +182 -5
- package/services/table-reconciliation.js +59 -7
package/ARCHITECTURE.md
CHANGED
|
@@ -1,460 +1,679 @@
|
|
|
1
|
-
# Reconciliation Core Architecture
|
|
2
|
-
|
|
3
|
-
This document describes the standalone, host-agnostic OOXML reconciliation package and how to work inside it safely.
|
|
4
|
-
|
|
5
|
-
## Scope
|
|
6
|
-
|
|
7
|
-
This repository contains only the publishable package surface:
|
|
8
|
-
|
|
9
|
-
- `adapters/`
|
|
10
|
-
- `core/`
|
|
11
|
-
- `document/` (universal complete-DOCX facade & ZIP archive layer)
|
|
12
|
-
- `engine/`
|
|
13
|
-
- `pipeline/`
|
|
14
|
-
- `services/`
|
|
15
|
-
- `orchestration/`
|
|
16
|
-
- `node/` (Node CLI & backward-compatibility facade)
|
|
17
|
-
- `index.js`
|
|
18
|
-
- `index.d.ts`
|
|
19
|
-
- `dist/`
|
|
20
|
-
|
|
21
|
-
No Word add-in entrypoints or host-specific integration layers are part of this package.
|
|
22
|
-
|
|
23
|
-
## Goals
|
|
24
|
-
|
|
25
|
-
- Preserve Word-compatible redlines by editing OOXML directly.
|
|
26
|
-
- Keep core logic and document-level operations host-independent (no Office.js globals, no Word API calls, no runtime Node built-ins like `node:zlib` or `node:crypto`).
|
|
27
|
-
- Standardize complete-document packages on `Uint8Array` binary payloads, pure-JS SHA-256 revision tokens, and `fflate` ZIP container manipulation.
|
|
28
|
-
- Reuse the same engine across Node.js, browsers, Cloudflare Workers, Deno, Bun, and sandboxed runtimes (e.g. n8n Code nodes).
|
|
29
|
-
- Keep generated OOXML schema-safe through shared Word element creation and
|
|
30
|
-
centralized revision metadata helpers.
|
|
31
|
-
|
|
32
|
-
## Folder Layout
|
|
33
|
-
|
|
34
|
-
```text
|
|
35
|
-
.
|
|
36
|
-
├── adapters/
|
|
37
|
-
│ ├── config.js
|
|
38
|
-
│ ├── logger.js
|
|
39
|
-
│ └── xml-adapter.js
|
|
40
|
-
├── core/
|
|
41
|
-
│ ├──
|
|
42
|
-
│ ├──
|
|
43
|
-
│
|
|
44
|
-
├──
|
|
45
|
-
│
|
|
46
|
-
|
|
47
|
-
│
|
|
48
|
-
├──
|
|
49
|
-
│
|
|
50
|
-
|
|
51
|
-
│ ├──
|
|
52
|
-
│ ├──
|
|
53
|
-
│
|
|
54
|
-
├──
|
|
55
|
-
├──
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
│ ├──
|
|
61
|
-
│ ├──
|
|
62
|
-
│ ├──
|
|
63
|
-
│ ├──
|
|
64
|
-
│ ├──
|
|
65
|
-
│ ├──
|
|
66
|
-
│ ├──
|
|
67
|
-
│
|
|
68
|
-
├──
|
|
69
|
-
│ ├──
|
|
70
|
-
│ ├──
|
|
71
|
-
│ ├──
|
|
72
|
-
│
|
|
73
|
-
├──
|
|
74
|
-
│ ├──
|
|
75
|
-
│ ├──
|
|
76
|
-
│
|
|
77
|
-
├──
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
##
|
|
94
|
-
|
|
95
|
-
- `
|
|
96
|
-
|
|
97
|
-
- `
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- `
|
|
108
|
-
-
|
|
109
|
-
|
|
110
|
-
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
-
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
- `core/
|
|
120
|
-
- Shared
|
|
121
|
-
|
|
122
|
-
-
|
|
123
|
-
|
|
124
|
-
- `
|
|
125
|
-
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
- `
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
-
|
|
135
|
-
|
|
136
|
-
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
-
|
|
153
|
-
|
|
154
|
-
- `
|
|
155
|
-
-
|
|
156
|
-
- `
|
|
157
|
-
-
|
|
158
|
-
|
|
159
|
-
- `
|
|
160
|
-
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
- `
|
|
192
|
-
-
|
|
193
|
-
|
|
194
|
-
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
- `
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
-
|
|
255
|
-
|
|
256
|
-
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
- `
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
- `
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
-
|
|
437
|
-
- `
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
1
|
+
# Reconciliation Core Architecture
|
|
2
|
+
|
|
3
|
+
This document describes the standalone, host-agnostic OOXML reconciliation package and how to work inside it safely.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
This repository contains only the publishable package surface:
|
|
8
|
+
|
|
9
|
+
- `adapters/`
|
|
10
|
+
- `core/`
|
|
11
|
+
- `document/` (universal complete-DOCX facade & ZIP archive layer)
|
|
12
|
+
- `engine/`
|
|
13
|
+
- `pipeline/`
|
|
14
|
+
- `services/`
|
|
15
|
+
- `orchestration/`
|
|
16
|
+
- `node/` (Node CLI & backward-compatibility facade)
|
|
17
|
+
- `index.js`
|
|
18
|
+
- `index.d.ts`
|
|
19
|
+
- `dist/`
|
|
20
|
+
|
|
21
|
+
No Word add-in entrypoints or host-specific integration layers are part of this package.
|
|
22
|
+
|
|
23
|
+
## Goals
|
|
24
|
+
|
|
25
|
+
- Preserve Word-compatible redlines by editing OOXML directly.
|
|
26
|
+
- Keep core logic and document-level operations host-independent (no Office.js globals, no Word API calls, no runtime Node built-ins like `node:zlib` or `node:crypto`).
|
|
27
|
+
- Standardize complete-document packages on `Uint8Array` binary payloads, pure-JS SHA-256 revision tokens, and `fflate` ZIP container manipulation.
|
|
28
|
+
- Reuse the same engine across Node.js, browsers, Cloudflare Workers, Deno, Bun, and sandboxed runtimes (e.g. n8n Code nodes).
|
|
29
|
+
- Keep generated OOXML schema-safe through shared Word element creation and
|
|
30
|
+
centralized revision metadata helpers.
|
|
31
|
+
|
|
32
|
+
## Folder Layout
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
.
|
|
36
|
+
├── adapters/
|
|
37
|
+
│ ├── config.js
|
|
38
|
+
│ ├── logger.js
|
|
39
|
+
│ └── xml-adapter.js
|
|
40
|
+
├── core/
|
|
41
|
+
│ ├── current-properties.js
|
|
42
|
+
│ ├── paragraph-text.js
|
|
43
|
+
│ ├── sha256.js
|
|
44
|
+
│ ├── word-revision-semantics.js
|
|
45
|
+
│ └── word-xml.js
|
|
46
|
+
├── document/
|
|
47
|
+
│ ├── docx-document.js
|
|
48
|
+
│ ├── docx-document.d.ts
|
|
49
|
+
│ └── zip-archive.js
|
|
50
|
+
├── engine/
|
|
51
|
+
│ ├── oxml-engine.js
|
|
52
|
+
│ ├── surgical-mode.js
|
|
53
|
+
│ ├── reconstruction-mode.js
|
|
54
|
+
│ ├── run-builders.js
|
|
55
|
+
│ ├── paragraph-mark-revisions.js
|
|
56
|
+
│ └── formatting-removal.js
|
|
57
|
+
├── orchestration/
|
|
58
|
+
├── pipeline/
|
|
59
|
+
├── services/
|
|
60
|
+
│ ├── comment-engine.js
|
|
61
|
+
│ ├── numbering-helpers.js
|
|
62
|
+
│ ├── numbering-allocation.js
|
|
63
|
+
│ ├── list-conversion-diagnostics.js
|
|
64
|
+
│ ├── list-format.js
|
|
65
|
+
│ ├── list-format-mutation.js
|
|
66
|
+
│ ├── list-format-change.js
|
|
67
|
+
│ ├── operation-batch-compiler.js
|
|
68
|
+
│ ├── localized-replacement-compiler.js
|
|
69
|
+
│ ├── revision-comment-management.js
|
|
70
|
+
│ ├── document-inspection.js
|
|
71
|
+
│ ├── inspection-comparison.js
|
|
72
|
+
│ ├── document-operation-session.js
|
|
73
|
+
│ ├── document-operation-applier.js
|
|
74
|
+
│ ├── document-operation-mutations.js
|
|
75
|
+
│ ├── batch-operation-orchestrator.js
|
|
76
|
+
│ ├── revision-fidelity-verifier.js
|
|
77
|
+
│ ├── operation-heuristics.js
|
|
78
|
+
│ ├── standalone-docx-plumbing.js
|
|
79
|
+
│ └── standalone-operation-runner.js
|
|
80
|
+
├── node/
|
|
81
|
+
│ ├── docx-document.js
|
|
82
|
+
│ ├── cli.js
|
|
83
|
+
│ ├── zip-archive.js
|
|
84
|
+
│ └── index.js
|
|
85
|
+
├── dist/
|
|
86
|
+
│ ├── docx-redline-js.esm.js
|
|
87
|
+
│ ├── docx-redline.bundle.js
|
|
88
|
+
│ └── docx-redline.bundle.cjs
|
|
89
|
+
├── index.js
|
|
90
|
+
└── index.d.ts
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Entry Points
|
|
94
|
+
|
|
95
|
+
- `index.js`: primary host-independent exports for OOXML reconciliation and universal complete-document operations (`openDocx`, `DocxDocument`, `computePackageRevisionToken`).
|
|
96
|
+
- `document/docx-document.js`: universal `Uint8Array`-based document facade and package-level operation runner.
|
|
97
|
+
- `dist/docx-redline.bundle.js` / `dist/docx-redline.bundle.cjs`: zero-dependency standalone sandbox bundles, exported via `@ansonlai/docx-redline-js/bundle`.
|
|
98
|
+
- `services/standalone-operation-runner.js`: stable complete-document XML
|
|
99
|
+
operations, exported as `@ansonlai/docx-redline-js/standalone-runner`.
|
|
100
|
+
- `node/index.js`: Node-compatible complete-DOCX facade re-exports, exported as
|
|
101
|
+
`@ansonlai/docx-redline-js/node`.
|
|
102
|
+
- `bin/docx-redline.js`: CLI launcher; command implementation lives in
|
|
103
|
+
`node/cli.js` and shared operation behavior lives in `services/`.
|
|
104
|
+
|
|
105
|
+
## Module Responsibilities
|
|
106
|
+
|
|
107
|
+
- `adapters/config.js`
|
|
108
|
+
- Runtime configuration for defaults (`setDefaultAuthor`, `getDefaultAuthor`, `setPlatform`, `getPlatform`).
|
|
109
|
+
- `adapters/xml-adapter.js`
|
|
110
|
+
- XML parser/serializer injection for browser or Node.js runtimes.
|
|
111
|
+
- `adapters/logger.js`
|
|
112
|
+
- Runtime logger injection and shared logging methods.
|
|
113
|
+
- `core/*`
|
|
114
|
+
- Shared types, OOXML identity helpers, target resolution, list/table targeting heuristics, and XML query helpers.
|
|
115
|
+
- `core/word-xml.js`
|
|
116
|
+
- Namespace-safe Word element creation, tracked-change detection, OOXML payload source-shape helpers, and
|
|
117
|
+
`resolveExistingRevisionsPolicy`, the single place `existingRevisions: 'auto'` is resolved (per target
|
|
118
|
+
paragraph or per `targetEnd` range; unattributed changes count as another author's).
|
|
119
|
+
- `core/types.js`
|
|
120
|
+
- Shared model enums/types plus revision metadata generation and document-aware revision ID seeding.
|
|
121
|
+
- `core/paragraph-text.js`
|
|
122
|
+
- Canonical accepted/rejected/current-view text shared by targeting,
|
|
123
|
+
ingestion, and inspection.
|
|
124
|
+
- `core/paragraph-targeting.js`
|
|
125
|
+
- Target descriptors and session-scoped paragraph metadata indexes. The
|
|
126
|
+
index groups IDs and normalized text while retaining canonical text,
|
|
127
|
+
fingerprints, document order, and table context for deterministic reuse.
|
|
128
|
+
- `core/redline-validation.js`
|
|
129
|
+
- Runtime structural validation (`validateRedlineOoxml`) mirroring the test-suite invariants: no nested revisions, `w:delText` inside `w:del`, complete revision metadata, unique revision ids, preserved boundary whitespace.
|
|
130
|
+
- `core/validation-delta.js`
|
|
131
|
+
- Stable issue signatures and multiset subtraction for classifying baseline
|
|
132
|
+
versus generated validation issues without hiding added duplicate errors.
|
|
133
|
+
- `core/word-revision-semantics.js`
|
|
134
|
+
- Pure DOM projection of what Word shows after Reject All or Accept All
|
|
135
|
+
(`projectWordRevisionView`, `wordRevisionParagraphTexts`,
|
|
136
|
+
`paragraphMarkRevision`). It encodes Word's paragraph-mark rules: a
|
|
137
|
+
tracked-inserted mark vanishes on Reject and its content merges into the
|
|
138
|
+
following paragraph of the same container, a tracked-deleted mark does the
|
|
139
|
+
same on Accept, a table cell keeps its last paragraph, rows marked inserted
|
|
140
|
+
or deleted vanish, and Reject restores `w:pPrChange` snapshots. It never
|
|
141
|
+
mutates or serializes. The rules are validated against recorded desktop
|
|
142
|
+
Word 16 COM results (`tests/fixtures/word-revision-semantics/`).
|
|
143
|
+
- `core/current-properties.js`
|
|
144
|
+
- Direct-child accessors for the current paragraph/run properties
|
|
145
|
+
(`getCurrentParagraphProperties`, `getCurrentRunProperties`,
|
|
146
|
+
`getCurrentChild`, `getCurrentNumIdElements`, ...). Descendant lookups cross
|
|
147
|
+
`w:pPrChange` / `w:rPrChange`, so a historical `numPr` or `pStyle` would look
|
|
148
|
+
current; the snapshot is reachable only through the explicit
|
|
149
|
+
`getHistoricalParagraphProperties` / `getHistoricalRunProperties` and
|
|
150
|
+
`getAllReferencedNumIdElements`. Depends only on `core/types.js`.
|
|
151
|
+
- `core/revision-cloning.js`
|
|
152
|
+
- Shared effective-property cloning strips historical revision descendants;
|
|
153
|
+
intentional revision-bearing splits refresh cloned property-change IDs.
|
|
154
|
+
- `engine/oxml-engine.js`
|
|
155
|
+
- Main reconciliation router, mode selection, existing-revision policy gate, and status/error result handling.
|
|
156
|
+
- `engine/route-selection.js`
|
|
157
|
+
- Internal reconciliation capability matrix and opt-in diagnostic route
|
|
158
|
+
instrumentation. It observes policy; it does not expose a route override.
|
|
159
|
+
- `engine/run-builders.js`
|
|
160
|
+
- Shared builders for insertion/deletion wrappers, visible run content, and run-property changes. It does not build paragraph-mark revisions.
|
|
161
|
+
- `engine/paragraph-mark-revisions.js`
|
|
162
|
+
- The single construction site for a tracked paragraph mark (`w:ins` / `w:del`
|
|
163
|
+
under `w:pPr/w:rPr`). DOM entry points (`markParagraphInserted`,
|
|
164
|
+
`markParagraphDeleted`, `setParagraphPropertiesMarkRevision`,
|
|
165
|
+
`clearParagraphMarkRevision`, `restoreParagraphMarkInserted`) and string
|
|
166
|
+
builders (`paragraphMarkRevisionXml`, `paragraphMarkPPrXml`) share one
|
|
167
|
+
metadata path, so ids are allocated through the document's allocator and
|
|
168
|
+
recorded in receipts. `planParagraphMarks` is a pure check that an
|
|
169
|
+
alignment gives every source paragraph exactly one untracked or deleted
|
|
170
|
+
mark and every new target paragraph an inserted mark, so Reject All yields
|
|
171
|
+
the source paragraph count and a deleted paragraph is never collapsed into
|
|
172
|
+
another. Reconstruction, list and table generation, and document-level
|
|
173
|
+
shortcuts all call it. Depends only on `core/`.
|
|
174
|
+
- `engine/surgical-*.js`
|
|
175
|
+
- Surgical run splitting, diff application, and span helpers for localized edits that preserve surrounding markup. Plain-text edit groups execute from right to left with a fresh live span index, while space/NBSP-only replacement hunks are refined to character-local changes so unchanged hyperlink containers survive.
|
|
176
|
+
The shared carrier splitter also handles explicit rejected-view deletion
|
|
177
|
+
splits, retaining `w:delText`, tabs, breaks, soft/non-breaking hyphens,
|
|
178
|
+
formatting, foreign metadata, and fresh trailing/property-change IDs.
|
|
179
|
+
- `engine/formatting-removal.js`
|
|
180
|
+
- Shared formatting removal and highlight helpers.
|
|
181
|
+
- `pipeline/list-markers.js`
|
|
182
|
+
- Dependency-light canonical list-marker grammar, classification, numbering
|
|
183
|
+
vocabulary, and parsed list-item representation.
|
|
184
|
+
- `pipeline/list-generation.js`
|
|
185
|
+
- Structural paragraph-to-list generation, paragraph-mark revisions, and
|
|
186
|
+
selective inheritance of source typography for inserted list runs.
|
|
187
|
+
- `pipeline/structured-content.js`
|
|
188
|
+
- Strict decomposition and planning for atomic mixed heading/paragraph/list/
|
|
189
|
+
table replacements. It rejects ambiguous table-like pipe text rather than
|
|
190
|
+
allowing a fallback to literal paragraphs.
|
|
191
|
+
- `pipeline/*`
|
|
192
|
+
- Ingestion, markdown preprocessing, diffing, patching, and serialization stages. Ingestion treats deleted and moved-from content as non-visible text and inserted/moved-to content as visible text.
|
|
193
|
+
- `services/comment-engine.js`
|
|
194
|
+
- Comment creation and package-level comment XML handling.
|
|
195
|
+
- `services/numbering-helpers.js`
|
|
196
|
+
- Document-scoped numbering ID state (`createDynamicNumberingIdState`,
|
|
197
|
+
`reserveNextNumberingIdPair`), numbering payload remapping, and shared DOM
|
|
198
|
+
predicates. It no longer owns a merge.
|
|
199
|
+
- `services/numbering-allocation.js`
|
|
200
|
+
- The single place that creates, continues, merges and validates numbering
|
|
201
|
+
definitions. `allocateNumberingDefinition` builds a new `w:abstractNum` +
|
|
202
|
+
`w:num` (source levels copied, chosen levels redefined, start overrides)
|
|
203
|
+
from ids reserved through the document state; `allocateNumberingForReplacement`
|
|
204
|
+
holds the reuse-source-list policy (`continue-source`, `allocate`,
|
|
205
|
+
`passthrough`) so no call site decides it; `continueBatchListSequence` lets
|
|
206
|
+
separate operations of one batch that generate items of the same kind,
|
|
207
|
+
format and level continue one numbering instance (reused only while its
|
|
208
|
+
`numId` is still bound in the live document). `mergeNumberingParts` /
|
|
209
|
+
`mergeNumberingXmlBySchemaOrder` are the one numbering merge, used by the
|
|
210
|
+
runner's consumers and the `openDocx` facade; an incoming `w:abstractNum` /
|
|
211
|
+
`w:num` id that exists with different content throws `NUMBERING_ID_CONFLICT`.
|
|
212
|
+
`checkNumberingBinding` validates a direct `{ numId, level }` binding and
|
|
213
|
+
`validateNumberingIntegrity` reports dangling `numId`, `abstractNumId` and
|
|
214
|
+
`ilvl` (`NUMBERING_*` issues) for the facade's baseline-delta validation.
|
|
215
|
+
Old definitions are never deleted or overwritten, and tracked-deleted source
|
|
216
|
+
paragraphs keep their `numPr`.
|
|
217
|
+
- `services/list-conversion-diagnostics.js`
|
|
218
|
+
- Safety diagnostics for text edits that retype list markers
|
|
219
|
+
(`LIST_MARKER_NOT_BOUND` / `UNSUPPORTED_LIST_CONVERSION`,
|
|
220
|
+
`LIST_FORMAT_NOT_APPLIED` / `UNSUPPORTED_LIST_FORMAT_CHANGE`; warning by
|
|
221
|
+
default, error under `strictListConversion`, silenced by
|
|
222
|
+
`listMarkers: 'literal'`), the closed `paragraph-format` key set (unknown
|
|
223
|
+
keys are `INVALID_OPERATION`) and the `numbering` shape check behind
|
|
224
|
+
`paragraph-format` numbering binding. Recovery points at `list-format`
|
|
225
|
+
through `recovery.suggestedOperation`.
|
|
226
|
+
- `services/standalone-docx-plumbing.js`
|
|
227
|
+
- Package-level extraction/wiring/validation for `word/document.xml`, `word/numbering.xml`, and `word/comments.xml`.
|
|
228
|
+
- `services/revision-comment-management.js`
|
|
229
|
+
- OOXML transforms for accepting/rejecting insertion, deletion, move, paragraph-mark, and property-change revisions by author/all-authors, plus deleting comments by author/all-authors.
|
|
230
|
+
- `services/standalone-operation-runner.js`
|
|
231
|
+
- Stable compatibility facade for full-document operations. It only re-exports
|
|
232
|
+
preflight, single-operation application, batch application, and scheduling.
|
|
233
|
+
- `services/document-operation-applier.js`
|
|
234
|
+
- Canonical single-operation validation, author resolution, dispatch, and
|
|
235
|
+
result metadata assembly. Before commit it validates the entire live
|
|
236
|
+
document against the operation savepoint and refuses newly generated
|
|
237
|
+
structural errors, including duplicate revision IDs. For localized
|
|
238
|
+
`replacements` it also checks, before commit, that the target's accepted text
|
|
239
|
+
(read from the successor paragraph via `SourceTargetRegistry.previewSuccessor`,
|
|
240
|
+
or the restored paragraph) equals the compiled desired text; otherwise the
|
|
241
|
+
savepoint is restored and `PATCH_ROUNDTRIP_MISMATCH` is returned, because a
|
|
242
|
+
tracked whole-paragraph deletion rejects back to the source and the Reject
|
|
243
|
+
All verifier alone cannot see it.
|
|
244
|
+
- `services/document-operation-mutations.js`
|
|
245
|
+
- Coupled OOXML mutation implementations for redline, highlight, and comment
|
|
246
|
+
operations. For text-bearing edits, the resolved paragraph's exact
|
|
247
|
+
accepted-view text is the source coordinate system even when target
|
|
248
|
+
selection used normalized whitespace;
|
|
249
|
+
bounded match-mode/code-point diagnostics are attached to resolved target
|
|
250
|
+
metadata. Explicit rejected-view insertion splits a supported direct
|
|
251
|
+
foreign `w:del` at an anchor-relative offset into sibling deletion,
|
|
252
|
+
insertion, and deletion carriers. Paragraph restoration emits its inserted
|
|
253
|
+
block after the untouched source range and verifies baseline-delta,
|
|
254
|
+
mutation-envelope, and lifecycle postconditions. These use leaf-module
|
|
255
|
+
imports and never import the root entry.
|
|
256
|
+
- `services/batch-operation-orchestrator.js`
|
|
257
|
+
- Comment-first stable scheduling, atomic policy, artifact aggregation,
|
|
258
|
+
per-operation results, one final document serialization, and deferred
|
|
259
|
+
runtime-context commit.
|
|
260
|
+
- `services/document-operation-session.js`
|
|
261
|
+
- One live document DOM and revision allocator per invocation, immutable
|
|
262
|
+
start-of-batch target snapshot, lazy paragraph metadata, per-operation DOM
|
|
263
|
+
and allocator savepoints, exact original XML rollback, index invalidation,
|
|
264
|
+
artifact/result accumulation, and isolated runtime-context helpers.
|
|
265
|
+
- `services/operation-heuristics.js`
|
|
266
|
+
- DOM-light decisions for list/plain adjacency insertion and explicit-range
|
|
267
|
+
list insertion. Canonical list/table targeting remains in `core/*`.
|
|
268
|
+
- `services/document-operation-contract.js`
|
|
269
|
+
- Compatibility normalization, canonical operation kinds, author precedence,
|
|
270
|
+
and stable runtime validation for document operations.
|
|
271
|
+
- `services/list-format.js` and `services/list-format-mutation.js`
|
|
272
|
+
- The `list-format` operation (`convert-plain`, plus the shared request shape
|
|
273
|
+
for `change-format` and `join`): contract shape, the narrow
|
|
274
|
+
typed-marker grammar (letter versus roman disambiguation by sequence or an
|
|
275
|
+
explicit `format`), and the in-place DOM core. Every refusal is decided
|
|
276
|
+
before mutation; the binding reuses the `paragraph-format.numbering` path
|
|
277
|
+
(`snapshotAndAttachPPrChange` + `applyParagraphPropertiesToPPr`), the marker
|
|
278
|
+
becomes one tracked `w:del`, definitions come from
|
|
279
|
+
`allocateNumberingDefinition`, and batch continuity uses
|
|
280
|
+
`runtimeContext.listFallbackSequenceState` (an entry is reused only while
|
|
281
|
+
its `numId` is bound in the live document). Text-equal redline header
|
|
282
|
+
conversions for letters, roman numerals and bullets call the same core.
|
|
283
|
+
The `paragraph-format` numbering binding (`properties.numbering`,
|
|
284
|
+
`{ numId, level }` or `null`) lives in `document-operation-mutations.js`: it
|
|
285
|
+
snapshots the earliest same-author `w:pPrChange`, refuses another author's
|
|
286
|
+
pending snapshot (`PARAGRAPH_FORMAT_REVISION_CONFLICT`), and checks the
|
|
287
|
+
binding with `checkNumberingBinding`. `list-format` is body-only; `openDocx`
|
|
288
|
+
refuses it with a `part` selector.
|
|
289
|
+
- `services/list-format-change.js`
|
|
290
|
+
- `list-format` `change-format` and `join` (`scope: 'list'` or `'range'`): rebind existing list items with
|
|
291
|
+
one tracked `w:pPrChange` each, never editing a shared definition.
|
|
292
|
+
`change-format` allocates a new definition through
|
|
293
|
+
`allocateNumberingDefinition` (source levels copied, one redefined, start
|
|
294
|
+
values on the new abstract); `join` reuses the earlier list's `numId`.
|
|
295
|
+
Planning models Word's counters (shared abstracts continue each other, a
|
|
296
|
+
level start re-applies on restart, deeper items start unused levels) and
|
|
297
|
+
refuses splits, joins and style-linked or instance-overridden definitions
|
|
298
|
+
it cannot reproduce exactly. Definitions are read from the source part plus
|
|
299
|
+
the batch's pending numbering parts.
|
|
300
|
+
- `services/operation-batch-compiler.js`
|
|
301
|
+
- Binds every operation to batch-start source paragraphs
|
|
302
|
+
(`SourceTargetRegistry`), compiles localized `replacements`, and detects
|
|
303
|
+
overlap before any mutation. Composition rule for `redline` + `list-format`:
|
|
304
|
+
exactly one single-paragraph `redline` (no `targetEnd`, anchor, part or
|
|
305
|
+
structured content) that keeps the typed marker and changes the text after
|
|
306
|
+
it composes with one `list-format` on the same paragraph; the compiler fixes
|
|
307
|
+
the order by setting `_compiledRunAfter` on the list-format, which the batch
|
|
308
|
+
orchestrator honours, so the redline always runs first. Everything else on a
|
|
309
|
+
list-format target is refused with `LIST_FORMAT_COMPOSITION_CONFLICT`
|
|
310
|
+
(`reason`: `marker-changed`, `redline-unchanged`, `redline-shape`, or
|
|
311
|
+
`list-rebind` when a `scope: 'list'` `change-format`/`join` rebinds an item
|
|
312
|
+
that another operation also writes). `list.continueFrom` is bound at batch
|
|
313
|
+
start as a read-only target. `previewSuccessor` lets the applier find a
|
|
314
|
+
target's replacement paragraph before commit.
|
|
315
|
+
- `services/operation-preflight.js`
|
|
316
|
+
- Read-only strict target and anchor resolution, revision-policy diagnostics
|
|
317
|
+
(reporting the policy `existingRevisions: 'auto'` resolves to per target via
|
|
318
|
+
`resolveExistingRevisionsPolicy` in `core/word-xml.js`),
|
|
319
|
+
artifact prediction, and same-paragraph conflict reporting.
|
|
320
|
+
- `services/document-inspection.js`
|
|
321
|
+
- Read-only paragraphs/comments inventory with target identity, headings,
|
|
322
|
+
revision authors, table context, and advisory visible numbering.
|
|
323
|
+
- `services/inspection-comparison.js`
|
|
324
|
+
- Pure comparison of two inspections by text, table position, list label,
|
|
325
|
+
merged review segments and comments, ignoring ids, dates and run boundaries.
|
|
326
|
+
Hosts that write a prepared package through their own API use it to verify
|
|
327
|
+
the committed result. Imports nothing.
|
|
328
|
+
- `document/docx-document.js`
|
|
329
|
+
- Universal transactional whole-DOCX editing, artifact wiring, validation, and rollback.
|
|
330
|
+
Standardized on `Uint8Array` binary payloads and pure-JS SHA-256 revision tokens.
|
|
331
|
+
Exposes `openDocx`, `DocxDocument`, and `computePackageRevisionToken` directly across all runtimes.
|
|
332
|
+
- `document/zip-archive.js`
|
|
333
|
+
- Universal, pure-JS ZIP archive reader and writer based on `fflate`. Operates on
|
|
334
|
+
`Uint8Array` byte arrays with zero Node built-ins (`node:zlib`, `Buffer`).
|
|
335
|
+
- `core/sha256.js`
|
|
336
|
+
- Pure-JS synchronous implementation of FIPS 180-4 SHA-256 with zero dependencies,
|
|
337
|
+
enabling deterministic revision tokens in sandboxed runtimes without `node:crypto`.
|
|
338
|
+
- `node/docx-document.js` and `node/zip-archive.js`
|
|
339
|
+
- Node-compatible re-exports maintaining `Buffer` compatibility for `@ansonlai/docx-redline-js/node`.
|
|
340
|
+
- `node/cli.js` and `bin/docx-redline.js`
|
|
341
|
+
- Cross-platform, JSON-only agent command boundary. Read commands never
|
|
342
|
+
mutate; write commands require attribution, use package transactions, and
|
|
343
|
+
only overwrite source files under explicit `--in-place` authorization.
|
|
344
|
+
Mutation commands expose a compact contract: package/XML payloads and full
|
|
345
|
+
validation arrays remain internal, while stdout contains durability fields,
|
|
346
|
+
per-operation evidence, validation counts, and a derived completion flag.
|
|
347
|
+
- `services/revision-fidelity-verifier.js`
|
|
348
|
+
- Post-apply invariant check built on `core/word-revision-semantics.js`.
|
|
349
|
+
`verifyRevisionFidelity` compares Reject All of the output with Reject All
|
|
350
|
+
of the savepoint (both reject every author's revisions) and reports
|
|
351
|
+
`REJECT_PARAGRAPH_COUNT`, `REJECT_PARAGRAPH_TEXT`, `REJECT_PARAGRAPH_PROPS`,
|
|
352
|
+
`REJECT_NUMBERING_REFS` and `REJECT_WRAPPERS`. Under accept-first
|
|
353
|
+
existing-revision policies the baseline is the accepted view, scoped to the
|
|
354
|
+
resolved target range when known (otherwise the changed region between the
|
|
355
|
+
unchanged prefix and suffix). `REJECT_NUMBERING_REFS`
|
|
356
|
+
(`verifyNumberingReferences`) also refuses a numId that the output binds or
|
|
357
|
+
Reject All restores when the source did not use it and no `w:num` ->
|
|
358
|
+
`w:abstractNum` -> `w:lvl` defines it in the numbering available to the
|
|
359
|
+
operation (numbering existence). `verifyUntrackedLocality` covers
|
|
360
|
+
`generateRedlines: false` single-target operations with `ACCEPT_NEIGHBORS`:
|
|
361
|
+
no paragraph outside the target may change in either view.
|
|
362
|
+
`verifyNoCreatedRevisions` adds `UNTRACKED_CREATED_REVISION`: an untracked
|
|
363
|
+
operation may not introduce a revision id that was not in the source
|
|
364
|
+
(fragments of a sliced source revision, same element, author and date, are
|
|
365
|
+
allowed). `verifyAcceptIntent` adds `ACCEPT_INTENT`: a tracked plain-text
|
|
366
|
+
`redline` with complete `modified` (single paragraph or `targetEnd` range)
|
|
367
|
+
must read as `modified` in the Accept All view of its target; accept-first
|
|
368
|
+
range targets use the exact accepted scope.
|
|
369
|
+
`buildRevisionFidelityError` produces the `REVISION_FIDELITY_VIOLATION`
|
|
370
|
+
payload (`violations[].explanation`, `source.sourceParagraphIndex`,
|
|
371
|
+
`recovery.retryGuidance`). Modes are `enforce` (default, refuse and roll
|
|
372
|
+
back), `warn` (apply and append a warning) and `off`, resolved by
|
|
373
|
+
`resolveRevisionFidelityMode`: explicit `verifyRevisionFidelity` option,
|
|
374
|
+
then the diagnostic `DOCX_REDLINE_REVISION_FIDELITY` environment variable,
|
|
375
|
+
then `enforce`. The view memo is `createFidelityViews(document)`: lazily
|
|
376
|
+
filled `accepted` / `rejected` projections, revision-id signatures and
|
|
377
|
+
numbering references for one DOM. The applier builds a memo for the savepoint
|
|
378
|
+
(taking over `session._fidelityViews` when it still describes the live
|
|
379
|
+
document) and one for the result; after a committed tracked or untracked
|
|
380
|
+
operation the result's memo is stored back in `session._fidelityViews` as
|
|
381
|
+
the next operation's baseline, and a refused operation restores the prior
|
|
382
|
+
memo, so each view is computed at most once per DOM.
|
|
383
|
+
- `orchestration/*`
|
|
384
|
+
- Route planning and list fallback orchestration utilities.
|
|
385
|
+
|
|
386
|
+
## End-to-End Flow
|
|
387
|
+
|
|
388
|
+
1. Caller imports from `index.js`.
|
|
389
|
+
2. Caller configures XML provider/logger/defaults when needed via `adapters/*`.
|
|
390
|
+
3. Caller invokes reconciliation APIs (`applyRedlineToOxml`, operation runner, ingestion/export helpers).
|
|
391
|
+
4. A full-document batch parses once, captures its initial target snapshot,
|
|
392
|
+
compiles the operations against it (`operation-batch-compiler.js`: batch-start
|
|
393
|
+
source binding, localized replacements, overlap and list-format composition
|
|
394
|
+
checks, all before any mutation), and creates an operation savepoint before
|
|
395
|
+
each mutation.
|
|
396
|
+
5. `engine/oxml-engine.js` routes each selected scope to format, table, list,
|
|
397
|
+
surgical, or reconstruction flows; successful scoped output is imported
|
|
398
|
+
into the live document.
|
|
399
|
+
6. After each mutation the applier (`services/document-operation-applier.js`)
|
|
400
|
+
validates the generated OOXML against the savepoint, checks the localized
|
|
401
|
+
replacement intent, then runs the revision fidelity verifier (Reject All,
|
|
402
|
+
Accept All intent, neighbor, created-revision and numbering-existence
|
|
403
|
+
invariants); a violation in `enforce` mode restores the savepoint and
|
|
404
|
+
refuses the operation before anything is committed. Failed and no-op
|
|
405
|
+
operations restore their savepoint. A successful changed
|
|
406
|
+
batch serializes the full document once; atomic failures and all-no-op
|
|
407
|
+
batches return the exact input string without serialization.
|
|
408
|
+
7. Pipeline/services return OOXML, optional package artifacts (`numberingXml`, comments payloads; numbering parts are merged by `mergeNumberingParts` and
|
|
409
|
+
checked by `validateNumberingIntegrity`), and non-breaking `status`/`error` fields where applicable.
|
|
410
|
+
8. Optional revision/comment management transforms can accept/reject revisions, including move revisions, or delete comments by author.
|
|
411
|
+
9. Caller writes resulting XML back to package/document boundaries.
|
|
412
|
+
|
|
413
|
+
## Public Surfaces
|
|
414
|
+
|
|
415
|
+
- Primary: `index.js`
|
|
416
|
+
- Types: `index.d.ts`
|
|
417
|
+
- Stable XML operation runner: `@ansonlai/docx-redline-js/standalone-runner`
|
|
418
|
+
- Node-only DOCX facade: `@ansonlai/docx-redline-js/node`
|
|
419
|
+
|
|
420
|
+
Keep public exports centralized through `index.js`; deep imports are supported by
|
|
421
|
+
the package `exports` map for advanced consumers, but new public APIs should
|
|
422
|
+
still be re-exported from `index.js`.
|
|
423
|
+
|
|
424
|
+
## Reliability Guardrails
|
|
425
|
+
|
|
426
|
+
- Create Word namespace elements with `createWordElement(xmlDoc, 'w:...')`.
|
|
427
|
+
Avoid direct `document.createElement('w:*')` or `createElementNS(NS_W, 'w:*')`
|
|
428
|
+
outside `core/word-xml.js`.
|
|
429
|
+
- Generate tracked-change metadata through `createRevisionMetadata(author)` so
|
|
430
|
+
`w:id`, `w:author`, and `w:date` stay consistent and document-unique.
|
|
431
|
+
- Seed revision IDs from parsed input with `seedRevisionIdsFromDocument(xmlDoc)`
|
|
432
|
+
before emitting new tracked changes.
|
|
433
|
+
- Treat revision metadata inside cloned content as identity-bearing state, not
|
|
434
|
+
ordinary formatting. When a run containing `w:rPrChange` is split or cloned,
|
|
435
|
+
preserve an existing revision ID on at most one output run and allocate a
|
|
436
|
+
fresh document-scoped ID for every additional copy.
|
|
437
|
+
- Use `containsTrackedChanges(xmlDoc)` and `getTrackedChangeAuthors(xmlDoc)`
|
|
438
|
+
to inspect existing revisions. By default (`existingRevisions: 'merge-same-author'`),
|
|
439
|
+
subsequent edits from the same author are merged against the pre-revision baseline
|
|
440
|
+
while revisions from different authors fail closed with `EXISTING_REVISIONS`.
|
|
441
|
+
Revised paragraphs containing comment anchors fail closed with
|
|
442
|
+
`COMMENTED_CONTENT_MERGE`; never discard anchors as a side effect of restoring
|
|
443
|
+
the pre-revision baseline.
|
|
444
|
+
Pass `existingRevisions: 'accept-all-first'` to normalize prior revisions explicitly.
|
|
445
|
+
- Every operation is checked after it is applied
|
|
446
|
+
(`services/revision-fidelity-verifier.js`, default `enforce`): Word's Reject
|
|
447
|
+
All of a tracked output must equal Word's Reject All of the source (`REJECT_*`),
|
|
448
|
+
a plain-text `redline` must read as its `modified` text under Accept All
|
|
449
|
+
(`ACCEPT_INTENT`), and an untracked edit must neither change other paragraphs
|
|
450
|
+
(`ACCEPT_NEIGHBORS`) nor create a revision (`UNTRACKED_CREATED_REVISION`). A
|
|
451
|
+
violation restores the operation savepoint and returns
|
|
452
|
+
`REVISION_FIDELITY_VIOLATION`; `warn` applies and warns, `off` is for
|
|
453
|
+
diagnosis only.
|
|
454
|
+
- Allocate, continue, merge and validate numbering only through
|
|
455
|
+
`services/numbering-allocation.js`. Never edit a shared `w:abstractNum` or
|
|
456
|
+
`w:num` in place, never delete a definition Reject All may need, and keep
|
|
457
|
+
`numPr` on tracked-deleted source paragraphs.
|
|
458
|
+
- Construct paragraph-mark `w:ins` / `w:del` only through
|
|
459
|
+
`engine/paragraph-mark-revisions.js`, and read current paragraph/run
|
|
460
|
+
properties only through `core/current-properties.js`. Two source-scanning
|
|
461
|
+
tests enforce this: `tests/paragraph_mark_single_site_tests.mjs` rejects
|
|
462
|
+
paragraph-mark revision construction (templates opening an `rPr` with
|
|
463
|
+
`ins`/`del`, DOM creation attached to an `rPr`, retired helper names) outside
|
|
464
|
+
the helper, and `tests/current_properties_usage_tests.mjs` rejects descendant
|
|
465
|
+
lookups of property names (`numPr`, `pStyle`, `b`, ...) outside the accessor.
|
|
466
|
+
Exceptions need an entry with a written reason in each test's allowlist.
|
|
467
|
+
- Do not write unknown `result.oxml` payloads directly into `word/document.xml`;
|
|
468
|
+
normalize with `extractReplacementNodesFromOoxml(...)` or use
|
|
469
|
+
`applyOperationToDocumentXml(...).documentXml` for full-document replacement.
|
|
470
|
+
- Run `validateRedlineOoxml(oxml)` on generated output before packaging it;
|
|
471
|
+
it reports structural invariant violations as `{ valid, issues }`.
|
|
472
|
+
|
|
473
|
+
## Integration Contracts
|
|
474
|
+
|
|
475
|
+
### Targeting and text fidelity
|
|
476
|
+
|
|
477
|
+
- Paragraph targeting and replacement have different contracts. Targeting may
|
|
478
|
+
use normalized text and fallback heuristics; replacement content is literal
|
|
479
|
+
and must preserve tabs, line breaks, non-breaking spaces, repeated spaces,
|
|
480
|
+
and boundary whitespace.
|
|
481
|
+
- Text extraction used for targeting represents the visible accepted view of
|
|
482
|
+
tracked content: insertions are visible and deletions are excluded.
|
|
483
|
+
- Paragraph indexes are transient integration references, not user-facing
|
|
484
|
+
document identifiers. Prefer paragraph IDs plus exact text where available.
|
|
485
|
+
- Ambiguous matches are unsafe for document mutation. New targeting surfaces
|
|
486
|
+
should report candidate matches or `AMBIGUOUS_TARGET` rather than silently
|
|
487
|
+
choosing the first paragraph.
|
|
488
|
+
- Do not remove operation-session savepoints merely to improve throughput.
|
|
489
|
+
Any replacement must prove that a thrown error or false no-op cannot leak a
|
|
490
|
+
partial DOM mutation or revision-ID allocation into later operations.
|
|
491
|
+
|
|
492
|
+
### Structural paragraph-to-list replacement
|
|
493
|
+
|
|
494
|
+
- In WordprocessingML, `w:numId w:val="0"` suppresses numbering. It represents
|
|
495
|
+
no reusable list context and must never be returned by numbering allocation
|
|
496
|
+
or assigned to a generated list item. Generated lists use a positive `numId`
|
|
497
|
+
bound to a compatible `w:abstractNum` definition.
|
|
498
|
+
- Expanding one non-list paragraph into multiple list paragraphs is a block
|
|
499
|
+
replacement, not an inline replacement. The original content remains in its
|
|
500
|
+
own paragraph with a deleted paragraph mark; every replacement item occupies
|
|
501
|
+
its own paragraph with an inserted paragraph mark and inserted text. This is
|
|
502
|
+
required so Word can accept the edit as the new paragraphs or reject it as
|
|
503
|
+
the exact original paragraph without joining the old heading to item one.
|
|
504
|
+
- Inserted list runs selectively inherit source typography (`w:rFonts`, size,
|
|
505
|
+
language, and related script properties). Semantic emphasis such as bold,
|
|
506
|
+
underline, italic, or strike is emitted only when requested by the replacement
|
|
507
|
+
markup; heading emphasis must not leak into ordinary list body text.
|
|
508
|
+
- Low-level list output can carry a package-fragment sentinel needed by its
|
|
509
|
+
standalone payload shape. Full-document mutation removes that sentinel while
|
|
510
|
+
importing replacement nodes, so it cannot become an empty document paragraph.
|
|
511
|
+
- Tests for this route must verify positive numbering IDs, separate physical
|
|
512
|
+
paragraphs, formatting inheritance/non-inheritance, valid revision markup,
|
|
513
|
+
and exact accepted and rejected paragraph sequences.
|
|
514
|
+
|
|
515
|
+
### Atomic mixed-content replacement
|
|
516
|
+
|
|
517
|
+
- `planStructuredReplacement(...)` converts agent-authored Markdown into one
|
|
518
|
+
replacement operation carrying `structuredContent: true`. The block plan is
|
|
519
|
+
diagnostic metadata; execution remains one mutation so replacing the anchor
|
|
520
|
+
cannot invalidate later blocks.
|
|
521
|
+
- Mixed-content parsing recognizes explicit Markdown headings, blank-line
|
|
522
|
+
paragraph boundaries, adjacent list items, and contiguous table rows. Tables
|
|
523
|
+
require a header separator and consistent column counts. Invalid input fails
|
|
524
|
+
with `STRUCTURED_CONTENT_INVALID`; silently degrading table pipes into visible
|
|
525
|
+
paragraph text is forbidden on this route.
|
|
526
|
+
- Newly inserted tables inside a mixed replacement are tracked once at block
|
|
527
|
+
scope. Cell runs are not nested in additional `w:ins` wrappers, preserving the
|
|
528
|
+
no-nested-revisions invariant while allowing Reject All to remove the table.
|
|
529
|
+
- Agents must use explicit heading markers and valid table grammar rather than
|
|
530
|
+
relying on capitalization or layout inference. The engine preserves the
|
|
531
|
+
declared block types and does not guess legal-document semantics.
|
|
532
|
+
|
|
533
|
+
### Package artifacts and transactions
|
|
534
|
+
|
|
535
|
+
- A real `.docx` stores document markup, comments, numbering, relationships,
|
|
536
|
+
and content types in separate parts. APIs operating on one XML string cannot
|
|
537
|
+
claim to update artifacts held in another part.
|
|
538
|
+
- Comment IDs must be allocated against both document anchors and the existing
|
|
539
|
+
`word/comments.xml` part. Revision IDs must be allocated against the complete
|
|
540
|
+
document revision scope.
|
|
541
|
+
- Numbering payloads are merged, never replaced, by the single merge in
|
|
542
|
+
`services/numbering-allocation.js` (`mergeNumberingParts`), which both the
|
|
543
|
+
runner's consumers and the `openDocx` facade call; conflicting ids fail with
|
|
544
|
+
`NUMBERING_ID_CONFLICT`.
|
|
545
|
+
- Safe package mutation is transactional: retain the original package, apply
|
|
546
|
+
operations, merge all artifacts, validate redline markup and package wiring,
|
|
547
|
+
and commit only if every required check succeeds.
|
|
548
|
+
- `openDocx(buffer)` implements this transaction for Node without adding a ZIP
|
|
549
|
+
dependency to browser or XML-only consumers. Unmodified part contents remain
|
|
550
|
+
byte-identical after extraction, although the ZIP container is reserialized.
|
|
551
|
+
|
|
552
|
+
### Operation results
|
|
553
|
+
|
|
554
|
+
- `hasChanges: false` does not imply success. Callers must check `status`,
|
|
555
|
+
`error`, and warnings to distinguish errors, missing anchors, and true no-ops.
|
|
556
|
+
- Batch results must preserve the caller's original operation indexes even when
|
|
557
|
+
execution is reordered for stable anchors.
|
|
558
|
+
- Author attribution is externally visible document data. Agent-facing APIs
|
|
559
|
+
should require an explicit author and report the author used for every
|
|
560
|
+
operation rather than relying silently on a configured fallback.
|
|
561
|
+
- Operation-level authors override the batch author. Runtime results expose
|
|
562
|
+
`authorUsed`, `authorsUsed`, `operationType`, `resolvedBy`, and resolved target
|
|
563
|
+
metadata so integrations can audit what the engine actually selected.
|
|
564
|
+
- Target fingerprints are revision-view scoped. Inspection returns a
|
|
565
|
+
`revisionView` beside every paragraph and computes its fingerprint from the
|
|
566
|
+
same text/view pair. Restore normalization defaults omitted target and range
|
|
567
|
+
endpoint views to `rejected`; all other operations default to `accepted`.
|
|
568
|
+
Explicit views remain authoritative, and cross-view text/fingerprint mistakes
|
|
569
|
+
return an actionable mismatch hint rather than silently weakening targeting.
|
|
570
|
+
- A normalized target match does not become an edit coordinate system for a
|
|
571
|
+
text-bearing mutation. Mutation uses canonical accepted-view source text, and `resolvedTarget.targetTextMatch`
|
|
572
|
+
records `exact`, `space_equivalent`, or `normalized` selection plus bounded
|
|
573
|
+
invisible-character diagnostics.
|
|
574
|
+
- The CLI contract (version 9; `docx-redline version` lists the capabilities) is deliberately narrower than library result objects.
|
|
575
|
+
`apply`, `accept`, `reject`, and `delete-comments` omit `documentXml`, package
|
|
576
|
+
parts, inspection text, and full issue arrays. `completion` is true only when
|
|
577
|
+
`written === true`, top-level status is neither error nor partial, and every
|
|
578
|
+
operation result is non-error. The `validate` command remains the full issue
|
|
579
|
+
reporting surface.
|
|
580
|
+
- `preflightOperations` is the read-only safety boundary for agent-generated
|
|
581
|
+
batches. It uses strict targeting by default; mutation APIs retain permissive
|
|
582
|
+
legacy targeting unless `strictTargets: true` is requested. In v1.0.0,
|
|
583
|
+
application will default to strict targeting; in the current warning cycle,
|
|
584
|
+
permissive resolution that chooses among multiple identical paragraphs emits
|
|
585
|
+
`AMBIGUOUS_TARGET_HEURISTIC_USED` with candidate count and migration guidance.
|
|
586
|
+
Both preflight and mutation runners share the same candidate resolver
|
|
587
|
+
(`resolveTargetParagraph`).
|
|
588
|
+
|
|
589
|
+
### Mutation receipts and output reconciliation oracle
|
|
590
|
+
|
|
591
|
+
- `ReceiptCollector` tracks exact allocations (revision IDs with kind and target part,
|
|
592
|
+
comment IDs, numbering IDs, relationship IDs, affected targets, and warnings)
|
|
593
|
+
directly at the point of allocation/attachment during operation execution.
|
|
594
|
+
- Collector state is snapshotted within operation savepoints. Failed or rolled-back
|
|
595
|
+
operations cleanly restore prior collector state without leaking orphaned allocations.
|
|
596
|
+
- Single operations expose `result.receipt`; batches expose per-item `results[i].receipt`
|
|
597
|
+
and top-level `result.receipts`, with dispositions (`applied`, `refused`, `no_change`,
|
|
598
|
+
`rolled_back`, `not_attempted`) and `committed: true/false`.
|
|
599
|
+
- The output reconciliation oracle (`reconcileReceiptsAgainstOutput`) performs an
|
|
600
|
+
independent, non-negotiable verification: every durable ID reported as committed
|
|
601
|
+
is parsed from the serialized output XML parts (`word/document.xml`, `word/comments.xml`,
|
|
602
|
+
`word/numbering.xml`). Any discrepancy immediately fails the transaction and triggers
|
|
603
|
+
atomic rollback.
|
|
604
|
+
- The oracle checks that durable IDs are committed; the revision fidelity
|
|
605
|
+
verifier checks what Reject All would restore. A refused operation restores
|
|
606
|
+
its savepoint and receipt state like any other refusal, so it contributes no
|
|
607
|
+
committed IDs (`finalDisposition: 'refused'`).
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
## Build Output
|
|
611
|
+
|
|
612
|
+
`npm run build` generates CDN-ready ESM bundles under `dist/`:
|
|
613
|
+
|
|
614
|
+
- `dist/docx-redline-js.esm.js`
|
|
615
|
+
- `dist/docx-redline-js.esm.js.map`
|
|
616
|
+
- `dist/docx-redline-js.esm.min.js`
|
|
617
|
+
- `dist/docx-redline-js.esm.min.js.map`
|
|
618
|
+
|
|
619
|
+
The bundle inlines `diff-match-patch` and keeps `@xmldom/xmldom` external.
|
|
620
|
+
|
|
621
|
+
## Testing
|
|
622
|
+
|
|
623
|
+
- `npm test`
|
|
624
|
+
- Runs all `tests/*.mjs` except setup helpers in separate Node processes with
|
|
625
|
+
a conservative four-worker cap. Use `DOCX_TEST_CONCURRENCY=1` to reproduce
|
|
626
|
+
the same sorted suite serially.
|
|
627
|
+
- `npm run benchmark:tests`
|
|
628
|
+
- Compares paired serial and bounded-parallel suite runs and records JSON
|
|
629
|
+
under ignored `tmp/benchmarks/`.
|
|
630
|
+
- `npm run test:isolation`
|
|
631
|
+
- Runs boundary checks for Word API markers and dependency-graph isolation.
|
|
632
|
+
- `npm run check:types`
|
|
633
|
+
- Smoke-checks `index.d.ts`.
|
|
634
|
+
- `node scripts/export-validation-fixtures.mjs`
|
|
635
|
+
- Writes release-time validation fixtures to `tmp/validation-docx/` as
|
|
636
|
+
`word/document.xml` parts, assembled `.docx` files, and expected-text sidecars.
|
|
637
|
+
- `tests/roundtrip_fuzz_tests.mjs` (part of `npm test`)
|
|
638
|
+
- Seeded fuzz sweep of the accept/reject round-trip invariant; tune with
|
|
639
|
+
`FUZZ_SEED` / `FUZZ_ITERATIONS`.
|
|
640
|
+
- `npm run smoke:word -- path/to/file.docx`
|
|
641
|
+
- Optional Windows/Word COM smoke test for a completed `.docx`.
|
|
642
|
+
- `npm run smoke:word:diff`
|
|
643
|
+
- Windows/Word COM differential test: Word itself accepts/rejects the
|
|
644
|
+
generated fixtures and the resulting text is compared to expectations.
|
|
645
|
+
Structural checks also compare paragraph counts, and list cases compare
|
|
646
|
+
`ListFormat.ListString` labels after `Revisions.AcceptAll()` /
|
|
647
|
+
`RejectAll()`.
|
|
648
|
+
- `bash scripts/validate-fixtures-xsd.sh`
|
|
649
|
+
- Validates exported fixtures against the ECMA-376 transitional `wml.xsd`.
|
|
650
|
+
- `tests/reject_roundtrip_matrix_tests.mjs` (part of `npm test`)
|
|
651
|
+
- Generated operation matrix over fixtures and operation kinds, asserting the
|
|
652
|
+
revision fidelity invariants and receipt reconciliation (a
|
|
653
|
+
`RECEIPT_RECONCILIATION_FAILED` fails the case). Replay with `FUZZ_SEED` /
|
|
654
|
+
`MATRIX_ITERATIONS`.
|
|
655
|
+
- `.github/workflows/validation.yml`
|
|
656
|
+
- Nightly independent-oracle validation: XSD schema check, LibreOffice
|
|
657
|
+
conversion, an extended 20k-case fuzz sweep with a fresh seed, and the
|
|
658
|
+
`matrix-extended` job (1000 reject-roundtrip matrix cases with a
|
|
659
|
+
date-derived seed; a failure prints its seed and replay command).
|
|
660
|
+
|
|
661
|
+
Use these checks before publishing or tagging. See [docs/TESTING.md](./docs/TESTING.md#release-validation-and-independent-oracles).
|
|
662
|
+
|
|
663
|
+
## Fast Orientation For Contributors
|
|
664
|
+
|
|
665
|
+
Use this sequence to understand or modify behavior without reading everything:
|
|
666
|
+
|
|
667
|
+
1. Start at `index.js` to locate the exported API.
|
|
668
|
+
2. Follow exports into `engine/oxml-engine.js` or relevant `services/*` module.
|
|
669
|
+
3. For targeting bugs, inspect `core/paragraph-targeting.js`, `core/list-targeting.js`, and `core/table-targeting.js`.
|
|
670
|
+
4. For package wiring issues, inspect `services/standalone-docx-plumbing.js`.
|
|
671
|
+
5. For revision/comment cleanup behavior, inspect `services/revision-comment-management.js`.
|
|
672
|
+
6. For numbering/list issues, inspect `services/numbering-allocation.js`,
|
|
673
|
+
`services/list-format*.js`, `services/list-conversion-diagnostics.js` and
|
|
674
|
+
orchestration list-fallback modules; for batch composition,
|
|
675
|
+
`services/operation-batch-compiler.js`; for Reject All refusals,
|
|
676
|
+
`services/revision-fidelity-verifier.js`.
|
|
677
|
+
7. For reliability regressions, start with `tests/roundtrip_invariant_tests.mjs`,
|
|
678
|
+
`tests/engine_reliability_tests.mjs`, `tests/paragraph_mark_revision_tests.mjs`,
|
|
679
|
+
`tests/move_revision_tests.mjs`, and `tests/hardening_status_tests.mjs`.
|