Skip to content

ci: build, review and deploy the documentation site - #84

Merged
soimy merged 16 commits into
docs/content-migrationfrom
docs/deploy-and-redirects
Oct 1, 2026
Merged

soimy merged 16 commits into
docs/content-migrationfrom
docs/deploy-and-redirects

Conversation

@soimy

@soimy soimy commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Phase D of #81, stacked on the content migration. Review order: #82 → content migration → this. CI runs here once the stack is on master; the branch is rebased at each step.

The workflow

.github/workflows/docs.yml builds on every change that can affect the site (docs/**, src/**, the manifests, the workflow itself), runs npm run typecheck before generating the API reference, uploads docs/.vitepress/dist as a Pages artifact, and deploys that same artifact to GitHub Pages from master — one build job, Node 24, npm ci --include=dev. The deploy job is gated on a push to master, so a pull request stops after the upload: it gets a downloadable build attached to the run, not a published site and not a preview URL.

One maintainer step is required before the first deployment: switch Settings → Pages → Build and deployment to GitHub Actions. Until that happens npm run doc:publish still pushes the built site to the gh-pages branch the old way, and docs/contributor/documentation.md says the two paths must not be used together.

Legacy URLs keep working

The old site served TypeDoc's HTML from the Pages root, so classes/*.html, interfaces/*.html, enums/PACKING_LOGIC.html, modules.html and hierarchy.html were published addresses. The build now writes a redirect page at each of them, pointing at the page that replaced it, and fails when a target is missing from the build rather than shipping a redirect into a 404 (measured: pointing the Bin redirect at a page that does not exist fails the build and names it).

Legacy anchors that used underscores are rewritten in the browser (#max_area → #max-area). Anchors that belonged to private members cannot be preserved — excludePrivate: true is what keeps implementation members out of the site — and are documented as landing on the page itself.

docs/site.json holds the base path for both the VitePress config and the redirect builder, so the published prefix cannot drift between them.

Verification

Served the built tree locally and fetched every route: the home page, /api/, the user and contributor sections, releases, and all five legacy class/interface/enum paths return 200, with assets referenced through /maxrects-packer/. lint, format:check, typecheck, verify:docs, docs:build, cover (100%) and verify:package all green.

The build ends with an output check

srcExclude is a config option, and a config option is not a check: scripts/verify-docs-output.mjs now inspects what was actually built and asserts both directions — no page tree for spec/ or plans/ (which is also what keeps them out of the search index, since VitePress only indexes pages it built), a built page for every handwritten page, and a search index that carries site text rather than nothing.

Measured both ways: dropping srcExclude fails naming dist/spec and dist/plans; widening it to user/** fails earlier, in VitePress's own dead-link check. The check's first version scanned built pages for the records' file names and failed on docs/contributor/documentation.html, which legitimately links to them — the page tree is the signal, not the names.

The workflow's action inputs were checked against the actions' own action.yml (upload-pages-artifact takes path, setup-node takes node-version/cache, deploy-pages publishes page_url), since none of this can be exercised locally.

Two more checks the build now ends with

  • Anchors, not just pages (build: resolve every internal link, anchors included): VitePress fails on a dead page link but says nothing about a missing #anchor, and TypeDoc's own cross-references are full of them. Every relative and root-absolute link in the published markdown is resolved against the built HTML — 188 today — and a renamed section fails the build naming the file, the link and the missing id.
  • Reachability (build: fail when a page is built but nothing links to it + build: report the nav coverage when it passes): the check verified each page was built, which is only half of reachable. The config's link: entries are now checked both ways, so a new page that nothing links to fails instead of shipping invisibly outside search. Deleting the Repacking sidebar entry reproduces it.

Clean-checkout verification

Every gate above was also run from a fresh git clone of the chain head into an empty directory — no node_modules, no dist/, no docs/api/, no ignored files — because the worktree the checks were written in is full of build products:

npm ci --include=dev → lint · format:check · typecheck · verify:docs · docs:build · test (126 passed, 2 skipped) · cover (100% on all four metrics) · verify:package, all exit 0; npm pack --dry-run = 28 files / 66,871 B.

The built tree was then served at the real base path, /maxrects-packer/, and all 16 routes returned 200: home, both guide sections, releases, the API root and two generated pages, the five legacy paths (classes/, interfaces/, enums/, modules.html, hierarchy.html), a referenced asset, and 404.html. The classes/MaxRectsPacker.html redirect carries url=/maxrects-packer/api/classes/MaxRectsPacker.html, so the base survives the redirect.

Hashes of this stack's commits are deliberately not quoted: they move on every rebase, so commits are named by subject instead.

Trigger list audited against what the build reads

typedoc.json names tsconfig: "tsconfig.json", so a compiler option there can change the generated API pages — and neither paths list mentioned it. A PR touching only the tsconfig would therefore have left the deployed site describing the previous options until something else triggered a rebuild. Both lists carry it now (12 filters each, checked by parsing the workflow), and the trigger sentence in docs/contributor/documentation.md names the inputs instead of saying "the TypeDoc and package manifests". The rest of the build's inputs were checked the same way and are covered: docs/**, src/**, scripts/**, the two package manifests, the root markdown files and the workflow itself; the site references nothing under assets/, so that directory is deliberately absent.

Review round

The deployment sentence promised a preview: upload-pages-artifact uploads a Pages artifact, it does not publish one, and deploy-pages runs only on master. A pull request therefore gets a downloadable artifact and no URL at all. docs/contributor/documentation.md and the workflow's header comment now say exactly that, and this description was corrected with them.

Consistency pass on the audit numbers

The legacy-anchor section and the spike report it links to disagreed — 105/170 here against a Phase A column adding up to 129/170 there. Nothing reproduces 129 from the published site (exact id 102, after the underscore rewrite 105, on any new page 106, substring of a new id 149, unique names 40 of 98), so the report marks its counts superseded, states the re-measured 105/170 with the three buckets, and this page's pointer says so.

@soimy
soimy force-pushed the docs/deploy-and-redirects branch from d217339 to 0b3d7b0 Compare September 30, 2026 07:40
@soimy
soimy force-pushed the docs/deploy-and-redirects branch 2 times, most recently from c0bc0a8 to 8bb7c98 Compare September 30, 2026 07:57
@soimy
soimy force-pushed the docs/deploy-and-redirects branch from 8bb7c98 to d1745c0 Compare September 30, 2026 08:00
@soimy
soimy force-pushed the docs/deploy-and-redirects branch from a5f8611 to e93bcfa Compare September 30, 2026 08:12
@soimy
soimy force-pushed the docs/deploy-and-redirects branch 2 times, most recently from 878a21b to d9aebe5 Compare September 30, 2026 08:32
@soimy
soimy added this pull request to stack #86 September 30, 2026 08:41
@soimy
soimy force-pushed the docs/deploy-and-redirects branch 3 times, most recently from 2fecdad to be35906 Compare September 30, 2026 10:50

@soimy soimy left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stack #86 review of current head 70bba8e05ef2049140d4e9786f6e047e8d262c2d.

The Documentation workflow itself is green on this PR: the Node 24 build, Pages configuration, and artifact upload all succeed, while the deploy job is correctly skipped for pull requests. I found one wording/expectation issue below; the workflow implementation otherwise looks sound.

Comment thread docs/contributor/documentation.md Outdated
@soimy
soimy force-pushed the docs/deploy-and-redirects branch 3 times, most recently from 09c993b to 84ee0ef Compare September 30, 2026 11:06
@soimy

soimy commented Sep 30, 2026

Copy link
Copy Markdown
Owner Author

Auditing the other contributor pages against the repository turned up one stale description this PR should own: docs/contributor/development.md's script table still described npm run docs:build as two steps ("Generates docs/api/ and builds the site into docs/.vitepress/dist/"), while docs/contributor/documentation.md in this same PR lists all four. The row now names the redirects and the output check (commit: docs: describe the whole \docs:build` command where it is listed`).

Everything else in the audit checked out against the source: the backend trigger lists, the deploy gate (github.event_name == 'push' && github.ref == 'refs/heads/master'), the Node 24 build job, npm ci --include=dev and the type check before docs:build.

@soimy

soimy commented Sep 30, 2026

Copy link
Copy Markdown
Owner Author

The legacy-URL section said only private-member anchors are lost. That was never measured against the real thing, so I crawled the published 2.7.4 site (https://soimy.github.io/maxrects-packer/, still live) and counted:

  • all 11 published HTML pages answer 200, and the internal links they carry are exactly the 10 pages the redirect builder writes plus the root;
  • of the 170 member anchors on those 11 pages, 105 resolve on the page that replaced them after the underscore rewrite (#max_area → #max-area);
  • the other 65 land on the page itself, and they split three ways: 22 private members (#_dirty, #findnode, #sort — the old theme published them, excludePrivate: true does not), 28 old-theme signature anchors with no heading on the new pages (#dirtydirty, #constructorbint, #widthwidth-1, #collide-2), and 15 sections of the two legacy index pages (modules.html#maxrectspacker, hierarchy.html#Bin), which redirect to api/index.html — that page lists classes, interfaces and enumerations but has no per-entry anchor.

The section now states that split instead of the private-members-only version (commit: docs: record which legacy deep links survive the move). No redirect was added for the last group: the anchors that would have to replace them are TypeDoc's generated slugs for the same entries, and pinning redirects to those would trade a documented landing spot for a brittle one.

@soimy
soimy force-pushed the docs/deploy-and-redirects branch 2 times, most recently from 431c602 to 3e5a3df Compare September 30, 2026 11:31

@soimy soimy left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of current head 17ddbf7cfb996b0a29043bdfe133cd37dedc4513.

The previous PR-preview wording issue remains correctly fixed, and both the Node.js CI matrix and Documentation workflow are green.

I found one remaining documentation-consistency issue in the new legacy-link audit:

Comment thread docs/contributor/documentation.md
@soimy
soimy force-pushed the docs/deploy-and-redirects branch 3 times, most recently from 525db81 to ab76278 Compare September 30, 2026 11:48
@soimy
soimy force-pushed the docs/deploy-and-redirects branch 7 times, most recently from 75b2771 to 83830c9 Compare September 30, 2026 13:30
soimy added 16 commits October 1, 2026 23:50
Phase D of #81: the site gets a workflow of its own, and the URLs the old
TypeDoc site published keep working.

- `.github/workflows/docs.yml` builds on every change that can affect the site
  (docs, src, the manifests, this workflow), runs the type check before the
  build, uploads `docs/.vitepress/dist` as an artifact so a pull request can be
  reviewed at a URL, and deploys that same artifact to GitHub Pages from
  `master`. Deploying needs the repository's Pages source switched to "GitHub
  Actions"; until that happens `doc:publish` still pushes to `gh-pages`, and the
  documentation page says the two must not run together.
- The build writes a redirect page for every URL the old site published —
  `classes/*.html`, `interfaces/*.html`, `enums/PACKING_LOGIC.html`,
  `modules.html`, `hierarchy.html` — and fails when a target is missing from the
  build instead of shipping a redirect into a 404. Legacy anchors that used
  underscores are rewritten in the browser (`#max_area` -> `#max-area`); the ones
  that belonged to private members are documented as gone.
- `docs/site.json` holds the base path, read by both the VitePress config and the
  redirect builder, so the published prefix cannot drift between them.

Verified locally by serving the built tree: the home page, the API index, the
user and contributor routes, releases and all five legacy class/interface/enum
paths return 200, and the assets are referenced through the base prefix.
The Pages deployment template keeps `cancel-in-progress: false` for a reason: a
run cancelled halfway through a deployment leaves the published site in a state
nobody chose. The group is per ref, so a pull-request build still queues behind
another build of the same branch and production deploys serialise.
`srcExclude` keeps `docs/spec/` and `docs/plans/` out of the page tree, and the
config comment said so — a comment is not a check. The build now ends with
`scripts/verify-docs-output.mjs`, which asserts both directions:

- no `dist/spec/**` or `dist/plans/**` page tree exists, and since VitePress only
  indexes the pages it builds, that is also what keeps the records out of search;
- every handwritten page (home, user, contributor, releases) has a built page, so
  the same option that hides the records cannot silently swallow a guide;
- a local search index was built and carries site text, or "excluded from search"
  would be vacuously true.

Measured both ways: dropping `srcExclude` makes it fail naming `dist/spec` and
`dist/plans`; widening it to `user/**` fails earlier, in VitePress's own dead-link
check, which the positive control then covers for the linkless case.

The first version scanned every built page for the records' file names and failed
on `docs/contributor/documentation.html`, which legitimately links to them — the
page tree is the signal, not the names.
VitePress fails the build on a dead page link but not on a missing anchor, so a
renamed section rots silently — and the anchors TypeDoc writes into its own
cross-references are the bulk of them. The output check now walks the published
markdown (user, contributor, releases, home and the generated API pages), resolves
each relative or root-absolute link against the built HTML, and fails when the
target page or the `#anchor` in it does not exist.

Measured: 188 internal links resolve on the current tree; renaming one anchor in
`docs/user/troubleshooting.md` fails the build naming the file, the link and the
missing id.
…it is

Two follow-ups to the previous commits on this branch:

- `oxlint` rejects a `$`-anchored regex where `String#endsWith` says the same
  thing (`unicorn/prefer-string-starts-ends-with`), which made `npm run lint`
  exit 1 while every other gate passed. The asset guard now uses `endsWith` for
  all three cases, and the no-op `.html$` replace next to it is gone.
- `docs/contributor/documentation.md` called the workflow "one job" and listed
  its triggers incompletely: there are two jobs (`build` and `deploy`), and the
  `paths` filters also cover `scripts/**` — which really is part of the build —
  plus the root markdown files.
The output check verified that every handwritten page is built, which is only
half of reachable: VitePress fails on a link that points nowhere, never on a page
that nothing links to, so a new page can be built, indexed and still invisible
from the navigation. The config's `link:` entries are now checked both ways —
each one has to resolve to a built page, and every published page has to be
reached by one.

Measured: 18 links, all resolving, no orphans; deleting the `Repacking` sidebar
entry fails the build naming `user/repacking.html`.
The other two checks print what they verified, so the nav one does too — a gate
whose only output is silence on success is easy to believe is not running.
VitePress rewrites the links it generates and the ones written in markdown, but a
raw-html href is left exactly as written — and such a URL works while serving
locally at the root and 404s on the project page. The output check now reads the
base from `docs/site.json` and fails on any absolute `href`/`src` in the built
html, js and css that does not start with it.

Measured: 638 absolute URLs, all under `/maxrects-packer/` today, and the legacy
redirect pages carry the base in both their canonical link and their refresh
target. Appending `<a href="/user/options">` to a guide page fails the build,
naming the page and the asset chunk that carries it.
The description had been written one PR ahead of the script it talks about — the
same statement-ahead-of-its-branch shape the review flagged on AGENTS.md. It now
sits on the branch that introduces `scripts/verify-docs-output.mjs` and covers
what the check actually does today rather than the three assertions it started
with: page tree, per-page reachability through the nav, links and anchors, the
search index, and the base prefix on every absolute URL — each measured against a
broken state before it was trusted.
`typedoc.json` names `tsconfig: "tsconfig.json"` explicitly, so a compiler option
there can change the generated API pages — but neither filter list mentioned it,
so a PR that touched only the tsconfig would leave the deployed site describing
the previous options until something else triggered a rebuild.

Both lists now carry it (12 filters each, verified by parsing the workflow), and
the trigger sentence in `docs/contributor/documentation.md` spells the inputs out
instead of saying "the TypeDoc and package manifests".
`upload-pages-artifact` uploads a Pages artifact; it does not publish one. `deploy-pages` is
gated on a push to `master`, so a pull request gets a downloadable artifact and no URL at all.
The deployment section and the workflow header now say what each job does.
`documentation.md` lists all four steps; the quick reference in `development.md` still stopped
after the site build, which is where the redirects and the output check were added.
The page said only private-member anchors are lost. Crawling the published 2.7.4 site gives the
real split: 105 of the 170 member anchors on the 11 redirected pages resolve, and the other 65
are private members (22), the old theme's own signature anchors (28) or sections of the two
legacy index pages (15), which have no per-entry anchor on `api/index.html`.
The build writes the legacy-URL redirects and runs the output check from here on, so the commands
table names all four steps instead of the two the content layer has.
The spike report's Phase A counts contradicted the numbers in this section. It now marks them
superseded and repeats the re-measurement, so the sentence that sends readers there says so.
The check verified that absolute URLs sit under the base, never that they resolve, so a page still
pointing at a deleted file stayed green — which is the shape of the theme retirement, where the CSS
and JS a page referenced stopped existing. Every root-absolute `href`/`src` is now resolved inside
the build (file, `.html`, or a directory's `index.html`).

Measured against a broken state: with `vp-icons.css` removed the check exits 1 and names 29
references; restored it reports 638 absolute URLs under the base that resolve.
@soimy
soimy force-pushed the docs/deploy-and-redirects branch from 83830c9 to 6500382 Compare October 1, 2026 15:51
@soimy
soimy marked this pull request as ready for review October 1, 2026 15:55
@soimy

soimy commented Oct 1, 2026

Copy link
Copy Markdown
Owner Author

Rebased on the current #83 head and out of draft. Gates on this head (6500382): Node 20/22/24 matrix green, Build documentation green, Deploy to GitHub Pages correctly skipped on a PR.

Merge order is #83 → #84 → #85: this layer's base is docs/content-migration, so after #83 lands the base needs retargeting to master (the tree is the same; the rebase is mechanical).

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[High risk] Adds documentation build and deployment workflow.

No blocking issues were identified; the reviewed changes appear safe to merge.

What we checked:

  • VitePress 1.6.4 built the mutated sidebar without error, but the rendered link pointed to a non-existent destination (HTTP 404). T-Rex
  • An output check detected the missing destination during the built-HTML absolute-URL scan, causing npm run docs:build to exit with code 1. T-Rex
  • Evidence shows the valid sidebar link loads the Class page, while the mutated link returns a 404, as demonstrated by the captured images and videos. T-Rex
  • The overall contract validation concludes that the docs build would fail due to the broken absolute URL, confirming the PoW outcome. T-Rex

Summary

Adds a documentation workflow to build, check, and deploy the site, including legacy URL redirects. No issues requiring changes were identified.

Reviews (1) · Last reviewed commit: "build: assert the output serves every UR..."

@soimy
soimy merged commit 1012c30 into master Oct 1, 2026
6 checks passed
@soimy
soimy deleted the docs/deploy-and-redirects branch October 1, 2026 16:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant