---
name: write-docs
description: Write or edit the Initiative help center under docs/en/ — the house voice, the structural rules, and how to build and check the site. Use when adding a docs page, rewriting one, documenting a new feature, or when the user says docs read dry, corporate, or off-brand.
user-invocable: true
---

# /write-docs — Writing the Initiative help center

The docs live in `docs/en/`, are built with [Zensical](https://zensical.org/),
and are navigated by an explicit `nav` list in `zensical.toml`.

This skill is mostly about **voice**, because that's the part that keeps going
wrong. The structural rules are at the bottom and are short.

---

## Who you are writing for

One person. Hold them in your head the whole time:

> A millennial with the tech confidence of somebody who misses their old
> indestructible phone. Fluent in memes and irony, genuinely unsure about
> software. Got volunteered to organise something — the fete, the rota, the
> committee — and is quietly worried this is going to be complicated and that
> they will break it.

Two consequences, and both matter more than they sound:

1. **Warmth is the useful part, not decoration.** For this reader, a dry page
   reads as intimidating. Reassurance is load-bearing documentation.
2. **They find things funny.** Humour lowers the barrier. A page that makes
   them snort is a page they finish reading.

Do **not** write for a developer skimming for an API signature. That reader
wants terseness. Ours wants a friendly human who knows the software.

---

## The voice

### Get the joke from recognition, not from quips

The best line on the whole site was written by the user, not by an AI:

> A group chat is where somebody pastes the thing that should have been a
> document, and six months later everyone is scrolling for it, and Jenny has
> left, and somebody is looking at the printer in a way the printer has done
> nothing to deserve.

That's the standard. It works because it is **specific**, **observed**, and
**escalates**. It names Jenny. Nobody had to be told it was a joke.

More of the register, all currently live on the site:

- "We know about the merged cells. We know somebody colour-coded it in 2019 and
  nobody now remembers what green means."
- "Made *four* projects called "test"? Also fine. Marginally funnier. Still fine."
- "**Whose turn it is to email the council.** Nobody's turn. It's always
  nobody's turn. Put it in a queue."
- "the tasks that are secretly three tasks wearing a coat"
- "A backup you have never restored is not a safety net. It's a hypothesis."
- "read from the other end of a draughty hall, at an angle, by somebody who
  left their glasses in the car"

### Reassure constantly

This reader's default assumption is that they will break something. Tell them
they can't, early and often. The home page leads with **"You cannot break
this"** for exactly this reason. Give explicit permission to stop, to skip a
page, to ignore a feature.

### Techniques that work here

- **Escalate a list.** Make the last item break the pattern.
- **Be absurdly specific.** "A club treasurer" beats "a user". A named year, a
  named day, a named Jenny.
- **Let a bit run one sentence longer than expected**, then stop dead.
- **Deadpan the ridiculous.** State something silly plainly.
- **Vary rhythm hard.** Short. Then a long one that turns halfway through.
  Then short again.
- **Name the reader's actual emotional state**, then defuse it.

---

## Keep it brief

Nobody reads a wall of text. Not our reader, not you, not anyone. A page that
sprawls doesn't get skimmed — it gets closed.

So: **clear, specific, focused.** Say the thing, then stop.

### Cut explanation, never voice

This is the whole trick, and it's easy to get backwards. Brevity is not a
licence to strip the personality back out and leave a spec sheet. The jokes
are what get the page read; the over-explaining is what stops it being read.

When a page is too long, the thing to delete is almost always a paragraph
patiently explaining something the reader would have understood in four
seconds of clicking.

> **Cut this:** "The Access tab allows you to configure permissions for the
> project. Permissions determine which users are able to perform which actions.
> By configuring permissions appropriately, you can ensure that only the
> intended people have access."
>
> **Keep this:** "Open the **Access** tab any time to add people, change a
> level, or remove somebody. Changes apply immediately."

### Trust the software

Somebody who opens the Access tab will see the Access tab. Our job is to tell
them it exists, what it's for, and the one thing that would surprise them —
not to narrate the screen back at them.

Document the **non-obvious**: the thing that catches people out, the reason
behind a design choice, the setting whose name doesn't quite say what it does.
Skip the parts the interface already makes plain.

Over-explaining is worse than under-explaining, because it buries the sentence
that actually mattered.

### Signs a page has got away from you

- A paragraph that could be a table row.
- A sentence restating the heading directly above it.
- Three examples where one specific one would land harder.
- Explaining *what* a button does when the reader can see the button.
- Any run of prose longer than about four lines without a break, list or table.

### Practical shape

- Lead with the answer. Context after, if it's needed at all.
- Prefer a table or a short list to a paragraph, whenever the content has any
  structure at all.
- Two or three sentences per paragraph. Then a break.
- If a guide passes roughly 1,200 words, ask what it's doing. It may genuinely
  need the room, or it may be two pages, or it may just be padded.

**Reference pages are exempt from the word count, not from the rule.** The FAQ
and `running-a-server/publishing-listings.md` are looked *up*, never read start to finish,
so length there costs nothing. Each individual entry still has to be short.

### Don't restate a definition that already has a home

The glossary once held 52 entries, most of them repeating — less well, with
less context — something the owning page already said. That is duplication with
a maintenance bill attached: it drifts silently, and every new tool means
remembering to go and update it. It had already drifted.

It now holds only the words where the everyday meaning **misleads**: community,
initiative, tool, handle, presence, full access, break-glass, archive vs. trash.
A reader can't guess those. They can guess "subtask".

Same test anywhere else: if a page is explaining something another page owns,
link to it instead.

---

## Never do these

Each of these was an actual failure on this site. They are not hypothetical.

### Never patch a dry page with jokes

Voice lives in structure — how a page opens, what it notices, what it lets you
skip. Swapping clauses into a dry page produces a dry page with winking asides
bolted on, which is worse than leaving it dry. **Rewrite the page.**

### Never wink at the camera

Cut "let's be honest", "quite satisfying", "honestly", "needless to say", and
every other phrase that announces a joke is happening. A joke that has to be
introduced isn't one.

### Never take a swipe at another tool

No "unlike most tools", no "the part everyone else gets wrong". It's
judgemental rather than funny, and it breaks the rule below about describing
what the software *is*.

### Never name another company for a laugh

Trademark risk, and it dates badly. Functional mentions are fine and necessary
— the tools you can import from, AI providers, identity providers — but no
brand as a punchline. "Survives being dropped down the stairs" is the move.

### Never force a wacky metaphor

Cut anything that reaches for a simile to make software seem fun. An early
draft had "without visiting each group in turn like a Victorian leaving calling
cards". It is trying, and you can hear it trying.

### Never describe the software by what it lacks

House rule, and it produces better copy anyway. Lead with what's there.

> **Wrong:** "There are no group chats."
> **Right:** "Everything here has comments on it, so the conversation about a
> thing sits on that thing — and all of it is searchable."

The absence is the consequence, never the headline.

### Never write the changelog's framing into a page

This is the one that goes wrong on every update, because updating a page
usually starts by reading the changelog — and a changelog entry is *about the
change*. A docs page is about **the software as it stands**. The reader has
never seen the old behaviour and has no idea a release happened.

So when you carry a fact across, drop the version scaffolding around it:

> **Wrong:** "Deleting an account no longer destroys anything on the spot."
> **Right:** "Deleting an account hides it immediately and erases it later."
>
> **Wrong:** "Browser default follows your browser's language, which is what it
> always did."
> **Right:** "Browser default takes its cue from your browser's language."
>
> **Wrong:** "All three start where every deployment has always had them."
> **Right:** "All three start open."

The tells, and all of them are a rewrite rather than a trim: **no longer**,
**used to**, **still**, **now**, **as before**, **which is what it always did**,
**has always**, **instead of**, and any sentence whose subject is the app in a
previous release. Describe what exists; a migration state is not a state the
software is ever in for a reader arriving today.

The same goes for a tab that moved, a setting that was renamed, and a source
that was dropped. **Delete the old context, don't narrate the move.** The page
says where the thing is. If somebody genuinely needs to find their way from the
old place — a rename people have muscle memory for — that is a one-line
announcement, not a paragraph that lives on the page forever. `CLAUDE.md` has
the rules for writing one.

---

## Security and compliance pages are excluded

**Do not apply this voice** to:

- `docs/en/security/**` (including `data-and-compliance.md`,
  `private-messages.md`, `how-your-data-is-kept-separate.md`)

Somebody reading those wants a straight answer. A joke in the middle of a
retention policy helps nobody, and may end up in front of a lawyer.

Running-a-server pages **do** take the voice, but must stay operationally exact. Being
funny never costs a command its accuracy.

### And never describe an attack

Repo-wide rule, enforced here too. Say what a protection **does**, never what
would happen without it, and never name the attack it stops.

> **Wrong:** "a secure session that can't be stolen by malicious scripts — a
> common way accounts get hijacked."
> **Right:** "Your sign-in session is held in a cookie the page's own scripts
> can't read."

Grep added lines for `attacker`, `hijack`, `steal`, `exploit`, `takeover`,
`malicious`, `without this`, `would let` before committing.

---

## Structure

- **Every page needs a nav entry** in `zensical.toml`. Files and nav must match
  exactly — there is a check for this below.
- **Nav nests arbitrarily.** A group is an inline table inside the list, so a
  sub-section is `{ "Tools" = [ "en/guides/tools.md", … ] }` inside
  `"Using Initiative"`. Because `navigation.sections` is enabled, a group
  renders as a heading rather than a collapsible link, so its index page shows
  as an ordinary child rather than being absorbed into the heading.
- **Frontmatter** is an `icon:` line (Lucide, e.g. `lucide/rocket`).
- **`??? techspec`** holds detail for technical readers, usually collapsed, so
  it never interrupts the plain-language flow.
- **`!!! screenshot`** marks where an image is still needed, saying what to
  capture and where to save it.
### Tools are a defined, growing list — treat them as one

`Tool` in `backend/app/core/tools.py` is a real enum, and it is the source of
truth for a lot of the app: role permissions, tag links, comment targets, the
frontend's `src/lib/tools.ts`, and the i18n namespace each tool owns.

Today it holds six: `project`, `document`, `queue`, `counter_group`,
`calendar`, `dashboard`.

Two rules follow, and the first one is the trap:

1. **Projects and documents are tools.** They are not a separate, more
   important category that "tools" sits beside. An early draft of
   `concepts/index.md` had three sections — "Projects and tasks", "Documents",
   and "Tools — there if you want them" listing only four — which taught the
   reader a model the app does not have. If you catch yourself writing "and
   also, tools", stop and restructure.

   Say it the way the app means it: everything inside an initiative is a tool,
   there are six kinds, two of them are where you start and four are where you
   grow. They share their sharing model, their tags, their comment threads and
   their `#` mentions, which is the actual payoff — learn one, know the rest.

2. **The list grows, so write so it can.** Avoid prose that hard-codes "the
   other four" as a permanent fact. When the enum gains a seventh, it needs a
   guide page, a nav entry under Tools, a glossary entry, and a mention
   wherever tools are enumerated — the concepts page, the tools hub, the roles
   permission table. Derive from the enum; never keep a parallel list.

Each tool has its own guide page, nested under **Tools** in the nav.
- **No emojis in prose.** They read as trying too hard.

---

## Build and check

Zensical isn't a project dependency. Install it once:

```bash
python3 -m venv .venv && source .venv/bin/activate && pip install zensical
```

Then, from the repo root:

```bash
zensical build                      # validates links AND heading anchors
zensical serve -a 127.0.0.1:8080    # live preview with hot reload
```

Use **8080**, not the default 8000 — this checkout's backend dev server holds
8000 (see `scripts/dev-ports.sh`).

`zensical build` reports a broken cross-reference anchor as an issue, so a
clean build is a real check rather than a formality. Run it before committing.

Nav and files should agree:

```bash
python3 - <<'PY'
import re, glob
files = set(glob.glob('docs/**/*.md', recursive=True))
nav = {f"docs/{m}" for m in re.findall(r'"(en/[^"]+\.md)"', open('zensical.toml').read())}
nav.add('docs/index.md')
print("in nav, no file:", sorted(nav - files) or "none")
print("file, not in nav:", sorted(files - nav) or "none")
PY
```

---

## Before you commit

- [ ] `zensical build` reports **No issues found**.
- [ ] Nav and files agree.
- [ ] Read the page aloud. If you'd never say a sentence out loud, rewrite it.
- [ ] Cut anything that explains what the reader can see on screen. Keep the
      jokes; lose the narration.
- [ ] No winking, no swipes, no brands-as-punchlines, no wacky similes.
- [ ] Nothing describes a previous release. Grep the added lines and read
      each hit — `still` and `now` have innocent everyday senses, the rest
      rarely do:

      ```bash
      git diff -U0 -- docs/ | grep '^+' | grep -v '^+++' \
        | grep -nEi "no longer|used to|\bstill\b|as before|has always|always did|instead of"
      ```
- [ ] Nothing describes an attack or what breaks without a guard.
- [ ] Security and compliance pages untouched, unless that was the actual task.
- [ ] Docs-only changes go **straight to `dev`** — no branch, no PR.
