Skip to content

feat(content-controls): support richText type and regenerate NDA demo fixture (SD-3131)#3275

Merged
caio-pizzol merged 2 commits into
mainfrom
caio-pizzol/SD-3131-detect-word-plain-text
May 14, 2026
Merged

feat(content-controls): support richText type and regenerate NDA demo fixture (SD-3131)#3275
caio-pizzol merged 2 commits into
mainfrom
caio-pizzol/SD-3131-detect-word-plain-text

Conversation

@caio-pizzol
Copy link
Copy Markdown
Contributor

@caio-pizzol caio-pizzol commented May 14, 2026

Adds 'richText' to ContentControlType so SuperDoc resolves typeless w:sdt SDTs to 'richText' per ECMA-376 §17.5.2.26 ("If no type element is specified, then the nearest ancestor structured document tag shall be of type richText"). Before this change, typeless SDTs — which is what Word emits for ContentControls.Add(0, range) and the default Add($null, range) — fell through to 'unknown', blocking customers from inspecting their Word-authored controls by type.

  • Detection (handle-structured-content-node.js): typeless sdtPr and explicit <w:richText/> both resolve to 'richText'. Unmodeled type children (w:equation, w:picture, w:citation, w:bibliography, w:docPartList) still return null so resolveControlType yields 'unknown'. unknown keeps its meaning of "unsupported or unrecognized type", not "typeless rich-text control".
  • Export/create maps: richText: 'w:richText' added to both CONTROL_TYPE_SDT_PR_ELEMENTS (wrappers) and CONTROL_TYPE_ELEMENT_MAP (converter), plus a 'richText' case in buildDefaultTypeSdtPrElement so create.contentControl({ controlType: 'richText' }) and setType(..., 'richText') produce valid OOXML.
  • text.setValue stays restricted to actual w:text controls. Use replaceContent for richText.

The contract-templates demo fixture had inline smart fields authored as default rich-text controls (typeless), which is why none of them detected as 'text'. Regenerated nda-template.docx with ContentControls.Add(1, range) for the 7 inline fields (now <w:text/>controlType: 'text') while leaving the 6 block clauses as typeless (controlType: 'richText'). Switched the demo's per-occurrence field update to text.setValue to exercise the typed API; clause replacement stays on replaceContent.

Verified:

  • Engine tests: 22 in handle-structured-content-node.test.js (10 new), 39 in content-controls-wrappers, 1189 in conformance, 1398 in @superdoc/document-api — all pass.
  • Fixture unzip: 13 SDTs total, 7 <w:text/>, 0 explicit <w:richText/>.
  • Browser smoke: receivingParty fan-out updates 2/2 occurrences (header + nested), clause replacement decrements "updates available" correctly.

Related: SD-3130, IT-1046.

Scope note: this PR fixes the richText content-control type classification and OOXML export/create maps. Rendering of substantive rich content inside richText SDTs uses the existing content-driven pipeline and will be covered by a follow-up fixture.

…Ts per ECMA-376 (SD-3131)

Adds 'richText' to ContentControlType so SuperDoc resolves typeless w:sdt
SDTs to 'richText' per ECMA-376 §17.5.2.26 ("If no type element is
specified, then the nearest ancestor structured document tag shall be
of type richText"). Before, typeless SDTs - which Word emits for
ContentControls.Add(0, range) and the default Add($null, range) - fell
through to 'unknown', blocking customers from inspecting Word-authored
controls by type.

- Detection: typeless sdtPr and explicit <w:richText/> both resolve to
  'richText'. Unmodeled type children (w:equation, w:picture, w:citation,
  w:bibliography, w:docPartList) still return null so resolveControlType
  yields 'unknown' - keeping 'unknown' as "unsupported or unrecognized",
  not "typeless rich-text control".
- Export/create maps: richText: 'w:richText' in both CONTROL_TYPE_SDT_PR_ELEMENTS
  (wrappers) and CONTROL_TYPE_ELEMENT_MAP (converter), plus a 'richText'
  case in buildDefaultTypeSdtPrElement so create.contentControl and setType
  produce valid OOXML.
- text.setValue stays restricted to actual w:text controls.

Tests: 22 in handle-structured-content-node.test.js (10 new), 39 in
content-controls-wrappers, 1189 in conformance - all pass.
…-3131)

The contract-templates demo fixture had inline smart fields authored as
default rich-text controls (typeless sdtPr), which is why none of them
detected as 'text' - they fell through to 'unknown' before SD-3131 and
resolve as 'richText' after.

Regenerated nda-template.docx with ContentControls.Add(1, range) for the
7 inline smart fields (now <w:text/> in sdtPr, controlType: 'text') while
leaving the 6 block clauses as typeless (controlType: 'richText').
Verified unzip: 13 SDTs total, 7 <w:text/>, 0 explicit <w:richText/>.

Switched per-occurrence field update from replaceContent to text.setValue
to exercise the typed API. Clause replacement stays on replaceContent
because rich-text controls don't have a typed setter.

README/JSDoc updated: "seven inline plain-text + six block rich-text"
(was "thirteen plain-text"). Honest limits section updated to reflect
the API split.

Browser smoke verified: receivingParty fan-out updates 2/2 occurrences
(header + nested), clause replacement decrements "updates available"
correctly, summary line accurate.
@caio-pizzol caio-pizzol requested a review from a team as a code owner May 14, 2026 09:19
@linear
Copy link
Copy Markdown

linear Bot commented May 14, 2026

SD-3131

@github-actions
Copy link
Copy Markdown
Contributor

Good — w:docPartObj is intercepted at the strategy level and never reaches detectControlType, so it doesn't need to be in the fall-through set. Let me note that the ecma-spec MCP tools weren't granted permission, so my review is based on diff analysis + standard OOXML knowledge rather than direct spec lookup.


Status: PASS

The schema-level changes look spec-correct:

  • w:richText is a valid w:sdtPr type child (empty element in CT_SdtPr's type-marker choice), so adding it to detection, the export map (translate-structured-content.js:42), CONTROL_TYPE_SDT_PR_ELEMENTS, and buildDefaultTypeSdtPrElement is correct.
  • The other type-marker children referenced (w:text, w:date, w:comboBox, w:dropDownList, w:group, w14:checkbox, w15:repeatingSection(Item), w:equation, w:picture, w:citation, w:bibliography, w:docPartList) all exist as defined sdtPr children.
  • w:docPartObj is missing from the fall-through TYPE_CHILD_NAMES set (handle-structured-content-node.js:27) but that's fine — sdt-node-type-strategy.js:22 intercepts docPartObj-typed SDTs upstream and routes them to a dedicated handler, so they never reach detectControlType.
  • The typeless-sdtPr → richText default at handle-structured-content-node.js:11 and :33 matches Word's documented behavior for ContentControls.Add(0, range). The spec citation given is §17.5.2.26 (which is sdtPr itself); the rule "absent type marker ⇒ rich-text SDT" is stated in the prose for the w:richText element (a nearby subsection in §17.5.2), not in §17.5.2.26 itself — minor citation nit, not a spec violation. See https://ooxml.dev/spec?q=sdtPr.

Caveat: I could not load the ecma-spec MCP tools (permission not granted in this session), so the spec-text verification above is from prior OOXML knowledge rather than live lookup. If you want me to re-verify the §17.5.2.26 citation precisely, allow the mcp__ecma-spec__* tools and I'll re-run.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 530fc63275

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@caio-pizzol caio-pizzol merged commit ed407d7 into main May 14, 2026
70 checks passed
@caio-pizzol caio-pizzol deleted the caio-pizzol/SD-3131-detect-word-plain-text branch May 14, 2026 09:50
caio-pizzol added a commit that referenced this pull request May 14, 2026
…t unknown (SD-3139) (#3281)

Follow-up to SD-3131 (#3275). After that PR, imported typeless w:sdtPr
correctly resolves to controlType: 'richText' per ECMA-376 §17.5.2.26,
but newly created controls via create.contentControl (no controlType)
and contentControls.wrap still defaulted to 'unknown'. Customers
filtering contentControls.list() by type saw different results before
vs after save/reopen — the same typeless OOXML round-tripped from
'unknown' to 'richText'.

- createWrapper: collapse three `?? 'unknown'` into a single
  `?? 'richText'` local.
- wrapWrapper: explicitly seed controlType, type, and a default
  sdtPr with <w:richText/> on the wrapper attrs (previously
  controlType was unset entirely, leaving resolveControlType to
  fall back to 'unknown').

Newly created controls now emit explicit <w:richText/> in sdtPr for
unambiguous engine state and exported markup. Imported typeless
Word-authored SDTs preserve their original raw sdtPr (import path
unchanged).

Per the SD-3131 design, 'unknown' keeps its meaning of "unsupported
or unrecognized type" — it's no longer the default for new controls.

Tests: 3 new (default-create → richText, explicit-richText-create
seeds <w:richText/>, wrap → richText + seeds <w:richText/>).
42 wrappers + 1189 conformance + 1398 document-api pass.
@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot Bot commented May 14, 2026

🎉 This PR is included in @superdoc-dev/mcp v0.3.0-next.90

The release is available on GitHub release

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot Bot commented May 14, 2026

🎉 This PR is included in vscode-ext v2.3.0-next.135

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot Bot commented May 14, 2026

🎉 This PR is included in @superdoc-dev/react v1.2.0-next.133

The release is available on GitHub release

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot Bot commented May 14, 2026

🎉 This PR is included in superdoc-cli v0.8.0-next.105

The release is available on GitHub release

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot Bot commented May 14, 2026

🎉 This PR is included in superdoc-sdk v1.8.0-next.89

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot Bot commented May 14, 2026

🎉 This PR is included in superdoc v1.30.0-next.87

The release is available on GitHub release

msviderok pushed a commit to msviderok/superdoc that referenced this pull request May 16, 2026
… fixture (SD-3131) (superdoc-dev#3275)

* feat(content-controls): support richText type and resolve typeless SDTs per ECMA-376 (SD-3131)

Adds 'richText' to ContentControlType so SuperDoc resolves typeless w:sdt
SDTs to 'richText' per ECMA-376 §17.5.2.26 ("If no type element is
specified, then the nearest ancestor structured document tag shall be
of type richText"). Before, typeless SDTs - which Word emits for
ContentControls.Add(0, range) and the default Add($null, range) - fell
through to 'unknown', blocking customers from inspecting Word-authored
controls by type.

- Detection: typeless sdtPr and explicit <w:richText/> both resolve to
  'richText'. Unmodeled type children (w:equation, w:picture, w:citation,
  w:bibliography, w:docPartList) still return null so resolveControlType
  yields 'unknown' - keeping 'unknown' as "unsupported or unrecognized",
  not "typeless rich-text control".
- Export/create maps: richText: 'w:richText' in both CONTROL_TYPE_SDT_PR_ELEMENTS
  (wrappers) and CONTROL_TYPE_ELEMENT_MAP (converter), plus a 'richText'
  case in buildDefaultTypeSdtPrElement so create.contentControl and setType
  produce valid OOXML.
- text.setValue stays restricted to actual w:text controls.

Tests: 22 in handle-structured-content-node.test.js (10 new), 39 in
content-controls-wrappers, 1189 in conformance - all pass.

* feat(demos): regenerate NDA fixture with plain-text inline fields (SD-3131)

The contract-templates demo fixture had inline smart fields authored as
default rich-text controls (typeless sdtPr), which is why none of them
detected as 'text' - they fell through to 'unknown' before SD-3131 and
resolve as 'richText' after.

Regenerated nda-template.docx with ContentControls.Add(1, range) for the
7 inline smart fields (now <w:text/> in sdtPr, controlType: 'text') while
leaving the 6 block clauses as typeless (controlType: 'richText').
Verified unzip: 13 SDTs total, 7 <w:text/>, 0 explicit <w:richText/>.

Switched per-occurrence field update from replaceContent to text.setValue
to exercise the typed API. Clause replacement stays on replaceContent
because rich-text controls don't have a typed setter.

README/JSDoc updated: "seven inline plain-text + six block rich-text"
(was "thirteen plain-text"). Honest limits section updated to reflect
the API split.

Browser smoke verified: receivingParty fan-out updates 2/2 occurrences
(header + nested), clause replacement decrements "updates available"
correctly, summary line accurate.
msviderok pushed a commit to msviderok/superdoc that referenced this pull request May 16, 2026
…t unknown (SD-3139) (superdoc-dev#3281)

Follow-up to SD-3131 (superdoc-dev#3275). After that PR, imported typeless w:sdtPr
correctly resolves to controlType: 'richText' per ECMA-376 §17.5.2.26,
but newly created controls via create.contentControl (no controlType)
and contentControls.wrap still defaulted to 'unknown'. Customers
filtering contentControls.list() by type saw different results before
vs after save/reopen — the same typeless OOXML round-tripped from
'unknown' to 'richText'.

- createWrapper: collapse three `?? 'unknown'` into a single
  `?? 'richText'` local.
- wrapWrapper: explicitly seed controlType, type, and a default
  sdtPr with <w:richText/> on the wrapper attrs (previously
  controlType was unset entirely, leaving resolveControlType to
  fall back to 'unknown').

Newly created controls now emit explicit <w:richText/> in sdtPr for
unambiguous engine state and exported markup. Imported typeless
Word-authored SDTs preserve their original raw sdtPr (import path
unchanged).

Per the SD-3131 design, 'unknown' keeps its meaning of "unsupported
or unrecognized type" — it's no longer the default for new controls.

Tests: 3 new (default-create → richText, explicit-richText-create
seeds <w:richText/>, wrap → richText + seeds <w:richText/>).
42 wrappers + 1189 conformance + 1398 document-api pass.
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.

2 participants