Repository navigation
Publish the docs site per release with a version selector - #1199
Merged
Alexandre Zollinger Chohfi (azchohfi) merged 4 commits intoSep 11, 2026
Merged
Conversation
The site was rebuilt from the tip of main on every docs push, so it only ever showed unreleased documentation and there was no way to read the docs for the release you were actually running. Version the site with mike and Material's native selector. Each version renders once into the gh-pages branch and the Pages artifact is that whole branch, so a version stays byte-identical after it ships. A release tag publishes its version and takes the `latest` alias; main publishes separately as `main (development)`; the site root redirects to `latest`. A tag only takes `latest` if it is the highest by version sort, so re-cutting an older tag cannot drag it backwards. Versioning moves every page under a version directory, which would turn already published links -- including the two in README.md -- into hard 404s. Add a root 404.html forwarding legacy paths to the same page under `latest`, preserving the query string and anchor, and skipping paths already under a published version so it cannot loop. Seed the existing releases by dispatching the workflow with backfill_tags; those tags predate the versioning config, so the backfill layers this ref's mkdocs.yml and theme override over each tag checkout, with mkdocs --strict as the gate that makes a tag which has drifted too far fail loudly instead of publishing broken. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 93d332ff-21e7-4f39-b900-0997087233a4
A backfill that omitted the newest tag could publish a site with no root index.html at all: `mike deploy` writes version directories and versions.json, but only `set-default` writes the root, and the fallback chain stopped at an echo when neither `latest` nor `main` existed. It now falls through to the highest version the run published, and the artifact assembly asserts the root exists rather than uploading a site whose landing page 404s. The redirect that keeps already published links working shipped as JavaScript that MkDocs never parses, so no gate could see it. Its cases now live next to it and run under plain `node`, with an xUnit wrapper so CI runs them too; the docs-build job is armed by any non-Markdown change, which both files are. Also: validate backfill tags before publishing anything, so a typo fails the run instead of leaving the site half-updated; reword the version banner, which told readers of `main` they were viewing an older version when it is newer than the latest release; fail rather than warn when the tracked 404.html is missing; resolve the fallback link from the same computed base as the redirect; and scope the runbook's claim about GitHub Pages to what the workflow controls. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 93d332ff-21e7-4f39-b900-0997087233a4
Alexandre Zollinger Chohfi (azchohfi)
requested a review
from Chris Anderson (codemonkeychris)
as a code owner
September 11, 2026 06:13
Contributor
📦 Build metricsArtifact sizes for Packages (compressed .nupkg)
Assemblies in Microsoft.UI.Reactor
Assemblies in Microsoft.UI.Reactor.Advanced
Assemblies in Microsoft.UI.Reactor.Devtools
No size change beyond the noise floor. ✅ ✅ smaller / |
Contributor
🧪 Merged coverageCoverage for
No coverage change beyond the noise floor. ✅ ✅ higher / |
The Release build promotes analyzer warnings to errors, so blocking on the redirected output failed the `Build solution`, `Pack` and `Merged coverage` jobs with four diagnostics the Debug build never surfaced: xUnit1031 for reading `Task.Result` inside a test method, and xUnit1051 for calling `ReadToEndAsync` without a cancellation token. The pattern was lifted from an existing helper in this suite, where it is legal because the analyzer only inspects test methods. Await the reads instead and thread `TestContext.Current.CancellationToken` through, matching how the rest of the repo cancels async test work. Both reads still start before the wait, so a child filling one pipe buffer cannot deadlock against it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 93d332ff-21e7-4f39-b900-0997087233a4
…-selector # Conflicts: # CHANGELOG.md
Alexandre Zollinger Chohfi (azchohfi)
deleted the
azchohfi-docs-version-selector
branch
September 11, 2026 07:20
This was referenced Sep 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The docs site at https://microsoft.github.io/microsoft-ui-reactor/ was rebuilt from the tip of
mainon every docs push, so it only ever showed unreleased documentation. There was no way to read the docs matching the release you were actually running. This versions the site with mike plus Material for MkDocs' native version selector.The model worth understanding before reviewing:
gh-pagesis a store of rendered HTML, one directory per version, not something derived from tags. A version exists on the site only because a build was run for it and the output filed there. That is what makes published versions stay byte-identical after they ship.main(docs paths)main (development)v*taglatestalias, repoints the site rootbackfill_tagsThe site root redirects to
latest, so readers landing on the bare URL get the newest release rather than unreleasedmain. A tag only takeslatestif it is the highest by version sort, so re-cutting or backporting an older tag cannot drag it backwards.Versioning moves every page under a version directory, which would turn already published links (including the two in
README.md) into hard 404s. The published site root therefore carries a404.htmlthat forwards legacy unversioned paths to the same page underlatest, preserving the query string and anchor, and skipping paths already under a published version so it cannot loop.Linked issue / spec
N/A. No issue or spec; this came from a direct request for a version selector on the docs site.
Test plan
dotnet test tests/Reactor.DocPipeline.Tests --filter-class "*SiteRootRedirectTests*"passes (succeeded: 1).404.htmlturns it red (failed: 1), and the C# assertion surfaces the exact failing cases. Breaking the hash handling gives 9/10 and the fallback link 5/10 undernode.versions.json,latestalias, root redirect and artifact assembly.latest/and0.1.0-preview.14/resolve to the same git tree OID, so--alias-type copycosts no extra storage.v0.1.0-preview.12pins mkdocs-material 9.5.49 and passesmkdocs build --strict. This is what the backfill depends on.python -m mkdocs build --strictclean, and verifieddocs/_site-root/does not leak into the built site.docs/_site-root/404.htmlis the one part of the published site MkDocs never parses, somkdocs build --strictcannot cover it. Its cases live indocs/_site-root/404.redirect.test.js(runnable with plainnode), andSiteRootRedirectTestsintests/Reactor.DocPipeline.Testsis what makes CI run them. That suite is already in thedocs-buildjob, which is armed by any non-Markdown change, and both files qualify.Risk / breaking changes
No product code and no public API change. The risks are all in the published site:
404.htmlis the mitigation and is covered by the tests above. Worth a careful look, since it is the piece protecting existing links and search results.main. To seed the releases, dispatchPublish docsonce withbackfill_tagsset tov0.1.0-preview.12 v0.1.0-preview.13 v0.1.0-preview.14. Until then the site root points atmainrather than 404ing, but the dropdown will not look right.gh-pagesbranch. I checked that no other workflow, branch-glob trigger, or CODEOWNERS rule fires on pushes to it.permissions: {}, withcontents: writeonly on the publish job andpages: write+id-token: writeonly on deploy.mkdocs.ymland theme override onto each old tag, since those tags predate the versioning config. That holds only while a tag's page set still satisfies the currentnav; the per-tagmkdocs build --strictis what makes a tag that has drifted too far fail loudly instead of publishing broken.Two commits, kept separate so the review history stays legible: the first is the feature, the second is exactly what a
pr-reviewpass changed. That pass found a real one, confirmed by a reproduction against mike:mike deploywrites version directories andversions.jsonbut never the rootindex.html, so a backfill omitting the newest tag would have published a site whose landing page 404s.