Skip to content

fix(docx-io): keep every nested inline formatting tag on DOCX export - #5150

Open
vincent69001 wants to merge 1 commit into
udecode:mainfrom
vincent69001:fix/docx-io-nested-marks
Open

vincent69001 wants to merge 1 commit into
udecode:mainfrom
vincent69001:fix/docx-io-nested-marks

Conversation

@vincent69001

@vincent69001 vincent69001 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor
  • Auto release

🐛 Fixes ➖ N/A
🧭 Task plan: docs/plans/2026-10-07-fix-docx-io-nested-inline-formatting.md
🟢 95% confidence in the verified package behavior

Phase 🧪 Tests 🌐 Browser
Reproduced 🔴 On main, 5 of the new htmlToDocxBlob rows and the new round-trip case fail: <em><b>bold italic</b></em> exports <w:b/> only ➖ N/A
Verified 🟢 15-row matrix plus highlight test (49 pass), app round trip (6 pass), docx-io build/typecheck/brl, pnpm check ➖ N/A: no browser surface

✅ Outcome

htmlToDocxBlob, and therefore exportToDocx and DocxExportPlugin, apply every enclosing inline formatting tag to a text run. A leaf that carries several marks keeps all of them in the DOCX, and a mark no longer leaks onto the sibling text that follows a nested tag.

Plate's static renderer emits one tag per mark plugin, nested, for a multi-mark leaf. With BoldPlugin and ItalicPlugin, { text: 'bold italic', bold: true, italic: true } becomes:

<span data-slate-leaf="true" ...><em class="slate-italic"><strong class="slate-bold"><span data-slate-string="true">bold italic</span></strong></em></span>
HTML <w:rPr> on main <w:rPr> with this PR
<p><em><b>bold italic</b></em></p> <w:b/> <w:b/> <w:i/>
<p><b><em>bold italic</em></b></p> <w:i/> <w:b/> <w:i/>
<p><u><em><b>all three</b></em></u></p> <w:b/> <w:b/> <w:i/> <w:u/>
<p><span><u><em><b>x</b></em></u></span></p> <w:b/> <w:b/> <w:i/> <w:u/>
<p><b><em>p <s>q</s></em><u>r</u></b></p> r: <w:b/> <w:i/> <w:u/> r: <w:b/> <w:u/>
<p><mark>a <code>x</code> c</mark></p> x: two <w:highlight> x: <w:highlight w:val="lightGray"/>
<p><b>a <em>b</em></b></p> a: <w:b/>; b: <w:b/> <w:i/> unchanged
<p><b><em>x</em> tail</b></p> x: <w:b/> <w:i/>; tail: <w:b/> unchanged
<p><em>a <b>x</b> c</em></p> a: <w:i/>; x: <w:i/> <w:b/>; c: <w:i/> unchanged
<p><em>a</em><b>b</b></p> a: <w:i/>; b: <w:b/> unchanged
<p><em><span><b>bold italic</b></span></em></p> <w:i/> <w:b/> unchanged

Cause: in buildRun (packages/docx-io/src/lib/internal/helpers/xml-builder.ts), the formatting-tag loop flattened the subtree into one queue and reset tempAttributes = {} at every formatting tag. The flag was only folded into the durable attributes when the tag had more than one child, so a single-child chain kept the innermost flag only, and a multi-child chain leaked its flags to the siblings that followed (r above).

⚠️ Caveat

Child order inside <w:rPr> still follows attribute insertion order, as before. Inside a formatting tag, <br> is still dropped and <a> still loses its hyperlink; both are pre-existing and out of scope. When highlight tags (mark, code, kbd) or vertical-alignment tags (sub, sup) nest, the innermost one wins, which keeps one <w:highlight> / <w:vertAlign> per run. Hosted CI fails on pnpm test:slowest for packages/markdown/src/lib/table.spec.ts and apps/www/src/registry/ui/media-file-node.spec.tsx, neither touched here; the same gate failed on #5145 and #5148 before they merged.

🏗️ Design

Each queued node now carries the run attributes inherited from its formatting ancestors, the way the span branch already passes { ...attributes, ...tempAttributes } down. The children.length > 1 heuristic and the reset go away. The loop is kept rather than recursing through buildRunOrRuns, because buildRun does not descend into tags such as <a> or <abbr> and the loop keeps their text today. buildFormatting and the runPropertiesFragment import stay, because they still feed the run emitted for an empty tag. The change is confined to this branch of buildRun; no public API or export changes.

🧪 Verified

  • Reproduced on main with the new tests: 5 of the first 10 matrix rows and the app round-trip case fail.
  • bun test ./packages/docx-io/src/lib/internal/html-to-docx.slow.ts: 49 pass.
  • bun test ./apps/www/src/__tests__/package-integration/docx-io.roundtrip.slow.tsx: a bold italic leaf round trips through serializeHtml → htmlToDocxBlob → mammoth import (fails on main with italic missing).
  • pnpm turbo build --filter=./packages/docx-io, pnpm turbo typecheck --filter=./packages/docx-io, pnpm turbo brl --filter=./packages/docx-io (no barrel drift), pnpm lint:fix, pnpm check: pass.
  • pnpm check was rerun after rebasing on main at 53.3.11.

AI-assisted (Claude Code), fully tested. Codex is not installed on the contributor's machine, so codex review --base origin/main was not run; the repo autoreview helper could not authenticate its nested Claude CLI, so an independent Claude review pass was run on the diff instead and its findings (nested highlight duplication, matrix coverage, changeset wording) are addressed.

@codesandbox

codesandbox Bot commented Oct 8, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web Editor • VS Code • Insiders

Open Preview

@changeset-bot

changeset-bot Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2c9dfda

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@platejs/docx-io Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

vincent69001 added a commit to vincent69001/plate that referenced this pull request Oct 8, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vincent69001
vincent69001 force-pushed the fix/docx-io-nested-marks branch from ca827c7 to 2c9dfda Compare October 8, 2026 07:57
@vincent69001
vincent69001 marked this pull request as ready for review October 8, 2026 08:10
@vincent69001
vincent69001 requested a review from a team October 8, 2026 08:10

This branch has not been deployed

No deployments
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