Skip to main content
ClaudeWave
Skill67 repo starsupdated 1mo ago

mindmap-markmap-viewer

This Claude Code skill renders hierarchical Markdown as an interactive, zoomable SVG mind map using markmap.js, generating a single self-contained offline-ready HTML file. Use it when a user requests a mind map, concept diagram, or visual structure of notes, outlines, plans, or documents, or when they need to embed an exploratory tree visualization in Streamlit, regardless of whether they explicitly use the term "mind map."

Install in Claude Code
Copy
git clone https://github.com/Jaderson-bit/mindmap-markmap-viewer ~/.claude/skills/mindmap-markmap-viewer
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Mindmap (markmap) Skill

Render hierarchical Markdown as an interactive SVG mind map with **markmap.js**, plus three custom layers: **white-font CSS**, **expand-by-level control**, and **search that filters the tree**.

```
source.md  ──►  filter / set expand level (Python)  ──►  build_html()  ──►  vendored markmap libs (local, offline)  ──►  SVG + toolbar
 (outline)        (manipulate the text)                  (HTML + CSS)        (transform + render in the browser)          (screen)
```

Helper functions live in `scripts/render_markmap.py`; a minimal source file is in `assets/example.md`; regression tests are in `evals/`. Deeper docs: [`references/internals.md`](references/internals.md) (how the helpers work) and [`references/lessons.md`](references/lessons.md) (real-world lessons + the adversarial counter-review behind the current code).

## When to use this skill

- The user asks for a mind map, markmap, or concept map of anything.
- Hierarchical content — an outline, notes, a plan, a document's structure, a taxonomy — should become navigable, scannable, or shareable.
- A map must be sent to someone or opened with no setup: the output is one self-contained `.html` that works offline.
- A mind map should be embedded in a Streamlit app.
- An existing markmap renders blank, white-on-white, or truncated — this skill carries the known fixes (§2).

---

## 1. Source format

markmap derives hierarchy from **headings (`#`)** and **nested list items** — `-`, `*`, `+`, or numbered (`1.` / `1)`) — indented by 2 spaces per level. (Tabs work too; the filter treats one tab as one level.) A YAML frontmatter block controls behavior:

```markdown
---
markmap:
  colorFreezeLevel: 2      # freeze color from level 2 down (branches keep the parent color)
  initialExpandLevel: 2    # levels kept open; root is level 1, so 2 = root + branches (-1 = all)
  maxWidth: 380            # max node width in px (forces wrapping)
---

# Root                     <- root (level 1)
## Branch A                <- level 2
### Sub-branch A1          <- level 3
- Leaf                     <- level 4 (bullet under a level-3 heading)
  - Detail                 <- level 5 (bullet indented +2 spaces)
```

**Presets (`apply_presets`).** You don't have to hand-write the `markmap:` block — `apply_presets(src, color=None, max_width=380)` fills sensible defaults *without overriding* anything you set: `colorFreezeLevel: 2`, `maxWidth`, and an `initialExpandLevel` sized by node count (expand-all for ≤30 nodes, else level 2). Pass `color=["#7fd1ff", "#ffd479"]` for a custom palette. Any key already in your frontmatter wins, so you can set one value and let presets fill the rest.

**Content rule — term → parent / description → child.** Put the label on the node and its explanation as a *child*, not on one line. Prefer:

```markdown
- Term
  - description of the term
```
over `- Term — description`. This keeps nodes short and the tree scannable.

Node text may contain `<`, `>`, and `&` freely (`a < b`, `List<String>`, even `</div>`). `build_html` HTML-escapes the source before embedding it and the browser decodes it back, so markmap sees exactly what you wrote. (Earlier versions broke on a literal `<`; that footgun is gone — don't pre-escape to `&lt;` yourself or it shows up literally.)

---

## 2. Rendering (white font)

The renderer is: the **vendored markmap `<script>`s** (local files), an `<svg class="markmap">` plus a hidden source `<div>` holding the Markdown, a small **init script** (transform → `Markmap.create` → toolbar), and the CSS. See `build_html()` / `render_markmap()` in [`scripts/render_markmap.py`](scripts/render_markmap.py), with the rationale in [`references/internals.md`](references/internals.md).

Non-obvious points (these cost rework):
- **Offline, single self-contained file by default.** `build_html` **inlines** the vendored markmap stack (d3 + markmap-view/-lib/-toolbar, pinned exact in [`assets/vendor/`](assets/vendor/)) directly into the HTML — **no CDN, no network, and no sibling files** — so the one `.html` opens offline anywhere you move or share it. (That single-file default is the fix for maps that failed to load their libs when opened alone.) Pass `inline=False` for a smaller HTML that instead references a `vendor/` folder via `vendor="vendor"`, which must then travel beside it.
- **LaTeX math is not rendered in the offline bundle.** markmap's KaTeX support needs `window.katex`, which isn't vendored, so `$...$` / `$$...$$` show as plain text. Everything else renders fully offline.
- **Navigation toolbar** (bottom-right): zoom in/out, fit-to-window, expand-all, collapse-all, and **download as SVG / PNG**. Pass `toolbar=False` to omit it. Expand/collapse set each node's `fold` then re-render with `setData()` **and no argument** — passing data re-derives `fold` from `initialExpandLevel` and would wipe the manual fold. Export snapshots the current fold state; the SVG inlines the white-font CSS + a dark backdrop so it stands alone, and PNG rasterizes at 2× (falling back to SVG if a browser refuses to rasterize the `<foreignObject>` labels — markmap draws node text as HTML-in-SVG, not `<text>`).
- **White font is invisible without a dark background.** This is the #1 way the map "renders blank": the font is white, the surface is white, so nothing shows. A standalone `.html` opens on the browser's white default, and a Streamlit `components.html` iframe is white by default too — neither inherits the host's dark theme. So `build_html` paints its **own** dark backdrop (`background="#0e1117"` by default). Only pass `background="transparent"` when you *know* the host behind the iframe is already dark and you want a seamless blend. White font + dark background travel together — never set one without the other.
- **White font needs TWO selectors + `!important`.** markmap draws text as SVG `<text>` **and** sometimes as `<foreignObject>` (HTML inside SVG). Style both or half the labels stay dark:
  ```css
  svg.markmap te