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 docsThat 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 --trustThat 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 documentsFront 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-USdocs/
├── greeting.md → /greeting shared scope
├── ja/
│ └── guide.md → /ja/guide Japanese scope
└── en-US/
└── guide.md → /en-US/guide US English scopeAt /, 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:
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:
| Diagram | Supported |
|---|---|
graph, flowchart | Directions TD, TB, LR, RL, BT. Shapes A[Box], A(Rounded), A{Decision}, A((Circle)). Edges -->, ---, -.->, ==>, each taking a |label|. |
sequenceDiagram | participant 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 beforeThe 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 untaggedOpenAPI 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 --trustThe 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.comdist/ 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-repoThis 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@v4Then, 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.