--- name: dynamo-docs description: Adds, updates, moves, or removes content on the Dynamo Fern docs site — standard docs pages, catalog-driven recipe and feature-benchmark pages, examples, recipes, and translations — keeping everything in line with the documentation style guide. Use for any change under docs/, recipes/, or examples/ (new page, edit, tab or section move, rename, removal, recipe/benchmark page, .zh-CN translation, version cut), when deciding which docs tab a page belongs in (Kubernetes Guide vs CLI Guide vs Reference vs Use Cases), and whenever content needs its frontmatter, headings, links, callouts, or terminology fixed. license: Apache-2.0 metadata: author: NVIDIA tags: - dynamo - docs - fern - style-guide --- # Dynamo Docs Maintenance Unified skill for adding, updating, moving, and removing content on the Dynamo Fern documentation site, in line with the project's authoring guides. Two authoring guides govern this work; read whichever applies before writing: - [`docs/fern/pages/community/contributing/documentation/documentation-style-guide.md`](../../../docs/fern/pages/community/contributing/documentation/documentation-style-guide.md) — the standard for **every** page: frontmatter, headings, prose, terminology, links, callouts. The must-fix subset is distilled in [Style Guide Is the Standard](#style-guide-is-the-standard) and [Content Rules](#content-rules) below. - [`docs/fern/pages/recipes/_catalog/README.md`](../../../docs/fern/pages/recipes/_catalog/README.md) — the standard for **recipe and feature-benchmark pages** (the catalog contract, the `.mdx` page blueprint, and the pure-CSS target picker). See [Add a Recipe or Feature Benchmark Page](#add-a-recipe-or-feature-benchmark-page). ## Branch Rule **ALL edits happen on `main` (or a feature branch based on `main`).** The `docs-website` branch is CI-managed and must **never** be edited by hand. ## Style Guide Is the Standard Every page under `docs/` (and the READMEs under `examples/` and `recipes/`) follows the [Documentation Style Guide](../../../docs/fern/pages/community/contributing/documentation/documentation-style-guide.md) (`docs/fern/pages/community/contributing/documentation/documentation-style-guide.md`). Read it before writing content. The `Docs Lint` job (`docs/fern/scripts/docs_lint.py`) enforces a **must-fix** subset on every PR — get these right or the checks fail: - **SPDX header** on every file, copyright range `2025-2026`. Fern pages put the two `#` lines *inside* the `---` frontmatter; plain READMEs use an HTML-comment block. - **Frontmatter with at least one metadata key** (`title`/`subtitle`/`sidebar-title`) and **no body `# H1`**. Fern renders the page H1 from the nav `page:` value, so a body `# H1` produces a duplicate title — and a bare `#` SPDX line left in the body also renders as an H1. Start the body at `##`. - **A nav entry** in `docs/fern/index.yml`, under the right tab, for every new page — a page not in the nav is unreachable. - **Links**: relative path *with extension* within `docs/` (`[Routing](router-concepts.md)`); absolute `https://github.com/ai-dynamo/dynamo/blob/main/` URL for targets outside `docs/` (examples, recipes, source; `/tree/main/` for a directory). No `../` path that escapes `docs/`, and never a hardcoded `https://docs.nvidia.com/...` link to a page in this repo. Link text names the destination, never "click here". - **No internal or sensitive references**: NVBug/JIRA/Linear IDs, internal hostnames, secrets, `TODO`/`FIXME`. Everything else in the style guide (page types, heading case, terminology, list and code-fence formatting, the pre-merge checklist) is guidance — the high-value rules are distilled in [Content Rules](#content-rules) below; apply them and deviate only with a reason. ## Content Rules Apply these on every page so the result reads like a person wrote it and passes review without a round-trip to the style guide. These are defaults; deviate with a reason. - **Page type (Diátaxis).** Each page serves one need — *tutorial* (a tab's `getting-started/`), *how-to* (a tab's feature or operations directory), *reference* (`pages/reference/`, for flags/APIs/config), or *explanation* (`pages/developer-guide/`). Don't blend a how-to into a flag reference; split and cross-link. - **Headings.** Title Case for short label / noun-phrase headings ("Routing Behavior"); sentence case for full-phrase headings ("Choosing a checkpoint flow"). Be consistent within a page. No end punctuation. Logical `##` → `###` hierarchy, no skipped levels. Renaming a heading breaks inbound `#anchor` links — rename deliberately. - **Terminology, exact casing.** Backends: **vLLM**, **SGLang**, **TensorRT-LLM** (or **TRT-LLM**) — never "vllm", "Sglang", "TensorRT LLM". **NVIDIA Dynamo** on first mention, then **Dynamo**; **KV router**, **NIXL**, **GPU**; **Kubernetes**, not "k8s", in prose. Expand acronyms on first use ("Time To First Token (TTFT)"). Use one word per concept. - **Inclusive terms.** "denylist"/"allowlist", not "blacklist"/"whitelist"; "primary"/"replica", not "master"/"slave". - **Cut marketing and bombast.** Remove "seamless, robust, powerful, blazing-fast, cutting-edge, effortless, unlock, leverage, delve, comprehensive, rich ecosystem, world-class, game-changing". Cut filler ("it's important to note", "simply", "just", "in order to") and difficulty words ("easy", "easily"). Start sentences with a verb; active voice; present tense; second-person imperative. Name the flag/default/command, not "configure the appropriate settings". Avoid the em-dash-aside tic. - **Procedures.** Condition before instruction ("To enable KV-aware routing, set `--router-mode kv`", not the reverse). One action per numbered step. - **Links.** Follow the must-fix Links rule in [Style Guide Is the Standard](#style-guide-is-the-standard) (relative + extension inside `docs/`, absolute GitHub URL outside, no `../` escape, no `docs.nvidia.com` self-link). - **Code fences** always tag a language (`bash`, not `sh`); no `$`/`#` prompt prefixes; put output in its own `text` block. Wrap flags, paths, and `DYN_*` env vars in backticks in prose. - **Lifecycle.** Mark preview features **Experimental.** and legacy ones **Deprecated.** (with a `> [!WARNING]`); note availability for new features ("Available since v0.X"). ## Operations Pick your operation: - Standard `.md` doc page → [Add a Page](#add-a-page) - Rendered recipe / feature-benchmark page (`.mdx` + catalog triple) → [Add a Recipe or Feature Benchmark Page](#add-a-recipe-or-feature-benchmark-page) - Code under `examples/` or `recipes/` → [Add an Example or Recipe (code)](#add-an-example-or-recipe-code) - Edit, move, or remove existing content → [Update a Page](#update-a-page), [Remove a Page](#remove-a-page) (recipes: [Move, defer, or remove a recipe](#move-defer-or-remove-a-recipe)) - Chinese translation or version cut → [Translations and Versioned Navs](#translations-and-versioned-navs) ### Add a Page 1. **Pick the tab, then the sibling.** Choose the tab from [Navigation](#navigation-tabs-and-sections) — this is the decision that matters, because fixing it later costs a move plus a redirect. Then open `docs/fern/index.yml`, find the existing page closest in topic to yours *within that tab*, and join **that** section, putting your file in that sibling's subdirectory. Page *type* narrows the field (tutorial → the tab's `getting-started/`, how-to → a feature or operations directory, reference → `pages/reference/`, explanation → `pages/developer-guide/`), but the nearest existing page is the tie-breaker — read the file, don't guess from section names. Note the tab, the section, the subdirectory, a kebab-case filename, and the page title. 2. Create `docs/fern/pages///.md` (use `.mdx` if the page needs Fern components). Frontmatter carries the SPDX header plus at least one metadata key; the body starts at `##` with a short intro — **no body `# H1`**: ```markdown --- # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 title: subtitle: --- Short intro paragraph stating what the page covers. ## ``` 3. Add a nav entry in `docs/fern/index.yml` under the section you chose in step 1 — a `- page:` in that section's `contents:`, 2-space indent, `path:` relative to `docs/fern/` so it always starts with `pages/` (see [Navigation](#navigation-tabs-and-sections) for the grammar): ```yaml - page: path: pages///.md ``` ### Update a Page 1. Locate by file path, page title, or keyword search (`grep -rn` in `docs/fern/pages/`). 2. **Content only** -- edit the markdown file directly; keep it within the style guide. 3. **Title/label change** -- update the frontmatter (`title`/`sidebar-title`) and the `- page:` name in `docs/fern/index.yml`. 4. **Section or tab move** -- `git mv` the file when the directory changes, move the nav entry to the new section (and tab), and update every incoming link. > [!IMPORTANT] > A page's URL joins the slug of every nav level that contributes one — tab, then section (sections > nest), then the page. Each slug comes from the nav **label**, not the file path, unless an explicit > `slug:` overrides it or that level carries `skip-slug: true`. A skipped tab still leaves its > sections in the URL: `pages/developer-guide/advanced-customizations/building-from-source.md` serves > at `/dynamo/dev/advanced-customizations/building-from-source`. So **renaming a label changes the URL > even when the file doesn't move**, and moving a file between directories changes nothing unless its > label, section, or tab changes. Add a redirect **only when the URL actually changes** — a file-only > `git mv` that leaves the tab, section, label, and explicit `slug:` alone needs none, and adding one > yields a self-redirect or a slug that doesn't exist. When the URL does change, add a > **dev-scoped** redirect to the `redirects:` list in `docs/fern/docs.yml`: `/dynamo/dev/` → > `/dynamo/dev/`. Editing `docs/fern/index.yml` regenerates only the `dev` nav, so do **not** redirect > the unversioned (`/dynamo/`) or `/dynamo/latest/` forms — those serve **Latest**, a frozen > release snapshot that `main` edits don't touch, and a redirect there would break a working URL. See > [Redirects and the version model](#redirects-and-the-version-model). ### Remove a Page Removing a page is destructive and breaks live URLs. Confirm with the user before step 2, and show them the incoming links and redirects you found in steps 1 and 2. 1. Find incoming links: `grep -rn "" docs/`. 2. Find redirects that already point at the page: grep its published URL in `docs/fern/docs.yml` as a `destination:`. Each hit has to be retargeted, or it starts serving a 404. 3. Remove the file, matching its real extension — pages are `.md` or `.mdx`: `git rm docs/fern/pages///.`. 4. Remove the `- page:` block from `docs/fern/index.yml`. If it was the last page in a section, remove the whole `- section:` block. 5. Fix or remove every incoming link found in step 1, retarget every redirect found in step 2, and add a `docs/fern/docs.yml` redirect for the page's own URL if it had a stable one. ### Add a Recipe or Feature Benchmark Page Recipe and feature-benchmark pages are **catalog-driven** and use `.mdx` (they embed a pure-CSS target picker). Authoritative guide: [`docs/fern/pages/recipes/_catalog/README.md`](../../../docs/fern/pages/recipes/_catalog/README.md). Each page is a triple — page + catalog entry + nav: 1. **Write the `.mdx`** at `docs/fern/pages/recipes/model-recipes/.mdx` (or `docs/fern/pages/recipes/feature-benchmarks/.mdx`). Frontmatter carries SPDX + `title` + one-sentence `subtitle`; body starts with a short intro, then the target picker — multi-target pages use the radio picker, single-target pages use the **static** form (exact classes under [Target picker](#target-picker) below) — then the fixed section order: `## Prerequisites` → `## Deploy` → `## Smoke Test` → `## Benchmark` → `## Expected Performance` (omit if no numbers) → `## Compare All Targets` (multi-target only) → `## Related Feature Benchmarks` → `## Notes` → `## Source`. **MDX rule:** blank line after `
` and before `
`; keep code fences at column 0. 2. **Add a catalog entry** — one file at `docs/fern/pages/recipes/_catalog/recipes/.yaml` (or `docs/fern/pages/recipes/feature-benchmarks/_catalog/benchmarks/.yaml`), SPDX header, exactly one object. **Read the sibling `schema.json` first for the exact field set** (`docs/fern/pages/recipes/_catalog/schema.json` for recipes, `docs/fern/pages/recipes/feature-benchmarks/_catalog/schema.json` for benchmarks — they are **different** schemas) — each is `additionalProperties: false`, so an invented or misspelled key fails validation; don't guess the shape. A **recipe** entry requires `id`, `title`, `provider`, `model`, `status`, `targets`, `maintainer`, and each `targets[]` item requires `id`, `recommended`, `hardware`, `runtime`, `topology`, `techniques`, `workload`, `deploy`, `expected_performance`. Internal `id:` **must equal the filename**; active entries carry `page:`, deferred ones carry `deferred_reason` and omit `page:`. Add the `` to the matching `_catalog/index.yaml` (`recipes:` for active, `deferred_recipes:` for deferred — it controls sidebar/landing order). 3. **Wire navigation** in `docs/fern/index.yml`: everything here lives under `- tab: recipes` — a `- page:` in the **Model Recipes** section for recipes, or in the **Feature Benchmarks** section for benchmarks. Per-benchmark pages are usually `hidden: true` (surfaced from the landing page). 4. **Patch `docs/fern/main.css` only if** the page introduces a picker axis value not already supported (`recipe-sku`: `b200`/`h200`/`h100`/`gb200`/`hopper`/`blackwell`; `recipe-usecase`: `chat`/`agentic`; `recipe-variant`: `agg`/`disagg`/…). A value missing from CSS renders but filters nothing. After editing `main.css`, run `python3 docs/fern/scripts/sync_site_css.py` so the footer's CSS mirror stays in sync — pre-commit fails otherwise. 5. **Add the landing card** in `docs/fern/pages/recipes/model-recipes/overview.mdx` and update the model/target counts. 6. **Validate**: `python3 docs/fern/pages/recipes/_catalog/validate.py` (covers both catalogs), then `fern check` and `fern docs broken-links`. #### Catalog entry shape `schema.json` is authoritative for the field set; this skeleton just anchors the **nested shapes and enums** that are easy to get wrong (`model`/`hardware`/`runtime`/`workload`/`deploy`/ `expected_performance` are **objects**, not scalars; `status` and `topology` are **enums**). Minimal valid active entry: ```yaml id: llama-3-1-8b # == filename; pattern ^[a-z0-9][a-z0-9-]*$ title: Llama 3.1 8B provider: meta # landing-page filter key (meta, qwen, nvidia, …) model: name: Llama 3.1 8B hf_id: Meta-Llama/Llama-3.1-8B precision: BF16 status: validated # enum: validated | experimental (NOT "active") page: recipes/llama-3-1-8b.mdx # active only; deferred → omit page:, add deferred_reason: maintainer: Jane Doe # or null (null is tracked as a gap) targets: # >= 1 item - id: vllm-agg-h100 recommended: true # bool hardware: { gpu: H100, count: 1 } runtime: { framework: vllm } topology: aggregated # enum: aggregated | disaggregated techniques: [bf16] workload: { type: chat } deploy: { asset: recipes/llama-3-1-8b/vllm/agg/deploy.yaml } expected_performance: { available: false } # add summary: when numbers exist ``` **Benchmarks use a different schema.** A `docs/fern/pages/recipes/feature-benchmarks/_catalog/benchmarks/.yaml` entry validates against `docs/fern/pages/recipes/feature-benchmarks/_catalog/schema.json`, whose required set is `id`, `title`, `page`, `claim`, `subtype` (enum: `ab-test`/`feature-stack`/`topology`/`provider-comparison`/`hands-on`), `features`, `model`, `hardware`, `traffic`, `arms`, `results`, `maintainer` — **no** `provider`, `status`, or `targets`. The skeleton above is recipe-only; read the benchmark schema for that shape. #### Target picker The picker is pure CSS under the `dynamo-*` namespace — **MDX uses `className`, not `class`**, and the exact class names matter (a wrong class name, or a `class=`-spelled wrapper, renders but filters nothing). A **multi-target** page renders `
` containing a `dynamo-target-picker-title`, one `dynamo-target-picker-row` per dimension (a `dynamo-target-picker-dim` label plus radio `` + `