Repository navigation
Rspress docsite deep-dive audit: 7 P1s (reference drift, twoslash any hovers, search race, llmsUI 404s), SSG/TypeDoc levers for a 186 s build, 20 % sentence drift #590
Description
Third of the three toolchain audits. #566 (§4 docsite P3s) and #572 (docsite P2: Rspress performance.buildCache written every CI run) are the baseline; nothing they list is restated here. Same format as #572 / #576.
Preamble
- Commit audited:
b75073b24(origin/mainat start, feat(workbench): launch the standalone MCP Inspector and open it in a new tab #579) in detached worktrees under/fast/projects/agent-bundle-wt/audit-rspress*(removed afterwards).mainmoved twice during the audit (ci: split Verify into a fast leg + 2 integration shards behind the required gate; pin the browser; retry setup; nightly pools (#576) #583, build: compile on one Rspack engine with Rslib 1.0 and Rsbuild 2.2 (#566) #575);website/**is byte-identical on both, and the two places where the workflow differs are marked "onmain". - PR feat(build): emit one composite plugin root; targets select host projections; remove the plugin target (#555 W1) #578 was closed unmerged (superseded by feat(build): emit one composite plugin root; targets select the host projections inside it (#555) #569, also closed unmerged), so this audits current
main. The only sampled statement that the Emit one composite plugin artifact; use targets to select the host projections inside it #555 composite-root rewrite would change is theplugintarget's install surface row (reference/targets-artifacts.mdx:19-23); nothing else below depends on per-host output roots. - Versions installed → latest:
@rspress/core+plugin-{llms,sitemap,twoslash,typedoc}2.0.21 → 2.0.21 ·@rsbuild/core2.2.1 via lockfile (main: 2.2.3) → 2.2.3 ·typedoc0.28.20 → 0.28.20 andtypedoc-plugin-markdown4.12.0 → 4.13.0, both pinned exactly by@rspress/plugin-typedoc(upstreamrspressmainstill pins 4.12.0, so a lockfile refresh cannot move them) ·typescript6.0.3 → 7.0.2, blocked by two peers (typedoc5.0–6.0.x,@rspress/plugin-twoslash^6.0.3; the pin itself is Rstack skills audit: build compiler, package builds, tests, docsite #566 §4) ·react/react-dom19.2.8 → 19.2.8 ·shiki4.4.3 → 4.4.3 · Node 22.19.0 and pnpm 11.23.0 indocs.yml(builds here ran on Node 22.23). Actions:checkout@v7,pnpm/setup@v2,upload-pages-artifact@v5,configure-pages@v6,deploy-pages@v5— all current majors. - Built and measured: full
rspress build×3 (cold ×2, warm ×1) plus an Rsdoctor run and three A/B builds (ssg.experimentalWorker: true; twoslash removed; TypeDocrouter: 'module'), each under/usr/bin/time -vwith a sampler that recorded whether any other build was running on the machine;pnpm docs:site:buildgate;rspress previewof the built site crawled with headless Chromium at 1440×900 (114 pages: all 74 authored + 40/api/**), 8 search-timing trials, language-switcher/dark-mode/404 matrices, anrspress devsample, andcurl -sIagainst the live Pages site to separate preview-server artefacts from production; a 25-sentence accuracy sample againstpackages/agent-bundle/src; a full sweep of 33 authored pages/locale and 916 TypeDoc pages/locale; dependency currency and the last 80docs.ymlruns. - Skills applied:
rspress-best-practices,rspress-v2-upgrade,rspress-custom-theme,rspress-docs-generator,rspress-description-generator,rsbuild-best-practices(forbuilderConfig),rsdoctor-analysis. Every option cited below was checked against the installed@rspress/shared@2.0.21typings (<shared>=node_modules/@rspress/shared/dist/index.d.ts) and@rspress/core@2.0.21runtime (<core>=node_modules/@rspress/core/dist), not memory.cfg=website/rspress.config.ts.
Findings
P1 — user-visible breakage or a wrong statement on a reference page
P1 · docs/diagnostics.md:821 · rspress-docs-generator "a source-backed reference must render what the source says" · The AB4834 row has four cells (Code | Severity | Trigger | Recovery) under a three-column header (:785, Code | Severity | Trigger). GFM drops the extra cell, so the recovery hint (Add ".agent-bundle/routes.d.ts" to tsconfig.json include …) is silently absent from both generated reference/diagnostics pages and from GitHub. It is the only ragged row in 29 tables / 240 rows. Change: fold the recovery text into the Trigger cell, as the neighbouring AB48xx rows do. Risk: none.
P1 · docs/diagnostics.md vs website/docs/en/index.mdx:230, guide/distribution/validation.mdx:29,195-201, guide/authoring/mcp.mdx:735, reference/cli.mdx:99, index.mdx:175 · rspress-docs-generator "every user-facing item has a reference entry" · The home page promises "Every diagnostic has a stable AB code documented in the diagnostics reference", but four codes the guides explain with specific semantics have no row: AB4204, AB6005, AB8003, AB8004 (covered only by a one-line family catch-all). Breadth: packages/agent-bundle/src emits 400 distinct ABnnnn literals; docs/diagnostics.md names 213 explicitly (237 with ranges); 169 emitted codes have no explicit or ranged row and 93 of those only a family row — including all 69 AB80xx dev-server codes. Every code does fall under some family row, and the 6 documented codes with no src literal are legitimate (AB8206, AB8233–8235 live in the workbench/example; AB4807, AB4816 are marked retired). Change: add the four rows now; add a check (website check script or pnpm lint) that fails when an AB\d{4} cited in website/docs/**/*.mdx is absent from docs/diagnostics.md explicitly or in-range — it fails on exactly these four today. Risk: low; an "every src literal must be explicit" variant would fail on 169 codes and needs a ratchet.
P1 · website/docs/en/reference/cli.mdx:288 (zh :267) · AGENTS.md "reference prose must match the source" · "2 | An argument-parsing failure from the command-line parser." Five option validators throw a plain TypeError, not InvalidArgumentError: port, trialCount, installHost, installMode, installScope (src/cli.ts:217-246). Commander rethrows anything that is not commander.invalidArgument, and runCli maps only CommanderError to 2 — everything else is one AB5000 diagnostic on stderr and return 1 (src/cli.ts:1156-1161). Only --profile, --allow and doctor --host (cli.ts:251,260,268) exit 2. Change: "2 — a parser-level failure: unknown option, missing argument, or an invalid --profile, --allow, or doctor --host value. An invalid install <host>, --scope, --mode, --port, or --trials value is reported as one AB5000 diagnostic and exits 1." (Or make the five validators throw InvalidArgumentError; the mixed usage in cli.ts:217-268 looks accidental.) Risk: scripts branching on exit 2 for bad install arguments currently see 1 plus a JSON diagnostic.
P1 · website/docs/en/guide/authoring/hooks.mdx:132 (zh :120) · AGENTS.md "reference prose must match the source" · "outcome | continue, deny, or stop." src/adapters/hook-handler.ts:58 says "No event admits outcome: 'stop'", hookResultContract (:62-71) has no stop capability, and the generated wrappers accept the literal in the enum check (hook-contract.ts:895) and then fail it for every event (:902-905, "stop only accepts continue or deny with a reason" / "cannot stop the parent flow"). Change: "outcome | continue or deny. 'stop' is not a legal result on any event: HookHandler<E> excludes it and the wrapper rejects it at run time." Risk: a reader who returns { outcome: 'stop' } gets a tsc error and a hook-time failure.
P1 · cfg:156-167, website/tsconfig.typedoc.json:6-7, .github/workflows/docs.yml:54-61 · rspress-best-practices "validate before deployment" / AGENTS.md "state what the code does" · The twoslash compilerOptions.paths maps only agent-bundle, /config, /test, /eval; the TypeDoc tsconfig maps ten more (@agent-bundle/runtime*, rsc-markdown-stream) and its comment claims twoslash "mirrors" them — it does not. packages/agent-bundle/src/test/{render,matchers,target-capabilities}.ts and eval/assertions.ts import types from @agent-bundle/runtime, whose types is ./dist/index.d.ts; packages/rsc-runtime/dist does not exist in a fresh checkout and docs.yml runs pnpm install → pnpm lint → pnpm docs:site:build with no pnpm build. Twoslash only validates the sample's diagnostics, so the unresolved module becomes any in the published hovers: in the built HTML, 18 of 784 twoslash popups render : any — renderRoute<…>(target: any, …), invokeMcpTool<…>(tool: any, …), RenderRouteOptionsBase.input?: any, ContractRouteFixture.input?: any — on guide/development/{testing,evaluations,workbench} in both locales (5 + 3 + 1 per locale). Change (minimal): add the ten aliases to the twoslash paths (absolute, like the existing four: '@agent-bundle/runtime': [path.join(repoRoot, 'packages/rsc-runtime/src/index.ts')], /plugin, /flight/server, /lineage, /mount, /notices, /notices/inbox-route, /state, /state/sqlite, 'rsc-markdown-stream'). Better: read tsconfig.typedoc.json once with ts.readConfigFile(file, ts.sys.readFile) (it is JSONC) and feed both consumers from one map — the "never copy a helper" rule. Risk: twoslash type-checks rsc-runtime/src too — slower fences, and TS-7-only syntax there fails under the pinned TS 6.0.3 (the failure mode cfg:122-126 already documents for TypeDoc).
P1 · cfg:79-81 (search) + website/plugins/mirror-api-locale.ts · rspress-best-practices "validate before deployment" · The first search query races the index build and shows "No matching results". The per-locale index is 1.71 MB raw / 338 KB gz (952 docs, of which 916 TypeDoc pages contribute 1.21 MB); it is prefetched, but FlexSearch construction takes 1.7–2.3 s after download, and a query typed ≤1.2 s after opening the panel returned nothing or a partial list in 7 of 8 trials (快速开始 0 → 3 on retype, 参与贡献 0 → 1, AB6005 1 → 3). Root cause is in the theme: LocalProvider.init (<core>/theme/components/Search/logic/providers/LocalProvider.js:81-85) calls addAsync for every document without awaiting, so SearchPanel.initSearch flips initStatus to 'inited' (SearchPanel.js:97-98) while indexing is still running, and NoSearchResult renders as soon as the list is empty and initStatus === 'inited' (:271) — no spinner, so the term reads as absent. tokenize: 'full' makes the cost proportional to content size, and this site's index is ~70 % API pages. Change: (a) file upstream (await Promise.all around the addAsync calls, or gate the pending query on completion); (b) site-side, shrink the window: a modifySearchIndexData hook (<shared>:815) in mirror-api-locale.ts that blanks content for /api/ pages (titles and headers stay indexed, so CapabilityStateError is still found by title) — or search: false frontmatter on the mirrored API pages (FrontMatterMeta.search, <shared>:90; honoured at createPageData.js:33) to drop them entirely. Risk: (b) trades API body-text search for a ~3× smaller index; it does not remove the race, only its window.
P1 · cfg:115 (llmsUI: { placement: 'outline' }) + cfg:170-191 (exclude: /api/) · rspress-best-practices "no broken assets" · "Copy Markdown" / "Open in ChatGPT" / "Open in Claude" are broken on all 1,832 /api/** pages. llmsUI renders the outline rows on every page (Outline/index.js:35-39) and the hidden LlmsHint advertises routePathToMdPath(page.routePath); both pluginLlms entries exclude /api/, and include/exclude apply to mdFiles as well as llms.txt (<llms>/index.js:142-148,236-244), so no .md exists for those routes. Live: https://scriptedalchemy.github.io/agent-bundle/api/classes/api.TargetRegistry.md → 404; the ChatGPT/Claude links embed that URL; getLlmsCopyContent.js:10 copies whatever response.text() returns with no status check (the 404 page — on preview, the home-page HTML). LlmsUI has no route gating (<shared>:292-315: injectLlmsHint, viewOptions, placement only). Change (cheapest, custom-theme level 3): in website/theme/index.tsx re-export LlmsCopyRow, LlmsOpenRow, LlmsHint wrappers that return null when usePageData().page.routePath includes /api/. Alternative that also closes #566 §4's llms-full.txt gap: a third pluginLlms entry { llmsTxt: false, llmsFullTxt: false, mdFiles: { mdxToMd: false }, include: ({ page }) => page.routePath.includes('/api/') } (Options allows false for both, <llms>/index.d.ts:42-50) — at the cost of 1,832 extra SSG-MD renders per build. Risk: none for the wrapper; build time for the alternative.
P2 — real quality / perf / maintainability gain with a concrete change
P2 · cfg:127-144 (pluginTypeDoc), website/docs/{en,zh}/api/_meta.json · rspress-docs-generator "shape the API surface for readers" / rsdoctor-analysis · @rspress/plugin-typedoc bootstraps TypeDoc with router: 'kind' (<typedoc>/index.js:41), which emits 916 pages per locale (13 modules, 151 functions, 21 classes, 499 interfaces, 186 type aliases, 45 variables). Consequences measured on the built site: every one of the 1,832 API routes renders a 921-item sidebar (the six dir groups carry no collapsed, and 2.0.21 defaults collapsed = false, SidebarGroup.js:25), items sort by filename module.Symbol while labels come from the h1, so the module grouping is invisible and two labels collide (Type Alias: AgentBundleConfig ×2, Variable: default ×2); each API HTML page averages 326 KB raw / 17.7 KB gz (authored: 54 KB) because the SSR'd <aside> embeds all 916 sidebar links — collapsed groups render their children with gridTemplateRows: '0fr', not conditionally — so 561 MB of the 602 MB of HTML (93 %) is sidebar, doc_build is 623 MB (the published Pages site sits at ~62 % of GitHub's documented 1 GB limit; the Pages artifact is 36 MB compressed per deploy); the entry chunk static/js/index.*.js (2.23 MB raw / 368 KB gz, loaded on every page) is O(pages): virtual-page-data 1,346 KB (219 KB gz — toc/frontmatter/title for all 1,906 routes) + virtual-routes 488 KB (73 KB gz) + virtual-site-data 287 KB (31 KB gz); 143 API pages have ≤2 body lines. The zh mirror (plugins/mirror-api-locale.ts) doubles all of it: 916 → 1,832 pages ≈ +300 MB HTML, ≈ +40 s single-thread SSG, +916 routes in every chunk above — while the mirrored body stays English. A/B (this audit, link checks disabled, otherwise identical): app.options.setValue('router', 'module') in the existing setup hook → 102 routes instead of 1,906, wall 90.3 s vs 186.3 s (−52 %), peak RSS 4.99 GB vs 8.52 GB (−41 %), doc_build 8.4 MB vs 623 MB, index.*.js 474 KB vs 2.23 MB; the largest module page (api/api.html) is 731 KB. Costs found by the A/B: 43 authored → API links per locale target member URLs (/api/classes/api.TargetRegistry) and must become module anchors; TypeDoc's slugger emits -1-suffixed anchors for duplicate names that Rspress's slugger does not, so checkAnchors fails on the generated pages until the titles are deduplicated; the tracked api/_meta.json (hand-maintained — the plugin only writes it when absent) must be flattened to the 13 module files. Change: (1) today, at zero risk: add "collapsed": true to the six dir entries in both _meta.json files (fixes the 921-item sidebar UX only — the HTML is unchanged); (2) then pick the lever — the module router as measured, an intermediate typedoc-plugin-markdown router, and/or dropping the zh mirror in favour of pointing zh API links at /api/ — and rewrite the 43 links; (3) either way, the requiredToBeDocumented warnings (~65/151 functions, ~250/499 interfaces, ~120/186 type aliases with no JSDoc prose) mean the thin pages stay thin until the source is documented. Risk: (1) none; (2) URL churn for any external links into /api/** (the sitemap advertises 1,832 of them today).
P2 · cfg (no ssg) · rsbuild-best-practices "measure the resolved config before tuning" · SSG is the largest build phase — 77–87 s of the ~186 s — because the default path (<core>/node/ssg/renderPages.js:85-91) renders all 1,906 routes in one thread (SSGConcurrency 32 is async, and rendering is CPU-bound). ssg.experimentalWorker: true (<shared>:1146-1157, default false) spawns a Tinypool of min(ceil(pages/100), cpus) threads (:46-50). Two quiet A/B runs: SSG 77.4 s → 8.7 s / 7.7 s, wall 186.3 s → 101.1 s / 110.8 s (−46 % / −41 %), peak RSS 8.52 → 9.82 / 10.35 GB; output identical (1,907 HTML files both, 7/7 sampled pages byte-identical after content-hash normalisation, HTML total 602,258,970 vs 602,258,375 B). Together with the TypeDoc surface above this is the whole "why does the docsite take three minutes" answer (#572 measured the persistent-cache lever at ~4 %; the warm run here was −3 %). Change: ssg: { experimentalWorker: true }, measured once on the hosted runner before committing to it — ubuntu-latest has 4 vCPU → 4 threads → projected SSG ≈ 20–25 s and wall ≈ −55–65 s, not −85 s. Risk: experimental API; worker-written HTML bypasses Rsbuild's asset table (the file-size table no longer lists HTML — cosmetic); +1.3–1.8 GB RSS to verify on the runner.
P2 · cfg:116-118 (editLink) · rspress-custom-theme "override at the lowest level that works" · useEditLink builds docRepoBaseUrl + page._relativePath unconditionally (<core>/theme/components/EditLink/useEditLink.js:10-11) and the link renders in every outline. en/api/** (916), zh/api/** (916) and the 8 */reference/{hosts,events,notices,diagnostics}.md pages are gitignored (.gitignore:19-25), so 1,840 pages carry an "Edit this page / 编辑此页面" link to a GitHub 404 (verified: …/tree/main/website/docs/en/reference/hosts.md, …/en/api/index.md). 2.0.21 has no per-page switch: EditLink is { docRepoBaseUrl } only (<shared>:31-37) and FrontMatterMeta has no editLink key. Change: in website/theme/index.tsx, wrap EditLink from @rspress/core/theme-original: return null when page._relativePath matches ^(en|zh)/(api/|reference/(hosts|events|notices|diagnostics)\.md$), or — better for the reference pages — point them at their real sources (docs/diagnostics.md, packages/agent-bundle/src/adapters/capabilities/*.json) with a small map. Risk: the regex must track .gitignore.
P2 · cfg:86 (markdown), AGENTS.md "code samples wrap at roughly 90 columns" · rspress-best-practices · Measured at 1440×900 with the outline present (every authored page has one): the code-block container is 690 px, the pre 686 px, 14 px monospace at 8.4 px/char → ~81 characters fit, so the 90-column rule is ~10 columns too wide. 66 of 201 visible code blocks on en authored pages (33 %, 18 of 33 pages) need a horizontal scrollbar (39 % across both locales, 132/336 blocks on 39 pages); every block is white-space: pre with the wrap toggle off by default. Worst: /reference/cli (158-char line, +682 px, 3 of 4 blocks), /guide/authoring/package-entries (139 chars, +530 px, 9/30), /guide/authoring/mcp (124 chars, +396 px, 17/38), /guide/distribution/installation (124, +396), /guide/authoring/hooks (114, +312, 5/9), /guide/development/testing (114, +312, 4/7), /reference/diagnostics (104, +228, 2/2). At the source level 26 fences exceed 90 columns and 11 exceed 100 (reference/cli.mdx:153 = 158; guide/distribution/installation.mdx:165, guide/authoring/mcp.mdx:155 = 124). Change: either markdown: { defaultWrapCode: true } (<shared>:383-387; the copy button still copies raw text) or correct AGENTS.md to "≤ 80 columns" and re-flow the 26 fences above 90 first. Risk: wrapped shell one-liners are less scannable; none for correctness.
P2 · cfg:110-113 (languageParity), website/package.json:11 (check) · AGENTS.md "en page and its zh translation in the same PR" · checkLanguageParity compares only the set of .md/.mdx relative paths across config.locales (checkLanguageParity.js:16-19,36-38,62-67); _meta.json, _nav.json, frontmatter, headings and code are not compared, and it runs in finally after the whole build, so a miss costs a full build. Today nothing is stale (all 33 pairs share the same last commit and commit count; h2/h3, table, link, AB-code and ::: counts match; fences are byte-identical after stripping comments — the only diffs are translated trailing #/// comments in 16 fences across 10 pages), but no gate keeps it that way. Change (minimal): website/scripts/check-locale-drift.mjs, run by check before rspress build, failing when for any en/X ↔ zh/X the fence count differs, fence bodies differ after stripping comment lines and trailing comments, the AB\d{4} sets differ, table-row counts differ, or h2/h3 counts differ by >2; plus a PR-level complement that fails when git diff --name-only origin/main...HEAD touches en/X.mdx without zh/X.mdx. Risk: false positives on a deliberately zh-specific example (none exist today) or translated string literals inside code (all kept English today).
P2 · AGENTS.md:116-117 ("pnpm docs:site:build is the gate"), cfg:98-106, website/package.json:11 · rspress-best-practices "verify broken links before deployment" · The gate does not check the link forms the site itself relies on. <core>/node/mdx/remarkPlugins/link.js:162-174 visits only mdast link/definition nodes and image.js:77,100-105 sees markdown images plus <img> only as mdxJsx nodes (i.e. in .mdx); nothing inspects frontmatter, _nav.json, or raw HTML/JSX in .md. A probe docs root with a dead hero.actions[].link, dead features[].link, missing hero.image, <a href="/dead"> in .md and .mdx, <Link href="/dead">, a dead _nav.json entry, an en page with no zh twin under a directory named capi/ (the parity exclude is a substring match on api/), and a zh page with unrelated content built with exit 0 — while the same probe root confirmed that [x](/abs), [x](./rel), [x](/x.md), definitions, #anchors (self, relative, absolute, cross-locale), markdown images, <img> in .mdx, _meta.json entries and a missing zh file all fail the build as they should. The live docs carry 28 frontmatter links (docs/{en,zh}/index.mdx:16-65) and 10 _nav.json links in exactly the unchecked forms. Second miss: with the persistent Rspack cache that a local build writes to website/node_modules/.cache/rspack/ by default, a dead anchor added to page A that targets unchanged page B passes (B's loader never runs, so the RouteService.onRouteAnchorIds consumer, route/RouteService.js:139-148, never fires) — reproduced: warm exit 0, same content cold exit 1. CI is safe only because the cache is not persisted between runs; a local pnpm docs:site:build re-run is not. Change: a ~50-line post-build script in website/ (run by check after rspress build) that walks doc_build/**/*.html, resolves every href/src under /agent-bundle/ to a file and every #id to an id= in the target — covering frontmatter, nav, sidebar, JSX, generated pages and the cache case uniformly (+2–3 s); plus RSPRESS_PERSISTENT_CACHE=false in the check script itself (extends #572's docs.yml-only proposal; the warm gain is 3 %). The locale-drift check above is the parity half of the same gap. Risk: low.
P2 · website/docs/en/reference/configuration.mdx:24,51-53 (zh :24,50) · AGENTS.md "reference prose must match the source" · (a) "targets | string[] | Adapter-selected." — exactly one adapter is registered as default (.register(portableAdapter, { default: true }), src/adapters/registry.ts:780-786) and resolveTargets returns registry.defaultTargetNames() when config omits targets and no --target was passed (src/config/normalize.ts:1114-1122); no page says so. (b) "defineConfig accepts an AgentBundleConfig … and validateSource enforces the rules on this page" while the table lists routes and evals — AgentBundleConfig (src/core/types.ts:309-328) declares neither; both ride the [key: string]: unknown index signature (types.ts:328, src/routes/graph.ts:336), config/validate.ts never reads evals, and the evals rules at :145-147 are enforced by normalizeEvalConfig (src/eval/config.ts:82-125) only when an eval or the Workbench loads the config, as EVAL_CONFIG_INVALID, not an AB diagnostic. Sibling: :38 marketplace "Adapter-selected." → false (only marketplace === true is carried, normalize.ts:1301). Change: (a) "['portable'] (the only default adapter); --target overrides both." (b) add after :53: "evals and routes are not members of AgentBundleConfig; they are read through its index signature, so tsc does not check them and agent-bundle validate does not report them — the evals rules below fire when an eval runs (EVAL_CONFIG_INVALID)." Risk: a user omitting targets expects host artifacts and gets only artifact/portable; a misspelled top-level key is accepted by both tsc and validateSource.
P2 · website/docs/en/guide/authoring/hooks.mdx:146 (zh :133) · AGENTS.md "reference prose must match the source" · "agentStop … on Codex it cannot add context." The Cursor wrapper also rejects it (hook-contract.ts:906, "Cursor … has no additional-context channel") and the typed contract sets agentStop: { additionalContext: false } for every host (hook-handler.ts:66-67: "Claude carries additionalContext on SubagentStop; Codex and Cursor reject it"). Change: "…and only Claude Code carries additionalContext from it — Codex and Cursor reject it, and the typed contract excludes it everywhere." Risk: none.
P3 — polish
cfg(nohead),website/docs/public/· rspress-best-practices "first-class options first" · SSG emitsog:title/og:descriptionand en/zhhreflangalternates on 114/114 pages but norel=canonical,og:url,og:image,og:type,twitter:card,theme-colororx-default; 25 pages have ameta description>160 chars (max 842 onapi/type-aliases/*, inherited from TypeDoc bodies);/api/is titled "agent-bundle - agent-bundle".UserConfig.head(<shared>:1079) accepts per-route functions evaluated at SSG, so canonical +og:urlare one entry (withBase/withSiteOrigin/normalizeHrefare exported from@rspress/core). Live/agent-bundle/robots.txt→ 404 andplugin-sitemapemits none;sitemap.xml(1,906<loc>) is therefore unadvertised — addwebsite/docs/public/robots.txtwithSitemap: https://scriptedalchemy.github.io/agent-bundle/sitemap.xml.cfg:83(cleanUrls: true) · deployment · leaf pages are emitted asx.html, so/agent-bundle/guide/start/quick-startand.htmlare 200 on Pages while the trailing-slash form…/quick-start/is 404 on Pages (200 underrspress preview, whose SPA fallback masks it). The site never emits that form (sitemap, hreflang and nav are extensionless), so this only bites shared links; the cheap fix is a redirect snippet in the 404 page (headscript: strip a trailing slash whenlocation.pathnameis not a directory route).rspress preview· acceptance workflow · the rsbuild static server servesindex.htmlfor every extensionless dotted route (/api/classes/api.TargetRegistry), producing React feat(routes): reference MCP App resourceUri from static config and resolve App templates route-relative (#388) #418 hydration errors and a content flash on 30/30 sampled API pages in preview only; the same pages hydrate clean as.htmland on Pages. Any browser acceptance against preview therefore shows fake errors and hides the trailing-slash 404 above — previewdoc_buildwith a plain static server (npx serve doc_build) or document it.website/plugins/mirror-api-locale.ts· i18n · 916 zh mirror pages carry<html lang="zh">and Chinese chrome (目录 / 上一页 / 下一页) around an English body with no notice that the API reference is English-only, and inherit the English site description. Prepend an:::infonote in the mirror plugin or setlang/descriptionfrontmatter there.cfg:137· rspress-best-practices ·pageTitleTemplates.index: '{projectName} {version}'renders# agent-bundle—{version}is empty becauseincludeVersionis not set; drop it orapp.options.setValue('includeVersion', true).website/docs/en/_nav.json:22-26: "Type API" lands on the bare generated index (13 bullets named by entry file —index,routes/public,test/browser— no prose, 0/916 API pages have adescription); the mapping toagent-bundle,agent-bundle/routes, … exists only inreference/api.mdx— point the nav there or add a "Module page" column toreference/api.mdx:12-29.website/plugins/generated-reference.ts:18-36· AGENTS.md "look for the helper before writing it" · re-declares looseCapabilityRow/HostCapabilityTableshapes whilesrc/adapters/capability-state.ts:47,94,99ownsEventRouteCapabilityTableEntry,CapabilityTableRow,NoticeDeliveryCapabilityTableEntry; the package does not export them, so a schema change renders stale columns silently. Export them from a public entry andimport typein the plugin (changeset +api/**regeneration).- build hygiene · (a) the entry chunk hash is non-deterministic: two builds of the same tree produced
index.88eed14fa7.jsandindex.5960409c3a.js, same length 2,227,901 B, first difference at byte 1,837,065 = sidebar key order invirtual-site-data("/reference"first vs"/contributing"first) — every deploy invalidates the 368 KB gz entry for every returning visitor; upstream@rspress/coreauto-nav-sidebar, no repo change. (b)sitemap.xmllastmod= source-file mtime (<sitemap>/index.js:51): 66 URLs carry the checkout time and 1,836 the build time, so every deploy tells crawlers every page changed. (c) Rsbuild's defaultperformance.printFileSizeprints a 3,900-line size table per CI log, 4.6–8.9 s afterready built;builderConfig: { performance: { printFileSize: { detail: false } } }removes the noise (rsbuild-best-practices; the seconds are not fully attributed — treat as log hygiene). cfg:92-96,cfg:79-81· rspress-best-practices "remove unused plugins" · the docs contain 2[!code highlight]markers and zero[!code ++/--]/[!code focus], sotransformerNotationDiff()andtransformerNotationFocus()run on every fence for nothing;search.codeBlocks: truerestates the default (<shared>:356-359).themeConfig.footeris unset (repo is Apache-2.0).- 13 en pages · rspress-description-generator "50–160 chars" · 13/33 en descriptions are 163–196 chars (
examples/audiobook-curator196,reference/limitations193,examples/mcp-app194,reference/runtime-environment179,reference/index176,guide/distribution/installation174,reference/security173,examples/hooks-and-scripts172,reference/cli170,guide/development/evaluations169,guide/development/testing168,reference/configuration164,guide/development/workbench163); all zh ≤111; 0 missing, 0 wrong language, 0 "This page…" openers. - shared snippets · the minimal
defineConfigfence is byte-identical in 4 en pages (guide/authoring/index.mdx:12,guide/start/project-structure.mdx:69,guide/start/quick-start.mdx:68,reference/configuration.mdx:10) and four paragraphs are duplicated verbatim across pages (reference/security.mdx:19≡guide/development/evaluations.mdx:196;reference/limitations.mdx:50-51≡guide/development/testing.mdx:350-351;guide/distribution/installation.mdx:7≡guide/start/quick-start.mdx:107;guide/distribution/validation.mdx:179≡guide/authoring/package-entries.mdx:223) — MDX fragment candidates. hooks.mdx:139· "sessionStart,afterTool, andagentStartcannot deny" is true forHookHandler<E>and the Claude/Codex wrapper, but the Cursor wrapper admits a denyingagentStartwith a reason (hook-contract.ts:899-901,hook-handler.ts:16) — "the typed contract rejects it; Cursor's runtime tolerates one".- theme a11y / contrast (upstream defaults;
styles/index.cssto override) · appearance switch is adiv.rp-switch-appearancewith no role/name and not focusable; the GitHub social link has no accessible name; no skip link; home page has no<main>/<h1>; prev/next 12 px labels 1.85:1 light / 3.19:1 dark; active-nav brand#0d8f80on white 3.99:1; Shiki light string token#31a94d3.04:1 (body/table/code base ≥11:1 in both modes). Override--rp-c-text-3and the brand token; addaria-labelviathemeConfig.socialLinks. .github/workflows/docs.yml:44·name: Docs site (Node 22.19)is a required context onmain(required_status_checksalso namesHost install proofs (Node 22.19),Examples check (Node 22.19),Release gates (Node 22.19)); bumping Node renames the check and blocks every PR until protection is edited — use a stablename: Docs site.package.json:24roottypecheckcovers root/workbench/create-agent-bundle/rsc-markdown-stream but notwebsite/tsconfig.json(config,plugins/**,theme/**, 1,307 lines), which onlydocs:site:buildcompiles — so AGENTS.md's "gate before pushing" never typechecks the website locally or inci.ymlVerify; appendpnpm --filter @agent-bundle/docs typecheck.website/package.json:24,28:@types/react-domandagent-bundle: workspace:*have no importer (twoslash resolvesagent-bundle*viapaths, TypeDoc viatsconfig.typedoc.json);react/react-domare also unreferenced by website code but@rspress/corealiases to<cwd>/node_modules/reactif present, else its own copy — remove both or neither.rstest.integration-tests.ts:8:workspaceTestFileGlob = 'packages/**/tests/**/*.test.ts'means anywebsite/**/*.test.tswould never be collected, andscripts/classify-docs-only.mjs:20skips Verify forwebsite/**-only PRs — zero tests exist for the ~900-linegenerated-reference.tstoday, so nothing is silently skipped yet; when the first test lands, addwebsite/**/*.test.tstorstest.unit.config.tsand run it indocs.yml. Actions are major-tag pinned, none by SHA (dependency-review-actionruns; Dependabot updates SHA pins). Onmain,.github/dependabot.yml:7-8: since ci: split Verify into a fast leg + 2 integration shards behind the required gate; pin the browser; retry setup; nightly pools (#576) #583pnpm/setup@v2lives only in the composite.github/actions/setup-workspace/action.yml, which thegithub-actionsecosystem withdirectory: /does not scan — usedirectories: ["/", "/.github/actions/*"].
Measurements
(a) Builds (/usr/bin/time -v, website/, Node 22.23, this machine; "quiet" = the sampler saw no other build during the run)
| run | wall | peak RSS | CPU | notes |
|---|---|---|---|---|
cold #1 (doc_build + caches removed) |
190.2 s | 8.45 GB | 173 % | overlapped lane 4's build |
| cold #2, quiet | 186.3 s | 8.52 GB | 184 % | baseline |
warm (doc_build, node_modules/.cache kept), quiet |
184.5 s | 8.34 GB | 188 % | Rspack cache populated (90 MB): −3 % (#572) |
| A/B twoslash removed | 159.9 s | 8.09 GB | 170 % | 12/16 samples saw a foreign build; compile −37.6 s is robust, wall is not |
A/B ssg.experimentalWorker: true, quiet |
110.8 s | 10.3 GB | 321 % | second run under load: 101.1 s / 9.8 GB |
A/B TypeDoc router: 'module' (link checks off) |
90.3 s | 4.99 GB | 201 % | 102 routes; doc_build 8.4 MB |
| Rsdoctor-instrumented build | 236.1 s | 8.14 GB | 174 % | |
| final real-config build (3 foreign builds running) | 224.3 s | 8.47 GB | not a timing sample | |
tsc -p website/tsconfig.json ×2 |
2.6 s / 2.5 s | 0.3 GB | 5 project files + 814 .d.ts; no MDX |
#572's 196.6 s / 188.8 s cross-check to 190.2 / 184.5 s here. Phase breakdown from log timestamps (s):
| phase | cold #2 | warm | worker (×2) | no twoslash |
|---|---|---|---|---|
TypeDoc (→ markdown generated) |
20.6 | 17.5 | 17.1 / 21.4 | 20.6 |
plugin + route init (→ build started) |
12.5 | 7.4 | 7.1 / 9.9 | 8.6 |
Rspack web + node compile incl. MDX/twoslash (→ Rendering pages) |
67.3 | 61.9 | 65.4 / 69.0 | 29.7 |
SSG (Pages rendered in) |
77.4 | 87.2 | 8.7 / 7.7 | 90.7 |
| emit + size table | 6.1 | 8.5 | 0.8 / 0.5 | 7.1 |
| parity + sitemap + llms | <0.1 | <0.1 | <0.1 | <0.1 |
Twoslash is worth its cost: 64 opted-in fences (32 per locale; explicitTrigger defaults to true, so the other 120 ts/tsx/js fences are not type-checked), 26 output pages carry hovers (guide/authoring/mcp 51), compile −37.6 s without it (~20 % of wall). Shiki and twoslash ship no client JS (@shikijs/langs 8.6 MB source, parsedSize 0, tree-shaken); hover HTML is inlined in the route chunks (mcp.mdx route 287 KB / 30 KB gz).
Client JS per page (raw / gz): index.*.js 2,227,901 / 368,666 · search chunk (flexsearch) 226,677 / 74,372, a blocking <script> on every page · lib-react 189,213 / 59,843 · lib-router 35,321 / 12,714 · styles 79,775 / 14,677 CSS · route chunk 4.8–29.5 KB / 1–4.6 KB → ≈ 2.70 MB raw / 520 KB gz per page, identical on home, guide and API pages, plus the 1.71 MB / 338 KB gz search index fetched at requestIdleCallback on every page (SearchPanel.js:177-182), not on focus. Rsdoctor 1.6.3: no E1001/E1002 duplicates; top retained modules by gz are .rspress/virtual-page-data.js 1,345.6 KB / 218.9 KB, virtual-routes.js 487.7 / 73.2, react-dom 174.1 / 54.9, the reference/diagnostics.md routes 257 / 53 each, guide/authoring/mcp.mdx routes 268–287 / 30–34, virtual-site-data.js 286.8 / 30.8 — i.e. the bundle is dominated by the repo's own route count, not by dependencies. Rspack cache after a build: 90 MB in website/node_modules/.cache/rspack/.
(b) Output — doc_build 623,206,738 B apparent (98 MB on a compressing filesystem), 3,909 files: .html 1,907 = 602.3 MB (sidebar 561.2 MB; API pages avg 326 KB, largest zh/api/modules/api.html 385.6 KB; authored avg 54 KB); .js 1,916 (1,911 async/route-*) = 14.8 MB; search index 2 × 1.71 MB; llms .txt 4 = 1.15 MB (llms-full.txt 565 KB per locale; llms.txt 9.2 KB, 36 URLs, all .md targets 200); llmsUI .md mirrors 74 = 1.12 MB; sitemap.xml 397 KB (1,906 <loc>, 953 /zh/, 1,832 /api/, 0 llms URLs); .css 80 KB; 404.html 11 KB; 0 .map, 3 .LICENSE.txt. Pages artifact 36 MB compressed (last 5 deploys 35.9–37.1 MB). Module-router variant: 8.4 MB total, static/js 2.9 MB, index.*.js 474 KB, largest page api/api.html 731 KB.
(c) Runtime (preview, 1440×900, 114 pages) — HTTP 114/114 → 200; console errors/warnings, pageerror, requestfailed, ≥400 responses: 0 on all 74 authored pages; hydration warnings 0 authored, 30/30 API at extensionless URL under preview only (0/30 via .html); <html lang> en 57 / zh 57 correct; meta description present 114/114, >160 chars 25; canonical 0 / hreflang 114; og:type,title,description 114, og:url,image/twitter:* 0; LCP avg 155 ms / p50 148 / max 444 ms, CLS 0.000 on 114/114; DCL avg 296 / max 653 ms; 14–15 requests per page; cold JS transfer 2.68 MB raw ≈ 520 KB gz + 338 KB gz search index; CDP heap 10.6 / 12.9 / 18.4 / 12.6 MB and DOM nodes 1,148 / 1,977 / 10,719 / 3,182 on home / guide / diagnostics / API; rspress dev (4 pages) 0 errors, first load 4.0–5.6 s. Tables: 278/278 wrapped in div.rp-table-scroll-container; 4 wider than their container (hosts "Install surface" +33 px, events "Canonical event routes" +36 px, ×2 locales) → they scroll; page-level scrollWidth > 1440: 0/114; <wbr> from rehypeTableCellBreaks present (24–48 per hosts table). Search matrix (steady state, 170–250 ms to first result): AB6005 en 3 hits (Artifact validation, MCP servers and MCP Apps), zh 3; AB6011 3 (diagnostics reference); AB4770 0 (correct, does not exist); hooks 61; defineConfig 16; readPluginRoot (fence-only) 1 — codeBlocks works; CapabilityStateError, DevRuntimeMcpAppRunBinding (API-only) found; 脚手架 6, 宿主能力 8 — zh indexed separately (952 docs each); --target 0 (leading punctuation stripped; target 119). Language switcher: same slug both directions for every page type (/reference/hosts ↔ /zh/reference/hosts, /api/classes/api.TargetRegistry ↔ zh mirror — English body), 404 → locale home by design. Dark mode toggles html.dark, persists via localStorage['rspress-theme-appearance']. Diagnostics anchors are heading ids: #ab6005 lands, #AB6005 does not.
(d) Live Pages matrix (curl) — /guide/start/quick-start 200 · .html 200 · …/quick-start/ 404 · /guide/start 301 → /guide/start/ 200 · /robots.txt 404 · /api/classes/api.TargetRegistry.md 404 (.html 200) · /guide/start/quick-start.md 200 · /sitemap.xml 200 · /404.html 200 with correct /agent-bundle/ asset and home paths.
(e) Accuracy sample — 25 reference sentences vs packages/agent-bundle/src: 5 / 25 = 20 % drift (1 DRIFT, 4 PARTIAL)
| # | page:line | claim | source | verdict |
|---|---|---|---|---|
| 1 | reference/cli.mdx:288 | exit 2 = any argument-parsing failure |
cli.ts:217-246, 1156-1161 | PARTIAL (five validators → AB5000, exit 1) |
| 2 | reference/cli.mdx:231 | --trials positive integer ≤ 100 |
cli.ts:225-229 | TRUE |
| 3 | reference/cli.mdx:138 | --scope default user; Codex/Cursor user-scoped |
cli.ts:884; install/install.ts:552,898 | TRUE |
| 4 | reference/cli.mdx:139 | --mode local/marketplace paths (Cursor only) |
install.ts:1026-1031,909-910; cursor-marketplace.ts:51 | TRUE |
| 5 | reference/cli.mdx:286 | exit 0 incl. --help/--version |
cli.ts:726,1157-1158 | TRUE |
| 6 | guide/start/quick-start.mdx:27-28 | --template mcp-server --targets … |
create-agent-bundle/src/options.ts:41-42,113-128 | TRUE |
| 7 | reference/configuration.mdx:24 | targets default "Adapter-selected" |
registry.ts:780-786; normalize.ts:1114-1122 | PARTIAL (['portable']) |
| 8 | reference/configuration.mdx:34 | runtime.node floor 22.12 |
core/runtime.ts:4 | TRUE |
| 9 | reference/configuration.mdx:37 | notices retention 7d / 500 / 16777216 |
config/notice-retention.ts:21-23 | TRUE |
| 10 | reference/configuration.mdx:145-147 | evals include / runsDir / grader / harness defaults | eval/config.ts:30-31,62-79,104-107 | TRUE |
| 11 | reference/configuration.mdx:51-53 | "validateSource enforces the rules on this page" (incl. evals, routes) |
types.ts:309-328; graph.ts:336; validate.ts | PARTIAL (untyped, unchecked) |
| 12 | guide/start/installation.mdx:12-13 | runtime.node raises, never lowers, the floor (AB4602) |
runtime.ts:4; validate.ts:888-893 | TRUE |
| 13 | reference/targets-artifacts.mdx:19-23 | install surface per target (#555 would change plugin) |
install/surface.ts:1393-1398 | TRUE |
| 14 | reference/targets-artifacts.mdx:21 | marketplace manifest only when marketplace: true |
cursor.ts:107; normalize.ts:1301 | TRUE |
| 15 | guide/start/installation.mdx:40-41,60-62 | @agent-bundle/runtime, @rstest/core, react optional peers |
package.json peerDependenciesMeta |
TRUE |
| 16 | guide/start/installation.mdx:9 | Node 22.19+ to run the compiler | engines.node >=22.19.0 |
TRUE |
| 17 | guide/authoring/skills.mdx:48 | no frontmatter → AB3001; invalid YAML → AB3002 |
config/skill.ts:106,113 | TRUE |
| 18 | guide/authoring/skills.mdx:86-87 | list targets: in Skill frontmatter → AB3006 |
skills/parse-ir.ts:264-269 | TRUE |
| 19 | index.mdx:174-175 | portable skipped silently; explicit → AB4204 |
normalize.ts:526-528; validate.ts:194-199 | TRUE |
| 20 | reference/cli.mdx:99 | AB8003/AB8004 on refusal |
dev/foreground-server.ts:808-827 | TRUE (family-level doc only) |
| 21 | guide/authoring/hooks.mdx:132 | outcome ∈ continue, deny, stop |
hook-handler.ts:58-71; hook-contract.ts:895-905 | DRIFT |
| 22 | guide/authoring/hooks.mdx:146 | agentStop cannot add context "on Codex" |
hook-contract.ts:906; hook-handler.ts:66-67 | PARTIAL (Cursor too) |
| 23 | guide/authoring/hooks.mdx:55 | timeout omitted → host default |
normalize.ts:478-487 | TRUE |
| 24 | guide/authoring/hooks.mdx:77-78 | Cursor maps all five matchers; Claude no agent; Codex no agent/file.read |
capabilities/*.json hooks.matchers |
TRUE |
| 25 | reference/runtime-environment.mdx:42 | AGENT_BUNDLE_ENV_FILE delimiter / later wins / none |
launch-env.ts:28-33,108 | TRUE |
Sweeps behind the sample: AB codes — 101 distinct on en pages, 0 without an emitter, 0 outside every family row, 4 named only at family level (the P1); CLI flags — 45 parsed, documented-not-parsed 0, parsed-not-documented 0 (after excluding 23 foreign-tool flags that are real in their own parsers); config keys — 19 + 4 host extensions documented, 17 + 4 typed, documented-but-untyped routes, evals, known-but-undocumented 0; unknown top-level keys are rejected nowhere (index signature) and no page claims otherwise.
(f) Dependency / CI currency — see preamble for versions. docs.yml last 80 runs: 62 success, 8 cancelled (PR supersession), 8 action_required (all changeset-release/main bot PRs — repo Actions-approval setting, ci.yml shows the same), 0 failures; build-job median 166 s (n = 9, 130–193 s), deploy 16 s, timeout-minutes: 25 / 10 ample; head-of-main guard and deploy-pages succeeded on every completed push run. pnpm audit: 2 moderate (qs@6.15.3), 0 of 28 paths through @rspress/typedoc/shiki/mdx; 0 open Dependabot alerts. Gate parity: rslint covers website/** in Verify, docs.yml and locally; tsc -p website/tsconfig.json, the TypeDoc + twoslash compile of the package sources under TS 6.0.3, and the Rspress dead-link/anchor/image/parity checks run only in docs.yml; website/** unit tests run nowhere (none exist).
Already compliant
- Config surface:
siteOriginis a documentedUserConfigkey (<shared>:1052-1055) consumed by the sitemap fallback,hreflang,llms.txtURLs and absolute-link normalisation;base: '/agent-bundle/'+ defaultoutDirmatchdocs.yml:66and the Pageshtml_url;lang: 'en'+route.localeRedirect: 'never'emit no redirect script, en at/, zh at/zh/;route.cleanUrls: true(extensionless hrefs, hreflang, sitemaplocand address bar all agree); zh UI strings resolve throughDEFAULT_I18N_TEXT(目录, 上一页/下一页, 搜索, 编辑此页面, 复制 Markdown, 切换代码换行) so nothemeConfig.localesblock is needed; local search builds one index per locale;markdown.shikilazy: truecovers every fence language in use (sh118,ts100,text20,tsx16,md2,json2) and the extralangsonly pre-warm grammars for twoslash hovers as the comment says;link.checkDeadLinks: { excludes },checkAnchors: true,image.checkDeadImages: truematchRemarkLinkOptions/RemarkImageOptions; plugin order (TypeDocconfig→setup→convert→writeOutput, thenmirrorApiLocale, thengeneratedReference) is sequential with no race in dev;theme/index.tsxuses the first-classafterFeaturesslot and re-exportstheme-originalwith no cycle;styles/index.cssuses only existing--rp-*tokens and BEM classes (no dead selectors);icon/logo/hero.imageare base-prefixed; no deprecated keys (themeConfig.localeRedirect,addRuntimeModules) and no v1 imports;website/tsconfig.jsonscope is correct;socialLinks.mode: 'link'valid. - Content architecture: nav parity (5 entries, same order, zh
activeMatchcarries/zh/); 10_meta.jsonper locale, identical order, translated labels,dir-section-headerfor the four Guide groups; a missing sidebar target throws at build ("Missing page file"), so the coupling to generated pages is fail-loud; exactly one h1 per page (66/66), 0 heading skips, titles unique per locale; all frontmatter keys valid; 0 orphans (33/33 pages reachable from nav + sidebar per locale; 953 generated pages reachable); no page under 120 words; only the two deliberate cross-locale links (reference/api.mdx); all 13blob/main/tree/mainGitHub targets exist; generated reference plugin fails loudly on malformed input, translates zh headings/prose/info boxes, escapes| < > { }, keeps all 240 diagnostics rows; TypeDoc surface: 916 pages, zh mirror 916/916 byte-identical, 3,149 internal links with 0 broken, 43 authored → API links per locale all resolve,<>{}escaped,\_absent from titles, noDefined inlines to rot. - Runtime: zero console errors/warnings/failed requests on all authored pages; CLS 0 everywhere; every table in a scroll container and no page-level horizontal overflow at 1440;
basehandled in assets, favicon, 404.html, sitemap, llms, home CTAs, hreflang; language switcher lands on the same slug in both directions; dark mode persists; body/table/code base contrast ≥11:1 both modes; search finds fence-only identifiers, API symbols and CJK phrases; all images havealt;rspress devloads clean. - Gate: dead link/anchor/image checks are build errors, not warnings, in production (
link.js:35,74,image.js:33); relative, absolute,.md-suffixed, definition, self/relative/absolute/cross-locale anchor, markdown-image,.mdx<img>,_meta.jsonand missing-zh-file probes all fail the build as documented; generated TypeDoc and reference pages are written inconfighooks beforeRouteService.create, so anchors into them resolve (cold);checkDeadLinks.excludescorrectly whitelists thellms(-full).txtassets;languageParity.exclude: ['api']has no accidental substring victim in the real tree;explicitTrigger: truekeeps twoslash to opted-in fences; Rsdoctor E1001/E1002 clean. - Deps / CI: all
@rspress/*at latest; no legacyrspresspackage; Node engines consistent across root, website, Rspress, Rsbuild, typedoc, shiki; concurrency groups correct (docs-mainnon-cancelling,docs-<n>cancelling for PRs,pagesgroup on deploy); permissions minimal; Pagesbuild_type: workflow,https_enforced, branch policymainonly; Dependabot covers the pnpm workspace sub-manifests and workflows;.gitignorecoversdoc_build/,api/**/*.md, the four generated reference pages.
Caveats
- Builds ran on a shared 20-thread machine; the sampler flagged foreign builds during cold feat: add RSC plugin framework and audiobook curator example #1 (overlapping lane 4's build) and the no-twoslash A/B (12/16 samples), so those two are upper bounds — the no-twoslash compile-phase delta is measured in-process and robust, its wall figure is not. Single runs, not medians (cold repeat variance 190.2 vs 186.3 s). The 4-vCPU CI projection for
experimentalWorkeris arithmetic, not measured, and its peak RSS (10.3 GB here) must be re-measured on the hosted runner before adoption. - The TypeDoc
router: 'module'A/B disabledcheckDeadLinks/checkAnchors(the variant fails them for the reasons listed) and flattenedapi/_meta.jsonin the scratch worktree only; its numbers are therefore a lower bound on the work, not a drop-in config. - Runtime numbers come from
rspress previewon localhost (LCP/timing not network-realistic); production behaviour was cross-checked withcurlonly, not a browser run against Pages. The API sample was 40 of 1,832 pages; all authored pages were crawled. a11y checks are hand-rolled (axe-core is not installed). - Search index-build timing was measured once per fresh browser context, 8 trials; the root-cause reading of
LocalProvider.initis from the shippeddist, not a debugger. - zh "word counts" are CJK character counts; JSDoc-coverage figures are a text heuristic; description lengths are code points.
- The
_meta.json/sidebar collapse behaviour and hook ordering were read from@rspress/core@2.0.21source and then confirmed on the rendered site (921-item sidebar observed). mainmoved during the audit; thedocs.yml/dependabot.ymlP3s were re-checked againstmain, everything else againstb75073b24.
Lanes
- Config surface — every
cfgkey vs<shared>/<core>typings and runtime; produced the twoslash-anyP1, the llmsUI and EditLink findings, thehead/robots P3s, and the "already compliant" config list. - Content architecture — 33 pages/locale, 10
_meta.json/_nav.jsonper locale, 916 API pages/locale; produced bothdiagnostics.mdP1s, the sidebar/collapsedand locale-drift P2s, descriptions/snippets/thin-page P3s, orphan/heading/frontmatter/link sweeps. - Build measurement — cold ×2 / warm builds, phase breakdown, Rsdoctor, the
experimentalWorkerand no-twoslash A/Bs, per-page client JS, and 18 probe docs roots against the dead-link/anchor/image/parity checks; produced theexperimentalWorkerP2, the gate false-negative P2 (frontmatter/nav/JSX links, warm-cache anchors), the sidebar-is-93 %-of-HTML analysis, and the hash/lastmod/printFileSizeP3s. - Runtime quality — headless-Chromium crawl of 114 pages plus search, switcher, dark-mode, 404 and live-Pages matrices; produced the search-race P1, the live 404 evidence for llmsUI/EditLink/robots, the overflow measurements, and the contrast/a11y P3s.
- Accuracy sampling — 25 sentences + AB-code / CLI-flag / config-key sweeps; 20 % drift, the
cli.mdx:288andhooks.mdx:132P1s, theconfiguration.mdxandhooks.mdx:146P2s. - Currency / deps /
docs.yml— versions vs latest, peer constraints, action pins, last 80 runs, gate parity; no P1/P2, six P3s. - Coordinator — the TypeDoc
router: 'module'A/B in a third worktree; verification of every P1 against the built HTML, the config, and live Pages; integration.
Cross-references: #566 (combined survey, §4 docsite), #572 (Rsbuild audit; performance.buildCache P2 not restated), #576 (format).
Activity
docs(website): Rspress docsite audit — every P1/P2 from #590 plus che…
docs(website): theme a11y/contrast and git-dated sitemap lastmod (#590 …
docs(website): TypeDoc router 'module' — one API page per entry point (…
docs(website): point en API links at module pages (#590)
docs(website): point zh API links at module pages (#590)
docs(website): finish module-routed API config (#590)
docs(website): describe the module-page API reference (#590)
docs(website): anchor rewrite requires a page-unique heading at any d…
fix(build): a name bound to a factory call's result is a module, not …
fix(build): bound the binding-position walks, read defaults as values…
docs(website): TypeDoc router 'module' — one API page per entry point (…
docs(website): point en API links at module pages (#590)
docs(website): point zh API links at module pages (#590)
docs(website): finish module-routed API config (#590)
docs(website): describe the module-page API reference (#590)
docs(website): anchor rewrite requires a page-unique heading at any d…
docs(website): TypeDoc router 'module' — one API page per entry point (…
ScriptedAlchemy commentedon Sep 5, 2026
Closing out the audit. Five PRs landed, squash-merged:
cfdaecafdrouter: 'module'f6deab1a4lastmod41883c541f90cc523a9b02115abBefore → after (1440 × 900, cold builds with
RSPRESS_PERSISTENT_CACHE=false, load 8–15 on 96 cores)rspress buildwallssg.experimentalWorker)docs:site:buildCI jobappmodule)doc_buildsize / HTML bytes: anyhoverslastmodDeferred
docs.ymljob nameDocs site (Node 22.19)is a required status context onmain; a static name needs the branch-protection edit in the same step — do it with the next Node bump.canonical/og:url/og:image: aheadfunction cannot crossworkerDatainto the SSG workers (DataCloneError), so it is incompatible withexperimentalWorker. Statictheme-colorlanded; the rest waits on Rspress supporting serialisable per-route head, or on giving up the worker speed-up.NavHamburgermounts the switch inside its own<button>, and its Enter/Space only opens the menu, so a keyboard user at those widths cannot toggle appearance (unchanged from the default theme; the ≥ 1281 px switch is now a named, focusable button). Fix is aNavHamburgerfork that renders the popover outside the trigger — upstream-worthy.<h1>: needs aHomeHerofork; the home page now has<main>and a skip target./api/api#build-2) encode heading order; the gate now checks the link label against the<h3>member heading's exact text (docs(website): exact-name guards for the TypeDoc anchor rewrite and the authored-label check (#610 follow-up) #624), so a silent re-point fails the build instead of shipping, but the ids themselves remain order-dependent by TypeDoc/github-slugger design.generated-reference.tsre-declaresCapabilityRow/HostCapabilityTable— needs a package export fromsrc/adapters/capability-state.ts; not a docs change.defineConfigfence in four pages, four duplicated paragraphs): editorial; en + zh in one PR when someone owns the prose.virtual-site-data): upstream Rspress.rspress previewservingindex.htmlfor dotted routes: preview-only, and moot for/api/**now that the module router emits no dotted paths.hooks.mdx:139CursoragentStartdeny tolerance: one-sentence nuance; leave until the Cursor wrapper contract is settled by Meta-framework architecture: one application graph, capability-driven projections, one execution kernel #592's work.agent-bundle/approw was not a translation of its en row (it listed the error-code count and the listener types); docs(website): TypeDoc router 'module' — one API page per entry point (#590) #610 replaced it with a faithful translation, so that extra zh-only detail is gone by design — restore in both locales if wanted.AB6005prose beyond the explicit row (feat(build): hold the package build's dist bundles to AB6005 #588) andplugin-target sentences (feat(build): emit one composite plugin root; targets select host projections; remove the plugin target (#555 W1) #578/Emit one composite plugin artifact; use targets to select the host projections inside it #555) — owned there.mainsince the audit (not from these PRs, filed as docs(website): home page artifact tree overflows its ab-compare column after #578 #617): feat(build): emit one composite plugin root; targets select host projections; remove the plugin target (#555 W1) #578 replaced the home page's tabbed per-host trees with one 76-columntexttree inside the two-columnab-compareblock; at 1440 px it scrolls horizontally (484 px column). Either shorten the comments or drop the compare layout for that block.Also filed
LocalProvider.init()returns before FlexSearchaddAsyncsettles (patched locally in docs(website): Rspress docsite audit — every P1/P2 from #590 plus cheap P3s #599).cli.tsoption validators throwTypeError(exit 1,AB5000) where the rest throwInvalidArgumentError(exit 2); docs now describe the current behaviour.ScriptedAlchemy commentedon Sep 5, 2026
All P1/P2 work landed in #599, #610, #614, #622, and #624. The home-page artifact-tree overflow filed as #617 is fixed by #626.
The remaining deferred items are recorded in the wrap-up comment:
experimentalWorkerNavHamburger/HomeHerotheme forkshooks.mdx:139nuance pending Meta-framework architecture: one application graph, capability-driven projections, one execution kernel #592docs.ymljob name with the next Node bumpReopen these individually if someone picks them up.