Skip to content
What is Tsumugu

How to Use#

This page is also available in Japanese. Every option named here is listed with its default on Options.

Start#

npx tsumugu dev docs

That serves the docs/ directory on localhost and prints the URL. No configuration file exists or is needed: the root is the directory you name (or ./docs by convention), and everything else derives from the files.

There is one flag worth knowing on your first run. Documentation you point Tsumugu at is content, not code, so scripts do not run and MDX components are shown as source. When the directory is your own and you want it to run, add --trust:

npx tsumugu dev docs --trust

That flag is the whole of the trust setting. What it covers is below.

Write#

Routes mirror the file system, and three formats go through one pipeline:

docs/
├── index.md          →  /            names the site with its own heading
├── guide/
│   ├── index.md      →  /guide       the section's own page
│   └── setup.md      →  /guide/setup
├── api.html          →  /api         HTML is accepted as source input
├── notes.mdx         →  /notes       MDX parses; --trust runs it
└── images/x.svg      →  served as a file beside the documents

Front matter is the whole option surface per document:

---
title: Setting up # otherwise the first heading, then the file name
description: One sentence. # shown in listings, llms.txt and search
order: 2 # sidebar position among siblings
hidden: true # unlisted everywhere, but still served
---

A typo like hiden gets a warning naming the key you probably meant. Tsumugu shows MDX expressions and components as source instead of running them, and removes <script> from HTML. The reason is recorded in ADR 6: content you point Tsumugu at is not code, so it does not run.

Separate locales#

Name the locale directories explicitly when one site contains translated documentation:

npx tsumugu dev docs --locales ja,en-US
docs/
├── greeting.md       →  /greeting    shared scope
├── ja/
│   └── guide.md      →  /ja/guide    Japanese scope
└── en-US/
    └── guide.md      →  /en-US/guide US English scope

At /, navigation and search contain greeting.md but exclude everything under ja/ and en-US/. At /ja, they contain only ja/; at /en-US, only en-US/. Each scope also gets its own documents.json, llms.txt, and search.json. The root sitemap.xml covers the whole site.

Locale names use Unicode locale identifiers. Tsumugu canonicalizes them, so en-us selects a directory named en-US. It stops before serving or building if a named directory is missing, or if two names canonicalize to the same locale. --lang fr sets the HTML language of the shared scope; a locale scope always uses its own locale. Without --locales, directory routing and exports work exactly as they do for an ordinary site.

Switching languages is a link you write, because only you know which page is the counterpart of which. This site is built with --locales en,ja: the guide you are reading is docs/en/, its translation is docs/ja/, each page opens with a link to its pair, and the shared scope at / holds the design records that exist in one language only.

Draw a diagram#

A fenced block tagged mermaid is drawn while the page is built. The reader gets an SVG and no script, the figure follows their light or dark theme, and its text stays selectable and searchable:

ScannerRendererTransformersThemeSerializer
The scanner feeds the renderer, which feeds the transformers, the theme and finally the serializer.

Tsumugu draws the diagram itself rather than running Mermaid, so what it understands is a subset — written down here, because a subset nobody documented is a guessing game:

DiagramSupported
graph, flowchartDirections TD, TB, LR, RL, BT. Shapes A[Box], A(Rounded), A{Decision}, A((Circle)). Edges -->, ---, -.->, ==>, each taking a |label|.
sequenceDiagramparticipant and actor, with as names. Messages ->>, -->>, ->, -->, -x, -). Note over, Note left of, Note right of. Self-messages.

Everything else — class, state, gantt, pie, ER, journey, mindmap and the rest, plus subgraph, classDef, style, click and %%{init}%% — stays a code block and reports a warning naming what was not drawn. A diagram Tsumugu cannot draw never costs you the page, and it is never silently half-drawn.

Two lines are worth writing. accTitle names the figure and accDescr says what it shows, and both go to a reader using a screen reader:

graph LR
  accTitle: How a document becomes a page
  accDescr: The scanner feeds the renderer, and the theme comes last.
  A[Scanner] --> B[Renderer]

Without them Tsumugu writes the description itself, from the diagram's direction and its edges. That is honest, and it is less than a sentence you wrote would say.

The reasoning, and the measurements that ruled out running Mermaid on the server, are in ADR 9.

Serve an API description#

An OpenAPI description is the durable source for an HTTP interface, so Tsumugu treats it as a document rather than a file to link to. Name it and it becomes a page:

docs/
├── api.openapi.yaml   →  /api        the description becomes the page
├── openapi.yaml       →  /openapi    the bare name works too
├── config.json        →  served as a file, as before
└── data.yaml          →  served as a file, as before

The name is the opt-in. Only *.openapi.json, *.openapi.yaml, *.openapi.yml and the bare openapi.* names become pages, so a project's lock files, fixtures and configuration stay exactly what they were.

The page takes its structure from the description: info.title is the page, each tag is a section, and each operation is a subsection headed by its method and path. An operation therefore has an anchor to link a colleague to, a place in the sidebar, and an entry in search, documents.json and llms.txt:

# Pet Store              info.title
## Pets                  a tag
### GET /pets            an operation, with an anchor
#### Parameters          a table: name, in, type, required, description
#### Responses           a table: status, description, content type
## Other operations      anything the description left untagged

OpenAPI 3.0 and 3.1 are read. A $ref inside the description is resolved where it is used, so a shared schema is shown rather than pointed at; a schema that refers to itself expands once and then shows the name. A reference into another file, a reference that does not exist, and a Swagger 2.0 description each report a warning that says what to do, and the page still renders.

Nothing is shipped to the reader for this: no viewer, no scripts, no network access. What is deliberately missing is a "try it" console, which needs both. ADR 10 has the reasoning.

When the content is yours#

Some documentation is the code: a <canvas> demo, an interactive example, an MDX file built from components. Pass --trust and Tsumugu runs it:

tsumugu dev docs --trust

The flag is you saying the directory is yours. Under it, markup Tsumugu cannot model reaches the page as written. Your scripts run: inline ones allowed by their hash, files by 'self', never an external origin. And .mdx executes while the page is built, so what a reader gets is static HTML that search and the exports can read.

A file that will not run says so on the page and falls back to its source. One broken file never costs you the site.

Leave the flag off for anything you did not write. The reasoning is in ADR 7.

Two things to know while you work this way. A <script> written inside an .mdx file cannot run, because MDX reads a script's contents as document content rather than as code — put it in a file beside the document and load it with <script src="./demo.js">. And a document is rebuilt when the document changes, not when a component it imports changes, so editing a component needs a restart today.

Tsumugu's own architecture pages are written this way: their diagrams are computed from the same lists the prose describes. Look at docs/designs/architecture/index.mdx and docs/.components/ in the repository for a working example. The dotted directory is deliberate — Tsumugu refuses dotfiles, so build inputs kept there are never published beside the documents that use them.

While you write#

Watch mode is on by default. Saving rebuilds only what changed and reloads open pages. A broken link, missing anchor, or front matter error appears on the page that contains it, with the source file and line. If a full rebuild fails, the server keeps serving the last good version.

Readers get section-ranked search as they type, a copy control on code blocks, heading anchors, and a table of contents that follows the reading position. All of it degrades: without JavaScript the search field submits to a real page, and everything else was server-rendered to begin with.

Ship#

npx tsumugu build docs --out dist --origin https://docs.example.com

dist/ is a static site with clean URLs. For example, /guide/setup is written to guide/setup/index.html. The build also writes documents.json, llms.txt, search.json, and sitemap.xml from the same source documents. Host the directory anywhere that serves static files. build accepts the same --locales and --lang options as dev.

GitHub Pages#

A project site is served under /your-repo/, so pass --base:

npx tsumugu build docs --out dist \
  --origin https://your-name.github.io --base /your-repo

This repository uses .github/workflows/pages.yml to publish its own documentation:

name: Pages
on:
  push:
    branches: [main]
permissions:
  contents: read
  pages: write
  id-token: write
jobs:
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with: { node-version: 24 }
      - run: npx tsumugu build docs --out dist
          --origin https://your-name.github.io --base /your-repo
      - uses: actions/upload-pages-artifact@v4
        with: { path: dist }
      - id: deployment
        uses: actions/deploy-pages@v4

Then, once, in the repository settings: Settings → Pages → Source → GitHub Actions.

Compose differently#

The CLI combines replaceable renderers, transformers, and a theme. To replace one, write a small script against the same API. See Composition.