---
title: Authoring guide
summary: "How to write pages for this site: front matter, the four depth levels, callouts, mathematics, code, tabs, charts, tables, status badges, the field viewer and diagrams. Every feature is shown live on this page."
section: reference
order: 10
level: 2
audience: [contributors, developers]
keywords: [markdown, front matter, depth levels, shortcodes, callouts, math, charts, field viewer, diagrams]
---
<!-- gyre docs: expanded Markdown of reference/authoring.md (generated tables, verification numbers and chart data are written out). Base level of this page: L2 Practice; passages at another level are bracketed by 'begin level N' / 'end level N' comments and start with [LN Name]. Built from revision ceb1f4b0be. -->

Pages are Markdown files under `docs/content/`. A small pre-processor adds depth levels, callouts, tabs, mathematics and data-driven components to standard Markdown; `gyre docs build` turns the files into this site. This page is both the reference and the test card: each feature is described and then used.

- **L1 Essentials**: what it is and why it matters (for anyone)
- **L2 Practice**: how to use it (for users and MDO integrators)
- **L3 Mechanics**: how it works: algorithms and architecture (for developers)
- **L4 Theory**: derivations, proofs and numerical analysis (for numerical analysts)

Move the **depth dial** in the top bar (or press `[` and `]`) while you read this page to see how passages fold and unfold.

## Pages and front matter

A page is one file. `docs/content/guide/quickstart.md` becomes `guide/quickstart.html`, and the Markdown source is published next to it as `guide/quickstart.md` for agents and tools. Files and directories whose name starts with `_` or `.` are ignored; any other non-Markdown file under `docs/content/` is copied to the site unchanged, so images can sit next to the page that uses them.

Every page starts with YAML front matter:

```yaml title="docs/content/guide/quickstart.md"
---
title: Quickstart
summary: The shortest path from an installed package to a converged flow solution.
section: guide
order: 20
level: 2
audience: [users]
keywords: [first run, preset]
---
```

| Field | Required | Meaning |
|---|---|---|
| `title` | yes | Page title, shown as the top heading. Do not repeat it as a `#` heading in the body. |
| `summary` | yes | One or two plain-text sentences. Shown as the "in one minute" lead of the page, in navigation cards, in search results and in `llms.txt`. No Markdown. |
| `section` | no | One of `overview`, `guide`, `examples`, `verification`, `engineering`, `theory`, `reference`. Defaults to the top-level directory of the file. |
| `order` | no | Integer position within the section (default 100; `index.md` defaults to 0). Ties are broken by title. |
| `level` | no | Base depth level of the page, 1 to 4 (default 1). See [Depth levels](#depth-levels). |
| `audience` | no | List of intended readers, shown as chips under the title. |
| `keywords` | no | List of search keywords. |
| `study` | no | Id of the verification study this page reports. Links the study card on the verification overview to this page, and lets `status` and `study` shortcodes omit the id. |
| `studies` | no | List of further study ids reported on this page. Their cards on the verification overview link here too; shortcodes for them name the id. |

The file `index.md` of a section is that section's landing page: the section title in the sidebar links to it. `docs/content/index.md` is the home page; its front matter also carries `hero`, `paths` (the persona reading paths) and `highlights`, which the home template lays out.

<!-- begin level 3 (Mechanics) -->
**[L3 Mechanics] How sections, order and navigation are derived**

Sections and their display names are fixed in `gyre.docsite.build.SECTIONS`; a page that names another section fails the build. Within a section the landing page comes first, then pages by `(order, title)`. The same linear order drives the previous/next links at the bottom of each page. The sidebar shows, next to each page, the deepest level that page reaches, measured from the rendered content rather than declared.

<!-- end level 3 -->

## Depth levels

Every passage of the documentation belongs to one of four levels. The reader's dial decides which passages are open; a passage deeper than the dial is not removed but folded into a one-line stub that shows its level, title and length, and opens in place.

Text outside any block belongs to the page's base level (`level` in the front matter). A block assigns a different level to a passage:

````markdown
::: level 3 "Why flux differencing conserves entropy"
Any Markdown: paragraphs, lists, tables, math, shortcodes,

```python
fenced_code = "also works"
```

and callouts.
:::
````

The level may be written as a number (`3`), as `L3`, or by name (`mechanics`). The quoted title is optional but strongly recommended: it is what the reader sees on the folded stub and in the table of contents. A block without a title shows its opening words instead.

Three blocks follow, one per level other than this page's base level 2.

<!-- begin level 1 (Essentials) -->
**[L1 Essentials] The idea in one sentence**

Write the short, plain answer first, then let readers who want more open the deeper passages.

<!-- end level 1 -->

<!-- begin level 3 (Mechanics) -->
**[L3 Mechanics] What the builder does with a block**

The pre-processor parses the page into a tree before Markdown runs. Each block is rendered on its own and wrapped in a `<section class="lv" data-level="3">` with a toggle button; whether it is folded is decided in the browser by two attributes, `data-depth` on the document (the dial) and `data-user` on the block (the reader's choice for that one block), using CSS only. The builder also counts the words at each level for the depth map in the right-hand column.

<!-- end level 3 -->

<!-- begin level 4 (Theory) -->
**[L4 Theory] Why the reading position is stable when the dial moves**

Before the dial changes, the script records the first content element at the top of the viewport and its offset $y_0$. After the change it measures the same element again (or, if that element is now inside a folded passage, the stub that replaced it) at offset $y_1$ and scrolls by $y_1 - y_0$. Content above the reading position may grow or shrink by any amount; the element being read does not move on screen.

<!-- end level 4 -->

Rules worth knowing:

- **Folding follows the dial.** A block is open when its level is at or below the dial. Opening or closing a single block by hand is remembered until the dial crosses that block's level.
- **Pages deeper than level 1 fold too.** On a page whose base level is 2 or more, the text under each `##` heading is treated as a passage at the base level and folds when the dial is shallower. The lead-in before the first `##` heading, the headings themselves, and any explicit block shallower than the base level always stay visible. This page has base level 2: set the dial to 1 to see its outline.
- **Do not nest a shallower block inside a deeper one.** It would be hidden whenever its parent is folded. Deeper inside shallower is fine.
- **Blocks start at the left margin.** The `:::` lines must not be indented.
- **Links unfold their target.** Following a link, a search result or a table-of-contents entry into a folded passage opens it.

Readers can pin a depth in a link with `?depth=3`, and the dial position is remembered between visits.

## Callouts

Five kinds of callout share one syntax. The title is optional.

```markdown
::: warning "Check the units"
The angle of attack is given in degrees.
:::
```

> **Note**
>
> A note adds context that is useful but not essential to the argument.

> **Tip: Prefer a preset**
>
> A tip suggests a better way of doing something.

> **Warning: Check the units**
>
> A warning marks something that goes wrong silently if it is ignored.

> **Definition: Summation-by-parts operator**
>
> A definition introduces a term. A first-derivative matrix $D$ with a diagonal, positive mass matrix $M$ is a summation-by-parts operator if $Q = MD$ satisfies $Q + Q\T = B$ with $B = \operatorname{diag}(-1, 0, \dots, 0, 1)$.

> **Result: Discrete integration by parts**
>
> A result states something that is proved or verified. For all grid functions $u$ and $v$, $u\T M D v + (Du)\T M v = u_N v_N - u_0 v_0$.

Callouts may sit inside level blocks, and level blocks inside callouts.

## Mathematics

TeX mathematics is written between dollar signs and rendered in the browser by a vendored copy of KaTeX, so it works offline. Inline math such as `$a^2 + b^2 = c^2$` gives $a^2 + b^2 = c^2$; a display equation goes between double dollars:

```markdown
$$
\pd{u}{t} + \nabla \cdot f(u) = 0
\label{eq:conservation}
$$
```

$$
\pd{u}{t} + \nabla \cdot f(u) = 0
\label{eq:conservation}
$$

A display equation that carries `\label{eq:name}` is numbered and can be cited from the text with `\eqref{eq:name}`, as in: the conservation law \eqref{eq:conservation}. References may come before the equation. Equation numbers are per page.

Things to know:

- Math is protected from Markdown: underscores, asterisks, backslashes and vertical bars inside it are left alone, including in tables and headings.
- A literal dollar sign is written `\$`. A `$` followed by a space, or a closing `$` followed by a digit, is not treated as math, so prices such as $5 and $10 are safe.
- `\(…\)` and `\[…\]` are accepted as alternative delimiters.
- Code spans and fenced code are never searched for math.
- Use multi-line environments inside a display: `aligned`, `cases`, `pmatrix` and the other KaTeX environments work.

$$
\begin{aligned}
\avg{u} &= \tfrac12\,(u^- + u^+), &\qquad \jump{u} &= u^+ - u^-, \\
\norm{u}_M^2 &= u\T M u, &\qquad \abs{J} &= \det \pd{x}{\xi}.
\end{aligned}
$$

The site defines a few macros for recurring notation:

| Macro | Renders as | Meaning |
|---|---|---|
| `\R` | $\R$ | the real numbers |
| `\dd` | $\dd x$ | upright differential, `\dd x` |
| `\pd{u}{x}` | $\pd{u}{x}$ | partial derivative |
| `\jump{u}` | $\jump{u}$ | jump across an interface |
| `\avg{u}` | $\avg{u}$ | average across an interface |
| `\norm{u}` | $\norm{u}$ | norm |
| `\abs{J}` | $\abs{J}$ | absolute value |
| `u\T` | $u\T$ | transpose |

> **Note: Macros and Markdown sources**
>
> The macros live in `docs/theme/static/js/site.js` (`MACROS`). Readers of the raw `.md` files, including AI agents, see the macro names, so keep the list short and the names self-explanatory.

## A worked example at four depths

This section shows how one topic reads at each level. The mathematics is standard[^fc13][^gwk16]; it is here to exercise long-form math, numbering and nested levels.

<!-- begin level 1 (Essentials) -->
**[L1 Essentials] In plain terms**

High-order methods can be made robust by arranging the discrete derivative so that it obeys the same integration-by-parts rule as the exact derivative. Whatever the exact equations conserve, the discrete equations then conserve too.

<!-- end level 1 -->

On the $N + 1$ Gauss–Lobatto nodes of an element, with quadrature weights $w_i$ and Lagrange polynomials $\ell_j$, the collocation derivative matrix and the mass matrix are

$$
D_{ij} = \ell_j'(\xi_i), \qquad M = \operatorname{diag}(w_0, \dots, w_N). \label{eq:dm}
$$

In practice the volume term of a conservation law is evaluated with a symmetric two-point flux $f^{\#}$ instead of the nodal flux, which is called flux differencing:

$$
\left.\pd{f}{\xi}\right|_i \;\approx\; 2 \sum_{j=0}^{N} D_{ij}\, f^{\#}(u_i, u_j). \label{eq:fluxdiff}
$$

<!-- begin level 3 (Mechanics) -->
**[L3 Mechanics] Mechanics: why this is still a derivative**

The two-point flux must be symmetric, $f^{\#}(u_i, u_j) = f^{\#}(u_j, u_i)$, and consistent, $f^{\#}(u, u) = f(u)$. With the central choice $f^{\#}(u_i, u_j) = \tfrac12 (f_i + f_j)$, formula \eqref{eq:fluxdiff} reduces to the standard collocation derivative, because the rows of $D$ sum to zero:

$$
2 \sum_j D_{ij}\, \tfrac12 (f_i + f_j) \;=\; f_i \sum_j D_{ij} + \sum_j D_{ij} f_j \;=\; (D f)_i .
$$

Other two-point fluxes change the aliasing behaviour of the scheme without changing its order of accuracy. The cost is one flux evaluation per pair of nodes along each grid line, $O(N^2)$ per line instead of $O(N)$.

<!-- end level 3 -->

<!-- begin level 4 (Theory) -->
**[L4 Theory] Theory: the summation-by-parts property**

**Claim.** With \eqref{eq:dm}, the matrix $Q = MD$ satisfies

$$
Q + Q\T = B, \qquad B = \operatorname{diag}(-1, 0, \dots, 0, 1). \label{eq:sbp}
$$

**Proof.** Gauss–Lobatto quadrature with $N + 1$ nodes is exact for polynomials of degree at most $2N - 1$. The product $\ell_i\, \ell_j'$ has degree $2N - 1$, so, using $\ell_i(\xi_k) = \delta_{ik}$,

$$
Q_{ij} = w_i\, \ell_j'(\xi_i) = \sum_{k=0}^{N} w_k\, \ell_i(\xi_k)\, \ell_j'(\xi_k) = \int_{-1}^{1} \ell_i\, \ell_j' \,\dd\xi .
$$

Therefore

$$
Q_{ij} + Q_{ji} = \int_{-1}^{1} (\ell_i\, \ell_j)' \,\dd\xi = \ell_i(1)\,\ell_j(1) - \ell_i(-1)\,\ell_j(-1) = B_{ij},
$$

since the end points are nodes. $\blacksquare$

<!-- end level 4 -->

<!-- begin level 4 (Theory) -->
**[L4 Theory] Theory: entropy conservation of the volume term**

Let $s(u)$ be the entropy function, $v = \partial s / \partial u$ the entropy variables, $\psi$ the entropy potential and $F = v\T f - \psi$ the entropy flux. Suppose the two-point flux satisfies Tadmor's condition[^t87]

$$
(v_j - v_i)\T f^{\#}(u_i, u_j) = \psi_j - \psi_i . \label{eq:tadmor}
$$

Contract the volume term \eqref{eq:fluxdiff} with $w_i\, v_i\T$ and sum over the nodes. Writing $2Q = (Q - Q\T) + B$ from \eqref{eq:sbp} and using the symmetry of $f^{\#}$,

$$
\begin{aligned}
\sum_{i,j} 2\, Q_{ij}\, v_i\T f^{\#}_{ij}
  &= \sum_{i,j} Q_{ij}\, (v_i - v_j)\T f^{\#}_{ij} \;+\; \sum_{i} B_{ii}\, v_i\T f_i \\
  &= -\sum_{i,j} Q_{ij}\, (\psi_j - \psi_i) \;+\; \bigl[v\T f\bigr]_0^N \\
  &= -\bigl[\psi\bigr]_0^N + \bigl[v\T f\bigr]_0^N \;=\; F_N - F_0 .
\end{aligned}
$$

The third line uses $\sum_j Q_{ij} = 0$ (rows of $D$ sum to zero) and $\sum_i Q_{ij} = B_{jj}$ (from \eqref{eq:sbp}). The volume term therefore changes the discrete entropy $\sum_i w_i\, s_i$ only through the entropy flux at the two end points, exactly as in the continuous equation: no entropy is produced or destroyed inside the element.

<!-- end level 4 -->

At dial level 2 this section shows the two definitions and three stubs; at level 4 it is a complete derivation. The depth map in the right-hand column shows how much of the page sits at each level.

[^fc13]: T. C. Fisher and M. H. Carpenter, "High-order entropy stable finite difference schemes for nonlinear conservation laws: finite domains", *Journal of Computational Physics* 252 (2013).
[^gwk16]: G. J. Gassner, A. R. Winters and D. A. Kopriva, "Split form nodal discontinuous Galerkin schemes with summation-by-parts property for the compressible Euler equations", *Journal of Computational Physics* 327 (2016).
[^t87]: E. Tadmor, "The numerical viscosity of entropy stable schemes for systems of conservation laws. I", *Mathematics of Computation* 49 (1987).

## Code

Fenced code is highlighted at build time. The info string after the opening fence takes the language and two optional attributes: `title="..."` labels the block and `hl="..."` highlights lines (numbers and ranges).

````markdown
```python title="build_docs.py" hl="3-4"
from gyre.docsite import build_site

site = build_site("site", verification_dir="var/verification")
print(f"documentation written to {site}")
```
````

```python title="build_docs.py" hl="3-4"
from gyre.docsite import build_site

site = build_site("site", verification_dir="var/verification")
print(f"documentation written to {site}")
```

Any language known to Pygments works; the ones used most here are `python`, `bash`, `json`, `yaml` and `http`. Use `console` for a terminal session: the copy button then copies the commands without the prompts and the output.

```console
$ gyre docs build -o site --verification var/verification
documentation written to site
```

```http
GET /api/v1/info HTTP/1.1
Accept: application/json
```

```json
{"name": "gyre", "version": "0.1.0", "describe": "abc123+dirty",
 "deployment": {"name": "dev", "host": "workstation"}}
```

Inline code uses single backticks: `build_site(output_dir="site")`.

## Tabs

Alternatives that a reader chooses between go into tabs. A tab group is a `::: tabs` block whose panels start with `=== "Label"`:

````markdown
::: tabs
=== "Command line"
```bash
gyre docs build -o site
```
=== "Python"
```python
from gyre.docsite import build_site
build_site("site")
```
:::
````

**[Alternative: Command line]**

```bash
gyre docs build -o site
```

**[Alternative: Python]**

```python
from gyre.docsite import build_site
build_site("site")
```

Choosing a label selects the tab with the same label in every group on the page and is remembered between pages, so a reader who prefers Python sees Python everywhere. A panel may hold any Markdown, not only code. Without JavaScript, and in print, all panels are shown one after the other with their labels.

## Tables, lists and other Markdown

Standard Markdown tables are supported, with alignment given by colons in the separator row. Wide tables scroll sideways instead of breaking the layout.

| Level | Name | Typical content | Words per page (guide) |
|:---:|---|---|---:|
| 1 | Essentials | what and why | 100 to 300 |
| 2 | Practice | commands, options, examples | 300 to 1200 |
| 3 | Mechanics | algorithms, architecture | as needed |
| 4 | Theory | derivations, proofs | as needed |

Also available: definition lists, abbreviations, attribute lists (`{#custom-id}` after a heading sets its anchor), footnotes (`[^key]` in the text and `[^key]: note` anywhere on the page), and raw HTML. Straight quotes become typographic quotes in prose; dashes are left as typed, so write `--flag` in code and an en dash or em dash as the character itself.

Term
:   A definition list pairs a term with its explanation.

## Links

Link to other pages by the relative path of their Markdown file, optionally with an anchor:

```markdown
See the [verification overview](../verification/index.md) and
[how levels work](authoring.md#depth-levels).
```

The builder rewrites `.md` links to the built pages and checks every internal link and anchor. A link to a page or anchor that does not exist **fails the build** with a list of all broken links. Because sources and built pages mirror each other, the same relative links also work for agents that read the `.md` files. Links that start with `/` (such as `/api/docs`, the interactive API explorer served by the Gyre service) and links with a scheme (`https://…`) are left untouched.

## Data components

Shortcodes embed data-driven components. A shortcode sits alone on a line:

```markdown
{{ chart: isentropic_vortex/error_vs_h }}
```

Verification numbers on this site come only from the files written by `gyre verify`, one `<study-id>.json` per study, and every chart and table shows the revision, time and device recorded in that file. The examples on this page use **synthetic sample data** from `docs/theme/samples/` instead, referenced with the prefix `sample:`. Anything rendered from a sample is labelled "Synthetic illustration"; never use the prefix on a page that reports results.

### Charts

`{{ chart: <study-id>/<chart-id> }}` draws one chart of a study. Charts are plain SVG and need no network; hover or use the arrow keys to read values, click a legend entry to hide a series, open the data table, or download the chart as a standalone SVG.

A `convergence` chart has logarithmic axes, ticks at the mesh sizes and a slope triangle for each reference slope:

**Chart: Error against mesh size** (Demonstration study). Convergence chart of L2 error against h, x axis log, y axis log; 3 series. The table holds the plotted data. Reference slopes drawn: 2 (h^2), 3 (h^3), 4 (h^4). Convergence chart: log-log axes, ticks at the mesh sizes, reference-slope triangles.

| h | p = 1 | p = 2 | p = 3 |
|---|---|---|---|
| 0.5 | 0.234467 | 0.0548869 | 0.0125882 |
| 0.25 | 0.0541215 | 0.00624785 | 7.7034e-04 |
| 0.125 | 0.0145244 | 8.6089e-04 | 4.9834e-05 |
| 0.0625 | 0.00342 | 9.7412e-05 | 2.9699e-06 |

*Synthetic illustration: made-up numbers that demonstrate the component; not solver output.*

A `history` chart draws dense series without markers:

**Chart: Residual history** (Demonstration study). History chart of relative residual against nonlinear iteration, y axis log; 3 series. The table holds the plotted data. History chart: dense lines without markers.

| nonlinear iteration | p = 1 | p = 2 | p = 3 |
|---|---|---|---|
| 0 | 1 | 1 | 1 |
| 1 | 1.15201 | 1.29888 | 1.42121 |
| 2 | 0.238064 | 0.302638 | 0.362324 |
| 3 | 0.0846956 | 0.121397 | 0.159025 |
| 4 | 0.103006 | 0.166466 | 0.2386 |
| 5 | 0.0582651 | 0.106166 | 0.166501 |
| 6 | 0.0199054 | 0.0408943 | 0.0701749 |
| 7 | 0.0114103 | 0.0264304 | 0.049626 |
| 8 | 0.00802557 | 0.0209604 | 0.0430617 |
| 9 | 0.00396425 | 0.0116734 | 0.0262408 |
| 10 | 0.00184179 | 0.00611497 | 0.0150404 |
| 11 | 0.00107928 | 0.00404019 | 0.0108731 |
| 12 | 6.2289e-04 | 0.00262902 | 0.00774163 |
| 13 | 3.1433e-04 | 0.00149585 | 0.00481962 |
| 14 | 1.6402e-04 | 8.8005e-04 | 0.00310256 |
| 15 | 9.2353e-05 | 5.5870e-04 | 0.00215516 |
| 16 | 5.0136e-05 | 3.4198e-04 | 0.00144338 |
| 17 | 2.6192e-05 | 2.0143e-04 | 9.3026e-04 |
| 18 | 1.4094e-05 | 1.2221e-04 | 6.1756e-04 |
| 19 | 7.7178e-06 | 7.5454e-05 | 4.1718e-04 |
| 20 | 4.1362e-06 | 4.5594e-05 | 2.7583e-04 |
| 21 | 2.2029e-06 | 2.7379e-05 | 1.8123e-04 |
| 22 | 1.1901e-06 | 1.6678e-05 | 1.2079e-04 |
| 23 | 6.4357e-07 | 1.0168e-05 | 8.0582e-05 |
| 24 | 3.4495e-07 | 6.1450e-06 | 5.3284e-05 |
| 25 | 1.8511e-07 | 3.7181e-06 | 3.5276e-05 |
| 26 | 9.9849e-08 | 2.2612e-06 | 2.3474e-05 |
| 27 | 5.3763e-08 | 1.3728e-06 | 1.5593e-05 |
| 28 | 2.8869e-08 | 8.3111e-07 | 1.0330e-05 |
| 29 | 1.5527e-08 | 5.0399e-07 | 6.8538e-06 |
| 30 | 8.3621e-09 | 3.0604e-07 | 4.5538e-06 |
| 31 | 4.4979e-09 | 1.8560e-07 | 3.0218e-06 |
| 32 | 2.4180e-09 | 1.1250e-07 | 2.0041e-06 |
| 33 | 1.3010e-09 | 6.8247e-08 | 1.3303e-06 |
| 34 | 7.0012e-10 | 4.1409e-08 | 8.8315e-07 |
| 35 | 3.7655e-10 | 2.5111e-08 | 5.8599e-07 |
| 36 | 2.0253e-10 | 1.5228e-08 | 3.8882e-07 |
| 37 | 1.0896e-10 | 9.2375e-09 | 2.5808e-07 |
| 38 | 5.8622e-11 | 5.6033e-09 | 1.7129e-07 |
| 39 | 3.1532e-11 | 3.3982e-09 | 1.1366e-07 |
| 40 | 1.6962e-11 | 2.0611e-09 | 7.5431e-08 |
| 41 | 9.1252e-12 | 1.2502e-09 | 5.0063e-08 |
| 42 | 4.9089e-12 | 7.5827e-10 | 3.3225e-08 |
| 43 | 2.6406e-12 | 4.5990e-10 | 2.2049e-08 |
| 44 | 1.4205e-12 | 2.7894e-10 | 1.4633e-08 |
| 45 | 7.6417e-13 | 1.6919e-10 | 9.7112e-09 |
| 46 | 4.1108e-13 | 1.0262e-10 | 6.4448e-09 |
| 47 | 2.2113e-13 | 6.2241e-11 | 4.2770e-09 |
| 48 | 1.2318e-13 | 3.7751e-11 | 2.8385e-09 |
| 49 | 1.0462e-13 | 2.2898e-11 | 1.8838e-09 |
| 50 | 1.7376e-13 | 1.3888e-11 | 1.2502e-09 |
| 51 | 2.6702e-13 | 8.4234e-12 | 8.2966e-10 |
| 52 | 2.9866e-13 | 5.1091e-12 | 5.5061e-10 |
| 53 | 2.3959e-13 | 3.0988e-12 | 3.6541e-10 |
| 54 | 1.4412e-13 | 1.8795e-12 | 2.4250e-10 |
| 55 | 1.0002e-13 | 1.1400e-12 | 1.6094e-10 |
| 56 | 1.4784e-13 | 6.9144e-13 | 1.0681e-10 |
| 57 | 2.4362e-13 | 4.8723e-13 | 7.0882e-11 |
| 58 | 2.9929e-13 | 5.9858e-13 | 4.7041e-11 |
| 59 | 2.6367e-13 | 5.2735e-13 | 3.1219e-11 |
| 60 | 1.6952e-13 | 3.3904e-13 | 2.0718e-11 |

*Synthetic illustration: made-up numbers that demonstrate the component; not solver output.*

A `bar` chart compares categories. It draws thin vertical bars on a zero baseline; when the category labels are long or numerous it switches to horizontal bars, and when the value axis is logarithmic it becomes a dot plot, because a bar has no honest length on a log scale. A `line` chart with a single series needs no legend:

**Chart: Relative cost per degree of freedom** (Demonstration study). Bar chart of cost (p = 1 residual = 1) against polynomial order; 2 series. The table holds the plotted data. Bar chart: grouped, thin bars from a zero baseline.

| polynomial order | residual | Jacobian-vector product |
|---|---|---|
| p = 1 | 1 | 2.1 |
| p = 2 | 2.6 | 5.3 |
| p = 3 | 5.9 | 12.4 |

*Synthetic illustration: made-up numbers that demonstrate the component; not solver output.*

**Chart: A single smooth curve** (Demonstration study). Line chart of lift coefficient against angle of attack (degrees); 1 series. The table holds the plotted data. Line chart with one series: no legend is needed.

| angle of attack (degrees) | lift coefficient |
|---|---|
| -2 | -0.2128 |
| -1.5 | -0.161963 |
| -1 | -0.1091 |
| -0.5 | -0.0548875 |
| 0 | 0 |
| 0.5 | 0.0548875 |
| 1 | 0.1091 |
| 1.5 | 0.161963 |
| 2 | 0.2128 |
| 2.5 | 0.260938 |
| 3 | 0.3057 |
| 3.5 | 0.346413 |
| 4 | 0.3824 |
| 4.5 | 0.412988 |
| 5 | 0.4375 |
| 5.5 | 0.455263 |
| 6 | 0.4656 |

*Synthetic illustration: made-up numbers that demonstrate the component; not solver output.*

Charts are forgiving about data, and say so:

- A `null` (or non-finite) value breaks a line: the series is drawn as separate segments, with no markers. Use this to draw several short pieces, such as tangents, as one series.
- Zero and negative values cannot be drawn on a logarithmic axis. They are left out and counted in a note under the chart; the data table keeps them.
- Series that lie on top of each other are drawn dashed with small markers, so both stay visible, and share one direct label.
- A reference slope is drawn as a triangle next to the part of a series it explains, on any chart with two logarithmic axes.

### Tables from a study

`{{ table: <study-id>/<table-id> }}` renders one table of a study. Numbers are right-aligned in tabular figures with six significant digits, in scientific notation below 0.001 and from a million up; booleans read yes or no and missing values are shown as a dash. A table wider than the text column scrolls sideways inside its frame.

**Table: Errors and observed orders** (Demonstration study)

| p | N | h | L2 error | order |
|---|---|---|---|---|
| 1 | 2 | 0.5 | 0.234467 | — |
| 1 | 4 | 0.25 | 0.0541215 | 2.115 |
| 1 | 8 | 0.125 | 0.0145244 | 1.898 |
| 1 | 16 | 0.0625 | 0.00342 | 2.086 |
| 2 | 2 | 0.5 | 0.0548869 | — |
| 2 | 4 | 0.25 | 0.00624785 | 3.135 |
| 2 | 8 | 0.125 | 8.6089e-04 | 2.859 |
| 2 | 16 | 0.0625 | 9.7412e-05 | 3.144 |
| 3 | 2 | 0.5 | 0.0125882 | — |
| 3 | 4 | 0.25 | 7.7034e-04 | 4.03 |
| 3 | 8 | 0.125 | 4.9834e-05 | 3.95 |
| 3 | 16 | 0.0625 | 2.9699e-06 | 4.069 |

*Synthetic illustration: made-up numbers that demonstrate the component; not solver output.*

### Status badges and whole studies

`{{ status: <study-id> }}` shows whether a study passed, and each of its checks with the measured value and the expectation. Pass and fail are marked by an icon and a word, never by colour alone.

**Verification status: Demonstration study** (study `sample:demo_convergence`): **PASSED**, 3 of 3 checks passed, 0 s.

Synthetic illustration: numbers generated by a formula to demonstrate the component. The numbers imitate a convergence study with design order p + 1.

| Check | Value | Expected | Result |
|---|---|---|---|
| observed order, p = 1 | 2.086 | >= 1.8 | pass |
| observed order, p = 2 | 3.144 | >= 2.8 | pass |
| observed order, p = 3 | 4.069 | >= 3.8 | pass |

*Synthetic illustration: made-up numbers that demonstrate the component; not solver output.*

**Verification status: Demonstration of a failing study** (study `sample:demo_failing`): **FAILED**, 2 of 3 checks passed, 0 s.

Synthetic illustration: numbers generated by a formula to demonstrate the component. One check is made to fail so that the failure styling can be seen.

| Check | Value | Expected | Result |
|---|---|---|---|
| free-stream residual | 3.1000e-15 | <= 1e-12 | pass |
| observed order, p = 3 | 3.12 | >= 3.8 | FAIL |
| entropy production | -2.4000e-16 | <= 1e-13 | pass |

*Synthetic illustration: made-up numbers that demonstrate the component; not solver output.*

`{{ study: <study-id> }}` renders everything a study recorded: the status, then every chart and every table. On a page that sets `study:` in its front matter the id can be omitted, so a verification page can consist of an explanation followed by `{{ study }}`.

`{{ studies }}` renders a summary grid of all studies found in the verification directory, each linked to the page that declares it. The grid below is the real one for this build:

**39 of 39 verification studies passed.** This site was built from revision `ceb1f4b0bed41de53e586df10858079f5a2bcdd8`.

| Study | Id | Result | Checks | Wall time (s) | Commit | Date | Device | Suite | Revision vs build |
|---|---|---|---|---|---|---|---|---|---|
| [Summation-by-parts operators](../verification/discrete-invariants.md) | `operators` | PASSED | 1/1 | 0.29 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:22:15-0500 | cpu | quick | Same code as this build |
| [Free-stream preservation on curved meshes](../verification/discrete-invariants.md) | `free_stream` | PASSED | 1/1 | 18.67 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:22:33-0500 | cpu | quick | Same code as this build |
| [Entropy conservation and stability](../verification/discrete-invariants.md) | `entropy_conservation` | PASSED | 41/41 | 15.77 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:22:32-0500 | cpu | quick | Same code as this build |
| [Structure of the BR2 viscous operator](../verification/discrete-invariants.md) | `viscous_operator` | PASSED | 17/17 | 116.91 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:24:33-0500 | cpu | quick | Same code as this build |
| [Independent 1-D model of the BR2 formulation](../verification/order-of-accuracy.md) | `diffusion_1d` | PASSED | 17/17 | 4.93 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:22:38-0500 | cpu | quick | Same code as this build |
| [Exact linearization by automatic differentiation](../verification/derivatives.md) | `linearization` | PASSED | 40/40 | 198.07 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:27:54-0500 | cpu | quick | Same code as this build |
| [Differentiable mesh deformation](../verification/derivatives.md) | `mesh_deformation` | PASSED | 9/9 | 2.45 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:22:42-0500 | cpu | quick | Same code as this build |
| [Advected density wave](../verification/order-of-accuracy.md) | `density_wave` | PASSED | 4/4 | 40.98 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:22:56-0500 | gpu · NVIDIA GeForce RTX 3060 Ti | quick | Same code as this build |
| [Isentropic vortex convection](../verification/order-of-accuracy.md) | `isentropic_vortex` | PASSED | 10/10 | 65.18 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:24:02-0500 | gpu · NVIDIA GeForce RTX 3060 Ti | quick | Same code as this build |
| [Manufactured solution: Euler](../verification/order-of-accuracy.md) | `mms_euler` | PASSED | 6/6 | 212.49 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:27:38-0500 | gpu · NVIDIA GeForce RTX 3060 Ti | quick | Same code as this build |
| [Manufactured solutions: Navier–Stokes](../verification/order-of-accuracy.md) | `mms_navier_stokes` | PASSED | 14/14 | 1225.27 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:48:07-0500 | gpu · NVIDIA GeForce RTX 3060 Ti | quick | Same code as this build |
| [Nonlinear and linear solver convergence](../verification/solvers.md) | `solver_convergence` | PASSED | 16/16 | 890.13 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:42:49-0500 | cpu | quick | Same code as this build |
| [Line-implicit smoothing on a wall-resolved mesh](../verification/solvers.md) | `line_smoother` | PASSED | 7/7 | 2159.53 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:58:43-0500 | cpu | quick | Same code as this build |
| [Line-implicit smoothing in three dimensions](../verification/solvers.md) | `line_smoother_3d` | PASSED | 6/6 | 620.35 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T10:58:47-0500 | gpu · NVIDIA GeForce RTX 3060 Ti | quick | Same code as this build |
| [Single-precision blocks in the preconditioner](../verification/solvers.md) | `mixed_precision` | PASSED | 6/6 | 2648.96 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:27:02-0500 | cpu | quick | Same code as this build |
| [Adjoint gradients versus finite differences](../verification/derivatives.md) | `adjoint_gradients` | PASSED | 18/18 | 1176.07 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:18:25-0500 | cpu | quick | Same code as this build |
| [Output error estimation](../verification/output-error.md) | `output_error` | PASSED | 5/5 | 367.66 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:33:17-0500 | cpu | quick | Same code as this build |
| [Laminar cylinder at Re = 40](../verification/validation.md) | `cylinder_re40` | PASSED | 7/7 | 417.19 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:25:40-0500 | cpu | quick | Same code as this build |
| [Three-dimensional consistency](../verification/three-dimensional.md) | `extruded_3d` | PASSED | 8/8 | 300.8 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:03:54-0500 | gpu · NVIDIA GeForce RTX 3060 Ti | quick | Same code as this build |
| [Inviscid flow past a sphere (3-D)](../verification/three-dimensional.md) | `sphere_inviscid` | PASSED | 9/9 | 424.67 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:11:04-0500 | gpu · NVIDIA GeForce RTX 3060 Ti | quick | Same code as this build |
| [Laminar flow past a sphere, Re = 100 (3-D)](../verification/three-dimensional.md) | `sphere_re100` | PASSED | 5/5 | 187.76 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:14:26-0500 | gpu · NVIDIA GeForce RTX 3060 Ti | quick | Same code as this build |
| [Adjoint derivatives of 3-D outputs](../verification/three-dimensional.md) | `adjoint_3d` | PASSED | 6/6 | 297.74 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:38:18-0500 | cpu | quick | Same code as this build |
| [Largest 3-D solve on one device](../verification/three-dimensional.md) | `scale_3d` | PASSED | 7/7 | 452.04 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:22:06-0500 | gpu · NVIDIA GeForce RTX 3060 Ti | quick | Same code as this build |
| [Manufactured solutions: RANS with the Spalart-Allmaras model](../verification/turbulence.md) | `rans_mms` | PASSED | 9/9 | 455.83 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:33:19-0500 | cpu | quick | Same code as this build |
| [Exact linearization with the Spalart-Allmaras model](../verification/turbulence.md) | `rans_linearization` | PASSED | 20/20 | 158.63 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:40:59-0500 | cpu | quick | Same code as this build |
| [Turbulent flat plate (NASA TMR) with the Spalart-Allmaras model](../verification/turbulence.md) | `rans_flat_plate` | PASSED | 8/8 | 162.14 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:36:05-0500 | cpu | quick | Same code as this build |
| [Three-dimensional consistency with the Spalart-Allmaras model](../verification/turbulence.md) | `rans_extruded_3d` | PASSED | 10/10 | 675.08 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:33:26-0500 | gpu · NVIDIA GeForce RTX 3060 Ti | quick | Same code as this build |
| [Adjoint gradients with the Spalart-Allmaras model](../verification/turbulence.md) | `rans_adjoint` | PASSED | 6/6 | 119 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:43:02-0500 | cpu | quick | Same code as this build |
| [Discipline contract for MDO integration](../verification/discipline-contract.md) | `discipline_contract` | PASSED | 15/15 | 249.94 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:40:21-0500 | cpu | quick | Same code as this build |
| [Imported meshes](../verification/imported-meshes.md) | `imported_mesh` | PASSED | 29/29 | 184.52 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:46:10-0500 | cpu | quick | Same code as this build |
| [Output-driven order selection](../verification/order-selection.md) | `order_selection` | PASSED | 13/13 | 364.44 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:46:30-0500 | cpu | quick | Same code as this build |
| [Example: inviscid NACA 0012](../examples/naca0012-euler.md) | `example_airfoil` | PASSED | 4/4 | 104.52 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:35:15-0500 | gpu · NVIDIA GeForce RTX 3060 Ti | quick | Same code as this build |
| [Example: laminar cylinder, Re = 40](../examples/cylinder-re40.md) | `example_cylinder` | PASSED | 4/4 | 51.5 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:47:06-0500 | cpu | quick | Same code as this build |
| [Example: turbulent flat plate (Spalart-Allmaras)](../examples/turbulent-flat-plate.md) | `example_flat_plate` | PASSED | 5/5 | 119.87 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:48:33-0500 | cpu | quick | Same code as this build |
| [Example: lift polar with adjoint slopes](../examples/lift-polar.md) | `example_polar` | PASSED | 5/5 | 686.1 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:58:34-0500 | cpu | quick | Same code as this build |
| [Example: coupled aero-structural adjoint](../examples/aerostructural-coupling.md) | `example_coupling` | PASSED | 2/2 | 822.97 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T12:02:29-0500 | cpu | quick | Same code as this build |
| [Example: transonic aerofoil with shock capturing](../examples/transonic.md) | `example_transonic` | PASSED | 9/9 | 151.78 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T11:37:50-0500 | gpu · NVIDIA GeForce RTX 3060 Ti | quick | Same code as this build |
| [Kernel throughput (cpu)](../engineering/performance.md) | `performance_cpu` | PASSED | 2/2 | 123.77 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T12:04:40-0500 | cpu | quick | Same code as this build |
| [Kernel throughput (NVIDIA GeForce RTX 3060 Ti)](../engineering/performance.md) | `performance_gpu` | PASSED | 2/2 | 103.55 | `39979fcf902ad6116eb550b881b4278d4599c8e2` | 2026-11-12T12:06:32-0500 | gpu · NVIDIA GeForce RTX 3060 Ti | quick | Same code as this build |

Records (JSON): `../data/verification/<id>.json`, index [index.json](../data/verification/index.json).

### When data is missing

A missing study, chart, table or field never fails the build. The reader sees a placeholder that says what to run, like this one for a study that does not exist:

*[Data not available: study `no_such_study` has no verification results in this build (`no_such_study.json` not found). Run `gyre verify` and rebuild the documentation.]*

### Field viewer

`{{ field: <path> | field=mach | title=… | height=420 }}` embeds an interactive viewer for a two-dimensional solution sample written by `gyre.io.field_payload` (the file `field.json` in a run directory). The path is relative to the runs directory, `var/runs` by default.

Drag to pan. Click the view, then scroll to zoom (or hold Ctrl while scrolling); pinch on a touch screen. The value under the pointer is read out in the corner. The tool bar selects the field and the colour map and toggles element edges and isolines.

**Flow field: Analytic vortex on an invented grid.** Interactive viewer in the HTML page (initially showing `mach`). 100 elements · 6×6 points each. Sampled solution (coordinates and fields, base64 float32): [analytic_vortex.json](../data/fields/samples/analytic_vortex.json).

| Field | Minimum in the sample | Maximum in the sample |
|---|---|---|
| mach | 0.023325 | 1.23468 |
| pressure | 0.375613 | 1 |
| density | 0.49687 | 1 |

*Synthetic illustration: made-up numbers that demonstrate the component; not solver output.*

The next viewer asks for a real sample, `smoke/field.json`. It appears if that run exists in the runs directory when the site is built, and is replaced by a placeholder otherwise:

*[Data not available: the field sample `smoke/field.json` is not found]*

| Option | Default | Meaning |
|---|---|---|
| `field` | first field | Field shown initially. |
| `title` | case name | Caption above the viewer. |
| `height` | `420` | Height of the view in pixels. |
| `view` | the sample's own `view`, else everything | Initial window as `xmin,xmax,ymin,ymax`; also what the reset button returns to. The window is fitted inside the view at equal scale in both directions. |
| `cmap` | `viridis` | Colour map: `viridis` or `magma` (both perceptually uniform). |
| `edges` | `off` | `on` to start with element edges drawn. |

<!-- begin level 3 (Mechanics) -->
**[L3 Mechanics] Data contracts**

A study record is a JSON object:

```json
{"id": "isentropic_vortex", "title": "Isentropic vortex", "description": "...",
 "passed": true, "wall_time": 12.3,
 "checks": [{"name": "observed order p=3", "value": 3.6, "expected": ">= 3.4", "passed": true}],
 "charts": [{"id": "error_vs_h", "type": "convergence", "title": "...",
             "xlabel": "h", "ylabel": "L2 error", "xscale": "log", "yscale": "log",
             "series": [{"name": "p=2", "x": [0.5, 0.25], "y": [1e-2, 1.3e-3]}],
             "reference_slopes": [{"slope": 3, "label": "h^3"}], "note": "optional caption"}],
 "tables": [{"id": "orders", "title": "...", "columns": ["N", "error", "order"],
             "rows": [[16, 1e-3, null]]}],
 "provenance": {"version": "0.1.0", "commit": "...", "describe": "abc123+dirty",
                "timestamp": "...", "device": {"platform": "gpu", "device_kind": "..."}}}
```

Chart `type` is `convergence`, `history`, `line` or `bar`; `xscale` and `yscale` are `log` or `linear`, and `xscale` may be `category` for bar charts, which take category names in `x`. Entries of `x` and `y` may be `null`; non-finite numbers are treated as `null`. A reference slope may name the series it belongs to with `"series": "p=2"`; otherwise the slopes are matched to the series in order of steepness when there are as many of one as of the other, and to the series with the closest fitted slope if not. A chart series may also hold `null` where a study deliberately omits a point. A study may also carry `level` (the suite it ran in), `notes` (a paragraph the study generates from the numbers of its own run, shown under the status) and `error` (the traceback of a study that raised, shown in the status block); a timing study also carries `device` and `host_load`. Files in the verification directory that are not study records (such as `index.json`) are ignored, except that `index.json` sets the order of the overview grid.

`index.json` is written by `gyre verify`. Its `studies` list has one entry per result file in the directory, each with `id`, `title`, `passed`, `checks`, `failed_checks`, `wall_time` and the `level`, `commit`, `device` and `timestamp` of the run that produced it, plus `stale` (true if that run was not the last invocation). `suite`, `generated`, `wall_time`, `provenance`, `requested` (the study ids of the last invocation), `skipped` (studies not run, each with `id` and `reason`) and `summary` (`ran`, `passed`, `failed`, `stale`, `stale_failed`) describe the last invocation.

A field sample is `{"m", "elements", "x", "y", "fields": {name: {"data", "min", "max"}}}`, where `x`, `y` and each `data` are base64-encoded little-endian `float32` arrays of shape `(elements, m, m)`: every element is a structured `m × m` patch. An optional key `"view": [xmin, xmax, ymin, ymax]` gives the window to show first; without it the viewer fits the whole sample. The builder copies the sample to `data/fields/` in the site. If a `result.json` lies next to the sample, its `provenance` is shown under the viewer.

<!-- end level 3 -->

## Diagrams

`{{ diagram: <file.svg> | caption=… | width=… }}` inlines an SVG file into the page, so that it can use the theme's colours and follow the light and dark themes. The file is looked up next to the page, then relative to `docs/content/`, then in `docs/theme/diagrams/` (where the one below lives). Keep diagram sources that belong to the content in a directory whose name starts with `_` (for example `docs/content/_diagrams/`) so that they are not also copied to the site as loose files.

**Diagram** (`build-pipeline.svg`, drawn in the HTML page): The build: inputs on the left, the three modules of `gyre.docsite` in the middle, the static site on the right.

Text in the diagram: How the documentation site is built; Markdown pages, verification records, field samples and the theme flow through markup.py, shortcodes.py and build.py into the static site.; docs/content/**/*.md; pages with front matter; var/verification/*.json; study records (gyre verify); var/runs/**/field.json; field samples (gyre run); docs/theme/; templates, CSS, JS, KaTeX; markup.py; level blocks, callouts, tabs, math, code; shortcodes.py; charts, tables, status, fields, generators; build.py; navigation, link check, templates; site/; <page>.html; <page>.md; search-index.json; llms.txt; site-manifest.json; assets/; data/fields/; static, offline,; byte-reproducible.

Do not put colours in the SVG. Use these classes instead:

| Class | Use |
|---|---|
| `dg-line`, `dg-accent`, `dg-faint` | strokes: normal, emphasised, background |
| `dg-box`, `dg-box-accent` | filled rectangles and shapes |
| `dg-fill`, `dg-fill-accent` | solid fills, such as arrow heads |
| `dg-text`, `dg-small`, `dg-mono` | labels: normal, secondary, code |
| `dg-l1` … `dg-l4` | fills in the colours of the four depth levels |

Give the `<svg>` a `viewBox`, a `<title>` and, when it carries information, a `<desc>`. Ids inside the file (markers, gradients) must be unique on the page, so prefix them.

## Generated content and page lists

`{{ generated: <name> }}` calls a registered Python function that returns Markdown and renders the result in place. This is how reference material that lives in the code, such as tables of presets or a schema, stays in step with the code. The build information below is produced this way:

| Build | |
|---|---|
| Package | `gyre` |
| Version | `0.4.0` |
| Revision | `ceb1f4b0be` |
| Commit | `ceb1f4b0bed41de53e586df10858079f5a2bcdd8` |
| Working tree | clean |

<!-- begin level 3 (Mechanics) -->
**[L3 Mechanics] Registering a generator**

Generators are registered in `src/gyre/docsite/generators.py`, which the builder imports before rendering. A generator takes no arguments and returns Markdown; anything the Markdown dialect supports may be used in it, including level blocks and other shortcodes.

```python title="src/gyre/docsite/generators.py"
from .shortcodes import register_generator

@register_generator("presets")
def presets_table() -> str:
    from ..cases import PRESETS                   # import heavy modules lazily

    rows = [f"| `{name}` | {spec.description} |" for name, spec in PRESETS.items()]
    return "\n".join(["| Preset | Description |", "|---|---|", *rows])
```

A generator that is not registered, or that raises, does not fail the build: the page shows a "Shortcode problem" box and the problem is recorded in the `warnings` of `site-manifest.json`.

<!-- end level 3 -->

Two more shortcodes help with navigation: `{{ pages }}` lists the pages of the current section as cards (`{{ pages: guide }}` lists another section), and `{{ levels }}` prints the legend of the four levels shown at the top of this page.

## Building and checking

```console
$ gyre docs build -o site --verification var/verification
documentation written to site
```

The build is fast, has no network access and needs no tool chain beyond Python. Opening `site/index.html` from the file system works; the live deployment stamp in the footer then falls back to the build-time stamp.

<!-- begin level 3 (Mechanics) -->
**[L3 Mechanics] What the build writes and where it looks**

`build_site(output_dir="site", verification_dir="var/verification", content_dir=None, runs_dir="var/runs")` returns the output path. `content_dir` defaults to `docs/content` and the theme to `docs/theme` of the source checkout that contains the package (falling back to the working directory); the environment variables `GYRE_DOCS_CONTENT` and `GYRE_DOCS_THEME` override them. The other paths are relative to the working directory.

| Output | Purpose |
|---|---|
| `<page>.html`, `<page>.md` | every page, and its Markdown source next to it |
| `search-index.json`, `search-index.js` | full-text index (the `.js` copy lets search work from the file system) |
| `llms.txt`, `llms-full.txt` | index of all pages for AI agents, and all pages in one file |
| `site-manifest.json` | pages, sections, levels, build stamp, warnings and the list of files written |
| `site-data.js` | build stamp and reading paths for the scripts |
| `assets/` | the theme's CSS, JavaScript and vendored KaTeX |
| `data/fields/` | field samples used by `field` shortcodes |

The output is deterministic: the same inputs give the same bytes, and no wall-clock time is written unless `SOURCE_DATE_EPOCH` is set. Files are rewritten only when their content changes, and files that a previous build wrote but this one does not are removed; nothing else in the output directory is touched.

Authoring errors (unknown section, unclosed block, broken internal link or anchor) raise `gyre.docsite.DocsBuildError` listing every problem. Missing data, unknown shortcodes and failing generators do not; they are shown on the page and recorded as warnings.

<!-- end level 3 -->

## Checklist for a new page

1. Front matter has `title`, `summary`, `order` and the right `level`.
2. The summary can be read alone and says what the page delivers.
3. The page reads well with the dial at its base level; deeper passages are in titled level blocks.
4. Numbers come from verification output through shortcodes, not typed by hand.
5. `gyre docs build` succeeds: no broken links, and no unexpected warnings in `site-manifest.json`.
