An image the manifest does not publish was rewritten to a raw.githubusercontent URL, which serves a private repository to nobody: raw answers 404 unauthenticated, and a github.com session does not authenticate it. Every image on the site was broken for every reader, and no gate could say so — link checking asks whether the file exists in the repository, not whether a site reader can fetch it. The projector now copies a referenced image into the generated tree beside the page and points the reference at it, so Vite bundles it like any other site asset and repository visibility cannot reach the published page. The copy lands beside the page so one relative URL is right from both locale trees and a page's assets leave with the page; two sources projecting onto one path throw rather than letting the last copy win. Canonical Markdown keeps writing ordinary repository-relative paths, so the same file still renders when read in the repository. The guide this was found through is renamed to its subject: 配置模型.
4.3 KiB
Agent Note: The documentation site carries its own images
Status: implemented
English | 中文
Problem
scripts/project-doc-site.ts rewrote every repository-relative target that the publication manifest does not publish into a GitHub URL, and for an image that meant https://raw.githubusercontent.com/<owner>/<repo>/<ref>/<path>. Nothing in the site build copies files: srcDir is the disposable .generated tree, VitePress sets no publicDir (its default, <srcDir>/public, is inside the tree the projector deletes on every run), and only Markdown is written there.
That works only for a public repository. This one is private, and raw.githubusercontent.com answers 404 to an unauthenticated request — a browser session on github.com does not authenticate it either, since GitHub's own UI serves private blobs through separately signed URLs. Every image on the site was therefore broken for every reader, and no gate said so: verify-md-links and the projector check that the target file exists in the repository, which is a different question from whether a site reader can fetch it.
Decision
rewriteMarkdown takes an optional placeImage(absPath): string. When a page references an image the manifest does not publish as a page, the projector copies that file into the generated tree beside the page and rewrites the reference to ./<basename>; Vite then bundles it like any other site asset. Nothing about repository visibility can reach the published page.
The copy lands beside the page rather than in a shared asset directory. Each locale's route tree gets its own copy, so one relative URL is correct from both guide/ and en/guide/ without computing per-locale prefixes, and a page's assets are removed with the page when the manifest drops it. Two sources that would project onto one path throw, in the same spirit as the existing duplicate-route check, rather than letting whichever copied last win.
placeImage is optional because rewriteMarkdown is also called directly by its spec, where no generated tree exists. Without it the old GitHub-raw behavior stands, which keeps that seam honest: the fallback is still the correct answer for a consumer that only rewrites text.
Canonical Markdown keeps writing ordinary repository-relative image paths, so the same file renders on GitHub and on the site. No document carries a site-absolute URL to satisfy VitePress.
Alternatives considered
Set publicDir outside .generated and reference site-absolute URLs. Fewer moving parts in the projector, but every image reference would then be broken when the same Markdown is read in the repository, and canonical docs are read both ways.
Host images on the assets branch, as demo GIFs already are. That branch exists to keep large binaries out of the main history, and its raw URLs have exactly the same visibility problem. It remains the right home for recordings; it does not solve this.
Wait for the repository to become public. It would fix the symptom without making the site self-contained, and the site would silently depend on GitHub's availability and rate limits for every image.
Consequences
Images in published documentation now work regardless of who is reading or whether the repository is public, and the site build has no runtime dependency on GitHub for them. The generated tree grows by one copy of each referenced image per locale — the four screenshots in the model-provider guide add roughly 270 KB per locale.
Images referenced from unpublished documents are untouched: they still resolve to GitHub raw, and still fail for a private repository. Nothing consumes them today, and a document that is not on the site has no site build to carry its assets.
Testing
scripts/project-doc-site.spec.ts covers the placer receiving the resolved absolute path and the returned URL landing in the Markdown, a published page link still resolving to its route when a placer is present, and the unchanged GitHub-raw fallback when no placer is supplied. pnpm docs:check builds the site with the model-provider guide's screenshots and fails on a missing source; the copied files and their ./<basename> references were verified in website/.generated and in a running docs:dev (naturalWidth > 0 in both locales).