Skip to content

DOCX export: text box re-serialization emits mc:AlternateContent with mc:Choice only, dropping mc:Fallback (breaks downstream OOXML consumers) #3790

Description

@bwnyasse

Environment

  • superdoc 1.43.2 (npm), browser editor, documentMode: 'editing'
  • Export via superdoc.export({ exportType: ['docx'], triggerDownload: false })
  • Source checked at commit 2b50f53 (2026-06-25)

Summary

When SuperDoc re-serializes a DrawingML text box on DOCX export, it writes an mc:AlternateContent element containing only the mc:Choice branch (Requires="wps") and no mc:Fallback. Per ECMA-376 Part 3 (Markup Compatibility and Extensibility), consumers that do not support the wps namespace rely on the Fallback branch. The resulting documents break downstream OOXML consumers.

We hit this while evaluating SuperDoc: a document that contained text box shapes (with proper Choice + Fallback pairs authored by Word) came back after an editing session with 4 Choice-only mc:AlternateContent blocks in word/document.xml.

Root cause (in source)

packages/super-editor/src/editors/v1/core/super-converter/v3/handlers/wp/helpers/translate-drawingml-textbox.js (line 51) builds the wrapper with the Choice branch only:

const alternateContent = {
  name: 'mc:AlternateContent',
  elements: [
    {
      name: 'mc:Choice',
      attributes: { Requires: 'wps' },
      elements: [drawing],
    },
    // no mc:Fallback
  ],
};

The generic translator already does this correctly. packages/super-editor/src/editors/v1/core/super-converter/v3/handlers/mc/altermateContent/alternate-content-translator.js (lines 76-88, decode()) emits both branches:

const fallback = {
  name: 'mc:Fallback',
  elements: [
    {
      name: 'w:drawing',
      elements: carbonCopy(drawing.elements || []),
    },
  ],
};

return {
  name: 'mc:AlternateContent',
  elements: [choice, fallback],
};

Impact

  • docx-preview (widely used OSS renderer) crashes on such documents: its checkAlternateContent returns the Fallback's first child when the Choice requires an unsupported namespace (wps is not in its supported list), so a missing Fallback yields Cannot read properties of undefined (reading 'localName') and the whole preview fails.
  • Any consumer without wps support (older Word versions, converters) will silently render nothing where the shape was, since the compat branch is gone.

Suggested fix

Mirror the decode() pattern of alternate-content-translator.js in translate-drawingml-textbox.js: append an mc:Fallback carrying a copy of the drawing (or, better, preserve the original Fallback branch captured at import time when one existed).

We are happy to submit a PR along those lines if the approach sounds right to you.

Possibly related observation (not yet isolated)

In the same editing session, the exported package came back minimal: 9 parts instead of the original ~30, with the embedded fonts under word/fonts/ and the theme dropped (5.9 MB down to 25 KB). Our controlled round-trips (open, edit body text, export) preserve the full package, so we could not isolate the trigger yet. We can file a separate issue with a reproduction once we pin it down, but mentioning it here in case the text box re-serialization path also switches the exporter to a rebuild-from-scratch package mode.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions