/migrate-mdc-to-comark
Port a legacy MDC-based PR onto the new comark-based `main`. Use when a contributor's PR was opened against the old `@nuxtjs/mdc` API (any commit before `feat(mdc): comark migration #355`) and now needs to be rebased / merged with the current Nuxt Studio main branch, which uses
$ npx -y skills add nuxt-content/nuxt-studio --skill migrate-mdc-to-comark --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/migrate-mdc-to-comark
Context preview
The summary Claude sees to decide when to auto-load this skill.
Port a legacy MDC-based PR onto the new comark-based `main`. Use when a contributor's PR was opened against the old `@nuxtjs/mdc` API (any commit before `feat(mdc): comark migration #355`) and now needs to be rebased / merged with the current Nuxt Studio main branch, which uses
SKILL.md
migrate-mdc-to-comark.SKILL.mdname: migrate-mdc-to-comark
description: Port a legacy MDC-based PR onto the new comark-based `main`. Use when a contributor's PR was opened against the old `@nuxtjs/mdc` API (any commit before `feat(mdc): comark migration #355`) and now needs to be rebased / merged with the current Nuxt Studio main branch, which uses `comark` everywhere.
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
Migrating a Legacy MDC PR to Comark
Since commit `5464103e feat(mdc): comark migration (#355)` (merged 2026-05-13), Nuxt Studio's `main` branch no longer uses `@nuxtjs/mdc`. The entire conversion layer, document utilities, and TipTap bridges were rewritten on top of [`comark`](https://github.com/nuxt-content/comark).
Any PR opened against `main` **before** this commit will conflict on merge. Do **not** simply resolve the conflicts mechanically — most legacy API calls have semantic equivalents in comark that must be re-implemented.
This skill is the reverse of the previous "merge-main-on-comark" skill: the comark branch is now `main`, and legacy PRs need to be ported forward.
1. Identify whether the PR is legacy
Run these checks at the root of the PR branch:
# Legacy imports — any match means the PR predates the comark migration
grep -rE "from '@nuxtjs/mdc'|from 'remark-mdc'|from 'unist-util-visit'|from '@nuxt/content/runtime'" src/
# Legacy function names
grep -rE "generateDocumentFromContent|generateDocumentFromMarkdownContent|generateDocumentFromYAMLContent|generateDocumentFromJSONContent|generateContentFromDocument|generateContentFromMarkdownDocument|generateContentFromYAMLDocument|generateContentFromJSONDocument|mdcToTiptap|tiptapToMDC|compressTree|decompressTree|parseMarkdown|stringifyMarkdown|parseFrontMatter|stringifyFrontMatter|remarkEmojiPlugin" src/
# Legacy file references (these files were deleted on main)
ls src/app/src/utils/tiptap/mdcToTiptap.ts src/app/src/utils/tiptap/tiptapToMdc.ts 2>/dev/null
If anything matches, the PR is legacy.
2. Conceptual Mapping: Legacy MDC → Comark
Types
| Legacy (pre-#355) | Comark (current main) | |---|---| | `MDCRoot`, `MarkdownRoot` from `@nuxt/content` | `ComarkTree` from `comark` | | `MDCNode`, `MDCElement`, `MDCText`, `MDCComment` from `@nuxtjs/mdc` | `ComarkNode`, `ComarkElement`, `ComarkComment` from `comark` | | MDC element: `{ type: 'element', tag: 'note', props: {…}, children: [...] }` | Tuple: `['note', attrs, ...children]` | | MDC text: `{ type: 'text', value: 'hello' }` | Plain string: `'hello'` | | MDC comment: `{ type: 'comment', value: 'txt' }` | Tuple: `[null, {}, 'txt']` |
Functions (document utilities)
Located in `src/module/src/runtime/utils/document/generate.ts`:
| Legacy | Comark | |---|---| | `generateDocumentFromContent(id, content, opts)` | `documentFromContent(id, content, opts)` | | `generateDocumentFromMarkdownContent(...)` | `documentFromMarkdownContent(...)` | | `generateDocumentFromYAMLContent(...)` | `documentFromYAMLContent(...)` | | `generateDocumentFromJSONContent(...)` | `documentFromJSONContent(...)` | | `generateContentFromDocument(doc)` | `contentFromDocument(doc)` | | `generateContentFromMarkdownDocument(doc)` | `contentFromMarkdownDocument(doc)` | | `generateContentFromYAMLDocument(doc)` | `contentFromYAMLDocument(doc)` | | `generateContentFromJSONDocument(doc)` | `contentFromJSONDocument(doc)` |
The shape `generate-X-from-Y` was reversed to `Y-from-X` for symmetry.
TipTap bridges
Located in `src/app/src/utils/tiptap/`:
| Legacy file (deleted on main) | Comark replacement | |---|---| | `mdcToTiptap.ts` | `comarkToTiptap.ts` | | `tiptapToMdc.ts` | `tiptapToComark.ts` | | `mdcToTiptap(body, frontmatterData, opts)` | `comarkToTiptap(tree, opts)` — options collapsed to a single object; frontmatter is part of the tree | | `tiptapToMDC(json, opts) → { body, data }` | `tiptapToComark(json, opts) → ComarkTree` — returns one tree with `frontmatter` embedded |
Parser / stringifier
| Legacy | Comark | |---|---| | `import { parseMarkdown } from '@nuxtjs/mdc/runtime/parser/index'` | `import { parse } from 'comark'` | | `import { stringifyMarkdown } from '@nuxtjs/mdc/runtime'` | `import { renderMarkdown } from 'comark/render'` | | `parseFrontMatter` / `stringifyFrontMatter` from `remark-mdc` | `js-yaml` (`yaml.load` / `yaml.dump`) for pure YAML/JSON files; for Markdown, frontmatter is now embedded in `ComarkTree.frontmatter` | | `compressTree` / `decompressTree` from `@nuxt/content/runtime` | No longer used in app code — see §4 "Legacy bridge" | | `visit` from `unist-util-visit` | Walk the tuple tree manually using the helpers from `src/app/src/utils/comark.ts` |
Plugin / option shape
// Legacy
parseMarkdown(content, {
contentHeading: …,
highlight: { theme },
remark: {
plugins: {
'emoji': { instance: remarkEmojiPlugin },
'remark-mdc': { options: { autoUnwrap: true } },
},
},
})
// Comark
import { parse } from 'comark'
import comarkEmoji from 'comark/plugins/emoji'
import highlight from 'comark/plugins/highlight'
import tocPlugin from 'comark/plugins/toc'
parse(content, {
autoClose: false,
autoUnwrap: true,
plugins: [
comarkEmoji(),
highlight({ themes: { default, dark, light } }),
tocPlugin({ depth: 2, searchDepth: 2, title: '', links: [] }),
],
})For rendering back to markdown:
// Legacy
const markdown = await stringifyMarkdown(body, data, {
frontMatter: { options: { lineWidth: 0 } },
plugins: { remarkMDC: { options: { autoUnwrap: true } } },
})
// Comark
const markdown = await renderMarkdown(tree, {
blockAttributesStyle: 'frontmatter',
components: { br: () => ':br' },
})Dependencies (`package.json`)
Removed from runtime deps: `@nuxtjs/mdc`, `remark-mdc` (moved to devDeps), `unist-util-visit`.
Added: `comark` (pulled from `pkg.pr.new` while it stabilises). `js-yaml` is now used directly for YAML.
`minimark` is also a devDep only — do not import it in runtime code.
If the
Read more
name: migrate-mdc-to-comark description: Port a legacy MDC-based PR onto the new comark-based `main`. Use when a contributor's PR was opened against the old `@nuxtjs/mdc` API (any commit before `feat(mdc): comark migration #355`) and now needs to be rebased / merged with the current Nuxt Studio main branch, which uses `comark` everywhere. allowed-tools: Read, Write, Edit, Glob, Grep, Bash
Migrating a Legacy MDC PR to Comark
Since commit `5464103e feat(mdc): comark migration (#355)` (merged 2026-05-13), Nuxt Studio's `main` branch no longer uses `@nuxtjs/mdc`. The entire conversion layer, document utilities, and TipTap bridges were rewritten on top of [`comark`](https://github.com/nuxt-content/comark).
Any PR opened against `main` **before** this commit will conflict on merge. Do **not** simply resolve the conflicts mechanically — most legacy API calls have semantic equivalents in comark that must be re-implemented.
This skill is the reverse of the previous "merge-main-on-comark" skill: the comark branch is now `main`, and legacy PRs need to be ported forward.
1. Identify whether the PR is legacy
Run these checks at the root of the PR branch:
# Legacy imports — any match means the PR predates the comark migration grep -rE "from '@nuxtjs/mdc'|from 'remark-mdc'|from 'unist-util-visit'|from '@nuxt/content/runtime'" src/ # Legacy function names grep -rE "generateDocumentFromContent|generateDocumentFromMarkdownContent|generateDocumentFromYAMLContent|generateDocumentFromJSONContent|generateContentFromDocument|generateContentFromMarkdownDocument|generateContentFromYAMLDocument|generateContentFromJSONDocument|mdcToTiptap|tiptapToMDC|compressTree|decompressTree|parseMarkdown|stringifyMarkdown|parseFrontMatter|stringifyFrontMatter|remarkEmojiPlugin" src/ # Legacy file references (these files were deleted on main) ls src/app/src/utils/tiptap/mdcToTiptap.ts src/app/src/utils/tiptap/tiptapToMdc.ts 2>/dev/null
If anything matches, the PR is legacy.
2. Conceptual Mapping: Legacy MDC → Comark
Types
| Legacy (pre-#355) | Comark (current main) | |---|---| | `MDCRoot`, `MarkdownRoot` from `@nuxt/content` | `ComarkTree` from `comark` | | `MDCNode`, `MDCElement`, `MDCText`, `MDCComment` from `@nuxtjs/mdc` | `ComarkNode`, `ComarkElement`, `ComarkComment` from `comark` | | MDC element: `{ type: 'element', tag: 'note', props: {…}, children: [...] }` | Tuple: `['note', attrs, ...children]` | | MDC text: `{ type: 'text', value: 'hello' }` | Plain string: `'hello'` | | MDC comment: `{ type: 'comment', value: 'txt' }` | Tuple: `[null, {}, 'txt']` |
Functions (document utilities)
Located in `src/module/src/runtime/utils/document/generate.ts`:
| Legacy | Comark | |---|---| | `generateDocumentFromContent(id, content, opts)` | `documentFromContent(id, content, opts)` | | `generateDocumentFromMarkdownContent(...)` | `documentFromMarkdownContent(...)` | | `generateDocumentFromYAMLContent(...)` | `documentFromYAMLContent(...)` | | `generateDocumentFromJSONContent(...)` | `documentFromJSONContent(...)` | | `generateContentFromDocument(doc)` | `contentFromDocument(doc)` | | `generateContentFromMarkdownDocument(doc)` | `contentFromMarkdownDocument(doc)` | | `generateContentFromYAMLDocument(doc)` | `contentFromYAMLDocument(doc)` | | `generateContentFromJSONDocument(doc)` | `contentFromJSONDocument(doc)` |
The shape `generate-X-from-Y` was reversed to `Y-from-X` for symmetry.
TipTap bridges
Located in `src/app/src/utils/tiptap/`:
| Legacy file (deleted on main) | Comark replacement | |---|---| | `mdcToTiptap.ts` | `comarkToTiptap.ts` | | `tiptapToMdc.ts` | `tiptapToComark.ts` | | `mdcToTiptap(body, frontmatterData, opts)` | `comarkToTiptap(tree, opts)` — options collapsed to a single object; frontmatter is part of the tree | | `tiptapToMDC(json, opts) → { body, data }` | `tiptapToComark(json, opts) → ComarkTree` — returns one tree with `frontmatter` embedded |
Parser / stringifier
| Legacy | Comark | |---|---| | `import { parseMarkdown } from '@nuxtjs/mdc/runtime/parser/index'` | `import { parse } from 'comark'` | | `import { stringifyMarkdown } from '@nuxtjs/mdc/runtime'` | `import { renderMarkdown } from 'comark/render'` | | `parseFrontMatter` / `stringifyFrontMatter` from `remark-mdc` | `js-yaml` (`yaml.load` / `yaml.dump`) for pure YAML/JSON files; for Markdown, frontmatter is now embedded in `ComarkTree.frontmatter` | | `compressTree` / `decompressTree` from `@nuxt/content/runtime` | No longer used in app code — see §4 "Legacy bridge" | | `visit` from `unist-util-visit` | Walk the tuple tree manually using the helpers from `src/app/src/utils/comark.ts` |
Plugin / option shape
// Legacy
parseMarkdown(content, {
contentHeading: …,
highlight: { theme },
remark: {
plugins: {
'emoji': { instance: remarkEmojiPlugin },
'remark-mdc': { options: { autoUnwrap: true } },
},
},
})
// Comark
import { parse } from 'comark'
import comarkEmoji from 'comark/plugins/emoji'
import highlight from 'comark/plugins/highlight'
import tocPlugin from 'comark/plugins/toc'
parse(content, {
autoClose: false,
autoUnwrap: true,
plugins: [
comarkEmoji(),
highlight({ themes: { default, dark, light } }),
tocPlugin({ depth: 2, searchDepth: 2, title: '', links: [] }),
],
})For rendering back to markdown:
// Legacy
const markdown = await stringifyMarkdown(body, data, {
frontMatter: { options: { lineWidth: 0 } },
plugins: { remarkMDC: { options: { autoUnwrap: true } } },
})
// Comark
const markdown = await renderMarkdown(tree, {
blockAttributesStyle: 'frontmatter',
components: { br: () => ':br' },
})Dependencies (`package.json`)
Removed from runtime deps: `@nuxtjs/mdc`, `remark-mdc` (moved to devDeps), `unist-util-visit`.
Added: `comark` (pulled from `pkg.pr.new` while it stabilises). `js-yaml` is now used directly for YAML.
`minimark` is also a devDep only — do not import it in runtime code.
If the
![npm version][npm-version-href] ![npm downloads][npm-downloads-href] ![License][license-href] Visual edition in production for your Nuxt Content website.
Repo: nuxt-content/nuxt-studio

