Skip to content

Write the voice down, then apply it - #137

Merged
adnaan merged 4 commits into
mainfrom
docs-voice
Aug 10, 2026
Merged

Write the voice down, then apply it#137
adnaan merged 4 commits into
mainfrom
docs-voice

Conversation

@adnaan

@adnaan adnaan commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

The docs had drifted into a register nobody chose. This writes the target down
and applies it to the 67 pages this repo owns.

Why "again"

The landing copy alone has been rewritten four times — #65, #83, #135, #136
and drifted back each time. Nothing in the repo recorded what the register was
supposed to be, so every rewrite re-derived it from scratch and landed on the
same flat default. VOICE.md is that record.

What the audit found

Not what I expected. The lexicon was already clean: one seamless, one
modern, zero powerful / effortless / robust / out of the box, zero
throat-clearing openers, sentence length a healthy 13–19 words.

The problem was that there were two hand-written voices, and only one was
broken. The ten pages under content/recipes/apps/ were ported once from
examples/<slug>/README.md and then cut loose:

recipes/apps/ (10 files) other 53
exclamation marks 9 2
emoji 14 5
Title Case headings 36 7

source-of-truth.md still claimed those pages were mirrored from
livetemplate/examples. They have not been since the consolidation, and
source-of-truth.yaml said so. That stale row is why nobody maintained them.

The register was producing false claims

This is the part worth a reviewer's attention.

chat.md said "Zero manual broadcasting code required!" while
examples/chat/main.go has an explicit ctx.Subscribe(ctx.SelfTopic()) and
three explicit ctx.Publish(...) calls. A reader who believed it would wonder
why their own app doesn't sync. Same page, same problem: "broadcasting happens
automatically", "automatic multi-tab syncing". All corrected against the real
file.

Counts

tell before after
emoji 19 0
Title Case headings 43 0
claudisms 14 0
product-as-subject openers 3 0
royal we 4 0
meta-commentary 3 0
hype lexicon 1 0
banned phrases 5 0
triadic negation 5 1
exclamation marks 11 3
passive voice 114 34

The three non-zero floors are deliberate and the script says why. The
exclamations are quoted UI copy ("Changes saved!" is what a flash message
says). The triadic negation is the landing's no hx-post, no onClick, no route to register, where each item names a real thing you would otherwise write.

34 passives is honest rather than lazy. Most of what remains is not passive
voice: "the title is required" describes an HTML attribute, "action methods are
exported" is Go, "is untouched" is an adjective. The regex cannot tell those
apart, so the ceiling stays at 34 instead of pretending 0 is reachable.

The checker is a ratchet

scripts/voice-check.sh runs in the validate job. Each ceiling is the count
on the day it was last tightened, so the build fails when a number goes up.
Bring one down, lower the ceiling in the same commit.

It will not catch a page that is merely flat. Most of VOICE.md is not
checkable — that is why it is written down rather than only encoded.

Three carve-outs, all documented:

  • Emoji in a comparison table are scan markers doing real work (the "does
    this scale?" matrix in recipes/counter/index.md). Table rows are skipped.
  • H1 carries the page name and must equal the front-matter title:. It
    feeds nav and breadcrumbs. Downcasing it is a rename — breadcrumb_test.go
    caught me doing exactly that to App Recipes. There is now an h1-vs-title
    check, and I verified it fires by breaking a title on purpose.
  • reach for stays. At 24 files it is house idiom, and it is deliberately
    absent from the checker.

Flagged, not fixed

Two things a reviewer should not read as oversights.

  1. chat.md's tutorial documents an API that does not exist. It shows
    func (s *ChatState) Change(ctx *livetemplate.ActionContext) error switching
    on ctx.Action with ctx.Bind(&data), and calls an undefined controller
    in main. The real examples/chat/main.go uses the controller pattern. That
    is a correctness fix, not a register one, and folding it into a prose PR
    would hide it.
  2. 15 intra-site anchor links are broken. 13 are in mirrored pages. Checked
    against main: identical on both, so this PR adds none.

Not in scope

32 pages carry an upstream source_repo and cmd/sync overwrites the whole
file on release, so editing them here is work that gets thrown away. They need
PRs against livetemplate, lvt, client and examples — including
examples/CONTRIBUTING.md, whose §4 tells the next author to write the README
that started this.

Verification

  • scripts/voice-check.sh green at the committed ceilings
  • tinkerdown validate content/ — 98/98, 0 errors
  • make test-e2e-local green, uncached, against the prod Docker image on :8084
  • make sweep — 194/196; the 2 are the pre-existing 4.9 MB large-table timeouts
  • anchor links diffed against main — 15 broken on both, 0 new
  • 24 rewrites read back out of a real browser via chromedp
  • every modified file checked for an upstream source_repo — no mirrored page touched

Note: this repo has no pre-commit hook installed, so do not read these commits
as hook-approved.

🤖 Generated with Claude Code

https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ

adnaan and others added 4 commits August 9, 2026 20:59
The landing copy has been rewritten four times (#65, #83, #135, #136) and
drifted back to the same flat register every time, because nothing recorded
what the target was. VOICE.md is that record.

An audit of the 67 docs-native pages found two hand-written voices, not one.
The ten pages under content/recipes/apps/ were ported from example READMEs and
then cut loose: 9 exclamation marks, 14 emoji and 36 Title Case headings,
against 2, 5 and 7 across the other 53 files. Everything else was already
restrained — zero occurrences of powerful, effortless, robust, out of the box.

The marketing register was also producing wrong claims. chat.md said "Zero
manual broadcasting code required!" while examples/chat/main.go has an explicit
Subscribe and three explicit Publish calls. A reader who believed it would
wonder why their own app doesn't sync.

Also in here:
- scripts/voice-check.sh, a ratchet. Ceilings are today's counts, so the build
  fails when a number goes up. Bring one down, lower the ceiling in the same
  commit. It will not catch a page that is merely flat; most of VOICE.md is not
  checkable, which is why it is written down.
- source-of-truth.md claimed recipes/apps/* was mirrored from livetemplate/examples.
  It hasn't been since the consolidation, and source-of-truth.yaml said so. That
  stale row is why those ten pages went unmaintained.

Verified: tinkerdown validate 98/98, e2e green on :8084, sweep 194/196 (the two
are the pre-existing 4.9 MB large-table timeouts), and the rewritten copy read
back out of a real browser on all ten pages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
Without this the checker is a script nobody runs, which ratchets nothing. It
goes in the validate job next to `tinkerdown validate` — both are content
gates and neither needs the browser stack.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
These were ported once from examples/<slug>/README.md and then cut loose, so
they still read like READMEs while the rest of the site moved on. Against the
other 53 docs-native pages they carried 9 exclamation marks to 2, 14 emoji to 5,
and 36 Title Case headings to 7.

Counts across all docs-native pages: emoji 19 -> 0, Title Case headings 43 -> 0,
exclamations 11 -> 3, passives 114 -> 83. The three exclamations left are quoted
UI copy ("Changes saved!"), which is what a flash message actually says.

Two more false claims went with the register, both in chat.md:
- "Just modify state - broadcasting happens automatically!" Mutating state does
  nothing on its own; Mount subscribes and each action publishes.
- The comparison table's "Auto-broadcasting" framing, same problem.

Two carve-outs, both in VOICE.md and the checker:
- Emoji in a comparison table are scan markers doing real work (the "does this
  scale?" matrix in recipes/counter/index.md). The checker skips table rows.
- H1 carries the page NAME and must equal the front-matter title. Downcasing it
  renames the page — breadcrumb_test.go caught me doing exactly that on
  App Recipes. Added an h1-vs-title check so it can't happen again, and verified
  the check fires by breaking a title on purpose.

Not fixed here, flagged instead: chat.md's tutorial documents an API that does
not exist (a Change(ctx *ActionContext) switch on ctx.Action, and an undefined
`controller` in main). The real examples/chat/main.go uses the controller
pattern. That is a correctness fix, not a voice one, and it needs its own PR.

Verified: voice-check green at the new ceilings, tinkerdown validate 98/98,
e2e green uncached on :8084, sweep 194/196 (the 2 are the pre-existing 4.9 MB
large-table timeouts), and all ten pages read back clean from a real browser.
Anchor links checked against main: 15 broken on both, 0 new.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
Phase 2b, across the 57 docs-native pages outside recipes/apps/.

Every countable tell is now at its floor:

  claudisms         14 -> 0
  product-as-subject 3 -> 0
  royal we           4 -> 0
  meta-commentary    3 -> 0
  hype lexicon       1 -> 0
  triadic negation   5 -> 1
  passive voice    114 -> 34

The passive number is where the work was. What is left is mostly not passive
voice at all — "the title is required" describes an HTML attribute, "action
methods are exported" is Go, "Email is required" is a quoted flash message, and
"is untouched" is an adjective. The regex cannot tell those apart, so the
ceiling stays at 34 rather than pretending 0 is reachable.

The one triadic negation left is the landing's "no hx-post, no onClick, no route
to register". Each item names a real thing you would otherwise write, so it is
an argument rather than a drumbeat.

Two judgement calls worth naming:

- "literate primitives" in source-of-truth.md was not upstream terminology —
  nothing else in the repo or in tinkerdown uses it. Renamed to "literate
  blocks", which is what the section actually documents.
- "reach for" stays. At 24 files it is house idiom, not drift, and it is
  deliberately absent from the checker.

Also fixed two checker false positives found by running it: "the rest of the
page" is usually literal ("without blocking the rest of the page"), so
meta-commentary now requires a following verb; and the h1-vs-title comparison
strips backticks, which a front-matter title cannot carry.

chat.md's remaining "automatic syncing" claims went too — leaving them would
have contradicted the correction made to the same file in the previous commit.

Verified: voice-check green at the new ceilings, tinkerdown validate 98/98,
e2e green uncached on :8084, sweep 194/196 (the 2 are the pre-existing 4.9 MB
large-table timeouts), anchors 15 broken on both branch and main so 0 new, and
14 rewrites read back out of a real browser. No mirrored page touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
@adnaan
adnaan merged commit 67698d3 into main Aug 10, 2026
4 checks passed
@adnaan
adnaan deleted the docs-voice branch August 10, 2026 23:36
adnaan added a commit that referenced this pull request Aug 15, 2026
…so (#141)

/contributing/examples described how to contribute to livetemplate/examples.
GitHub has that repository archived and read-only, so the workflow it documented
cannot be followed: it told the reader to create a directory there, write a
go.mod pinning livetemplate v0.1.0 (core is at v0.25.0), and add a README in the
register that #137 spent a PR removing from the ten pages that copied it.

Two things turned out to be true that were not obvious.

The page was never actually mirrored. It carries source_repo front matter
pointing at the archived repo, but /contributing/examples has no entry in
source-of-truth.yaml, and cmd/sync only walks entries from that file
(sync.go Run -> filterByRepo over cfg.Pages; nothing scans front matter). So it
was already docs-native and merely mislabelled, and correcting the front matter
changes no sync behaviour.

source-of-truth.md and CLAUDE.md both claimed otherwise. Both now say the page is
docs-native and why, so the next person does not skip it as unfixable.

The page itself now says the repo is archived, points at this repo's
CONTRIBUTING.md for the current steps, and repeats the two rules that have
already cost real work here: include code rather than retyping it, and read
VOICE.md first.

Verified: voice-check green — it failed twice on this page first, once for
quoting a banned phrase as an example of what not to write, and once for three
passives I had written, so both got fixed rather than the ceiling raised.
tinkerdown validate 98/98, e2e green, and the page read back in a real browser
with all six of its links resolving.


Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant