Offline docs

Turmeric's documentation is available with no network, in three places, from one artifact. This guide covers what that artifact is, how it is produced, and how to reach the docs from each of the three.

The short version

You want Use
One symbol's docstring, in a terminal tur doc <symbol>
The rendered guides and API pages, in a terminal tur docs --open (or --serve)
The rendered guides and API pages, in the browser The Docs button in Try Turmeric
Same, on a plane The same button. Try precaches everything on install.

Nothing above needs a connection once the relevant piece is installed, and none of it is opt-in.

The docs pack

just docs produces two renderings of the same documentation:

web/public/docs-pack/
  index.json            version, nav tree, search strings, precache manifest
  guide.css guide.js    shared guide typography and code-block runtime
  guides/<slug>.html    article body only -- no <head>, no nav, no CSS
  api/<module>.html     same, one per stdlib module
  spices/<spice>.html   same, when ../turmeric-spices/ is present

Both renderings come out of the same generators, and each page's body is rendered exactly once and wrapped twice. That is the point: the website and Try Turmeric's in-app pane cannot drift apart, because there is no second renderer to drift from.

The pack is a build output and is gitignored, like turmeric.wasm and doc-names.json beside it.

index.json is the contract

{
  "version": "0.38.0",
  "generated": "2026-08-26T01:35:38Z",
  "guides": [{ "slug": "...", "path": "...", "title": "...", "category": "...",
               "description": "...", "bytes": 15435, "words": "..." }],
  "api":    [{ "slug": "...", "module": "...", "symbols": ["..."], ... }],
  "spices": [ ... ],
  "files":  ["guides/....html", "api/....html", "guide.css", ...]
}

The size budget

tools/genpack.py fails the build if the raw pack exceeds 16 MB (images referenced by guides are copied in and counted). Today it is about 4.8 MB.

The budget exists to protect a decision, not to hedge one. Try Turmeric precaches the whole pack unconditionally -- there is no "download docs for offline" toggle -- so an over-budget pack has to be a build error here rather than silent PWA bloat someone discovers on a phone.

If the budget trips, shrink the pack. Never make precaching conditional. In descending order of preference:

  1. compress or drop oversized images;
  2. exclude a doc category from the pack -- the pack is Try's docs, and it does not have to carry every generated page the website carries;
  3. split rarely-read reference pages into a second, lazily-fetched tier the pane loads on demand.

Each of those keeps "installed means complete" true for what the pack claims to contain. A toggle does not.

Reading docs in Try Turmeric

Open the docs browser with the Docs toolbar button, the :docs REPL meta-command, or a #doc= URL. It opens over the REPL and never navigates: your editor buffer, console history, and WASM session all survive a docs session untouched.

:docs                    open the browser where you left it
:docs hkt-guide          jump to a guide
:docs tur-list           jump to a stdlib module's API page

Inside the pane:

/try/#doc=guides/hkt-guide restores a docs location, and /try/#doc=api/tur-list#map restores it scrolled to an anchor. The key composes with the #code= share hash rather than replacing it, so a link can carry both a snippet and the page explaining it.

Offline

The service worker precaches the pack on install, alongside the wasm. No toggle, no opt-in, no first-run prompt, and no state in which the docs pane exists but its content does not -- installing Try Turmeric means having the docs, in the same way it already means having the compiler.

Two details worth knowing:

Visiting a /docs/html/... page while offline now gets a small "you're offline" page linking to the same page inside Try, instead of the REPL shell.

Reading docs from a terminal

tur doc <symbol> -- one symbol, no install needed

$ tur doc arc-new
arc-new -- allocate an Arc holding an int payload, strong count 1.

Parameters:
  v -- the value to share

Returns:
  A new Arc with strong count 1 and the weak sentinel held.

Example:
  (arc-get (arc-new 42))  ; => 42

Since: 2026-08-20

This works out of the box on every install: it reads the docstring table in <stdlib>/docstrings.tur, which ships with the stdlib the binary already needs. Builtins and special forms (let, defn, match, ...) answer from the compiler's own table. --json prints {"name": ..., "doc": ...} for tooling.

An unknown symbol exits non-zero -- a lookup that prints nothing and succeeds is indistinguishable from one that worked.

tur docs -- the rendered guides

$ tur docs                       # where are they?
$ tur docs --open                # open them in a browser
$ tur docs --serve               # serve them from localhost:8137
$ tur docs --serve --port 9000

It looks for the rendered docs in, in order:

  1. $TUR_DOCS_DIR
  2. <prefix>/share/doc/turmeric -- installed alongside the binary
  3. <repo>/docs/html -- a source checkout

--serve exists for a specific reason: some browsers refuse cross-page navigation under file://, which breaks every link between guides. The server is loopback-only, GET-only, and handles one connection at a time -- it is a way to read your own documentation, not a web server.

With no docs installed, tur docs names the two ways to get them and exits non-zero. tur doc <symbol> still works; it needs none of this.

Installing the rendered docs

Each release attaches turmeric-docs-<version>.tar.gz, containing docs/html/ and the docs pack. Unpack it and point TUR_DOCS_DIR at the directory holding guides/ and api/:

tar -xzf turmeric-docs-v0.38.0.tar.gz
export TUR_DOCS_DIR="$PWD/turmeric-docs-v0.38.0/docs/html"
tur docs --open

In a source checkout, just docs writes docs/html/ and tur docs finds it with no configuration. To build the archive locally: just docs-tarball.

Regenerating and checking the pack

just docs          # site HTML + docs pack, then the pack checks
just docs-tarball  # the above, plus turmeric-docs-<version>.tar.gz

just docs runs the three generators with --emit-pack, then tools/genpack.py, which is the pass that runs once the whole pack is on disk. It:

  1. merges each generator's manifest sidecar into index.json;
  2. rewrites cross-links into the pack's #doc= URL space -- only it knows what the finished pack contains, so only it can tell an in-pack link from a website one -- and reports the ones it could not resolve;
  3. checks every fragment is well-formed and carries no page chrome;
  4. sweeps files the manifest does not claim, so a deleted guide's fragment cannot linger in the pack and get precached forever;
  5. enforces the size budget.

Useful flags:

python3 tools/genpack.py web/public/docs-pack/ --max-bytes 1000000   # try the budget
python3 tools/genpack.py web/public/docs-pack/ --strict-links        # fail on dead links

--strict-links is off by default because guides legitimately link to pages the pack does not carry (plans under docs/upcoming/, notes under docs/archive/). Those links are reported on every run.

Dependencies

The guide and spice generators need the Python markdown package:

python3 -m pip install -r tools/requirements.txt

Without it they stop with that instruction rather than half-generating.

Where things live

Path What
tools/genguides.py guide markdown -> site pages + pack fragments
tools/gendocs.py stdlib docstrings -> API pages + pack fragments + docstrings.tur
tools/genspices.py sibling spice checkout -> spice pages + pack fragments
tools/packlib.py fragment writing, search strings, pack link rewriting
tools/genpack.py merge, check, budget
web/public/sw.js precache and serve the pack offline
web/main.js the docs pane (search In-app docs browser)
web/tests/docs-pane.spec.js pane behaviour
web/tests/docs-offline.spec.js the offline guarantee
tests/run-tur-docs.sh tur doc and tur docs

Notes for contributors

Spices are optional, and the pack tells the truth about that. The pack's spices section only lists pages this build actually produced, so a checkout without ../turmeric-spices/ yields a pane with no spice nav rather than entries that 404 offline. "Installed means complete" stays true for whatever the pack claims to contain.

The offline spec runs against a production build on its own port. Playwright's context.setOffline() and its request routing both intercept before the service worker, so under either one an offline navigation dies before the worker can serve from cache -- neither can test this at all. The spec therefore starts its own server and stops it mid-test. It also asserts the unconditional guarantee rather than a happy path: it loads /try/ once online without ever opening the docs pane, and only then goes offline. Visiting docs first would pass even against network-first caching, which is exactly the behaviour the offline work replaced.

Guide typography is emitted, not duplicated. guide.css and guide.js in the pack come from the same constants the site's guide pages inline. If you change how a guide looks or how its code blocks behave, change it in tools/genguides.py and both follow.