Make the site discoverable to agents, and serve the docs as markdown - #72
Merged
Merged
Conversation
The pages were already static HTML with titles, descriptions, canonicals and OG tags — readable, but with nothing telling a machine what is here. Nothing enumerated the 16 docs pages, and nothing typed the claims the prose makes. Four additions, none of them a list to maintain by hand: - @astrojs/sitemap, since `site` was already configured. 18 URLs, 404 excluded. It hard-codes `<filenameBase>-index.xml`, so a build hook renames the index to the /sitemap.xml a crawler actually guesses; renaming the index rather than the chunk keeps the split intact. - public/robots.txt, pointing at /sitemap.xml. - llms.txt (llmstxt.org), generated from the docs collection — so a new page under the repo's docs/ lists itself, for the same reason it appears on the /docs hub. A static file in public/ would drift on the next page added, which is what plugins/docs-pages.mjs exists to prevent. - JSON-LD, as a `schema` prop on Base.astro passed by the page that knows what it is describing: SoftwareApplication from index.astro (softwareVersion off latestVersion, so it tracks releases), FAQPage from docs/[...slug].astro. The FAQ's questions are collected by rehype-faq-accordion as it builds the accordion and handed over as render()'s remarkPluginFrontmatter, so the visible answers and the structured ones cannot disagree — 30 questions, no second parse of the markdown. That coupling has a silent failure mode: a stale Astro content cache re-emits the old HTML after a plugin edit and the JSON-LD just isn't there, build still green. Recorded in web/CLAUDE.md under Machine readers and Verify, alongside the FAQPage schema's assumption that every h3 in docs/faq.md is a real question. The <head> description moves to site.ts, which llms.txt needs verbatim too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JGMQ49hvA936om3GQfNTAn
An agent that wants a docs page has to parse the prose back out of the HTML, and a reader who wants to paste one into an LLM has no way to get it. Both want the file the page was rendered from. /docs/reading is the page; /docs/reading.md is its markdown. All 15 pages, including /docs/faq.md — where the raw `### question` headings are a better read for a machine than the accordion is. The body is served byte for byte except for its links. Those are written as on-disk relative paths (`../cli.md`) so they resolve in Reader.md and on GitHub, and flattening docs/features/ onto /docs/ breaks them, so they are resolved to absolute URLs — through the same rewriteLink the rendered page uses, now exported from remark-docs-assets.mjs, so a link cannot mean one thing in HTML and another in markdown. A sibling docs page points at that page's .md, so following a link out of one lands in another. No markdown parser: stringifying would reflow the prose, and the corpus has no link titles and no reference definitions (66 destinations, all inline). The "Copy markdown" button on each page fetches its own twin rather than the HTML carrying a second copy of the prose. A failed fetch leaves the button alone — claiming "Copied" over an empty clipboard is worse than appearing to do nothing. Verified in a browser: 5,469 characters of reading.md on the clipboard, the label and the tick agreeing, and the FAQ's 30 `###` headings matching its 30 accordion rows and 30 JSON-LD questions — three renderings of one source file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JGMQ49hvA936om3GQfNTAn
An agent that follows an entry out of llms.txt wants the source, and every docs page now has a .md twin to give it. The index linked the HTML, which sent it back to parsing prose out of markup. The Optional section keeps its HTML links: the home page and the changelog have no .md twin, and Source is the repository. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JGMQ49hvA936om3GQfNTAn
This was referenced Sep 6, 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.
The site was crawlable — static HTML, titles, descriptions, canonicals, OG tags — but nothing told a machine what was here. Nothing enumerated the docs pages, nothing typed the claims the prose makes, and an agent that wanted a page had to parse the prose back out of the HTML.
Two commits.
Discoverability
sitemap.xml@astrojs/sitemap, sincesitewas already configured. 18 URLs, 404 excluded.robots.txtllms.txtdocscollection.SoftwareApplicationon/,FAQPageon/docs/faq.Nothing here is a list to maintain by hand.
llms.txtis built off the same collection the/docshub is, so a new page underdocs/lists itself.SoftwareApplicationreadssoftwareVersionfromlatestVersion, so it tracks releases. A staticllms.txtinpublic/would have drifted on the next page added, which is whatplugins/docs-pages.mjsexists to prevent.The integration hard-codes
<filenameBase>-index.xml, so a build hook renames the index to the/sitemap.xmla crawler actually guesses. Renaming the index rather than the chunk keeps the split intact.Markdown twins + copy button
/docs/readingis the page,/docs/reading.mdis the markdown it was rendered from — all 15 pages, including/docs/faq.md, where the raw### questionheadings read better for a machine than the accordion does.Served byte for byte except the links. Those are on-disk relative paths (
../cli.md) so they resolve in Reader.md and on GitHub, and flatteningdocs/features/onto/docs/breaks them — so they're resolved to absolute URLs through the samerewriteLinkthe rendered page uses, now exported, so a link can't mean one thing in HTML and another in markdown. No markdown parser: stringifying would reflow the prose, and the corpus has no link titles or reference definitions (66 destinations, all inline).The Copy markdown button on each page fetches its own twin, so the HTML doesn't carry a second copy of the prose. A failed fetch leaves the button alone — claiming "Copied" over an empty clipboard is worse than appearing to do nothing.
The FAQ's answers now reach a reader three ways — accordion, JSON-LD,
/docs/faq.md— all derived fromdocs/faq.mdalone.Verification
Clean rebuild: 15
.mdtwins, JSON-LD indist/index.htmlanddist/docs/faq/index.html, copy button on all 15 docs pages, no unrewritten relative links in the markdown, and the existinghrefinvariant (only absolute GitHub URLs) still holds.Driven in a browser: 5,469 characters of
reading.mdon the clipboard, the label and tick through the full cycle, no console errors, FAQ accordion and search unaffected.Two things worth a look
.mdfiles will serve astext/markdown, which most browsers download rather than display. That's right for agents, so it's left alone. Forcingtext/plainneeds a_headersrule, and I didn't want to guess whether Cloudflare's wildcard supports a/docs/*.mdsuffix — if it degraded to/docs/*it would serve all 15 docs pages as plain text. Easy to add and verify once deployed.web/CLAUDE.mdgains a Machine readers section, including the silent failure mode this introduces: theFAQPageJSON-LD comes fromrehype-faq-accordionviaremarkPluginFrontmatter, and a stale Astro content cache drops it with the build still green. That bit me once while building this.🤖 Generated with Claude Code
https://claude.ai/code/session_01JGMQ49hvA936om3GQfNTAn