Skip to content

feat: open markdown files in a read-only markdown view (like the diff view) #54

Description

@kevmtt

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

  1. 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.
  2. 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).
  3. 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.
  4. 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

  • 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).
  • Relative paths resolve against the terminal's cwd, not the Electron main-process cwd.
  • A manual UI entry point opens a markdown file into a view.
  • resources/claude-instructions.md documents the markdown command; verified to land in ~/.claude/CLAUDE.md.
  • Non-markdown / oversized / missing files are rejected with a clear error (existing load_file guards preserved).

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions