Skip to content

fix: preserve recipe top level attributes in mdast#1324

Merged
HugoHSun merged 2 commits intonextfrom
hugo/cx-2848-recipe-mdx-component-inside-accordion-disappears-in-edit
Feb 13, 2026
Merged

fix: preserve recipe top level attributes in mdast#1324
HugoHSun merged 2 commits intonextfrom
hugo/cx-2848-recipe-mdx-component-inside-accordion-disappears-in-edit

Conversation

@HugoHSun
Copy link
Copy Markdown
Contributor

@HugoHSun HugoHSun commented Feb 6, 2026

PR App Fix CX-2848

🧰 Changes

This bug was caused by a mismatch between readmeComponents and readme-to-mdx when we deserialise the MDX string into Slate nodes.

  1. Editor receives MDX string from the backend (correct, with all attributes)
  2. Deserialize: MDX string → MDASTrenderingLibrary.mdast(doc) parses the string. During this, the readme-components plugin converts <Recipe slug="..." /> (an mdxJsxFlowElement) into a recipe node, storing attributes only in data.hProperties
    i.e.
    root
    └── mdxJsxFlowElement (name: "Accordion", attributes: [title, icon])
    ...........└── mdxJsxFlowElement (name: "Recipe", attributes: [slug, title])
    becomes
    root
    └── mdxJsxFlowElement (name: "Accordion", attributes: [title, icon])
    ...........└── recipe (data.hProperties: { slug, title }) ← attributes moved to hProperties
  3. Deserialize: MDAST → Slate nodesmdastToSlate walks the tree. The Accordion is an mdxJsxFlowElement → goes to JsxFlow.deserialize()
  4. JsxFlow needs to store the JSX as a string — so it calls mdx(node) to serialize the Accordion subtree back to a string. During this, the readme-to-mdx plugin converts the recipe child back to mdxJsxFlowElement, but looks for slug/title at the top level → not found → <Recipe />
    i.e.
    mdxJsxFlowElement (name: "Accordion", attributes: [title, icon])
    └── mdxJsxFlowElement (name: "Recipe", attributes: []) ← lost!

🧬 QA & Testing

@HugoHSun HugoHSun marked this pull request as ready for review February 6, 2026 07:57
Copy link
Copy Markdown
Contributor

@dannobytes dannobytes left a comment

Choose a reason for hiding this comment

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

@HugoHSun would you mind expanding the summary to include details about what the root cause of the problem is, what the proposed solution should be doing to fix it, and then any other edge-cases you can think of that are adjacent or related to it?

the customer reported this specifically for a recipe nested inside an accordian. but have you investigated deeper and checked to see if this is happening for other combinations of nested node types?

for example, what happens when you nest other block types like tables, images, code inside other components like Tabs, Cards, etc?

the fix here shouldn't just be solving the one very specific customer issue, but thinking about the more generalized problem of rendering a nested block inside a component

Comment on lines +31 to +33
const markdown = `<Accordion title="My Accordion" icon="fa-info-circle">
<Recipe slug="recipe-title-1" title="Another One" />
</Accordion>`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what happens when there's more than one <Recipe> inside the template? should we add another test for some other edge cases?

const tree = mdast(markdown);
const result = mdx(tree);

expect(result).toContain('<Recipe slug="recipe-title-1" title="Another One" />');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

does it make sense to only check for the presence of this? or should we make sure to check that the structure is also preserved? i.e. verify the same structure declared in the markdown test str.

} as Recipe;

parent.children[index] = mdNode;
} else if (node.name in types) {
Copy link
Copy Markdown
Contributor Author

@HugoHSun HugoHSun Feb 9, 2026

Choose a reason for hiding this comment

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

The recipe node went to this general handler which only keep the attributes in hProperties. But when we serialise MDAST to MDX in readmeToMdx we expect it to be in the top-level, see readme-to-mdx.ts line 54-76.

// Converts tutorial tiles to Recipe components in the migration process
  // Retaining this for backwards compatibility
  visit(tree, NodeTypes.tutorialTile, (tile, index, parent: Parent) => {
    const { ...attrs } = tile as Recipe;

    parent.children.splice(index, 1, {
      type: 'mdxJsxFlowElement',
      name: 'Recipe',
      attributes: toAttributes(attrs, ['slug', 'title']),
      children: [],
    });
  });

  visit(tree, NodeTypes.recipe, (tile, index, parent: Parent) => {
    const { ...attrs } = tile as Recipe;

    parent.children.splice(index, 1, {
      type: 'mdxJsxFlowElement',
      name: 'Recipe',
      attributes: toAttributes(attrs, ['slug', 'title']),
      children: [],
    });
  });

@HugoHSun
Copy link
Copy Markdown
Contributor Author

HugoHSun commented Feb 9, 2026

Update

  • Updated the description detailing the root cause
  • Changed tests to be more strict and added various other components to prevent regression
  • Also manually tested other components - I believe Recipe is the only component going through this pathway and have handler mismatches
image

@HugoHSun HugoHSun requested a review from dannobytes February 9, 2026 09:13
Copy link
Copy Markdown
Contributor

@dannobytes dannobytes left a comment

Choose a reason for hiding this comment

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

ty!

@HugoHSun HugoHSun merged commit 98f466b into next Feb 13, 2026
11 checks passed
@HugoHSun HugoHSun deleted the hugo/cx-2848-recipe-mdx-component-inside-accordion-disappears-in-edit branch February 13, 2026 00:26
rafegoldberg pushed a commit that referenced this pull request Feb 13, 2026
## Version 13.1.2
### 🛠 Fixes & Updates

* **magic blocks:** ensure newline characters processed as hard breaks ([#1329](#1329)) ([bb37d62](bb37d62))
* fix callout magic blocks when rendered directly below a list item ([#1331](#1331)) ([de2b82a](de2b82a))
* fix rendering content in table magic blocks ([#1318](#1318)) ([0ea1cfc](0ea1cfc))
* preserve recipe top level attributes in mdast ([#1324](#1324)) ([98f466b](98f466b))
* **stripComments:** properly pass in the micromark extensions ([#1335](#1335)) ([7ec9d46](7ec9d46))
* **mdxish:** properly terminate html blocks ([#1336](#1336)) ([d221861](d221861))

<!--SKIP CI-->
@rafegoldberg
Copy link
Copy Markdown
Contributor

This PR was released!

🚀 Changes included in v13.1.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants