@ansonlai/docx-redline-js 0.8.2 → 0.8.4

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 (33) hide show
  1. package/CHANGELOG.md +75 -1
  2. package/README.md +28 -13
  3. package/core/list-targeting.js +12 -10
  4. package/dist/docx-redline-js.esm.js +837 -549
  5. package/dist/docx-redline-js.esm.js.map +4 -4
  6. package/dist/docx-redline-js.esm.min.js +93 -90
  7. package/dist/docx-redline-js.esm.min.js.map +4 -4
  8. package/dist/docx-redline.bundle.cjs +837 -549
  9. package/dist/docx-redline.bundle.cjs.map +4 -4
  10. package/dist/docx-redline.bundle.js +837 -549
  11. package/dist/docx-redline.bundle.js.map +4 -4
  12. package/docs/AGENT_KNOWLEDGE_BASE.md +4 -1
  13. package/docs/schemas/document-operations.schema.json +168 -168
  14. package/engine/oxml-engine.js +215 -195
  15. package/engine/reconstruction-mode.js +183 -183
  16. package/index.d.ts +4 -0
  17. package/index.js +36 -1
  18. package/node/cli.js +2 -1
  19. package/orchestration/list-structural-fallback.js +530 -518
  20. package/package.json +1 -1
  21. package/pipeline/list-generation.js +203 -168
  22. package/pipeline/serialization.js +111 -94
  23. package/services/batch-operation-orchestrator.js +6 -0
  24. package/services/document-inspection.js +10 -7
  25. package/services/document-operation-applier.js +2 -0
  26. package/services/document-operation-contract.js +553 -537
  27. package/services/document-operation-mutations.js +1140 -1011
  28. package/services/numbering-helpers.js +108 -0
  29. package/services/operation-batch-compiler.js +440 -436
  30. package/services/operation-heuristics.js +197 -173
  31. package/services/revision-comment-management.js +601 -594
  32. package/services/standalone-operation-runner.d.ts +429 -427
  33. package/services/table-reconciliation.js +48 -46
package/CHANGELOG.md CHANGED
@@ -1,6 +1,80 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.8.4
4
+
5
+ ### Added
6
+
7
+ - **`textOccurrence` on `format` / `character-format`:** a 1-based match of `textToFormat` inside the resolved paragraph,
8
+ independent of `target.occurrence` (which selects the paragraph). Out of range fails with `PATCH_SOURCE_NOT_FOUND`;
9
+ calls without it still read `target.occurrence`. CLI capability `format-text-occurrence-v1`; contract stays version 8.
10
+ - **`applyRedlineToOxml` numbering options:** `numberingXml` and `numberingIdState` remap generated list definitions to
11
+ IDs that do not collide with the source numbering.
12
+
13
+ ### Fixed
14
+
15
+ - **Soft-break text can be converted to a list:** a paragraph whose `A. …`, `B. …` items are separated by `<w:br/>` was
16
+ refused with `TARGET_NOT_FOUND`, and an explicit list request identical to typed markers was a no-op. In-run `\n`/`\t`
17
+ now serialize back to `<w:br/>`/`<w:tab/>`, including inside `w:delText`, so Reject All restores the breaks.
18
+ - **Localized `replacements` work in paragraphs with soft line breaks:** they were refused with `INVALID_OPERATION`.
19
+ A `find` within one line now applies and keeps the breaks; a `find` or `replace` that spans or adds a break is refused.
20
+ - **Generated bullets no longer reuse existing numbering IDs:** bullets generated into a document that already defined
21
+ `numId 1` continued an existing decimal list in Word. The bare runner and `applyRedlineToOxml` now allocate new IDs
22
+ from the supplied `numberingXml`, as `openDocx` already did.
23
+ - **Appending a table after a paragraph keeps its underline:** the paragraph was tracked as deleted and re-inserted as
24
+ plain text, so a prior same-author underline, or a `++underline++` sent with the table, was lost on Accept All. The
25
+ source paragraph is now kept in place.
26
+ - **Word's Reject All removes generated tables:** tracked tables were wrapped in one `w:ins` around `w:tbl`, which Word
27
+ does not recognize. Reject All left an empty table, and a table at the end of a document could hang Word. Tables now
28
+ use row-level `w:trPr/w:ins` with inserted cell content and marks, and `rejectTrackedChangesInOoxml` removes a table
29
+ once all of its rows are rejected. Confirmed in Word.
30
+
31
+ ### Tests
32
+
33
+ - New `tests/soft_break_list_conversion_tests.mjs`, `tests/soft_break_localized_replacements_tests.mjs`,
34
+ `tests/generated_bullet_numbering_collision_tests.mjs`, `tests/format_text_occurrence_tests.mjs` and
35
+ `tests/table_append_fidelity_tests.mjs`.
36
+
37
+ ### Known limitations
38
+
39
+ - Localized restore in the rejected view still refuses paragraphs with line breaks.
40
+ - The table-append path covers one paragraph followed by one table.
41
+ - Under `merge-same-author`, editing the text of a paragraph that carries your own pending formatting change still drops
42
+ that formatting on Accept All.
43
+
44
+ ## 0.8.3
45
+
46
+ ### Fixed
47
+
48
+ - **Reject All no longer leaves an empty paragraph after an inserted paragraph:** `modified: "<original>\n<new>"` on a plain
49
+ paragraph did not track the new paragraph's mark as inserted. It now does.
50
+ - **Reject All no longer merges source paragraphs after a list range replacement:** replacing a multi-paragraph list range
51
+ (`target` + `targetEnd`) collapsed the sources into one deleted paragraph, so Reject All gave results such as
52
+ `Bullet Root ABullet Insertion Anchor`, `AlphaGamma` for an empty middle paragraph, and lost both paragraphs of an
53
+ all-empty range. Each source paragraph, empty ones included, now keeps its own tracked-deleted paragraph. Confirmed in Word.
54
+ - **Inspection no longer reports historical paragraph properties as current:** `list`, `styleId` and `headingLevel` (and list
55
+ targeting) could read `numPr`, `pStyle` or `outlineLvl` stored under `w:pPrChange`. Only the paragraph's direct `w:pPr`
56
+ is read now.
57
+ - **`openDocx` list range replacement no longer renumbers or re-points lists:** deleted source paragraphs were remapped to a
58
+ new generic `numId` (Reject All restored them with the wrong list type), new items started a separate list (untouched
59
+ following items restarted at 1), and unused numbering definitions were added. Deleted source paragraphs now keep their
60
+ `numPr`, and a same-kind replacement reuses the source `numId`, leaving `numbering.xml` unchanged. Reuse applies when the
61
+ range uses one `numId` and the generated level formats match; format-changing edits still allocate new definitions, and
62
+ definitions created earlier in the same batch are not reused. Confirmed in Word.
63
+ - **Explicit list start values are applied:** generated list fragments without `xmlns:w` failed to parse in
64
+ `orchestration/list-structural-fallback.js`, so `7. Header` lost its `startOverride` and xmldom logged parse warnings.
65
+
66
+ ### Tests
67
+
68
+ - New `tests/list_reject_fidelity_tests.mjs` and `tests/facade_list_numbering_tests.mjs`, with the Word-authored fixture
69
+ `tests/fixtures/agentic-lists/nested-lists-source.docx`; extended inspection and list structural fallback suites.
70
+
71
+ ### Known limitations
72
+
73
+ - Plain paragraph to list conversion and list format changes are not yet supported canonically; see
74
+ `docs/plans/2026-09-30-canonical-list-operations.md`.
75
+ - `1. Header` to `1. Header` on a bare `document.xml` (not a packaged `.docx`) can fail with `RECEIPT_RECONCILIATION_FAILED`.
76
+
77
+ ## 0.8.2
4
78
 
5
79
  ### Fixed
6
80
 
package/README.md CHANGED
@@ -20,19 +20,34 @@ Converts AI-generated or programmatic text/markdown edits into valid Office Open
20
20
  - Package plumbing helpers for numbering.xml, comments.xml, content types, and relationships
21
21
  - Zero host dependencies: works in Node.js, browsers, Deno, and similar JS runtimes with DOM parsing support
22
22
 
23
- ## What's New in 0.7.0
24
-
25
- Version 0.7.0 adds contract-8 localized edits. Agents can send a small
26
- `find`/`replace` request against an inspected target instead of reproducing a
27
- complete paragraph. An optional speculative form may omit the strong target
28
- when the source literal resolves uniquely; advanced scoping is described below.
29
-
30
- Speculative edits fail without writing when the anchor, paragraph, or source
31
- span is missing or ambiguous. Successful localized results include a committed
32
- `change` object with the selected legal location, bounded before/after excerpts,
33
- and actual accepted-view verification. Existing full-paragraph operations,
34
- tracked-change semantics, validation, rollback, and review policies remain
35
- available unchanged. See the [complete 0.7.0 release notes](./docs/releases/0.7.0.md).
23
+ ## What's New in 0.8.x
24
+
25
+ The current release is 0.8.4. The CLI contract remains version 8 throughout the 0.8 line.
26
+
27
+ - **0.8.0, universal document facade:** `openDocx` and `DocxDocument` are exported from the primary entry point and work
28
+ on `Uint8Array` input in any JavaScript runtime. ZIP handling uses `fflate` and SHA-256 is pure JavaScript, so there are no
29
+ Node built-in dependencies. The XML provider falls back to `@xmldom/xmldom` automatically, and the
30
+ `@ansonlai/docx-redline-js/bundle` export provides zero-dependency single-file bundles for sandboxes such as n8n Code
31
+ nodes. The `/node` entry point keeps its existing API. See the [0.8.0 release notes](./docs/releases/0.8.0.md).
32
+ - **0.8.1, headers, footers and comment threads:** an operation's `part` field edits headers and footers;
33
+ `inspect().headersFooters` lists them. Comment threads can be resolved or reopened with `doc.resolveComment` or the
34
+ `comment_resolve` operation, and comments can be deleted by id with `doc.deleteComments({ ids })` or
35
+ `docx-redline delete-comments --comment-id`. See the [0.8.1 release notes](./docs/releases/0.8.1.md).
36
+ - **0.8.2, fixes:** a replacement beside a hyperlink no longer pulls adjacent plain text into the link, and Reject All no
37
+ longer reorders text around a manual line break. See the [0.8.2 release notes](./docs/releases/0.8.2.md).
38
+ - **0.8.3, list fixes:** Reject All now restores list edits exactly (no extra empty paragraph after an inserted
39
+ paragraph, no merged source paragraphs after a list-range replacement, original list numbering kept through `openDocx`),
40
+ and inspection no longer reports tracked historical list, style or heading properties as current. See the
41
+ [0.8.3 release notes](./docs/releases/0.8.3.md).
42
+ - **0.8.4, fixes:** localized replacements and list conversion work in paragraphs with soft line breaks, generated
43
+ bullets get their own numbering IDs, `format` accepts `textOccurrence` to format a later match, and tracked tables use
44
+ Word's row-level markup, so Word's Reject All removes them and an appended table keeps the paragraph's formatting. See
45
+ the [0.8.4 release notes](./docs/releases/0.8.4.md).
46
+
47
+ Version 0.7.0 introduced contract-8 localized edits: a small `find`/`replace` request against an inspected target instead
48
+ of a complete paragraph, plus an optional speculative form that fails without writing when the anchor is missing or
49
+ ambiguous. They remain available unchanged; see the [0.7.0 release notes](./docs/releases/0.7.0.md).
50
+
36
51
  ## Documentation Index
37
52
 
38
53
  | Document | Description |
@@ -9,12 +9,13 @@ import {
9
9
  } from './paragraph-targeting.js';
10
10
  import { parseListItem, stripListMarker } from '../pipeline/list-markers.js';
11
11
 
12
- function getFirstDescendantByLocalName(node, localName) {
13
- if (!node || typeof node.getElementsByTagNameNS !== 'function') return null;
14
- const namespaced = node.getElementsByTagNameNS(WORD_MAIN_NS, localName);
15
- if (namespaced.length > 0) return namespaced[0];
16
- const anyNs = node.getElementsByTagNameNS('*', localName);
17
- return anyNs.length > 0 ? anyNs[0] : null;
12
+ /** Direct element child by local name; never crosses w:pPrChange / w:rPrChange boundaries. */
13
+ function getDirectChildByLocalName(node, localName) {
14
+ if (!node) return null;
15
+ for (const child of Array.from(node.childNodes || [])) {
16
+ if (child?.nodeType === 1 && child.localName === localName) return child;
17
+ }
18
+ return null;
18
19
  }
19
20
 
20
21
  function readValAttribute(element) {
@@ -178,17 +179,18 @@ function buildListEntriesForInsertion(parsedItems, normalizedTargetText, anchorL
178
179
  export function getParagraphListInfo(paragraph) {
179
180
  if (!paragraph) return null;
180
181
 
181
- const pPr = getFirstDescendantByLocalName(paragraph, 'pPr');
182
+ // Current properties only: direct w:pPr/w:numPr, never historical w:pPrChange/w:pPr.
183
+ const pPr = getDirectChildByLocalName(paragraph, 'pPr');
182
184
  if (!pPr) return null;
183
- const numPr = getFirstDescendantByLocalName(pPr, 'numPr');
185
+ const numPr = getDirectChildByLocalName(pPr, 'numPr');
184
186
  if (!numPr) return null;
185
- const numIdEl = getFirstDescendantByLocalName(numPr, 'numId');
187
+ const numIdEl = getDirectChildByLocalName(numPr, 'numId');
186
188
  if (!numIdEl) return null;
187
189
 
188
190
  const numId = readValAttribute(numIdEl);
189
191
  if (!numId || numId === '0') return null;
190
192
 
191
- const ilvlEl = getFirstDescendantByLocalName(numPr, 'ilvl');
193
+ const ilvlEl = getDirectChildByLocalName(numPr, 'ilvl');
192
194
  const ilvlRaw = readValAttribute(ilvlEl);
193
195
  const ilvl = Number.parseInt(ilvlRaw || '0', 10);
194
196