Summary
Let users review markdown — Claude's plan-mode plans, spec files, design docs — directly in a wmux pane, the same way the diff view shows changes. Claude should be able to open a markdown file into a view with a single CLI call, and the user should be able to open one manually.
The markdown surface type, the secure file-reading pipe handler, and the marked + DOMPurify renderer already exist — but the rendering chain is currently broken and there's no convenient "open this file" entry point. This issue wires the existing pieces together and adds the missing entry points.
Current state
markdown is already a SurfaceType (src/shared/types.ts), rendered by MarkdownPane.tsx (read-only viewer, marked GFM + DOMPurify). It's a viewer, not an editor.
- V2 pipe handlers
markdown.set_content and markdown.load_file exist (src/main/index.ts); load_file reads from disk in the main process with an extension whitelist (.md/.markdown/.mdx/.txt/.text/.rst) and a 5 MB cap.
- Bug:
__wmux_setMarkdownContent (src/renderer/pipe-bridge.ts) dispatches a wmux:markdown-update CustomEvent that no component listens to, and MarkdownPane is mounted with no content prop (content is never persisted). So markdown content never renders — the pane always shows the empty placeholder "No content. Use wmux markdown set to add content." The original design (docs/superpowers/plans/2026-04-07-wmux-orchestrator-plugin.md) called store.setMarkdownContent(...) directly; the shipped code regressed to a dead event.
- There is no command to create a markdown surface and load a file in one step (unlike the diff view), and Claude is not told these commands exist (
resources/claude-instructions.md documents only wmux browser).
Proposed behaviour
- Fix the render chain so
markdown.set_content / markdown.load_file actually display. Persist content per surface in the store (setMarkdownContent(surfaceId, content) action) and thread it into MarkdownPane via a content prop, mirroring the original design. Prefer a store update over the dead CustomEvent.
- Add a one-shot CLI command
wmux markdown <file>: create a markdown surface and load the file into it. The existing wmux markdown set <id> --content/--file stays as a subcommand; a bare/non-set first arg is treated as a file path to open. Resolve relative paths to absolute in the CLI using the caller's cwd before sending over the pipe (the main-process cwd ≠ the terminal's cwd). Reuse the existing markdown.load_file handler and surface-creation bridge (__wmux_createSurface).
- Manual UI affordance to open a markdown file into a view without Claude — e.g. a command in the command palette and/or a pane-toolbar action, opening a file picker filtered to the allowed extensions.
- Teach Claude about it: add a
## Markdown section to resources/claude-instructions.md (injected into ~/.claude/CLAUDE.md by claude-context.ts) documenting wmux markdown <file>, so Claude knows it can surface plans/specs for review.
Acceptance criteria
Out of scope / follow-ups
- Editing + save-back to disk (turn the viewer into an editor) — nice-to-have, separate issue.
- Auto-detecting clickable file paths in terminal output (xterm link provider + path resolution) — larger, separate issue;
wmux markdown <file> is the stepping stone toward that.
- Syntax highlighting inside fenced code blocks (currently plain).
Implementation pointers
- Render chain:
src/renderer/store/surface-slice.ts (add setMarkdownContent + per-surface storage on SurfaceRef in src/shared/types.ts), src/renderer/pipe-bridge.ts (__wmux_setMarkdownContent → call the store action), PaneWrapper.tsx (pass content), MarkdownPane.tsx (consume it).
- CLI:
src/cli/wmux.ts — in the existing markdown case, branch on args[1]: set keeps the current behaviour, otherwise treat args[1] as a file path. path.resolve() the arg; call surface.create (type markdown) then markdown.load_file, or a new combined V2 method.
- Pattern to mirror: the diff view (
src/renderer/components/Diff/DiffPane.tsx, the diff.refresh handler in src/main/index.ts, and wmux diff in src/cli/wmux.ts).
- Claude awareness:
resources/claude-instructions.md + src/main/claude-context.ts.
Summary
Let users review markdown — Claude's plan-mode plans, spec files, design docs — directly in a wmux pane, the same way the diff view shows changes. Claude should be able to open a markdown file into a view with a single CLI call, and the user should be able to open one manually.
The markdown surface type, the secure file-reading pipe handler, and the
marked+DOMPurifyrenderer already exist — but the rendering chain is currently broken and there's no convenient "open this file" entry point. This issue wires the existing pieces together and adds the missing entry points.Current state
markdownis already aSurfaceType(src/shared/types.ts), rendered byMarkdownPane.tsx(read-only viewer,markedGFM +DOMPurify). It's a viewer, not an editor.markdown.set_contentandmarkdown.load_fileexist (src/main/index.ts);load_filereads from disk in the main process with an extension whitelist (.md/.markdown/.mdx/.txt/.text/.rst) and a 5 MB cap.__wmux_setMarkdownContent(src/renderer/pipe-bridge.ts) dispatches awmux:markdown-updateCustomEvent that no component listens to, andMarkdownPaneis mounted with nocontentprop (content is never persisted). So markdown content never renders — the pane always shows the empty placeholder "No content. Use wmux markdown set to add content." The original design (docs/superpowers/plans/2026-04-07-wmux-orchestrator-plugin.md) calledstore.setMarkdownContent(...)directly; the shipped code regressed to a dead event.resources/claude-instructions.mddocuments onlywmux browser).Proposed behaviour
markdown.set_content/markdown.load_fileactually display. Persist content per surface in the store (setMarkdownContent(surfaceId, content)action) and thread it intoMarkdownPanevia acontentprop, mirroring the original design. Prefer a store update over the dead CustomEvent.wmux markdown <file>: create a markdown surface and load the file into it. The existingwmux markdown set <id> --content/--filestays as a subcommand; a bare/non-setfirst arg is treated as a file path to open. Resolve relative paths to absolute in the CLI using the caller's cwd before sending over the pipe (the main-process cwd ≠ the terminal's cwd). Reuse the existingmarkdown.load_filehandler and surface-creation bridge (__wmux_createSurface).## Markdownsection toresources/claude-instructions.md(injected into~/.claude/CLAUDE.mdbyclaude-context.ts) documentingwmux markdown <file>, so Claude knows it can surface plans/specs for review.Acceptance criteria
wmux markdown set <id> --content "# Hi"and--file <path>actually render in the pane.wmux markdown <relative-or-absolute.md>opens a new markdown surface showing the file.wmux markdown set <id> ...still works as before (no regression to the existing subcommand).resources/claude-instructions.mddocuments the markdown command; verified to land in~/.claude/CLAUDE.md.load_fileguards preserved).Out of scope / follow-ups
wmux markdown <file>is the stepping stone toward that.Implementation pointers
src/renderer/store/surface-slice.ts(addsetMarkdownContent+ per-surface storage onSurfaceRefinsrc/shared/types.ts),src/renderer/pipe-bridge.ts(__wmux_setMarkdownContent→ call the store action),PaneWrapper.tsx(passcontent),MarkdownPane.tsx(consume it).src/cli/wmux.ts— in the existingmarkdowncase, branch onargs[1]:setkeeps the current behaviour, otherwise treatargs[1]as a file path.path.resolve()the arg; callsurface.create(typemarkdown) thenmarkdown.load_file, or a new combined V2 method.src/renderer/components/Diff/DiffPane.tsx, thediff.refreshhandler insrc/main/index.ts, andwmux diffinsrc/cli/wmux.ts).resources/claude-instructions.md+src/main/claude-context.ts.