← Writing

mdmux: a standalone markdown TUI (with optional cmux integration)

github.com/nero408/mdmux →

I take a lot of notes in markdown. Design docs, meeting notes, half-baked ideas, code review walkthroughs — all .md files scattered across a directory tree on disk. The two ways I'd been reading them were both bad:

  1. Editor preview panes (VS Code, Obsidian). Great rendering, but they pull me out of the terminal where I actually live.
  2. cat, bat, less, glow. Stays in the terminal, but I either lose the rich rendering or I lose the tree — the ability to skim a folder, hop between files, and watch them update as I edit.

What I actually wanted was an nnn/yazi-style file picker on the left, a live-rendered markdown panel on the right, no mouse, no Electron, no context switch. So I built one.

mdmux is a Rust TUI that does exactly that. On a machine with cmux running you get cmux's native renderer; on any other machine — or if you pass --no-cmux — mdmux renders the markdown panel itself, inside its own ratatui surface. No external dependencies required.

terminal
 mdmux  /home/me/notes
┌ Files (12) ────────────────────────┬────────────────────────────────────────┐
│  📝 README.md                      │  # endpoints                           │
│▾ 📁 docs                           │                                        │
│  ▾ 📁 api                          │  The API uses REST with JSON bodies.   │
│    ► 📝 endpoints.md               │  All requests require an `Auth` header.│
│      📝 errors.md                  │                                        │
│      📝 webhooks.md                │  ## Routes                             │
│    📝 getting-started.md           │                                        │
│▾ 📁 notes                          │  `GET /v1/items`   list all items      │
│    📝 2026-05-13-design.md         │  `POST /v1/items`  create an item      │
│    📝 random.md                    │                                        │
└────────────────────────────────────┴────────────────────────────────────────┘
 open  /home/me/notes/docs/api/endpoints.md  [in-process]
↑/↓ move · enter open · / filter · j/k scroll preview · ? help · q quit

Two rendering modes, one tool

At startup, mdmux runs cmux ping. If it succeeds, you're in cmux mode: pressing Enter on a file shells out to cmux markdown open <path>, which splits the current pane and renders the file in cmux's native viewer with live reload. The key design choice here is replace, don't stack — selecting another file calls cmux close-surface --surface <prev> before opening the new one. No tab hoarding.

If cmux ping fails — or you pass --no-cmux — mdmux silently falls back to in-process mode: the TUI itself grows a second pane in a 40/60 split. The rendering is done by ratkit's markdown-preview widget, which gives headings, lists, blockquotes, inline emphasis, fenced code blocks with syntect-powered syntax highlighting, and pipe tables with header rows and column alignment — all for free. Live reload comes from a notify watcher — same experience as cmux, just rendered inside our own pane.

The status line tells you which mode you ended up in, so there's no guessing.

Why a separate tool, why not a cmux feature?

Three reasons, still the same as on day one:

The original launch had an awkward story: "install cmux first, or you get a browse-only TUI with error overlays on Enter." That's the wrong gate for a Day-1 open-source release — it limits the audience to people who already use cmux. Adding the in-process renderer fixed that without compromising the cmux experience at all.

What I learned building it

A few things were surprisingly easy:

A few were surprisingly hard:

Install

sh
brew install nero408/tap/mdmux
# or
cargo install mdmux

macOS and Linux are supported. Rust ≥ 1.85 to build from source. cmux is optional — if it's not running, mdmux renders the preview pane itself.

Where it's headed

Short list of things I'm thinking about:

If any of those sound useful — or you have a use case I haven't thought of — open an issue. PRs welcome; cargo test should stay green and new behaviour gets a unit test against the CmuxClient trait.

Try it

sh
brew install nero408/tap/mdmux
cd ~/notes
mdmux

That should be all it takes. cmux not required. If something breaks, tell me what broke.


@nero408 ← All posts