Intro
How markdown docs in this app are written, structured, and rendered — read this before adding or editing any doc.
Every doc in this app is written and maintained by LLMs, not just rendered for them — so the structure has to be unambiguous enough that a model editing a file next month, with no memory of this session, gets it right without guessing. This page is both the explanation and the literal template to copy.
Why structure matters here
Two audiences read every file in content/: whoever's browsing the
site, and whichever LLM is asked to write or update the next one.
Documentation gets split into independent chunks by heading in most
retrieval and context-assembly pipelines, and the same principle
holds here even without a retrieval layer: each section should be a
complete, self-contained idea, understandable without having read the
section before it. A reader — human or model — jumping straight to
one heading, off a search result or the table of contents, shouldn't
need the rest of the page for that section to make sense.
Required frontmatter
Every file needs exactly two fields, parsed by gray-matter in
lib/content.ts:
title— rendered once, bydoc-page.tsx's heading component. Don't also open the body with a#heading for the title — it won't get picked up by the table of contents (only##/###are read) but it will render as a second, redundant heading.description— shown under the title, and used as the page's meta description. Specific enough that someone — or something — scanning a list of pages can tell what's on this one without opening it.
No other frontmatter fields are read yet. Adding new ones (tags, an
updated-at date, etc.) won't break anything, but they won't do
anything either until lib/content.ts is extended to read them.
Headings
- Start at
##—#is reserved for the title. - Only
##/###appear in the table of contents (extractHeadingsinlib/content.ts) — going deeper reads as noise in a sidebar meant for scanning, not a full outline. - One complete idea per section, per the reasoning above.
Code blocks
- Always tag the language right after the opening fence
(
```ts,```sh,```json, etc.). An untagged block still renders — it falls back to plain text — but loses syntax coloring and line numbers. - Every opening fence needs a matching closing fence. An unclosed one silently swallows everything after it into one giant code block.
- Diffs: mark changed lines with a trailing comment —
[!code ++]for an addition,[!code --]for a removal (seetransformerNotationDiffinmarkdown.tsx).
Tables
GitHub-flavored, via remark-gfm — needs a header row and a
separator row directly under it, with every row having the same
number of columns. A missing separator row, or a row with a different
column count than the rest, renders as a broken block instead of a
table.
Nesting
- A flat page is one entry in
lib/nav.ts: a label and a slug, with the file atcontent/<slug>.md. - A nested page adds a list of children to a parent entry — a
child's slug is its full path (
apps/smm/composer, not justcomposer), and its file lives atcontent/<parent>/<child>.md.apps/smmis a working example of a parent page with children.
Common mistakes to avoid
- Missing blank lines around lists, code blocks, and headings — without one, a parser can fold the following line into the previous paragraph instead of treating it as its own block.
- Unescaped special characters in prose where they're meant literally rather than as markdown syntax — wrap them in backticks, or escape with a backslash.
- Tabs in frontmatter — use spaces; a YAML parser can treat a tab as invalid indentation.
- Wrapping the entire file in one big fence — a common habit when asked to "output markdown." The frontmatter and body need to be real markdown, not markdown-shaped text sitting inside a single code block.
Template
Copy this structure for a new page. This block itself uses a longer fence than the code example inside it — exactly how a markdown example that contains its own code block has to be written, or the inner fence would end the outer one early:
---
title: Page Title
description: One sentence describing what this page covers.
---
Opening paragraph, plain prose, no heading — orient a reader with
zero prior context. State what this page covers and why it exists.
## First section
One complete idea. Don't assume the reader has seen any other
section on this page.
## Second section
- Keep lists shallow — one level, not deeply nested.
- Prefer short, declarative sentences over long ones.
```ts
// Always tag the language.
export function example(): boolean {
return true;
}
```
| Column | Column |
| --- | --- |
| Use tables | for structured comparisons |