Skip to content

Repository files navigation

teenygrad docs

One site for every teenygrad project's documentation, at docs.teenygrad.org.

It replaces two separately-built, separately-styled sites — docs.teenygrad.org (built in the teenygrad repo's docs-site/) and vision-rs.teenygrad.org (the same pattern duplicated in vision-rs) — with a single Nuxt site that shares the design system of teenygrad.org.

Books /teenygrad, /kernels, /vision-rs, /cargo-teeny
API reference /api/<project>/ — generated rustdoc, served as-is
Stack Nuxt 3 · Tailwind v4 · IBM Plex — the same as the marketing site
Output fully static (nuxt generate), served by nginx

The book markdown is not in this repo

It lives beside the code it documents, in each project's own repo, and stays valid mdbook inputbook.toml is untouched and mdbook serve still works there for local authoring. This site is a second renderer of the same files.

teenygrad/books/teenygrad/src/**.md  ─┐
teenygrad/books/kernels/src/**.md    ─┼─→ scripts/fetch-books.mjs  →  .books/
vision-rs/book/src/**.md             ─┤       (sparse git clone)
cargo-teeny/README.md                ─┘
                                              │
                                   scripts/build-books.mjs
                                              │
                            site/generated/ + public/search-index.json
                                              │
                                        nuxt generate

books.config.mjs is the registry of what gets published. Everything else reads it.

Adding a project

Add an entry to books.config.mjs. Point bookDir at the directory holding book.toml and src/, nested or not; if the project has no book yet, point readme at its README instead and the site publishes that as a one-page book until a real one exists (this is how cargo-teeny is published today — swap the field over and nothing else changes).

Entries are keyed by slug, not by repo, so one repo can publish more than one book — /teenygrad (books/teenygrad) and /kernels (books/kernels) are both teenygrad/teenygrad.

What of mdbook is supported

Everything the books actually use: SUMMARY.md (parts, nesting, draft chapters, prefix/suffix chapters), mermaid, fence annotations (rust,ignore), mdbook's hidden-line convention (# in a Rust block), relative .md cross-links, and mdbook's own heading-id scheme — so anchors published on the old site still resolve.

{{#include}} and {{#rustdoc_include}} are expanded before parsing, with mdbook's anchor (file.rs:name) and line-range (file.rs:10:20) forms — the kernels book keeps every code sample in a file its CI compiles and pulls it in by reference. Included paths may not climb out of the project's own checkout, and a missing file or anchor fails the build rather than publishing a hole.

Not supported: {{#playground}}, which needs a runtime this static site does not have. A build that meets one warns and leaves it as written.

The build also reports two classes of problem mdbook was silent about: a .md file that no SUMMARY.md lists, and an internal link that points at no chapter.

Local development

npm install
npm run dev          # fetches the books on first run, then starts Nuxt

npm run books re-fetches and recompiles on its own. Fetches are cached in .books/; node scripts/fetch-books.mjs --force re-pulls.

To work against unpushed book edits in sibling checkouts (../teenygrad, ../vision-rs), either set it per-command:

BOOKS_FROM_SIBLINGS=1 npm run books

or put it in a .env at the repo root, which npm run books and npm run dev both read (it is gitignored):

# .env
BOOKS_FROM_SIBLINGS=1

Switching between the two invalidates the fetch cache automatically — the mode is recorded in each .books/<slug>/.fetch-stamp.json, so turning siblings on re-pulls rather than reporting "up to date" and quietly serving the GitHub copy.

To build exactly what deploys:

npm run generate     # → .output/public
npm run serve        # serve it, the way nginx will

npm run serve is a plain static file server over .output/public, which is what production actually does — nuxt preview runs it through Nitro instead, so serve is the closer approximation.

It binds to 0.0.0.0:3001, so the build is reachable by anything that can route to the machine, with no authentication. That default exists because these builds usually get checked from another machine; override it when you would rather not:

HOST=127.0.0.1 npm run serve      # this machine only
PORT=8080 npm run serve

The API reference locally

npm run generate does not build rustdoc — that needs each project's full Rust toolchain, and teenygrad's needs the CUDA headers teeny-cuda's build.rs requires. Without it, /api/<project>/ gets a placeholder page and the rest of the site builds normally.

To include a real one, drop cargo doc output at api-docs/<project>/ (or set API_DOCS_TEENYGRAD / API_DOCS_VISION_RS to a directory).

npm run dev mounts the same thing into public/api/ (gitignored, and excluded from the Docker build context) so the API links in the header and sidebar resolve in dev instead of 404ing. The built site mounts rustdoc straight into .output/public after nuxt generate rather than through public/, because Nitro enumerates public assets and a workspace's worth of generated HTML does not belong in that manifest.

Deployment

GitHub Actions → Docker → Kamal, onto the same host as the other sites. .github/workflows/deploy.yml builds each project's rustdoc in its own job, then builds and deploys the site.

The rustdoc jobs are continue-on-error. If a project's workspace stops compiling, the books still ship — that project's API reference falls back to a placeholder rather than blocking the deploy. The run is marked with a warning when this happens.

Because the books live elsewhere, a push to this repo is not the only reason to rebuild. The workflow also runs on a daily schedule, on workflow_dispatch, and on a docs-updated repository_dispatch — see Remaining migration steps.

Migration steps

The site is complete and deployable, but these have to happen outside this repo. Until the first two are done, two Kamal services claim the same hostnames.

  1. Retire the old services, from a machine with Kamal SSH access:

    # in teenygrad/       (service: teenygrad-docs,  host: docs.teenygrad.org)
    kamal app remove
    # in vision-rs/       (service: vision-rs-docs,  host: vision-rs.teenygrad.org)
    kamal app remove

    Then deploy this repo, so kamal-proxy routes both hostnames here.

  2. Delete the superseded build machinery in those repos: docs-site/ and .github/workflows/deploy-docs.yml in both teenygrad and vision-rs, and the config/deploy.yml that names the retired service. Keep the book sources — this site builds from them.

  3. Trigger rebuilds on book changes. Add to each project repo a workflow that fires when its book changes:

    on:
      push:
        branches: [main]
        paths: ["book/**"]   # teenygrad watches books/** instead
    jobs:
      rebuild-docs:
        runs-on: ubuntu-latest
        steps:
          - run: |
              curl -X POST -H "Accept: application/vnd.github+json" \
                -H "Authorization: Bearer ${{ secrets.DOCS_DISPATCH_TOKEN }}" \
                https://api.github.com/repos/teenygrad/docs/dispatches \
                -d '{"event_type":"docs-updated"}'

    Without it the daily schedule still picks the change up, just later.

  4. Secrets on this repo: KAMAL_REGISTRY_PASSWORD and KAMAL_SSH_PRIVATE_KEY, the same values the other two deploys use.

  5. DNS needs no change. Both hostnames already point at the same host, and vision-rs.teenygrad.org keeps resolving — nginx redirects each of its URLs to the matching page here rather than dropping readers on the front page.

Old URLs

Handled in nginx.conf, and rewritten in the book markdown at build time so cross-book links resolve in-site rather than through a redirect:

Old New
docs.teenygrad.org/book/x/y.html /teenygrad/x/y
docs.teenygrad.org/api/teeny-core/ /api/teenygrad/teeny-core/
vision-rs.teenygrad.org/book/x/y.html docs.teenygrad.org/vision-rs/x/y
vision-rs.teenygrad.org/api/… docs.teenygrad.org/api/vision-rs/…

Layout

books.config.mjs        what is published, and where it comes from
nginx.conf              routing, legacy redirects, cache headers
scripts/
  fetch-books.mjs       sparse-clones each book's source directory
  build-books.mjs       compiles books → site/generated/ (+ search index)
  summary.mjs           mdbook SUMMARY.md parser
  includes.mjs          {{#include}} / {{#rustdoc_include}} expansion
  markdown.mjs          markdown → HTML (Shiki, anchors, link rewriting)
  mount-api-docs.mjs    places rustdoc under /api/<project>/
site/                   the Nuxt app (srcDir)
  assets/css/main.css   design system — shared half is kept in sync with
                        teenygrad/website's own main.css

About

Teenygrad documentation, and books.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages