Skip to content

fix: inherit run formatting when inserting inline structured content (SD-1421)#2501

Open
luccas-harbour wants to merge 2 commits intomainfrom
luccas/sd-1421-bug-when-you-add-sdt-fields-text-should-match-font
Open

fix: inherit run formatting when inserting inline structured content (SD-1421)#2501
luccas-harbour wants to merge 2 commits intomainfrom
luccas/sd-1421-bug-when-you-add-sdt-fields-text-should-match-font

Conversation

@luccas-harbour
Copy link
Contributor

Summary

  • When inserting an inline SDT field in the middle of formatted text, the inserted text now inherits the surrounding font/formatting instead of appearing unstyled
  • Fixes a structural issue where insertStructuredContentInline produced run > structuredContent > run > text nesting — the SDT is now placed at paragraph level between split sibling runs
  • Handles ranged selections correctly by removing the selected text when splitting the parent run

Problem

insertStructuredContentInline used a bare tr.replaceWith(from, to, sdtNode) to insert the SDT. When the cursor was inside a run, ProseMirror placed the SDT as a child of the run (since run has content: 'inline*'). Then wrapTextInRunsPlugin normalized this into run{null} > SDT > run > text, creating unwanted nesting with an empty outer run.

Additionally, the SDT's text content was created without any marks or run wrapping, so it appeared unformatted regardless of the surrounding text style.

Solution

Two changes to insertStructuredContentInline:

  1. Formatting copy — resolves marks and runProperties from the cursor position via getFormattingStateAtPos and wraps the text in a properly formatted run inside the SDT
  2. Run splitting — when the cursor is inside a run, manually splits it into left/right halves and inserts [leftRun, sdtNode, rightRun] at the paragraph level, producing the correct flat structure

@linear
Copy link

linear bot commented Mar 20, 2026

Copy link

@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: 7747620487

ℹ️ 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".

// When the user has a ranged selection, cut the right half from
// the end of the selection so the selected text is removed.
const $to = state.doc.resolve(to);
const endOffset = $to.parent === parentRun ? $to.parentOffset : startOffset;

Choose a reason for hiding this comment

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

P1 Badge Replace the entire selection when it crosses run boundaries

If the selection starts inside a run but ends in a different sibling run or paragraph, $to.parent !== parentRun makes endOffset fall back to startOffset, and this branch still replaces only runStart..runEnd. In that case insertStructuredContentInline inserts the new SDT next to the original text but leaves the rest of the selected content untouched, so a cross-run selection is no longer actually replaced. The old replaceWith(from, to, node) path handled that case correctly.

Useful? React with 👍 / 👎.

// from wrapping the SDT itself.
const runType = schema.nodes.run;
if (runType && !options.json && content.isText) {
const formattingState = getFormattingStateAtPos(state, from, editor);

Choose a reason for hiding this comment

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

P2 Badge Pass stored marks into formatting resolution

This now copies formatting with getFormattingStateAtPos(state, from, editor), but on a collapsed selection that helper ignores state.storedMarks unless they are passed explicitly. If a user toggles bold/font size on an empty cursor and then inserts an inline field, the new structuredContent will inherit the surrounding run instead of the pending toolbar formatting, which is inconsistent with normal text insertion.

Useful? React with 👍 / 👎.

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.

1 participant