Skip to content

refactor: support multiple markdown flavors in marimo export md#9586

Open
peter-gy wants to merge 20 commits into
marimo-team:mainfrom
peter-gy:ptr/flavored-markdown-exports
Open

refactor: support multiple markdown flavors in marimo export md#9586
peter-gy wants to merge 20 commits into
marimo-team:mainfrom
peter-gy:ptr/flavored-markdown-exports

Conversation

@peter-gy
Copy link
Copy Markdown
Contributor

@peter-gy peter-gy commented May 18, 2026

📝 Summary

Refactors the internals of marimo export md so markdown export can target multiple markdown flavors through a shared renderer interface. Supports https://github.com/marimo-team/quarto-marimo/ and https://github.com/marimo-team/jupyter-book-marimo.

Prior to this, markdown export only needed to distinguish between marimo's default markdown format (pymdown) and Quarto, so an is_qmd branch in the exporter was simple enough to handle markdown output differences. As we add MyST /
Jupyter Book support, that does not scale well as each target has its own way to represent executable cells, page-level metadata, callouts, options, and tabsets.

This PR introduces explicit markdown flavors for:

  • pymdown (already existed): marimo's existing markdown authoring/export surface
  • qmd (already existed): Quarto markdown
  • mystmd (new addition, hence the enhancement label on this PR): MyST markdown, including the {marimo} / {marimo-config} surface used by marimo-team/jupyter-book-marimo#1

The source authoring surface is still assumed to be marimo markdown / pymdown. The flavor layer maps from that source representation into each target format.

Details

This refactor sets up marimo core to support markdown flavor-specific rendering of callouts, tabs, etc. It allows us to first build a small intermediate export document instead of directly concatenating strings and if-else branching on detected markdown flavor.

image

As part of a follow-up, flavor implementations can then decide how to render:

  • top-level metadata, e.g. YAML frontmatter versus {marimo-config} for mystmd
  • executable code cell syntax
  • cell options
  • PyMdown admonitions / callouts
  • tabsets
  • unknown directive fallbacks

For Quarto, PyMdown admonitions are mapped to Quarto callouts and tab groups are mapped to panel tabsets.
The QMD exporter no longer injects filters: marimo-team/marimo, since the Quarto marimo engine extension now auto-detects marimo code blocks.

For MyST, marimo cells are emitted as {marimo} directives and page-level execution config is emitted as {marimo-config}, because executable plugins do not currently get a clean frontmatter access path. The spelling remains unparsable throughout; no support for unparseable spelling to stay coherent and to ensure we do not express the same thing in multiple ways in the codebase.

Flavor Differences

PyMdown

https://facelessuser.github.io/pymdown-extensions/

/// Tip | Data sources panel

    Click the database "barrel" icon in the left toolbar
    to see all dataframes and in-memory tables that your notebook has access to.
///
Screenshot 2026-05-18 at 21 11 49

QMD

https://quarto.org/docs/authoring/markdown-basics.html

::: {.callout-tip title="Data sources panel"}

Click the database "barrel" icon in the left toolbar to see all dataframes and in-memory tables that your notebook has access to.

:::
Screenshot 2026-05-18 at 21 04 50

MyStmd

https://mystmd.org/

:::{tip} Data sources panel

Click the database "barrel" icon in the left toolbar to see all dataframes and in-memory
tables that your notebook has access to.

:::
Screenshot 2026-05-18 at 21 07 24

@vercel
Copy link
Copy Markdown

vercel Bot commented May 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment May 19, 2026 1:59pm

Request Review

@peter-gy peter-gy added the enhancement New feature or request label May 18, 2026
@github-actions github-actions Bot added bash-focus Area to focus on during release bug bash and removed enhancement New feature or request labels May 18, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 21 files

Architecture diagram
sequenceDiagram
    participant CLI as CLI (export md)
    participant API as Export API
    participant Pyodide as Pyodide Session
    participant Exporter as Exporter (export_as_md)
    participant Converter as MarimoConverter
    participant FlavorReg as Flavor Registry
    participant PymdownF as PymdownFlavor
    participant QmdF as QmdFlavor
    participant MystF as MystFlavor
    participant ToIR as Markdown → IR Converter
    participant FromIR as Markdown Export Builder
    participant App as InternalApp / Notebook IR

    Note over CLI,Pyodide: Entry points for markdown export

    CLI->>CLI: Parse --flavor (pymdown/qmd/mystmd)
    CLI->>Exporter: export_as_md(path, flavor)
    Exporter->>Converter: from_ir(ir).to_markdown(flavor)
    
    API->>Exporter: export_as_md(path, flavor)
    Exporter->>Converter: from_ir(ir).to_markdown(flavor)
    
    Pyodide->>Pyodide: export_markdown(request with flavor)
    Pyodide->>FromIR: convert_from_ir_to_markdown(ir, flavor)

    alt PyMdown flavor
        Converter->>FlavorReg: normalize_markdown_flavor("pymdown")
        FlavorReg-->>Converter: PymdownMarkdownFlavor
        Converter->>FromIR: _notebook_to_markdown_export_document(ir)
        FromIR->>App: Iterate cells
        App-->>FromIR: Code + metadata
        FromIR->>FromIR: Build MarkdownExportDocument with MarkdownCellBlock, CodeCellBlock
        FromIR-->>Converter: MarkdownExportDocument
        Converter->>PymdownF: render_document(document)
        PymdownF->>PymdownF: render_preamble (YAML frontmatter)
        PymdownF->>PymdownF: transform_blocks (passthrough)
        PymdownF->>PymdownF: render_code_cell (fenced code with .marimo class)
        PymdownF->>PymdownF: render_directive (/// blocks unchanged)
        PymdownF->>PymdownF: render_tab_set (consecutive /// tab blocks)
        PymdownF-->>Converter: Complete markdown string
    else Qmd flavor
        Converter->>FlavorReg: normalize_markdown_flavor("qmd")
        FlavorReg-->>Converter: QmdMarkdownFlavor
        Converter->>FromIR: _notebook_to_markdown_export_document(ir)
        FromIR->>App: Iterate cells
        App-->>FromIR: Code + metadata
        FromIR->>FromIR: Build MarkdownExportDocument (no filters injection)
        FromIR-->>Converter: MarkdownExportDocument
        Converter->>QmdF: render_document(document)
        QmdF->>QmdF: render_preamble (YAML frontmatter, no marimo filter)
        QmdF->>QmdF: transform_blocks (split pymdown blocks, group tabs)
        QmdF->>QmdF: render_code_cell (```{marimo .python} fence)
        QmdF->>QmdF: render_directive
        alt Known admonition → callout
            QmdF->>QmdF: :: {.callout-tip title="..."}
        else Unrecognized → pandoc div
            QmdF->>QmdF: :: {.details title="..." #id .class}
        end
        QmdF->>QmdF: render_tab_set (:: {.panel-tabset})
        QmdF-->>Converter: Complete markdown string
    else Myst flavor
        Converter->>FlavorReg: normalize_markdown_flavor("mystmd")
        FlavorReg-->>Converter: MystMarkdownFlavor
        Converter->>FromIR: _notebook_to_markdown_export_document(ir)
        FromIR->>App: Iterate cells
        App-->>FromIR: Code + metadata
        FromIR->>FromIR: Build MarkdownExportDocument with header/pyproject metadata
        FromIR-->>Converter: MarkdownExportDocument
        Converter->>MystF: render_document(document)
        MystF->>MystF: render_preamble
        MystF->>MystF: YAML frontmatter (title, etc.)
        MystF->>MystF: {marimo-config} block (header, pyproject)
        MystF->>MystF: transform_blocks (split pymdown blocks, group tabs)
        MystF->>MystF: render_code_cell (```{marimo} python with :options:)
        MystF->>MystF: render_directive (:::{tip} / :::{dropdown})
        MystF->>MystF: render_tab_set (::::{tab-set})
        MystF-->>Converter: Complete markdown string
    end

    Converter-->>Exporter: Markdown string
    Exporter-->>CLI/API: ExportResult with contents

    Note over ToIR,App: Import path (reverse direction)

    ToIR->>ToIR: convert_from_md_to_marimo_ir(markdown)
    ToIR->>App: Parse frontmatter, header
    ToIR->>ToIR: MystMarimoPreprocessor normalizes {marimo} directives
    ToIR->>ToIR: Extract options from :hide-code: style lines
    ToIR->>App: Build InternalApp with cell configurations
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread marimo/_convert/markdown/flavor/qmd.py Outdated
Comment thread marimo/_server/api/endpoints/export.py Outdated
Comment thread marimo/_convert/markdown/flavor/pymdown.py Outdated
Comment thread marimo/_convert/markdown/flavor/pymdown.py Outdated
Copy link
Copy Markdown
Collaborator

@dmadisetti dmadisetti left a comment

Choose a reason for hiding this comment

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

I had a couple reservations initially, but I think it's a good abstraction.

I think my major concern is handling admonitions differently. Ideally we don't want to change user code- so if anything I think they should be less of a parse concern and more of a render one.

Comment thread marimo/_cli/export/commands.py
Comment thread marimo/_convert/markdown/flavor/qmd.py Outdated
Comment thread marimo/_convert/markdown/to_ir.py Outdated
Comment thread marimo/_convert/markdown/to_ir.py Outdated
Comment thread marimo/_tutorials/markdown_format.md
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 9 files (changes from recent commits).

Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

Re-trigger cubic

Comment thread marimo/_convert/markdown/flavor/qmd.py Outdated
@peter-gy peter-gy force-pushed the ptr/flavored-markdown-exports branch from 5866953 to 5a79966 Compare May 19, 2026 08:11
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label May 19, 2026
@peter-gy
Copy link
Copy Markdown
Contributor Author

@dmadisetti as per our conversation I reduced the scope of this PR to carry only the flavor-based refactoring and the introduction of mystmd, without baking in callout/tabset etc. rendering-specific logic.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 10 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread marimo/_server/api/endpoints/export.py Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 2 comments.

Comment thread marimo/_server/api/endpoints/export.py
Comment thread marimo/_session/state/session_view.py Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 2 comments.

Comment thread marimo/_convert/markdown/to_ir.py
Comment thread marimo/_convert/markdown/flavor/pymdown.py
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 11 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread marimo/_convert/markdown/to_ir.py Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@peter-gy peter-gy force-pushed the ptr/flavored-markdown-exports branch from 7f579b0 to fc0a8bc Compare May 19, 2026 11:56
@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai Bot commented May 19, 2026

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@peter-gy peter-gy requested a review from mscolnick May 20, 2026 10:12
@peter-gy peter-gy changed the title feat: support multiple markdown flavors in marimo export md refactor: support multiple markdown flavors in marimo export md May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bash-focus Area to focus on during release bug bash enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants