Skip to content

Docs conventions

How the published documentation site is put together, and how to write a page that fits it.

The site is built from two sources that behave very differently:

Tree Origin Edit it here?
docs-pub/ Synced automatically from the METIS source repo No — changes are overwritten on the next sync. Edit upstream in METIS.
automation/, README.md, CONTRIBUTING.md Written and maintained in this repo Yes
site-overlay/ Written and maintained in this repo Yes — this is how you change the site's view of docs-pub/ durably

How the build works

.github/workflows/docs.yml runs three steps:

  1. .github/scripts/assemble_docs.py builds site_src/ (the mkdocs docs_dir): copies the published trees in, renames every README.md to index.md and rewrites links to match, lays site-overlay/ on top, normalizes Markdown for Python-Markdown, and injects tags: front matter from docs-pub/tags.yml (the docs pages, which also declares the tag vocabulary) and site-overlay/tags.yml (this repo's own pages).
  2. .github/scripts/check_nav_coverage.py fails the build if any assembled page is missing from the nav, or the nav points at a page that doesn't exist.
  3. mkdocs build --strict.

Nothing needs a workflow edit when a page is added — but step 2 means a new page does need a nav entry, in whichever of the two nav files owns it.

The nav is two files, and which one you edit depends on where the page lives

There is no nav: block in mkdocs.yml. mkdocs-literate-nav reads SUMMARY.md — a nested Markdown list — instead, and it reads one per section:

  • site-overlay/SUMMARY.md — this repo's own pages: the home page, automation/, the tags index, Contributing and this file. Its - [METIS Docs](docs-pub/) entry is a cross-link: a nav link ending in / makes literate-nav read that directory's own SUMMARY.md.
  • METIS's docs/pub/SUMMARY.md, arriving here as docs-pub/SUMMARY.md — every published docs page. Edit it in METIS. Editing it here does nothing: docs-pub/ is overwritten wholesale on the next sync. It lives beside the pages because five docs deploys broke when it did not: a page added upstream, a menu entry that had to be added here, and nothing comparing the two until this build ran (METIS-pub#434). METIS's deploy/check_docs_pub.py now refuses that merge.

Two rules, for both files:

  • The list must be tight (no blank lines between items). A blank line makes Python-Markdown wrap each item in <p>, and literate-nav rejects that.
  • Order is meaningful. It sets both the sidebar order and the prev/next footer links. Order by the reader's path through the material, not alphabetically — the IRIS job pages are ordered by pipeline position, which is the order an operator meets them.

A section whose title line carries a link uses that page as the section's landing page (Material's navigation.indexes), so "Job reference" opens the IRIS job index rather than merely expanding.

Adding a page

  • Upstream in METIS, under docs/pub/: add the file, its line in docs/pub/SUMMARY.md and, if the page belongs on the landing page, a card in docs/pub/README.md — all in METIS, in the same change. deploy/check_docs_pub.py refuses the merge otherwise, which is the point: an unreachable published page is worse than a red build, and a red build here means the whole site stops updating. Nothing to do in this repo. Write links and nav entries as README.md; assemble_docs.py rewrites them to index.md.
  • In this repo, under automation/: any README.md under automation/ is picked up automatically; still add the SUMMARY.md line.

Tags

Two files map page paths to tags, matching the nav split: METIS's docs/pub/tags.yml for the docs pages (edit it there), and site-overlay/tags.yml for this repo's own. Tags live in a mapping rather than in the pages because front matter added to docs-pub/ would be lost on the next sync.

The vocabulary is deliberately small (linkedin, publishing, permissions, api, configuration) and is declared once, under vocabulary: in METIS's docs/pub/tags.yml. Both mappings are checked against it — by assemble_docs.py here and by deploy/check_docs_pub.py in METIS — so a typo fails before it can quietly create a one-page tag. mkdocs.yml no longer carries a tags_allowed list, because a second copy of the vocabulary would have to agree with the first with nothing checking it. A tag that lands on half the site sorts nothing; if a new tag wouldn't group at least three pages that are far apart in the nav, it isn't earning its place.

Writing a page

The IRIS job pages are the model to copy. Every one of them opens with the same key-value table and then follows the same heading spine — so a reader who has read one can navigate all nineteen without re-orienting. Aim for that property per document type.

Every page

  1. An H1 that names the thing, not the section it sits in.
  2. A lede of one or two sentences that says what the page covers and who it is for. A page must stand on its own: most readers arrive from search, not from the index, so "who should read this" belongs on the page rather than only in the index table.
  3. No hand-numbered headings. Don't write ## 2.2.4 Job Steps. The sidebar numbers itself, and hand numbering has to be rewritten every time a page is inserted.
  4. Links to sibling pages by relative .md path (youtube-uploader.md, ../using-iris.md), so they work both on GitHub and on the site.

Reference pages (one page per unit of a repeating thing)

Follow the IRIS job pattern exactly:

  • A key-value table at the top: what identifies this thing (label, slug, source file, class, dependencies).
  • Then the fixed spine: Purpose → Pipeline position → Data flow → Requirements → Behavior details → Testing this step → Related runbooks.
  • The set of them gets an index page that is a table, not prose: one row per page, with the columns a reader scans by. docs-pub/metis_apps/coherence/iris/jobs/README.md uses # / Job / Slug / Depends on / Purpose, and fits nineteen pages on one screen.
  • The index says what has no page of its own and what is legacy. Navigation honesty: a reader who can't find a page should learn why from the index.
  • Where the structure isn't linear, draw it — the ASCII branch diagram in the IRIS job index conveys the two-branch shape faster than any amount of prose.

Guides and how-tos

  • Lead with the outcome, then the click-path in order.
  • Prerequisites go in an !!! note admonition before step 1, not scattered inline.
  • If the guide is for a specific audience (participants, administrators), say so in the lede — see iris/participant-approval.md.

Placeholder pages

A page whose guide isn't written yet must say so in its own body, in an admonition:

!!! warning "Placeholder"

    The usage guide for this app has not been written yet. This page exists so the
    section has a home; it will be filled in.

Don't leave the fact that a page is a stub visible only from the index — readers arriving from search deserve to know within one screen. metis_apps/audax/quests-and-missions.md, metis_apps/outreach/linkedin-outreach.md and web/invite/signup.md are the current stubs; they still need this treatment upstream in METIS.

Building locally

python3 -m venv .venv
.venv/bin/pip install mkdocs-material mkdocs-literate-nav
python3 .github/scripts/assemble_docs.py
python3 .github/scripts/check_nav_coverage.py
.venv/bin/mkdocs serve

site_src/ and site/ are both generated and both git-ignored — never edit them.