Contributing to these docs
This site lives in docs/ in the tadasant/zimmer
repository. It is an Astro Starlight site, deployed to Cloudflare
Pages.
Brand and voice
Section titled “Brand and voice”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 rule
Section titled “The rule”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.rb | The session lifecycle |
app/jobs/agent_session_job.rb, the CLI adapters | Spawning 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 runtime | Adding an agent harness |
app/extensions/** | Extensions |
OAuth, ClaudeAccount, McpOauthCredential | The Auth section |
infra/, .github/workflows/**, Dockerfile* | Deploying, Provisioning |
config/goals.json | Goals and stop conditions |
| Any cron job | Background jobs |
docs/scripts/generate-icons.mjs, docs/scripts/zimmer-icon-source.jpg, public/icons/**, public/favicon.ico, docs/public/*.png, public/manifest.json | The icon set, below |
The sync-docs skill (default-on for the zimmer root) runs this check as a pre-PR step.
Running it locally
Section titled “Running it locally”cd docsnpm installnpm run dev # → http://localhost:4321npm run build # astro check && astro build → docs/dist/npm run preview # serve the built outputnpm 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.
Diagrams
Section titled “Diagrams”Write a fenced mermaid block in any .md or .mdx page:
```mermaidstateDiagram-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.
Callouts
Section titled “Callouts”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.
Deploying
Section titled “Deploying”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):
- Cloudflare Dashboard → Workers & Pages → Create → Pages → Connect to Git → pick
tadasant/zimmer. - 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)
- Deploy. Cloudflare gives you a
*.pages.devURL immediately. - Custom domain: Pages → Custom domains → add
docs.zimmer.tadasant.com. Cloudflare already hosts thetadasant.comzone, so it will create the CNAME for you. - If you pick a different hostname, set
SITE_URLto it —docs/astro.config.mjsreads that env var and falls back tohttps://docs.zimmer.tadasant.com.
Structure
Section titled “Structure”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.cssThe icon set
Section titled “The icon set”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. Itsiconsarray deliberately splitspurposeinto separateanyandmaskableentries 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. Theicon-maskable-*.pngfiles are padded for that safe zone; the plain ones are not. Keep the two purposes on separate files.theme_colorandbackground_colorlive here too, and the layout’s<meta name="theme-color">must agree with them.- The
?v=cache busters on/manifest.jsonand the two reused iconsrcs. Nothing inpublic/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.