diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a77ff77..9f945b7 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -30,6 +30,17 @@ jobs:
- name: Validate content
run: tinkerdown validate content/
+ # The voice ratchet (VOICE.md, scripts/voice-check.sh). Ceilings are the
+ # counts on the day each was last tightened, so this fails when a tell
+ # goes UP. It is scoped to docs-native pages — mirrored ones are owned by
+ # their source repo and would fail the wrong PR.
+ #
+ # It cannot tell whether a page reads well, only whether it regressed in
+ # the ways that are countable. The landing copy was rewritten four times
+ # before this existed; that is what it is here to stop.
+ - name: Voice check
+ run: ./scripts/voice-check.sh
+
- name: Test sync tool
working-directory: cmd/sync
run: go test ./... -count=1
diff --git a/.gitignore b/.gitignore
index f24c1f4..0329d25 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,4 @@ cmd/site/site
examples/avatar-upload/uploads/*
!examples/avatar-upload/uploads/.gitkeep
examples/avatar-upload/.uploads/
+.prereview/
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..e4436d9
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,44 @@
+# Working in this repo
+
+## Before writing any prose, read `VOICE.md`
+
+Every rewrite of the landing page so far has drifted back to the same flat
+register (#65, #83, #135, #136). `VOICE.md` is the spec that exists to stop the
+fifth. It covers the register, the banned vocabulary, and the two constructions
+the repo owner has called out by name: **claudisms** and **rhetorical
+reversals**. It applies to PR descriptions too.
+
+`scripts/voice-check.sh` catches the countable tells. It will not catch a page
+that is merely flat — that part is on you.
+
+## Never hand-edit a mirrored page
+
+A page with an upstream `source_repo` in its front matter is overwritten
+wholesale by `cmd/sync` on the next release (`cmd/sync/sync.go`, `os.WriteFile`).
+Editing it here is work that gets thrown away. Fix it in the source repo.
+
+- Mirrored: `content/reference/*`, `content/changelog/*`, `content/guides/*`
+ (except `index.md`), `content/cli/*`, `content/client/*`, `content/contributing/*`
+- Docs-native: `content/index.md`, `content/getting-started/*`,
+ `content/recipes/**` (including `recipes/apps/*`), the section `index.md` files
+
+`content/_meta/source-of-truth.yaml` is the machine-readable mapping and the
+authority when it and `source-of-truth.md` disagree.
+
+## Builds and tests
+
+```bash
+GOWORK=off make test # everything
+make test-e2e # chromedp, needs the site serving
+make sweep SWEEP_URL=... # sitemap crawl for overflow
+```
+
+Verify UI in a real browser, never `curl`. `/recipes/ui-patterns/lists/large-table`
+is a 4.9 MB page that times out the sweep on production too — that flag is
+pre-existing.
+
+## Editing `content/index.md`
+
+Never put a blank line inside a `
` block. Goldmark ends the HTML block
+there and re-parses the rest as markdown: indentation is stripped and `
` tags
+appear inside the ``. The file carries a note saying so.
diff --git a/README.md b/README.md
index e0feb9a..1f3a124 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,15 @@ Most reference content is mirrored from canonical files in the source repos:
| Recipes (markdown) | this repo, under `content/recipes/` | Authored directly here |
| Runnable apps + their tests | this repo, under `examples//` | Authored directly here |
-The source-of-truth matrix lives at `content/_meta/source-of-truth.md`.
+The source-of-truth matrix lives at `content/_meta/source-of-truth.md`, and
+`content/_meta/source-of-truth.yaml` is the machine-readable version the sync
+reads. Where the two disagree, the YAML wins.
+
+## Writing
+
+`VOICE.md` is how the prose here is written — the register, the vocabulary to
+avoid, and why. Read it before editing anything under `content/`.
+`scripts/voice-check.sh` checks the parts of it that can be checked.
Every runnable demo cited by a recipe lives at `examples//` —
one folder per app, containing the Go package, template, chromedp
diff --git a/VOICE.md b/VOICE.md
new file mode 100644
index 0000000..bef799a
--- /dev/null
+++ b/VOICE.md
@@ -0,0 +1,123 @@
+# Voice
+
+How the prose on this site is written. This file exists because the landing copy
+has been rewritten four times (#65, #83, #135, #136) and drifted back to the same
+flat register every time. Nothing recorded the target, so every rewrite guessed.
+
+Applies to `content/**` and to the writing around it — PR descriptions, this
+file, `README.md`. The register leaks into the meta-writing first.
+
+## Do
+
+**Argue by concession.** Claim, then the objection, then concede it, then
+resolve. Four short sentences beat one balanced one:
+
+> A process enforced by a human performs a little better than documents. But a
+> human is a human. They get sick, go on a vacation or quit.
+
+**Vary sentence length hard.** Four words, then twenty. Fragments are fine.
+Start sentences with But, So and And.
+
+**Ask, then answer in one word.**
+
+> But doesn't the import fetch the module over the network? Yes. That could be a
+> problem.
+
+**Understate.** "quite powerful", "fairly simple", "a little better", "for the
+most part". Never a superlative.
+
+**Name the catch.** "The catch here is to use `T.Errorf`…"
+
+**Rank what matters.** "This snippet is the most interesting part."
+
+**Admit limits without defending them.** "the abstraction is not complete."
+
+**Position honestly against neighbours, conceding where they win.** htmx, templ,
+Alpine and LiveView are all reasonable choices. Say where they're better.
+
+**Stop.** No call to action, no recap, no "happy coding". When the page is done,
+end it.
+
+**Bare imperative for instructions.** "Use it in a test." "Run it." The reader is
+`you`. Nothing is `we`.
+
+## Don't
+
+**Product as the subject.** Not "LiveTemplate builds reactive web UIs" — write
+what the reader does, or what the mechanism does.
+
+**Triadic negation as cadence.** "no client-side framework, no second state
+model, no build step" is a drumbeat, not an argument. One negation, doing work.
+
+**Meta-commentary about the document.** "This is the idea the rest of the page
+elaborates" tells the reader nothing about the software.
+
+**Exclamation marks, emoji, Title Case headings.** Headings are sentence case.
+"The magic:" and "That's it!" are banned outright.
+
+**Claudisms.** The vocabulary that signals a language model wrote it. It comes
+back on every rewrite because it reads as thoughtful:
+
+> load-bearing · spine · crisp · surfaces (as a verb) · delve · testament to ·
+> underscore · nuanced · at its core · fundamentally · it's worth noting · that
+> said · crucially · importantly · genuinely · meaningfully · north star ·
+> unpack · double-click on · orthogonal · non-trivial · heavy lifting · footgun ·
+> batteries included · sane defaults · first-class · opinionated · ergonomic ·
+> primitives · the shape of · earns its keep · seam · tapestry · isn't just X,
+> it's Y
+
+Say the plain thing. "load-bearing" is "this line is what makes it work".
+"surfaces an error" is "shows the error". "primitives" is usually "functions".
+
+**The rhetorical reversal.** A contrast whose second half exists only to make the
+sentence land:
+
+> so you can check the claim rather than take it
+
+The test: **does the contrast tell the reader something?** "sends a frame instead
+of a form POST" does — both halves are concrete and you learn what the
+alternative was. "check the claim rather than take it" doesn't. This is not a ban
+on `rather than`; most uses on this site are the good kind. It's a ban on the
+ornamental kind.
+
+## No first person
+
+The docs stay impersonal. That's a deliberate choice, and it means rhythm,
+concession and understatement have to carry the voice on their own — there's no
+`I` to lean on. Where a blog post would say "too costly for me", a page here says
+"that cost is the thing this avoids".
+
+## Four things that are not drift
+
+**Emoji in a comparison table.** ✅ / ⚠️ / ❌ down a column are scan markers doing
+real work — see the "does this scale?" matrix in `recipes/counter/index.md`. The
+checker skips table rows for that reason. In prose they're decoration, and the
+checker still counts them there.
+
+**Title Case in an H1.** The H1 carries the page *name*, and it must be either
+the front-matter `title:` verbatim or `Title — a sentence-case clause`. It feeds
+the nav and breadcrumbs that `docs_ia_test.go` and `breadcrumb_test.go` assert
+on, so downcasing it is a rename, not a copy edit — `breadcrumb_test.go` caught
+exactly that during this pass. Rename a page deliberately or not at all. H2–H6
+are prose and are sentence case.
+
+**`reach for`** appears on 24 pages and is now house idiom, not a slip. Thin it
+where the choosing isn't the point. It's deliberately not in the checker.
+
+**The `ui-patterns/` heading skeleton** — `Template` / `Handler & state` / `When
+to use` — is a catalog template. Fix the prose under it, not the shape.
+
+## What the checker does
+
+`scripts/voice-check.sh` counts the tells that can be counted: emoji,
+exclamation marks, Title Case headings, product-as-subject openers, royal we,
+claudisms, passive constructions. It runs on docs-native paths only.
+
+It will not find a page that is merely flat. Most of this file is not
+checkable — that's why it's written down.
+
+## Pages this file can't fix
+
+Anything with an upstream `source_repo` in its front matter is mirrored, and
+`cmd/sync` overwrites the whole file on the next release. Fix those at the
+source repo. See `content/_meta/source-of-truth.yaml` for the mapping.
diff --git a/content/_meta/source-of-truth.md b/content/_meta/source-of-truth.md
index acc010a..e633e67 100644
--- a/content/_meta/source-of-truth.md
+++ b/content/_meta/source-of-truth.md
@@ -32,7 +32,7 @@ It is consumed by humans deciding where to edit content, and (Phase 3 onwards) b
| `*/CLAUDE.md` files | NEVER mirrored | They're agent instructions, not user docs |
| `docs/proposals/`, `docs/plans/`, `docs/archive/`, `design/`, `WORKFLOWS.md`, `ROADMAP.md`, `AGENT_*.md` | NEVER mirrored | Internal RFCs / planning / agent setup |
| `docs/performance/` | NOT mirrored in v1 | May graduate to public if stabilized |
-| `examples//README.md` | All mirrored under `/recipes/apps/` | These are user-facing showcases |
+| `examples//README.md` | NOT mirrored | The `/recipes/apps/` pages are docs-native (see App Recipes below); the READMEs serve the example directory |
| `examples/patterns/<...>` | NOT mirrored | Served live via reverse proxy at `/recipes/ui-patterns/*` (Phase 1 PR-D) |
| `*/CONTRIBUTING.md` | All mirrored under `/contributing/` | Centralizes contributor onboarding |
| Per-repo `README.md` | Mirrored conditionally; some split (see below) | The four repos each have a different role |
@@ -114,22 +114,23 @@ Order: getting-started -> guides -> reference -> CLI -> client -> recipes -> con
|---|---|---|---|---|
| Recipes (8–10 of them) | this repo | `content/recipes/*.md` | `/recipes/` | recipe (Phase 5 authors directly) |
-### App Recipes (mirrored from examples)
+### App Recipes (docs-native)
+
+App recipes used to be mirrored from `livetemplate/examples`. They aren't any
+more. After the consolidation, the pages at `content/recipes/apps/.md` are
+authored here and this repo is their source of truth; the runnable apps live at
+`examples//` and are mounted by `cmd/site`.
+
+The rows below said `Mirror? yes` until 2026-08-09. They were wrong, and the
+mistake was expensive: those ten pages were ported once from the example READMEs
+and then cut loose, so they still carry the README register — Title Case
+headings, emoji, exclamation marks — while the rest of the site moved on. See
+`VOICE.md`.
| Concept | Source repo | Source path | Site URL | Mirror? |
|---|---|---|---|---|
-| App recipes index | `examples` | `README.md` | `/recipes/apps/` | yes |
-| counter | `examples` | `counter/README.md` | `/recipes/apps/counter` | yes |
-| todos | `examples` | `todos/README.md` | `/recipes/apps/todos` | yes |
-| chat | `examples` | `chat/README.md` | `/recipes/apps/chat` | yes |
-| avatar-upload | `examples` | `avatar-upload/README.md` | `/recipes/apps/avatar-upload` | yes |
-| flash-messages | `examples` | `flash-messages/README.md` | `/recipes/apps/flash-messages` | yes |
-| login | `examples` | `login/README.md` (when present) | `/recipes/apps/login` | yes |
-| dialog-patterns | `examples` | `dialog-patterns/README.md` (when present) | `/recipes/apps/dialog-patterns` | yes |
-| live-preview | `examples` | `live-preview/README.md` (when present) | `/recipes/apps/live-preview` | yes |
-| progressive-enhancement | `examples` | `progressive-enhancement/README.md` | `/recipes/apps/progressive-enhancement` | yes |
-| shared-notepad | `examples` | `shared-notepad/README.md` (when present) | `/recipes/apps/shared-notepad` | yes |
-| ws-disabled | `examples` | `ws-disabled/README.md` | `/recipes/apps/ws-disabled` | yes |
+| App recipes index | this repo | `content/recipes/apps/index.md` | `/recipes/apps/` | no |
+| counter, todos, chat, avatar-upload, flash-messages, login, progressive-enhancement, shared-notepad, ws-disabled, file-tree, seat-picker, upload-modes | this repo | `content/recipes/apps/.md` | `/recipes/apps/` | no |
### Contributing (mirror, per-repo)
@@ -187,10 +188,10 @@ These don't currently exist anywhere and must be written from scratch — likely
---
-## Literate primitives in mirrored content
+## Literate blocks in mirrored content
Mirrored upstream READMEs may use tinkerdown's literate authoring
-primitives (since tinkerdown v0.2.0). The sync tool passes them through
+blocks (since tinkerdown v0.2.0). The sync tool passes them through
byte-for-byte and mirrors a single adjacency convention so they resolve
correctly post-sync.
diff --git a/content/changelog.md b/content/changelog.md
index 3a0d41b..be139c4 100644
--- a/content/changelog.md
+++ b/content/changelog.md
@@ -8,9 +8,7 @@ source_path: content/changelog.md
# Changelog
LiveTemplate ships as three released pieces, each with its own history. Every
-page below is mirrored straight from that repository's `CHANGELOG.md` on
-release, so it stays current by construction rather than by anyone remembering
-to copy it across.
+page below mirrors that repository's `CHANGELOG.md` at release time. Nobody has to remember to copy it across.
- [**Core library**](/changelog/livetemplate) — `github.com/livetemplate/livetemplate`.
The Go server: the reactive tree, actions, sessions, uploads, pub/sub.
diff --git a/content/getting-started/install.md b/content/getting-started/install.md
index 2c8e8f9..39dbaad 100644
--- a/content/getting-started/install.md
+++ b/content/getting-started/install.md
@@ -5,7 +5,7 @@ description: "Add LiveTemplate to a Go project — one go get and you have what
# Install
-LiveTemplate is a Go framework. The minimum to get a reactive page running is one `go get`.
+One `go get` is the whole install. Everything below is optional.
## Add the framework
@@ -17,7 +17,7 @@ That's it for the server side. The TypeScript client wires up the browser side a
## Loading the browser client
-Add these to your template. The `lvtClientScriptURL` / `lvtClientStyleURL` functions are provided by the framework on every template:
+Add these to your template. The framework hands every template two functions, `lvtClientScriptURL` and `lvtClientStyleURL`:
```html
diff --git a/content/getting-started/introduction.md b/content/getting-started/introduction.md
index 44ba39d..e2ae250 100644
--- a/content/getting-started/introduction.md
+++ b/content/getting-started/introduction.md
@@ -7,11 +7,12 @@ source_path: content/getting-started/introduction.md
# Introduction
-LiveTemplate builds reactive web UIs in **standard HTML and Go**. You write an
-ordinary `html/template` and a small Go controller; the browser sends ordinary
-form data; the server re-renders, diffs, and patches the page. There is no
-client-side framework to learn, no second state model to keep in sync, and no
-build step for the common app screens.
+Write an `html/template` and a small Go controller. The browser posts a form, the
+server re-renders, and only the changed parts of the page get patched.
+
+A client framework does this too, and for a canvas editor it should. But for a
+settings screen it means keeping two copies of the same data in sync. That cost
+is the one thing this is trying to avoid.
The defining idea is that you never leave HTML. A `