Skip to content

fix(docx-core): preserve reply comments in rebuild mode (#108)#112

Merged
stevenobiajulu merged 1 commit intomainfrom
108-fix-rebuild-comment-replies-20260427
Apr 28, 2026
Merged

fix(docx-core): preserve reply comments in rebuild mode (#108)#112
stevenobiajulu merged 1 commit intomainfrom
108-fix-rebuild-comment-replies-20260427

Conversation

@stevenobiajulu
Copy link
Copy Markdown
Member

Summary

Fix issue #108: in rebuild mode, threaded reply comments — and their commentsExtended.xml / people.xml entries — were silently dropped from output.

Reply <w:comment> entries do not have their own <w:commentReference> in document.xml; they thread through commentsExtended.xml via w15:paraIdParent. Two compounding bugs caused the drop:

  1. The comment-merge post-pass walked only the directly-merged root comment IDs. Replies are flat siblings, never inspected.
  2. The post-pass call site was gated on mergeResults.has('comment'), so it didn't even run when the original already contained the root and the revised side only added replies under it. Surfaced by peer review of the initial plan.

Fix shape

  • Drive the post-pass off <w:commentReference> IDs in the result document.xml, so it runs whenever any comment is anchored — regardless of what the generic auxiliary merge appended.
  • Build full source-comment maps (id ↔ paraId ↔ author) once.
  • BFS the w15:paraIdParent graph from each root paraId, only including children that resolve to a real source <w:comment> (so orphaned commentEx entries can't drag in dangling ancillary XML).
  • Append any included <w:comment> definitions still missing from the result comments.xml (replies — and defensively, any roots).
  • Call the existing mergeCommentsExtended / mergePeople with the expanded paraId / author sets.

The generic mergeAuxiliaryPartDefinitions helper stays generic — paraIdParent is a comment-only concern.

Regression coverage (3 new tests in rebuild-auxiliary-merge.test.ts)

  • Reply added to existing root — catches the gating bug specifically (without it, replies are dropped even when the BFS expansion is correct).
  • Bootstrap with reply — root and reply both new on the revised side.
  • Deep reply chain (root → reply → grandchild) — justifies the BFS by behavior, not by implementation detail.

Fixes: #108
Ref: #94, #101

Test plan

  • Targeted regression tests are RED before fix, GREEN after (verified locally — 3 added)
  • Full @usejunior/docx-core suite — 1097 passed | 1 skipped
  • npm run build — clean
  • npm run lint:workspaces — clean (1 pre-existing warning in unrelated file)
  • npm run test:run — full suite green
  • npm run check:spec-coverage — all PASS
  • Verify CI pipeline green

Reply comments — and their `commentsExtended.xml` / `people.xml` entries —
were silently dropped from rebuild output. Reply `<w:comment>` entries do
not have their own `<w:commentReference>` in document.xml; they thread
through `commentsExtended.xml` via `w15:paraIdParent`. The previous
implementation only walked the directly-merged root comment IDs, so
reply entries never entered the merge set.

Two compounding bugs:

1. The comment-merge post-pass walked only `commentMergeResult.mergedIds`
   — root IDs that the generic auxiliary-part merge appended. Replies
   are siblings, not nested, so they were never even inspected.

2. The post-pass call site was gated on `mergeResults.has('comment')`,
   meaning it only ran when at least one root comment was newly merged.
   When the original already contained the root and the revised side
   only added replies under it, the pass never ran at all and replies
   were dropped even with the BFS fix from (1) in place. (Surfaced by
   peer review of the v1 plan.)

Fix:
- Drive the post-pass off `<w:commentReference>` IDs in the result
  document, so it runs whenever any comment is anchored — regardless of
  what the generic merge appended.
- Build full source-comment maps (id ↔ paraId ↔ author) once.
- BFS the `w15:paraIdParent` graph from each root paraId, only
  including children that resolve to a real source `<w:comment>` so
  orphaned `commentEx` entries can't drag in dangling ancillary XML.
- Append any included `<w:comment>` definitions still missing from the
  result `comments.xml` (replies — and defensively, any roots).
- Then call the existing `mergeCommentsExtended` / `mergePeople` with
  the expanded paraId / author sets.

Regression coverage adds three rebuild-mode tests: reply-to-existing-root
(catches the gating bug), bootstrap with reply (root and reply both new),
and a 3-level chain that justifies the BFS by behavior rather than by
implementation detail.

Fixes: #108
Ref: #94, #101
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
site Ready Ready Preview, Comment Apr 28, 2026 2:08am

Request Review

@github-actions github-actions Bot added the fix label Apr 28, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

❌ Patch coverage is 91.15044% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...kages/docx-core/src/baselines/atomizer/pipeline.ts 89.47% 8 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@stevenobiajulu stevenobiajulu merged commit e914618 into main Apr 28, 2026
23 checks passed
stevenobiajulu added a commit that referenced this pull request Apr 28, 2026
* chore(release): bump workspace versions to 0.9.1

Patch release covering bug fixes accumulated since v0.9.0:

- fix(docx-core): merge auxiliary parts in rebuild mode (#94, #101)
- fix(docx-core): emit paragraph-level markers outside <w:r> on rebuild (#109)
- fix(docx-core): preserve reply comments in rebuild mode (#108, #112)
- test(docx-mcp): harden vitest path allowlist against /tmp symlink topology (#105)
- ci: SHA-pin all GitHub Actions, add dependency review and Dependabot
- ci: fix PR title check for acronyms and fork PRs (#88)
- chore(skills): sync docx-editing to 0.3.0 (#95)

Generated via scripts/bump_version.mjs.

* fix(docx-mcp): declare @xmldom/xmldom dependency explicitly

The bumped package-lock.json regenerated cleanly and exposed a latent
bug: docx-mcp directly imports @xmldom/xmldom in tag_parser.ts but did
not declare it as a dependency. It was previously masked by npm
hoisting from docx-core's transitive dep — the workspace happened to
work because the lockfile carried a stale hoisted xmldom@0.8.11.

Pinning to ^0.8.11 (matching what was already running in production via
the silent hoist) preserves observable behavior. tag_parser depends on
xmldom 0.8's lenient parsing of cross-nested tags
(<b><i>text</b></i>) — xmldom 0.9 throws on such input. Migrating
docx-mcp's tag_parser to xmldom 0.9 is its own concern; this commit
keeps the 0.9.1 release a pure version bump.

docx-core continues to use xmldom ^0.9.9 (PR #75); the two versions
coexist under their respective package's node_modules. No DOM nodes
cross the docx-core/docx-mcp boundary, so the version split is safe.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reply comments dropped in rebuild mode (mergeCommentAncillaryParts only walks root comments)

1 participant