Skip to content

fix(tiptap): preserve YAML array-of-objects component props in round-trip#485

Open
hendrikheil wants to merge 2 commits into
nuxt-content:mainfrom
hendrikheil:fix/normalize-props-type-faithful
Open

fix(tiptap): preserve YAML array-of-objects component props in round-trip#485
hendrikheil wants to merge 2 commits into
nuxt-content:mainfrom
hendrikheil:fix/normalize-props-type-faithful

Conversation

@hendrikheil

@hendrikheil hendrikheil commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Problem

Editing a component with YAML block array-of-objects props in Studio corrupts them on save:

::authors
---
authorsOne:
  - name: John Doe
    role: contributor
---
::

becomes:

::authors{:authorsOne="[{"name":"John Doe","role":"contributor"}]"}
::

Closes #457. Supersedes #477.

Root cause (two bugs)

1. propsMDCToComark didn't unwrap @nuxtjs/mdc binding-syntax props

@nuxtjs/mdc serialises non-primitive YAML block props (arrays, objects) as Vue binding syntax — key :authorsOne (: prefix) with a JSON-stringified string value "[{\"name\":\"John\"}]". The propsMDCToComark bridge in legacy.ts passed these through unchanged, so the entire pipeline carried a JSON string instead of a real JavaScript array. When comark rendered the ComarkElement back to markdown, it emitted the inline binding form {:authorsOne="[...]"} — exactly the corruption seen in production.

2. normalizeProps was built on a "all props are strings" assumption

String(value).trim() on every prop value was wrong in principle. Comark attrs are already the correct type from the parser (string for inline attrs, number/boolean/array/object for YAML block attrs), and comark's renderer handles all these types natively. The stringification was a latent correctness bug even when it wasn't the immediate cause of corruption.

Fix

legacy.tspropsMDCToComark (root cause fix): detect :key binding props produced by @nuxtjs/mdc and JSON.parse their values back to real JavaScript values, stripping the : prefix.

props.tsnormalizeProps (type correctness): rewritten to pass values through untouched, returning ComarkElementAttributes directly instead of Array<[string, unknown]>. Key trimming and empty-key filtering are preserved — they guard against user-typed custom props from the form editor which may have stray whitespace.

tiptapToComark.ts: both call sites updated — Object.fromEntries(propsArray) middleman removed since normalizeProps now returns the attrs object directly.

Test plan

  • Unit tests for normalizeProps: values pass through as their original type (number stays number, array stays array, null stays null); key trimming and empty-key filtering work
  • Unit tests for propsMDCToComark: :key bindings are unwrapped to real JS values; full round-trip through the legacy bridge renders YAML block format, not inline JSON
  • Integration test: full comark → TipTap → comark → markdown round-trip with YAML array-of-objects props preserves structure and renders correct YAML block output
  • All 347 tests pass

🤖 Generated with Claude Code

…trip

## Root cause (two bugs)

**1. `propsMDCToComark` didn't unwrap `@nuxtjs/mdc` binding-syntax props**

`@nuxtjs/mdc` serialises non-primitive YAML block props (arrays of objects)
as Vue binding syntax — key `:authorsOne` with a JSON-stringified string
value `"[{\"name\":\"John\"}]"`. `propsMDCToComark` passed these through
unchanged, so the entire pipeline carried a JSON string instead of a real
array. This is the actual root cause of the inline-JSON corruption on save.

**2. `normalizeProps` was built on a "all props are strings" assumption**

`String(value).trim()` on every prop value was both wrong for complex values
and unnecessary — comark attrs are already the correct type and comark's
renderer handles numbers, booleans, arrays, and objects natively.

## Fixes

- `legacy.ts` — `propsMDCToComark`: detect `:key` binding props and
  JSON.parse their values back to real JavaScript, stripping the `:` prefix.
- `props.ts` — `normalizeProps`: rewritten to pass values through untouched,
  returning `ComarkElementAttributes` (the real domain type) directly instead
  of `Array<[string, unknown]>`. Key trimming and empty-key filtering are
  kept — they guard against user-typed custom props from the form editor.
- `tiptapToComark.ts`: both call sites updated, `Object.fromEntries` removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

@hendrikheil is attempting to deploy a commit to the Nuxt Team on Vercel.

A member of the Team first needs to authorize it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@hendrikheil hendrikheil self-assigned this Jun 13, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jun 13, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/nuxt-studio@485

commit: fc9baea

@hendrikheil hendrikheil requested a review from larbish June 13, 2026 12:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Comark re-rendering causes invalid .md syntax

1 participant