A Nextra 4 (Next.js App Router + MDX) documentation site
for the Donkey Development Kit. Content lives in content/**/*.mdx; navigation
is declared in the _meta.js files next to the pages. A single catch-all route,
app/[[...mdxPath]]/page.tsx, renders every page, and the theme is composed in
app/layout.tsx (Nextra 4 removed theme.config.tsx).
cd website
npm install
npm run dev # http://localhost:3000Build the static export (what CI ships to Pages) and preview it:
npm run build # `output: 'export'` → writes a static site to ./out
npm run preview:pagesSearch is Pagefind (Nextra 4 replaced the built-in
FlexSearch). It indexes the built HTML, so the postbuild hook runs
pagefind --site out --output-subdir _pagefind after every next build — which
means search only works against the built export, not npm run dev.
The preview command serves the generated static files from out/, matching
GitHub Pages more closely than the Next.js development server. The deployed site
is served from the domain root, so a plain npm run build is what ships. To
check a build hosted under a project sub-path instead, set the same variable for
both commands:
DOCS_BASE_PATH=/donkey-development-kit npm run build
DOCS_BASE_PATH=/donkey-development-kit npm run preview:pages
# Open http://localhost:4173/donkey-development-kit/GitHub's Jekyll preview instructions do not apply here: this site is a Nextra/Next.js static export, and the Pages workflow disables Jekyll before deployment.
The site publishes its docs in the llms.txt convention so coding assistants can read them (#205, BG §1.10):
public/llms.txt— a link index, grouped by nav section.public/llms-full.txt— every page inlined into one file.public/<path>.md— the raw markdown for each page, served beside its HTML.
These are generated from the pages by
scripts/generate-llms.mjs — the source of truth
is _meta.js (ordering + titles) and each .mdx (content + frontmatter);
nothing is hand-maintained. npm run build regenerates them first (via the
prebuild hook), and the docs-llms-drift CI job regenerates and fails on any
diff, so the committed artifacts cannot drift from the pages.
npm run generate:llms # regenerate after editing pages; commit the resultThe artifacts stay committed on purpose (#795). Every PR diff shows how a page
edit changes what assistants read, the drift job checks them without a build,
and .gitattributes marks them linguist-generated so GitHub collapses them
in review.
Some pages restate facts that live in the Python code. Those parts are written
by python/scripts/generate_docs.py, not
by hand:
content/reference/api.mdx, the whole Python API page, from the public docstrings and signatures;- the
BEGIN GENERATEDregions incontent/reference/configuration.mdx(settings index),content/cli.mdx(command reference) andcontent/frameworks/index.mdx(adapter capabilities).
Edit the code or docstring, then run the script from python/ and commit the
result. The unit test tests/unit/test_generated_docs.py fails when a
committed copy is stale. The prose around a region is hand-written as usual.
The version banner at the top of every page is built at build time by
lib/version.mjs: it reads __version__ from
../python/src/donkey_kit/__init__.py (the package's one version source), and asks PyPI for the latest release with a
3-second timeout. If PyPI can't be reached, the banner shows only the
documented version.
The site is published by .github/workflows/docs.yml
on every push to main that touches website/** (and on manual
workflow_dispatch). The workflow builds the static export for the domain
root (no DOCS_BASE_PATH), adds .nojekyll, and deploys the out/ artifact to
Pages. The published site lives at the custom domain
https://docs.donkey-kit.dev/, declared by public/CNAME (#909). GitHub Pages
redirects the old donkey-development-kit.github.io/donkey-development-kit/
URLs there.
One-time setup: in repo Settings → Pages, set Source = "GitHub
Actions" and Custom domain = docs.donkey-kit.dev (with Enforce
HTTPS). DNS: a CNAME record docs → donkey-development-kit.github.io at
the donkey-kit.dev registrar. The workflow cannot flip these switches; until
the source is set, the deploy job has nowhere to publish.
basePath/assetPrefix are gated on DOCS_BASE_PATH, so npm run dev and the
deployed custom domain both serve at the root without a sub-path.
Production pages load the cookie-less
Cloudflare Web Analytics
beacon when the repository variable CF_WEB_ANALYTICS_TOKEN holds the site
token (Settings → Secrets and variables → Actions → Variables). The workflow
passes it to the build as NEXT_PUBLIC_CF_WEB_ANALYTICS_TOKEN; the root layout
renders the beacon only for a well-formed 32-character hex token (see
lib/analytics.mjs), so npm run dev and unconfigured builds ship no
analytics. The site token is public by design and appears in the page source.
Unset the variable and redeploy to remove the beacon. Collection and reporting
live in the private donkey-development-kit-metrics dashboard.
One-time setup: in Cloudflare (Analytics & Logs → Web Analytics → Add a
site), register the hostname docs.donkey-kit.dev, choose the manual
JavaScript snippet, and copy the token from its data-cf-beacon attribute
into the CF_WEB_ANALYTICS_TOKEN variable. Because only main is published,
the beacon goes live on the first Pages deploy after both the variable is set
and the beacon code has reached main. Confirm it in browser devtools:
beacon.min.js loads and the page reports to
cloudflareinsights.com/cdn-cgi/rum with no console or Content Security Policy
errors.
npm test runs the beacon gate's unit tests.
Needs Node >= 22 (CI uses 24). Run after npm ci:
npm run typecheck # tsc --noEmit
npm run lint # syntax-check the .mjs files (scripts/lint.mjs)
DOCS_BASE_PATH=/donkey-development-kit npm run build
DOCS_BASE_PATH=/donkey-development-kit npm run check:links # internal links + #anchors in out/There is no start script: the site is a static export (output: 'export'),
so serve out/ with npm run preview:pages instead of next start.
The sidebar is grouped into capability pillars by separators in
content/_meta.js; page URLs are flat, so moving a page between pillars never
breaks a link.
content/
index.mdx, quickstart.mdx,
feature-overview.mdx Get started
frameworks/ Model access — one page per framework
errors, budget, identity,
hitl, policies Governance
telemetry Observability
simulator, testing, cli,
use-with-your-agent Developer tooling
tool-access/, a2a, publishing Registry & catalog
scenarios/ Scenarios
examples/ Examples (from donkey-development-kit-demos)
roadmap.mdx Roadmap — rendered from data/roadmap.json
community/ Team, Contribute
reference/ Configuration, unsupported boundary
Every page declares its status in frontmatter, in one vocabulary that follows
the verification ledger (docs/verified-apis.md, §0.3):
---
description: …
status: live # or offline-verified, or roadmap
---status |
Means | Badge |
|---|---|---|
live |
Shipped, and the gateway contract it rests on is VERIFIED (LIVE) / VERIFIED (CLI) in the ledger. |
Live |
offline-verified |
Shipped and proven offline (conformance against the simulator, verify_frameworks.py signature checks), with no live round-trip yet: the ledger row reads UNVERIFIED (signature confirmed). |
Offline-verified |
roadmap |
Planned design, or an API that raises _verify.blocked(...) until its ledger row is confirmed. |
Roadmap |
The page-level badge is rendered from that value (see
lib/status.mjs), and the same value is shown in
llms.txt and under the H1 of each per-page .md, so don't hand-write a
<Badge> under the H1. statusBadge: false hides the badge on pages that
document no SDK surface (the landing page, the roadmap, the community pages).
Inline badges in tables, cards and legends are still written by hand. A page
is rated by the surface its main code uses (a scenario built on
donkey.langgraph.chat_model(...) is offline-verified, like the framework
pages); when one section of a page stands on a weaker ledger row than the rest,
that section carries its own inline badge, as the framework card on the
Feature overview does. A missing or unknown status fails npm test, node scripts/generate-llms.mjs
and npm run build.
The status is also what the Python snippet check reads
(python/scripts/check_doc_snippets.py, run by the unit suite in CI): every
Python fence must import, resolve and bind against the real SDK, roadmap
pages are exempt, and a live page may not call an API that raises
_verify.blocked(...). Delivery detail (phases, milestones, issues) belongs on
the Roadmap page only.
data/roadmap.json is a snapshot of the phase milestones and their issues,
written by scripts/generate-roadmap.mjs from
the GitHub API. npm run build refreshes it first (via prebuild); if the API
is unreachable the committed snapshot is kept, so builds never depend on the
network. Set GITHUB_TOKEN to avoid the unauthenticated rate limit.
npm run generate:roadmapNever document an endpoint, header, or class name that isn't verified. Where
a value is unconfirmed, say so on the page itself rather than omitting it.
The engineering source of truth for what is verified is
../docs/verified-apis.md.