Skip to content

docs: rebuild the documentation site on MkDocs Material - #516

Merged
shenxianpeng merged 4 commits into
docs/left-sidebar-and-rules-referencefrom
docs/migrate-to-mkdocs-material
Aug 3, 2026
Merged

docs: rebuild the documentation site on MkDocs Material#516
shenxianpeng merged 4 commits into
docs/left-sidebar-and-rules-referencefrom
docs/migrate-to-mkdocs-material

Conversation

@shenxianpeng

@shenxianpeng shenxianpeng commented Aug 3, 2026

Copy link
Copy Markdown
Member

Note

Stacked on #515 — that is the base branch, so this diff shows only the migration. Merge #515 first and this retargets to main cleanly.

Why

The site read as a repository, not a product. The homepage of docs.commit-check.com was the GitHub README included verbatim — docs/index.md was a one-line include of ../README.md.

So the front door was eight CI badges above a hand-written ## Table of Contents. Badges are a trust signal for GitHub visitors and a ToC is a workaround for GitHub's lack of navigation — neither belongs on a product's documentation site. Behind it, every page was reference material: no installation page, no tutorial, no task-oriented guides.

Framework

MkDocs Material, for reasons specific to this project:

  • It is what ruff and uv use. "Look like ruff's docs" and "use MkDocs Material properly" are close to the same sentence.
  • The toolchain stays Python. Docs deps live in pyproject.toml, CI stays a single pip install, and contributors need no Node — which matters while we are actively recruiting them through good-first-issues.
  • sphinx-immaterial was a partial port of it, so the palette, admonitions and feature flags carried over almost unchanged. Docs extras drop from five Sphinx packages to one.

What changed

A real landing page. Value proposition, the same policy shown running four ways (CLI / pre-commit / GitHub Actions / MCP) in linked tabs, and cards routing each rule family to its reference.

The pages the site never had:

Section Pages
Getting started Installation, Quick start, Why Commit Check
Guides Pre-commit, GitHub Actions, Organization-wide policy, Signoff and DCO, AI attribution, Command-line recipes
Reference Rules, Configuration, CLI
About Migrating from v1, Troubleshooting, Release highlights, Changelog

The quick start is a genuine tutorial: install, watch a real failure, read what each part of the diagnostic is for, fix it, then write the policy down.

Brand. The 10 KB JPEG logo becomes a 483-byte SVG, and there is a favicon for the first time — the Sphinx config had it commented out.

Content. All RST converted to Markdown. Cross-document :ref: targets were rewritten to real cross-page links; converting them naively would have produced same-page anchors that silently go nowhere.

Three things that could have broken the live site

Rule URLs. MkDocs serves directory URLs, so anchors move from /rules.html#cc003 to /rules/#cc003. RULES_DOCS_URL is updated to match. This is safe to change now only because the rule-ID feature has not shipped to PyPI yet — no released version emits the old form.

Everything else that was published. Links to configuration.html, rules.html and friends exist in the README, on PyPI and outside this repository. A build hook emits a redirect stub for every URL the Sphinx site served — each one a canonical link plus a refresh meta pointing at the new location, e.g. rules.htmlhttps://docs.commit-check.com/rules/.

The custom domain. gh-pages carries a CNAME of docs.commit-check.com, and the publish step force-replaces that branch. cname: is now pinned explicitly on the action rather than relying on the file surviving.

Tests

The anti-drift guards follow the content rather than being dropped. They now parse Markdown pipe tables and { #ccNNN } heading attributes, and still check every documented default against get_default_config().

$ mkdocs build --strict
Documentation built in 0.77 seconds        # zero warnings

$ pytest tests/ -q
460 passed

18/18 rule anchors present in the rendered rules/index.html; the CLI page is generated from commit-check --help at build time, replacing the old conf.py setup() hook.

The one failing test, test_load_config_file_permission_error, reproduces on main and is unrelated — it uses os.chmod(0o000), which does not restrict root.

Not in this PR

example.md and what-is-new.md are carried over converted but not rewritten. They overlap the new guides and changelog respectively, and folding them in is a content decision worth making separately.

The site read as a repository rather than a product. Its homepage was the
GitHub README included verbatim — eight CI badges above a hand-written table
of contents — and everything behind it was reference material, with no path
for somebody meeting the tool for the first time.

Move to MkDocs Material, which is what ruff, uv, Pydantic and Typer use, and
which sphinx-immaterial was a partial port of. The toolchain stays Python, so
contributors still need nothing but pip, and the docs extras shrink from five
Sphinx packages to one.

Replace the homepage with an actual landing page: a value proposition, the
same policy shown running as a CLI, a pre-commit hook, a GitHub Action and an
MCP server, and cards linking each rule family to its reference. Badges belong
on the README and stay there.

Add the pages the site never had. Installation, a quick start that ends with a
working local policy, and the reasoning behind the tool; then task-oriented
guides for pre-commit, GitHub Actions, organization-wide config, DCO signoff,
and AI attribution. The reference pages carry over converted to Markdown.

Replace the JPEG logo with an SVG mark and add a favicon, neither of which the
site previously had.

Rule anchors move from RST explicit targets to heading attributes, so rule
URLs become /rules/#cc003 rather than /rules.html#cc003. Every URL the Sphinx
site served now redirects to its replacement, since links to them exist in the
README, on PyPI and elsewhere. The publish step pins the CNAME explicitly so
the custom domain cannot be lost when the branch is replaced.

The anti-drift tests follow the content: they parse Markdown tables and
heading anchors now, and still verify every documented default against
get_default_config().
@shenxianpeng
shenxianpeng requested a review from a team as a code owner August 3, 2026 16:36
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f7aad492-7521-47e3-8b8e-f598d37153c7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests Add test related changes labels Aug 3, 2026
pre-commit-ci Bot and others added 2 commits August 3, 2026 16:37
Material's emoji extension is configured with `!!python/name:` tags, which
yaml.safe_load cannot construct, so the hook fails on a file that is
perfectly valid for the tool that consumes it.

MkDocs parses and validates the same file on every build, and CI runs that
build with --strict, so excluding it here loses no coverage.
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.43%. Comparing base (8ac35f5) to head (3b4faa2).

Additional details and impacted files
@@                          Coverage Diff                           @@
##           docs/left-sidebar-and-rules-reference     #516   +/-   ##
======================================================================
  Coverage                                  97.43%   97.43%           
======================================================================
  Files                                         12       12           
  Lines                                       1207     1207           
======================================================================
  Hits                                        1176     1176           
  Misses                                        31       31           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Deploy previews stopped appearing on documentation pull requests. The build
settings lived in Netlify's web UI and still pointed at Sphinx's _build/html,
so the move to MkDocs left them publishing a directory that no longer exists —
a break that nothing in the repository could show in review.

Move the build into netlify.toml, so the preview build changes in the same
commit as the toolchain it builds.

Deploy previews also now set SITE_URL to their own address, and mkdocs.yml
reads it. Otherwise the canonical links and the generated redirect stubs on a
preview point back at the production site, which sends a reviewer away from
the very change they are reviewing.
@sonarqubecloud

sonarqubecloud Bot commented Aug 3, 2026

Copy link
Copy Markdown

@shenxianpeng
shenxianpeng merged commit d684332 into docs/left-sidebar-and-rules-reference Aug 3, 2026
24 checks passed
@shenxianpeng
shenxianpeng deleted the docs/migrate-to-mkdocs-material branch August 3, 2026 16:51
@shenxianpeng shenxianpeng removed the tests Add test related changes label Aug 3, 2026
shenxianpeng added a commit that referenced this pull request Aug 3, 2026
* docs: move navigation to the sidebar and expand the rules reference

Replace the top tab bar with a left sidebar, and group the pages into
Getting started / Configuring / Reference / About so the whole
documentation set is visible at a glance instead of one tab at a time.
On a rule page the sidebar also nests that page's own table of contents,
putting every rule one click away.

Rewrite the rules reference along the lines of a linter's rule
documentation: an index table per category giving each rule's code,
name, message, the CLI flag that runs it, and whether it is on by
default, followed by a section per rule answering what it does, why it
matters, and how to fix it, with a before/after example and the options
that control it.

Correct several defaults in the configuration reference that had drifted
from the source: subject_capitalized and subject_imperative are off by
default (documented as on), allow_empty_commits and allow_wip_commits
are on (documented as off), and allow_commit_types was missing perf,
build, and ci. The "Default Behavior" tip repeated the same mistake.

Guard all of it with tests, so the documentation cannot drift again:
documented boolean and list defaults are compared against the values in
commit_check/__init__.py, and every rule must have a section heading
that answers what it does, why it is bad, and which options apply.

Also fix four reStructuredText title underlines that were shorter than
their titles, letting the docs build cleanly under -W.

* docs: correct the subject-length defaults and verify docs against the runtime

The subject-length rules are on by default, not off. `get_default_config()`
sets subject_max_length to 80 and subject_min_length to 5, and
`ConfigMerger.from_all_sources()` starts from it, so with no config file at
all a subject over 80 characters fails CC004. Both the rules reference and
the configuration reference claimed there was no limit.

The same review turned up four more inaccuracies:

- author_email_pattern defaults to `^.+@.+$`, not an empty string.
- require_rebase_target defaults to `""`, not None.
- CC201's option is branch.conventional_branch; it was documented under the
  commit section, where configuration merging would ignore it.
- required_signoff_name and required_signoff_email were documented in the
  example config and in CC012's options, but no such options exist anywhere
  in the code.

Two rule examples also demonstrated values that pass the very check they
illustrate: `root` satisfies the built-in author name pattern, and
`root@localhost` satisfies `^.+@.+$`. Both now use values that actually fail,
with a note explaining how permissive the built-in patterns are and when to
replace them.

Rework the drift guard so this class of error is caught mechanically. Rather
than comparing against a couple of constants, the options table is now parsed
and every row checked against `get_default_config()` — the same dict the CLI
builds its configuration from. Two further tests assert the table and the
runtime describe the same set of options, in both directions, which is what
catches an invented option like required_signoff_name or a newly added one
that nobody documented.

Also scope the rule-index table styling to those tables so long regexes in
the configuration table stay wrappable, and check each rule's heading inside
its own section instead of anywhere on the page.

* test: narrow the documented-default parser to a type mypy accepts

The regex match may be None, so reading .group() from it before the None
check failed mypy and took the lint session down with it.

* test: stop benchmarking the documentation consistency checks

CodSpeed reported test_every_rule_is_documented as a 27% regression. It is
not one: that test reads docs/rules.rst and scans it for anchors, and this
branch grows that file from 7.4 KB to 22.2 KB. A 2.99x larger file taking
1.37x longer to read is the expected, sub-linear result, and no runtime code
changed on this branch at all.

The marker is documented as meaning "performance-related tests", which these
are not. Left in place, the performance gate would report a regression every
time somebody expands the rules reference — penalising the act of writing
documentation.

The catalog and rule-builder tests in this file keep their marker; those do
exercise the package.

* docs: rebuild the documentation site on MkDocs Material (#516)

* ci: expand DEPLOY_PRIME_URL where the shell can see it

The deploy preview failed to build:

    ERROR - Config value 'site_url': The URL isn't valid, it should
            include the http:// (scheme)

Values in a [context.*.environment] block are literals — Netlify does not
interpolate them — so SITE_URL reached MkDocs as the unexpanded string
"${DEPLOY_PRIME_URL}", which is indeed not a URL.

Set it in the context's build command instead, which runs in a shell where
the variable expands. The redirect hook now also tolerates a site_url without
a trailing slash, since that is the form DEPLOY_PRIME_URL takes.

* docs: fix the redirect loop and eleven review findings

The deploy preview could not open /rules/. The build emits a redirect stub at
rules.html for the old Sphinx URL, and Netlify normalises "/rules" and
"/rules/" to the same resource, so that stub was served in place of the page
it points at and redirected to itself forever.

Turn the normalisation off and declare the legacy URLs as Netlify redirects,
which resolve before file lookup and so cannot collide with the page they
target. The emitted stubs stay for GitHub Pages, and now refuse to redirect a
page to itself, so the same collision cannot reappear on another host.

The stubs also carry the fragment across. The links most worth keeping alive
are the per-rule ones — rules.html#cc003 — and a bare redirect dropped the
anchor, landing the reader at the top of a 700-line page.

From review, all verified against the source before fixing:

- The RST converter missed four constructs: `.. Attention::` (it only matched
  lowercase), `.. tip ::` (a space before the colons), and two links whose text
  wrapped across a line. They were rendering as literal text.
- The changelog documented `forbid_ai_attribution`, which does not exist; the
  option is `ai_attribution`, defaulting to "ignore".
- The options table mapped `allow_force_push = true` to
  `CCHK_ALLOW_FORCE_PUSH=false`, describing opposite behaviour for equivalent
  settings.
- Troubleshooting had its two bypass headings the wrong way round: --no-verify
  skips every hook, SKIP= skips one.
- Link colour was #1e85a8 on white, 4.2:1, under the 4.5:1 needed for body
  text. Now #176b89 at 6.0:1.
- The landing page claimed attestation "verified at install time"; only a
  manual verification step exists, so it now says what is true.
- The organization guide said "three lines" above a one-line example.
- pr-comments cannot post on fork pull requests, because a fork's
  `pull_request` run gets a read-only token that `pull-requests: write` does
  not override. Documented, along with why `pull_request_target` is not a
  casual substitute.

* docs: add the original logo as a vectorized SVG

The previous logo.svg was a new line-art icon that did not match the
project's actual logo. Vectorize docs/_static/logo.jpg (a 900x504 PNG in
the old Sphinx site) with potrace instead, so the SVG is pixel-identical
to the original: the 'commit' wordmark, the check mark and the branch
shape in the brand color #2C9CCD.

The old logo.jpg could not be used as-is on the new site: Sphinx's
conf.py had it commented out ('can not display well in blue background'),
it carried 10 KB of transparent padding around a 300x293 mark, and the
new header renders the logo via <img>, where currentColor would resolve
to black. The traced SVG keeps the exact shapes with a hardcoded fill,
so it renders identically in the light and dark palettes.

* docs: put the logo on a white tile so it shows on the blue header

The header background is the brand color #2c9ccd (--md-primary-fg-color),
the same blue the logo is filled with, so the logo was invisible. This is
the same reason the old Sphinx site had html_logo commented out ('can not
display well in blue background').

Keep the original blue logo unchanged and give it a white rounded tile.
On the blue header the tile makes the logo visible; in the drawer, which
sits on a light or dark gray background, the blue logo shows directly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant