Formatting markdown, MDX, JSON and YAML
Every Rust file in this repository is rustfmt-formatted, every TOML file is
taplo-formatted, and everything under ui/ is prettier-formatted against
ui/.prettierrc. Until #1695 the prose and data files outside ui/ were the
one part of the tree nothing owned: docs/dev-docs/, docs/user-docs/,
AGENTS.md, README.md and the ADRs were wrapped, padded and bulleted however
each author or agent happened to type them. A one-word edit to a hand-padded
table showed up in review as a rewritten table.
They are now formatted by prettier too, from a root .prettierrc and
.prettierignore.
Running it
just fmt-docs # rewrite
just fmt-docs-check # check only, the way CI does
Both call scripts/format-docs.sh, which is also what the
prettier (markdown / json / yaml outside ui/) prek hook and the
docs formatting (prettier) step in quality.yml’s ui, storybook, docs job
run. Never invoke prettier here with ad-hoc flags: the hook, the CI step and a
local run all read the same
two config files, and a flag that only one of them passes turns into a diff
nobody can reproduce.
The prettier version is not pinned in a second place. scripts/format-docs.sh
reads it out of ui/package.json, so the whole repository formats with one
build and a dependabot bump moves both halves at once.
The style, and how it was chosen
{
"printWidth": 100,
"embeddedLanguageFormatting": "off"
}
Derived from the tree rather than imposed on it, the same way ui/.prettierrc
was — each candidate was applied to every markdown and MDX file outside ui/
and scored by the size of the diff it produced:
proseWrap | embeddedLanguageFormatting | Files touched | Lines changed |
|---|---|---|---|
preserve | auto (prettier’s default) | 141 | 4840 |
always | auto | 162 | 23942 |
always | off | 162 | 22423 |
preserve | off — chosen | 129 | 3596 |
The JSON and YAML outside ui/ were measured separately and cost 2 files and
34 lines, so they are in scope too rather than left as a second unformatted
island.
Three things follow from that table.
proseWrapstays at prettier’spreservedefault.alwaysrewraps every paragraph in the repository — a six times larger diff, and every later prose edit reflows its neighbours, which is the failure this was meant to end rather than a new form of it. Line wrapping in prose is therefore still the author’s choice; what is normalized is structure: table padding, list markers, emphasis delimiters, heading and fence spacing.embeddedLanguageFormattingisoff. Prettier’s default reaches inside fenced code blocks and reformats the JSON, YAML and TSX in them. A code block in the docs is a quotation of real code or real config, and rewriting it is wrong in both directions: it made aprometheus.ymlsnippet disagree with the file it is quoting, and it turned documented JSX fragments such as{keys.error && (<LoadError … />)}into statements with a trailing semicolon. Turning it off also removed the only case where prettier rewrapped prose againstproseWrap: preserve— inside a Mintlify<Note>or<Warning>, whose children it was printing as JSX.printWidthis a free choice, so it matchesui/.prettierrc. WithproseWrap: preserveand embedded formatting off, nothing in these files is wrapped at all: the diff is byte-identical at 80, 100 and 120 columns. 100 is set so there is one number to remember for the repository, not because the measurement preferred it.
What is excluded, and why
.prettierignore carries the reasoning per entry. In summary: ui/ (it has its
own config, ignore file and check step), charts/rolter/templates/ (Go
templates that end in .yaml and no YAML parser can read), crates/*/CHANGELOG.md
(release-plz regenerates them), CLAUDE.md (a symlink to AGENTS.md),
docs/dev-docs/book/ and the other build outputs.
.github/workflows/ is formatted like any other YAML. It was excluded at first
because the release-handoff check grepped the workflow files as text, and a
needs: [a, b, c] list that prettier reflowed onto several lines failed it
without changing anything the runner sees. scripts/check-release-handoff.py
now reads the workflows as parsed YAML (#1723), so a workflow can be laid out
however prettier likes. actionlint and zizmor still audit what the
workflows do; prettier only owns how they are laid out.
The one-time reformat
The reformat is a single commit of its own, separate from the commit that added
the tooling, so git blame can be told to walk past it. That is what
.git-blame-ignore-revs is for — GitHub reads it with no setup, and git does
after one config:
git config blame.ignoreRevsFile .git-blame-ignore-revs
The SHA it lists is the one master got, recorded after the merge (#1741): this repository squash-merges, so the SHA a branch carries is not the SHA that lands on master, and an entry naming a commit master has never seen is ignored in silence, which reads as done while blaming exactly as badly as before.
.github/workflows/ joined the formatted set later, in #1723. That reformat
is two hunks in release.yml (a comment’s spacing and the verify-parity
needs list), and it landed in the same squash as the rewritten
release-handoff check, so that commit is not formatter output alone and is not
listed.
Gating only changed files was the alternative and was rejected: a per-file-changed gate leaves the tree permanently half-formatted, so the “reformatted paragraph” diffs keep arriving one file at a time forever, and the hook has to grow a notion of which files are in the club.
Nothing in that commit changes what a page means. It was verified by parsing
every one of those files before and after with remark-parse + remark-gfm +
remark-mdx and comparing the resulting document trees: headings, link targets, code-block
contents, list nesting and every MDX element name and attribute are identical.
That is the check that matters for the two navs — docs/dev-docs/SUMMARY.md for
mdBook and docs/user-docs/docs.json for Mintlify — since a reflow that moved a
link target would break the book build or the docs site silently. The
llms.txt tracks the docs nav check was confirmed green on the reformatted tree
for the same reason.