Panache & md2rich: the Markdown lint-then-publish workflow
Panache is a fresh Rust Markdown formatter and linter that went up on Hacker News on August 22, 2026 and started on GitHub the day before. It is a quality-control layer for your Markdown source: it reflows, reformats, and flags issues so the plain-text file is consistent and valid before it goes anywhere. md2rich is the layer that runs at the other end of the pipeline — converting that finished Markdown into rich text for LinkedIn, X Articles, Medium, or Notion, entirely in the browser. This article walks through a lint-then-publish workflow: clean the source with Panache, then convert it to rich text with md2rich.
Most Markdown publishing advice stops at "write in Markdown, paste into the platform." What it skips is the quality of the source itself. If your draft has inconsistent line lengths, a mix of - and * list markers, headings that skip from ## to ####, or trailing whitespace at the end of lines, all of that noise is preserved as you convert to rich text. The result is a paste that looks a little off in the editor — and the fix, oddly, is to treat your Markdown as something with a quality bar, not just a scratch format.
What Panache actually is
Panache describes itself as a "language server, formatter, and linter for Markdown, Quarto, and R Markdown." It is written in Rust, MIT-licensed, and lives on GitHub at jolars/panache. The key engineering detail is that it does not just regex-match lines the way older Markdown tidy-uppers do. It parses your document into a lossless concrete syntax tree, then rewrites it from that tree, so it understands the structure — headings, lists, tables, code fences, links — rather than the literal characters.
That structural understanding is what makes its formatting stable. Panache normalizes line width (reflowing paragraphs to a set column), unifies heading and list styles, strips trailing whitespace, and tidies fenced code blocks. Because the parse is lossless, it preserves your content exactly; only the formatting changes. And because it is a proper linter, it reports problems rather than silently guessing — which is what makes it safe to drop into an automated quality gate.
The Panache CLI, in practice
Panache is installed the usual ways for a modern Rust tool — cargo install --locked panache, brew install panache, or through npm — so it fits a laptop workflow or a CI pipeline. The two commands you reach for every day:
# Format a single file in place
panache format post.md
# Check without changing anything — exits non-zero if files need formatting
panache format --check post.md
# Lint a glob of drafts
panache lint **/*.md
# Pipe stdin through the formatter
cat post.md | panache format
The --check flag is the heart of a quality gate: it reports which files need reformatting and exits non-zero if any do, without mutating them. That is exactly what you want in a pre-commit hook or a GitHub Action, because it fails the run loudly when a draft drifts off standard instead of quietly rewriting it. For anything stricter, Panache also has an LSP mode so your editor formats and lints as you type, plus a config file (.panache.toml or panache.toml) for project-wide rules. And because it plugs into pre-commit and ships a jolars/panache-action@v1 GitHub Action, "the draft must be clean before it is committed or merged" becomes a one-line rule instead of a habit you have to remember.
Panache also extends beyond plain Markdown. Because it targets the Quarto and R Markdown ecosystems too, it can delegate formatting of fenced code blocks to external formatters configured in [formatters] — for example python = ["isort", "black"] or javascript = "prettier" — moving further toward "one command keeps the whole project tidy." For a writer, that is neat; for a solo operator with a docs repo or a newsletter, it turns Markdown hygiene into something automatic.
Why lint the source before you convert it
The reason a linter matters to a publishing workflow is that a Markdown-to-rich-text converter is faithful: it carries your formatting choices into the output. Feed it clean, well-formed Markdown and you get predictable headings, unified lists, tidy code blocks, and clean paragraphs. Feed it a draft with skipped heading levels and mixed list styles and the mess carries through to the rich text paste box, where it is much more annoying to dig out by hand.
So a sensible pipeline puts a quality gate before the conversion step. A rough block that a linter would normalise:
## Why clean Markdown matters
Long run-on paragraph line that goes on and on well past
the preferred line width and makes the file hard to
read and the converted output unpredictable.
- an item with a dash
* an item with a star
+ an item with a plus
#### A heading that skipped a level
Panache's formatter reflows the long paragraph, normalizes all three list markers to one style, fixes the heading to the correct level, and strips any trailing whitespace — giving you a consistent source file where the structure reads correctly and the conversion behaves. It is the difference between fighting the paste box and pasting clean rich text the first time.
The full lint-then-publish loop
Pulling it together, a complete publish run is only a few steps, and the private draft never leaves your machine:
- Draft in Markdown. Write your post in whatever editor you like; the source is a plain-text file.
- Lint and format with Panache. Run
panache format --check post.md, thenpanache format post.mdto apply it. Add a pre-commit hook or the GitHub Action if the draft lands in a repo. - Convert to rich text with md2rich. Open md2rich, paste the clean Markdown, and get rich text with real headings, bold, lists, and tables.
- Paste and publish. Drop the rich text into LinkedIn, X Articles, Medium, or Notion. No raw Markdown left in the paste box, no formatting lost.
That ordering matters. Panache runs on the source because it edits text; md2rich runs at the moment of publishing because it creates the final rich-text shape. They never overlap, and they both value the same thing — keeping your words as durable, private, plain-text files that can be turned into any output the platform expects.
Panache and md2rich in the same workflow
| Panache | md2rich | |
|---|---|---|
| What it does | Formats, reflows, and lints the Markdown source | Converts Markdown to rich text for publishing |
| Stage in the workflow | Quality gate on the source, before you ship | The conversion step, at the moment you publish |
| Where it runs | Local Rust CLI, LSP, pre-commit, GitHub Action | Browser, client-side, nothing uploaded |
| Output | Consistent, valid Markdown | Rich text ready to paste into a platform |
| Why combine them | Clean source makes the conversion predictable | Takes the clean source and ships it as formatted output |
Panache is brand new (the HN story and its heaviest activity landed August 21-22, 2026), so treat it as a fast-moving early tool rather than a settled standard. But the shape is already right: a lossless structural formatter that doubles as a CI-friendly linter is exactly the tool a Markdown-first publishing workflow has been missing on the authoring side.
Write clean. Convert once. Publish everywhere.
Keep your drafts as plain Markdown, run Panache on them so the source is consistent, then paste into md2rich when it is time to post. It runs entirely in your browser, uploads nothing, and turns clean Markdown into polished LinkedIn, X, Medium, or Notion posts in seconds.
Try md2rich — it's freeFAQ
What is Panache?
Panache is an open-source language server, formatter, and linter for Markdown, Quarto, and R Markdown, written in Rust and published under the MIT license by jolars. It parses Markdown into a lossless concrete syntax tree, then rewrites it with consistent formatting: normalized line width, consistent heading and list style, removed trailing whitespace, and tidy fenced code blocks. It runs as a CLI (panache format, panache lint), as a language server inside an editor, or as a pre-commit hook or GitHub Action.
Why lint Markdown before converting it to rich text?
Because how your Markdown is written determines how the converted rich text lands. Inconsistent line breaks, mixed list markers, a heading hierarchy with skipped levels, trailing whitespace, and ragged fenced code blocks all survive a Markdown-to-rich-text conversion and become messy output in LinkedIn, X Articles, Medium, or Notion. Linting and formatting the source first gives you predictable headings, unified lists, tidy code blocks, and clean paragraphs in the rich-text paste box.
Does Panache replace a Markdown-to-rich-text converter?
No, they do different jobs. Panache is a quality-control tool for the source: it formats, reflows, and lints the Markdown so it is consistent and valid. A converter like md2rich turns the finished Markdown into rich text that a platform understands (real headings, bold, lists, tables) for pasting into LinkedIn, X Articles, Medium, or Notion. You use Panache before you publish, and a converter at the moment you publish.
How do I run a Markdown lint check before publishing?
Install Panache with cargo install --locked panache, brew install panache, or npm, then run panache format --check post.md. The --check flag reports files that need reformatting without changing them, and panache lint post.md flags issues. You can also add it as a pre-commit hook or a GitHub Action (jolars/panache-action@v1) so every draft is checked automatically. Once the draft is clean, convert it to rich text with md2rich.
Is running Panache and md2rich offline-friendly?
Yes on both sides. Panache is a local Rust CLI, so it formats and lints entirely on your machine with no network call. md2rich is a client-side converter, so the Markdown-to-rich-text conversion also runs entirely in your browser with nothing uploaded. That means a private, in-progress draft never has to leave your machine to be both linted and converted for publishing.
Published by md2rich. This article is for informational purposes; the Panache facts are sourced from the project's official GitHub repository (github.com/jolars/panache) and its site (panache.bz), and md2rich is referenced only for the conversion step.