Skip to content

fix(super-editor): support nested content controls#3616

Merged
caio-pizzol merged 4 commits into
superdoc-dev:mainfrom
xy200303:fix/nested-content-control-import
Jun 3, 2026
Merged

fix(super-editor): support nested content controls#3616
caio-pizzol merged 4 commits into
superdoc-dev:mainfrom
xy200303:fix/nested-content-control-import

Conversation

@xy200303
Copy link
Copy Markdown

@xy200303 xy200303 commented Jun 3, 2026

Summary

  • Fix DOCX import failures for legal nested Word content controls (w:sdt).
  • Classify imported SDTs using translated ProseMirror JSON content and import context instead of only inspecting direct w:sdtContent XML child names.
  • Safely normalize bare inline content inside block SDTs by wrapping inline runs into paragraphs.
  • Preserve SDT metadata including id, tag, alias, lockMode, controlType, and raw sdtPr.
  • Add regression coverage for nested block SDTs and mixed block/inline SDT content.

Root Cause

The importer previously decided whether to emit structuredContent or structuredContentBlock by checking whether the direct children of w:sdtContent included w:p, w:tbl, or a known block field node.

That logic fails for valid Word documents where w:sdtContent directly contains another w:sdt. In those cases, the real content shape is only visible after translating the nested SDT. As a result, the importer could emit:

  • structuredContent with block children, violating the inline-only schema.
  • structuredContentBlock with bare inline children, violating the block-only schema.

This produced errors such as:

  • RangeError: Invalid content for node structuredContentBlock
  • RangeError: Invalid content for node structuredContent

Changes

  • Updated handleStructuredContentNode to translate SDT content first, then classify the SDT based on the translated node shape.
  • Kept the existing direct XML block detection as a fallback for known block signals such as paragraphs, tables, and block fields.
  • Added context-aware handling so nested inline SDTs can still be emitted as inline content when appropriate.
  • Added block normalization that groups contiguous inline translated nodes into generated paragraphs when a block SDT needs block-safe content.
  • Added image to the inline-node fallback list for schema-unavailable converter paths.
  • Added regression tests covering:
    • w:sdtContent -> w:sdt -> w:sdtContent -> w:p
    • An outer block SDT containing a nested inline SDT, a paragraph, and a table
  • Confirmed nested SDT metadata remains readable after import.

Verification

  • pnpm --filter @superdoc/super-editor test src/editors/v1/core/super-converter/v3/handlers/w/sdt --run
  • Commit hooks passed:
    • format
    • lint
    • commitlint

Notes

  • No schema change was required. The existing schema distinction is correct:
    • structuredContent accepts inline content.
    • structuredContentBlock accepts block content.
  • The fix is scoped to the DOCX import/converter path and does not modify generated dist output.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 4 files

Re-trigger cubic

@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Real .docx fixtures + integration test validating the translate-first SDT classifier from PR superdoc-dev#3616: nested block, nested inline, mixed-block (a defensive malformed-normalization case), and inline picture control. Each fixture asserts source XML shape, imported PM node types with no content loss, and export wrapper round-trip. A portable generator regenerates them. Row-level SDTs (w:tbl > w:sdt > w:tr) are out of scope here; see SD-3118/IT-1040.
@caio-pizzol
Copy link
Copy Markdown
Contributor

@xy200303 thanks for this. The approach looks right: decide whether a content control is block or inline from where it appears in the document, not only from its direct child nodes.

I pushed commit 4848caa02 with real .docx tests for:

  • nested block controls
  • nested inline controls
  • mixed block content, including the malformed defensive case
  • inline picture controls, where the image and original control metadata are preserved

The tests check the source XML, the imported document shape, and that export keeps the content-control wrappers.

Scope note: this covers nested block and inline content controls. Picture controls are preserved, but not fully supported as their own control type yet. Table-row wrappers (w:tbl > w:sdt > w:tr) are still separate work tracked in #3242.

Copy link
Copy Markdown
Contributor

@caio-pizzol caio-pizzol left a comment

Choose a reason for hiding this comment

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

lgtm

@xy200303
Copy link
Copy Markdown
Author

xy200303 commented Jun 3, 2026

@xy200303 thanks for this. The approach looks right: decide whether a content control is block or inline from where it appears in the document, not only from its direct child nodes.

I pushed commit 4848caa02 with real .docx tests for:

  • nested block controls
  • nested inline controls
  • mixed block content, including the malformed defensive case
  • inline picture controls, where the image and original control metadata are preserved

The tests check the source XML, the imported document shape, and that export keeps the content-control wrappers.

Scope note: this covers nested block and inline content controls. Picture controls are preserved, but not fully supported as their own control type yet. Table-row wrappers (w:tbl > w:sdt > w:tr) are still separate work tracked in #3242.

Thanks for the review.

@caio-pizzol caio-pizzol enabled auto-merge June 3, 2026 15:18
@caio-pizzol caio-pizzol merged commit 6eeb8d7 into superdoc-dev:main Jun 3, 2026
68 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants