Skip to content

Contributing to these docs

This site lives in docs/ in the tadasant/zimmer repository. It is an Astro Starlight site, deployed to Cloudflare Pages.

Before you write, read the two references that govern all of Zimmer’s user-facing prose. They live in the repo at references/ and travel with the sync-docs skill:

  • references/BRAND.md — what Zimmer is and who it’s for. The short version: self-hostable, open-source, standards-driven orchestration for a single circle of trust (one person, a couple, or partners — not teams or enterprise). The human stays in control; Zimmer handles the toil. Frame the trust model as intent, not apology.
  • references/BRAND_VOICE.md — how Zimmer sounds: plain, direct, specific, honest, and free of the AI-slop tells (the “not X, it’s Y” reflex, em-dash overload, bold sprinkling, hype adjectives). Read it aloud; if it sounds like a brochure, cut until it doesn’t.

Every page on this site should pass both. When you edit one, keep it in voice.

The content structure deliberately mirrors the code structure, so “which page describes this?” usually has an obvious answer:

You changed…Update…
app/models/concerns/session_state_machine.rbThe session lifecycle
app/jobs/agent_session_job.rb, the CLI adaptersSpawning and monitoring
config/routes.rb, app/controllers/api/**The REST API
air.json, roots.json, mcp.json, skills/, plugins/, hooks/The AIR section
RuntimeRegistry, a new runtimeAdding an agent harness
app/extensions/**Extensions
OAuth, ClaudeAccount, McpOauthCredentialThe Auth section
infra/, .github/workflows/**, Dockerfile*Deploying, Provisioning
config/goals.jsonGoals and stop conditions
Any cron jobBackground jobs
docs/scripts/generate-icons.mjs, docs/scripts/zimmer-icon-source.jpg, public/icons/**, public/favicon.ico, docs/public/*.png, public/manifest.jsonThe icon set, below

The sync-docs skill (default-on for the zimmer root) runs this check as a pre-PR step.

Terminal window
cd docs
npm install
npm run dev # → http://localhost:4321
Terminal window
npm run build # astro check && astro build → docs/dist/
npm run preview # serve the built output
npm run icons # re-cut every icon — also writes ../public/, not just docs/

The docs_site job in .github/workflows/ci.yml runs npm ci && npm run build on every PR, so a broken link, a bad frontmatter field, or a type error fails CI. The site cannot silently rot.

Write a fenced mermaid block in any .md or .mdx page:

```mermaid
stateDiagram-v2
[*] --> waiting
waiting --> running: start
```

A remark plugin (src/plugins/remark-mermaid.mjs) swaps the fence for a placeholder before Expressive Code can claim it, and a client script in src/components/Head.astro renders it with Mermaid in the browser, re-rendering on light/dark toggle.

Diagrams should be accurate to the code, not illustrative. If you change the state machine, change the state diagram.

Use Starlight’s asides, and use them for the honest parts:

:::caution[This is brittle because…]
:::danger[This is actively broken]
:::note[Unclear / needs confirmation]

The :::note[Unclear / needs confirmation] form is deliberate — a visible “we don’t know” is more useful than a confident guess, and it’s an issue waiting to be filed.

The site is live at docs.zimmer.tadasant.com, built from docs/ by Cloudflare Pages. Every push to main redeploys it, and every PR gets a preview URL.

That is the only place the docs are deployed. They are deliberately kept out of the published application image — .dockerignore excludes /docs, and two CI checks assert that the exclusion held rather than trusting the line, so a second drift-prone copy cannot start shipping inside the product. If you move docs/, move that .dockerignore line with it; the checks are content-based and will fail the build if you don’t. See The docs never ship in the image.

Re-creating the Pages project from scratch (you need a human with Cloudflare access):

  1. Cloudflare Dashboard → Workers & PagesCreatePagesConnect to Git → pick tadasant/zimmer.
  2. Configure the build:
    • Framework preset: Astro
    • Root directory: docs
    • Build command: npm ci && npm run build
    • Build output directory: dist
    • Environment variable: SITE_URL = the final public URL (see below)
  3. Deploy. Cloudflare gives you a *.pages.dev URL immediately.
  4. Custom domain: Pages → Custom domains → add docs.zimmer.tadasant.com. Cloudflare already hosts the tadasant.com zone, so it will create the CNAME for you.
  5. If you pick a different hostname, set SITE_URL to it — docs/astro.config.mjs reads that env var and falls back to https://docs.zimmer.tadasant.com.
docs/
├── astro.config.mjs # site config + sidebar
├── package.json
├── public/
│ ├── favicon.ico # + favicon-16x16/32x32.png, apple-touch-icon.png
│ └── mcp.schema.json # served at /mcp.schema.json
├── scripts/
│ ├── zimmer-icon-source.jpg # the master artwork
│ └── generate-icons.mjs # `npm run icons` — see The icon set, below
└── src/
├── assets/ # logo
├── components/Head.astro # the Mermaid client renderer + favicon links
├── content/docs/**/*.md # every page
├── plugins/remark-mermaid.mjs
└── styles/custom.css

Every icon in the repo — the docs site’s favicons, the Rails app’s favicons, its PWA icons and its apple-touch icon — is derived from a single master render at docs/scripts/zimmer-icon-source.jpg. Nothing is hand-edited: change the master, run npm run icons from docs/, and commit what it writes. The generator lives here because docs/ is the only npm workspace in the repo, and image resizing needs sharp.

It writes twelve files into three places, and nowhere else: docs/public/ (the docs site’s favicon.ico, its two favicon PNGs and its apple-touch icon), public/icons/ (the app’s favicon PNGs, apple-touch icon, and both the full-bleed and maskable PWA icons), and public/favicon.ico.

Two things about the icons are not generated and have to be edited by hand:

  • public/manifest.json. Its icons array deliberately splits purpose into separate any and maskable entries rather than declaring one file both. The artwork is full-bleed, and Android crops a maskable icon to a circle or squircle guaranteeing only the centre 80% — so the full-bleed render would lose the mascot’s ears and the gold ring. The icon-maskable-*.png files are padded for that safe zone; the plain ones are not. Keep the two purposes on separate files. theme_color and background_color live here too, and the layout’s <meta name="theme-color"> must agree with them.
  • The ?v= cache busters on /manifest.json and the two reused icon srcs. Nothing in public/ is digest stamped, so replacing an icon in place does not reach a browser that already has it (see Deploying). Bump the number when you replace artwork at a URL that already shipped.

Adding a page means creating the markdown file and adding it to the sidebar array in astro.config.mjs. Starlight won’t auto-discover it into the nav.