Skip to content

Docs authoring conventions

Every markdown file under docs/ ships through the same VitePress pipeline and ends up in front of operators, integrators, and contributors who do not have access to the internal task tracker. To keep the corpus readable and to keep the rendered HTML free of internal task identifiers, three rules are non-negotiable:

  1. Headings are sentence case.
  2. Each file carries a complete frontmatter block.
  3. PX-XXXX, REQ-YYY, S0NN never appear in the rendered output — neither in headings nor in body prose. When the trace is still useful for grep or audit, keep it as an HTML comment.

These rules are enforced at go test time by the gates in tests/docs/ (heading_convention_test.go, frontmatter_test.go, anchor_links_test.go). A failing test is the authoritative signal — when in doubt, run make test against tests/docs/ and let the failure name the file.

Heading style

  • One H1 per file, on the first content line below the frontmatter. H1 is the page title in the rendered sidebar / breadcrumb.
  • All headings (H1–H6) are sentence case: only the first word and proper nouns are capitalised.
  • No trailing parentheticals carrying internal IDs. The forbidden shapes are PX-XXXX, REQ-YYY, REQ-PX-XXXX-YYY, S0NN, and the review-item codes W-NNN, I-NNN, C-NNN, B-NNN — none of these may appear in any heading anywhere in the corpus.
  • Inline code is allowed (# The `state/` sub-package) but unquoted angle-bracketed placeholders are not — the VitePress build parses them as Vue/HTML elements and fails. Wrap placeholders in backticks: write `<Domain>`, not <Domain>.

Examples

Do:

markdown
# Storage topology

## Row-to-package map
## SpiceDB and Postgres wiring
### Per-Domain residency

Don't:

markdown
# Storage Topology — plexsphere platform packages

## Row-to-Package Map
## SpiceDB↔Postgres wiring (PX-0011)
### Per-Domain Residency (REQ-013)

Frontmatter contract

Every file under docs/ opens with a YAML frontmatter block delimited by --- lines. The contract:

FieldTypeRequiredNotes
titlestringyesSentence case, matches the H1. Used by VitePress for the <title> tag and breadcrumbs.
descriptionstringyesOne-sentence summary of the page. Used by VitePress for the meta description and search snippets.
statusenumyesdraft or stable. draft pages render with a banner.
audienceenumyesoperator, contributor, or developer. Drives later filter / role-based navigation.
quadrantenumyesOne of tutorial, how-to, reference, explanation, contributing. The Diátaxis quadrant the page belongs to — must match the section the file lives under. The explanation quadrant spans three directories (architecture/, contexts/, explanation/); the other quadrants map one-to-one to their directory, with one exception: a cross-cutting operational runbook under docs/operations/ carries quadrant: how-to (its voice) even though it lives outside docs/how-to/, because it spans more than one bounded context. contributing is a deliberately non-Diátaxis fifth value for the contributor handbook. See how this documentation is organised for the placement rule.
sidebarbooloptionalDefaults to true. Set to false only for files intentionally unreachable from the sidebar (e.g. archived roadmaps). The drift gate in tests/docs/vitepress_config_test.go honours this opt-out.

The previously-permitted feature: field is no longer allowed — frontmatter values render into the <title> tag, the <meta> description, the sidebar tooltip, and the search index snippet, and must therefore not carry an internal task identifier. See the traceability identifier conventions for the full surface map and the regression gate.

Example

yaml
---
title: Manage domain settings
description: Operator how-to for renaming a Domain, rotating its IdP binding, and rolling the per-Domain signing key.
status: stable
audience: operator
quadrant: how-to
---

(Earlier revisions of this convention added a feature: PX-0024 key. That allowance has been retired — see the section above and the traceability identifier conventions.)

Traceability without leaking PX

When the link to an internal feature task carries real value (a ground-truth pointer, a follow-up gate, an audit trail), keep it — but as an HTML comment, immediately under the frontmatter:

markdown
---
title: 

---

<!-- traceability: PX-0017, REQ-005 -->

# …

HTML comments survive in the markdown source (so grep PX-0017 still finds the page) but VitePress strips them from the rendered HTML. The reader never sees an opaque task ID.

The same rule applies inline. When a paragraph today reads:

The relay assignment loop fans out signed envelopes onto the per-node JetStream subject.

rewrite it so the sentence stands on its own and move the trace to a comment if it is still useful:

markdown
<!-- traceability: PX-0017, REQ-009 -->

The relay assignment loop fans out signed envelopes onto the per-node
JetStream subject.

The body sentence is now a self-contained statement; the comment preserves the PR/audit link for future contributors who need it.

Cross-doc anchor links (./foo.md#some-section) resolve through the GitHub-style slug of the target heading. When you rename a heading, every anchor that pointed at the old slug must be updated in the same change. The anchor_links_test.go gate walks the whole corpus and fails on any stale fragment, so the rule is enforced — but it is faster to keep the references coherent in the editor than to chase the test failure later.

A common gotcha: legacy anchors that ended in -px-XXXX (because the heading carried the parenthesised PX). Once the heading is rewritten in sentence case, the slug shortens — update every link in the corpus in the same commit.

Section landing pages

Every top-level section under docs/ (architecture/, contexts/, contributing/, explanation/, how-to/, reference/, tutorials/) ships an index.md answering three questions in 15–30 lines:

  1. What is in this section?
  2. When do I need it? (i.e. which Diátaxis quadrant, which audience)
  3. Where is the entry point?

The home page at docs/index.md links into each section index; the section index links into its individual pages. Adding a new top-level section directory therefore requires adding an index.md to it; the sidebar drift gate in tests/docs/vitepress_config_test.go fails until it is wired up.

Cross-references