Skip to content

feat: emit word/commentsIds.xml with durable comment ids (w16cid)#3472

Open
alexvcasillas wants to merge 2 commits into
dolanmiu:masterfrom
alexvcasillas:feat/comments-ids-durable-id
Open

feat: emit word/commentsIds.xml with durable comment ids (w16cid)#3472
alexvcasillas wants to merge 2 commits into
dolanmiu:masterfrom
alexvcasillas:feat/comments-ids-durable-id

Conversation

@alexvcasillas

@alexvcasillas alexvcasillas commented Jun 23, 2026

Copy link
Copy Markdown

Summary

Adds support for the word/commentsIds.xml OOXML part, which maps each comment's volatile paragraph id (w14:paraId) to a stable w16cid:durableId that Word preserves across edits (unlike the volatile w:id in comments.xml).

This mirrors the existing commentsExtended.xml feature end to end. A new optional durableId field on a comment opts a document into the part: when any comment carries a durableId, the library now generates and registers commentsIds.xml.

Why

Word, and the apps and services that interoperate with it (for example collaborative editors that round-trip DOCX), rely on w16cid:durableId to track comment identity stably across save and edit cycles. Without it, comment ids can be reassigned and external systems lose the ability to reconcile a comment with its original. docx already emits comments.xml and commentsExtended.xml, but had no way to write the durable-id part.

What changed

Public API

  • ICommentOptions gains an optional readonly durableId?: string. When it is omitted on a comment that lives in a commentsIds-bearing document, the comment's generated paraId is reused as its durableId (matching how Word emits the part).
  • New exported CommentsIds component (sibling of CommentsExtended).
  • New exported ICommentIdData type (sibling of ICommentThreadData).
  • New File#CommentsIds getter (sibling of File#CommentsExtended).

OOXML parts produced

  • Root element w16cid:commentsIds with namespaces:
    • xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid"
    • xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    • mc:Ignorable="w16cid"
  • Child w16cid:commentId elements with w16cid:paraId and w16cid:durableId.
  • Content-type override application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml for /word/commentsIds.xml.
  • Relationship type http://schemas.microsoft.com/office/2016/09/relationships/commentsIds pointing at commentsIds.xml.

Generation logic

  • Comments now assigns deterministic paraIds when threading is active or when any comment carries a durableId (previously threading only). It exposes a new CommentIdsData getter (sibling of ThreadData), built only when a durableId is present.
  • File wires up the CommentsIds part, relationship, and content-type override whenever Comments#CommentIdsData is set (sibling of the commentsExtended wiring).
  • The packer and compiler serialize the part to word/commentsIds.xml (sibling of the existing commentsExtended.xml block).

Files touched

  • src/file/paragraph/run/comment-run.ts: durableId option, ICommentIdData, generator and getter
  • src/file/paragraph/run/comments-extended.ts: CommentsIds component
  • src/file/file.ts: part wiring, relationship, content-type, getter
  • src/file/content-types/content-types.ts: addCommentsIds()
  • src/file/relationships/relationship/relationship.ts: new relationship type
  • src/export/packer/next-compiler.ts: serialize part to word/commentsIds.xml

Tests

Added to the existing specs (vitest), covering threaded and single (non-threaded) cases:

  • comment-run.spec.ts: CommentIdsData mapping, paraId fallback, threaded plus durable, paraId injection
  • comments-extended.spec.ts: CommentsIds XML serialization
  • file.spec.ts: File#CommentsIds presence and absence
  • next-compiler.spec.ts: commentsIds.xml emitted, content-type and relationship registered

Full suite passes (npx vitest run): 196 files, 1032 tests. npm run build, lint, and prettier are clean.

Notes

  • There is no behavior change for documents that do not set durableId. The part is only created when at least one comment opts in.
  • durableId is a string so callers can supply Word-style 8-character hex ids (for example "12AB34CD") verbatim, without numeric coercion.

Summary by CodeRabbit

  • New Features
    • Added support for stable, persistent comment identifiers in DOCX output.
    • When available, the export now generates and wires the word/commentsIds.xml part so comment IDs remain consistent across edits, including for threaded comments.
  • Tests
    • Expanded test coverage to validate creation/omission behavior for commentsIds.xml, durable-id mapping, and related XML parts and relationships.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b9e22bf0-bb49-41a4-b145-e072a0efb32b

📥 Commits

Reviewing files that changed from the base of the PR and between f5e6bd2 and c74aa32.

📒 Files selected for processing (2)
  • src/export/packer/next-compiler.spec.ts
  • src/file/file.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/file/file.spec.ts

📝 Walkthrough

Walkthrough

Adds support for the DOCX word/commentsIds.xml part, which maps stable durableId values to paraId values for comments. The change spans the comment data model (ICommentOptions, Comments, new ICommentIdData type), a new CommentsIds XML component, File-level wiring for the relationship URI and content type, and conditional serialization in the compiler.

Changes

commentsIds.xml support

Layer / File(s) Summary
ICommentIdData type and Comments constructor logic
src/file/paragraph/run/comment-run.ts, src/file/paragraph/run/comment-run.spec.ts
Adds optional durableId to ICommentOptions, introduces exported ICommentIdData type (paraId + durableId), broadens the Comments constructor to generate paraId values and populate commentIdsData when durable IDs or threading are present, adds CommentIdsData public getter, and tests all branching including fallback and threading combinations.
CommentsIds XML component
src/file/paragraph/run/comments-extended.ts, src/file/paragraph/run/comments-extended.spec.ts
Imports ICommentIdData, adds internal CommentIdAttributes, CommentId, CommentsIdsAttributes classes, and exports CommentsIds which builds the w16cid:commentsIds root with correct namespace/ignorable attributes and one w16cid:commentId per entry; tests verify XML output for single and multiple entries.
File wiring: relationship, content types, and CommentsIds getter
src/file/relationships/relationship/relationship.ts, src/file/content-types/content-types.ts, src/file/file.ts, src/file/file.spec.ts
Adds commentsIds relationship URI to RelationshipType, adds addCommentsIds() to ContentTypes, wires CommentsIds construction in File from CommentIdsData, conditionally registers its relationship and content type, exposes CommentsIds getter, and tests presence/absence of CommentsIds for all durableId scenarios.
Compiler serialization of word/commentsIds.xml
src/export/packer/next-compiler.ts, src/export/packer/next-compiler.spec.ts
Extends IXmlifyedFileMapping with optional CommentsIds entry; xmlifyFile conditionally emits word/commentsIds.xml when file.CommentsIds is present; tests assert absence without durableId, XML content for single comment, and paraIddurableId mappings plus commentsExtended.xml for threaded comments.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hop hop, through the XML maze I go,
A durableId planted, watch the comments grow!
w16cid namespaces blooming in spring,
Each paraId mapped to a stable ring.
The rabbit stamps the ZIP with glee—
commentsIds.xml now hops free! 🌸

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: adding support for emitting word/commentsIds.xml with durable comment IDs (w16cid namespace).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (309b972) to head (c74aa32).

Additional details and impacted files
@@            Coverage Diff            @@
##            master     #3472   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          311       311           
  Lines         3237      3265   +28     
  Branches       732       739    +7     
=========================================
+ Hits          3237      3265   +28     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/file/file.ts (1)

25-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a project path alias for the new import.

The added relative import should follow the repository alias convention for TypeScript imports.

♻️ Proposed fix
-import { CommentsExtended, CommentsIds } from "./paragraph/run/comments-extended";
+import { CommentsExtended, CommentsIds } from "`@file/paragraph/run/comments-extended`";

As per coding guidelines, "**/*.ts: Use path aliases @file/, @export/, and @util/ for imports".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/file/file.ts` at line 25, The import statement for CommentsExtended and
CommentsIds uses a relative path import pattern instead of the project path
alias convention. Replace the relative import path
"./paragraph/run/comments-extended" with the corresponding path alias
"`@file/paragraph/run/comments-extended`" to follow the repository's TypeScript
import guidelines that require using path aliases like `@file/` for imports in
this file.

Source: Coding guidelines

src/file/file.spec.ts (1)

447-481: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert CommentsIds XML shape here, not only object presence.

These tests currently pass even if CommentsIds serializes the wrong structure. Add at least one assertion on the formatted XML tree (root/tag/attrs) for the positive durableId case.

As per coding guidelines, "**/*.spec.ts: Test the XML output structure, not just that code runs".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/file/file.spec.ts` around lines 447 - 481, The test cases for CommentsIds
creation are only validating that the property exists or is undefined, but not
asserting the actual XML structure being serialized. For at least one of the
positive durableId test cases (either the single comment or threaded comments
test), add an assertion that validates the formatted XML output structure by
checking the root element, tags, and attributes rather than just verifying the
CommentsIds property is not undefined. This will ensure the XML serialization
produces the expected structure, not just that the code runs.

Source: Coding guidelines

src/export/packer/next-compiler.spec.ts (1)

169-219: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a mixed durable/non-durable integration test for fallback behavior.

Please add a case where at least one comment has durableId and another omits it, then assert word/commentsIds.xml maps the omitted one to its generated paraId. That option combination is part of the feature contract and is not covered here yet.

As per coding guidelines, "**/*.spec.ts: Cover edge cases and option combinations in test files".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/export/packer/next-compiler.spec.ts` around lines 169 - 219, Add a new
test case using the `it()` block pattern to cover the mixed durable/non-durable
comment scenario. Create a File with comments where at least one comment
includes a durableId and another omits it, then compile the file using
compiler.compile(). Assert that word/commentsIds.xml is generated and contains
proper mappings for both types of comments: verify that comments with durableId
are mapped with the correct durableId attribute, and verify that comments
without durableId are mapped to their generated paraId values. This ensures the
fallback behavior when durableId is omitted is properly tested and documented.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/export/packer/next-compiler.spec.ts`:
- Around line 169-219: Add a new test case using the `it()` block pattern to
cover the mixed durable/non-durable comment scenario. Create a File with
comments where at least one comment includes a durableId and another omits it,
then compile the file using compiler.compile(). Assert that word/commentsIds.xml
is generated and contains proper mappings for both types of comments: verify
that comments with durableId are mapped with the correct durableId attribute,
and verify that comments without durableId are mapped to their generated paraId
values. This ensures the fallback behavior when durableId is omitted is properly
tested and documented.

In `@src/file/file.spec.ts`:
- Around line 447-481: The test cases for CommentsIds creation are only
validating that the property exists or is undefined, but not asserting the
actual XML structure being serialized. For at least one of the positive
durableId test cases (either the single comment or threaded comments test), add
an assertion that validates the formatted XML output structure by checking the
root element, tags, and attributes rather than just verifying the CommentsIds
property is not undefined. This will ensure the XML serialization produces the
expected structure, not just that the code runs.

In `@src/file/file.ts`:
- Line 25: The import statement for CommentsExtended and CommentsIds uses a
relative path import pattern instead of the project path alias convention.
Replace the relative import path "./paragraph/run/comments-extended" with the
corresponding path alias "`@file/paragraph/run/comments-extended`" to follow the
repository's TypeScript import guidelines that require using path aliases like
`@file/` for imports in this file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a3e4fe6-8cf5-418d-877a-b9f9e5ec3498

📥 Commits

Reviewing files that changed from the base of the PR and between 309b972 and f5e6bd2.

📒 Files selected for processing (10)
  • src/export/packer/next-compiler.spec.ts
  • src/export/packer/next-compiler.ts
  • src/file/content-types/content-types.ts
  • src/file/file.spec.ts
  • src/file/file.ts
  • src/file/paragraph/run/comment-run.spec.ts
  • src/file/paragraph/run/comment-run.ts
  • src/file/paragraph/run/comments-extended.spec.ts
  • src/file/paragraph/run/comments-extended.ts
  • src/file/relationships/relationship/relationship.ts

@alexvcasillas

alexvcasillas commented Jun 23, 2026

Copy link
Copy Markdown
Author

Addressed the nitpicks in c74aa32:

  • Assert XML shape in file.spec.ts: the positive durableId test now formats CommentsIds and asserts the actual tree (w16cid:commentsIdsw16cid:commentId with w16cid:paraId / w16cid:durableId), instead of only checking presence.
  • Mixed durable / non-durable coverage in next-compiler.spec.ts: added a test with one comment carrying a durableId and one omitting it, asserting the omitted comment falls back to its generated paraId in word/commentsIds.xml.
  • Path alias in file.ts: left as a relative import. The @file/ alias is used for reaching across top-level areas, but imports within src/file/ use relative paths (every other import in file.ts, and sibling files like header-wrapper.ts, do the same). The flagged line sits next to the existing relative ./paragraph/run/comment-run import and matches that local convention, so switching only this one to an alias would be inconsistent.

Full suite passes (npx vitest run): 196 files, 1033 tests. Build, ESLint, and Prettier are clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant