Write the voice down, then apply it - #137
Merged
Merged
Conversation
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
This was referenced Aug 15, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.mdis that record.What the audit found
Not what I expected. The lexicon was already clean: one
seamless, onemodern, zeropowerful/effortless/robust/out of the box, zerothroat-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 fromexamples/<slug>/README.mdand then cut loose:recipes/apps/(10 files)source-of-truth.mdstill claimed those pages were mirrored fromlivetemplate/examples. They have not been since the consolidation, andsource-of-truth.yamlsaid 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.mdsaid "Zero manual broadcasting code required!" whileexamples/chat/main.gohas an explicitctx.Subscribe(ctx.SelfTopic())andthree explicit
ctx.Publish(...)calls. A reader who believed it would wonderwhy their own app doesn't sync. Same page, same problem: "broadcasting happens
automatically", "automatic multi-tab syncing". All corrected against the real
file.
Counts
The three non-zero floors are deliberate and the script says why. The
exclamations are quoted UI copy (
"Changes saved!"is what a flash messagesays). 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.shruns in thevalidatejob. Each ceiling is the counton 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.mdis notcheckable — that is why it is written down rather than only encoded.
Three carve-outs, all documented:
this scale?" matrix in
recipes/counter/index.md). Table rows are skipped.title:. Itfeeds nav and breadcrumbs. Downcasing it is a rename —
breadcrumb_test.gocaught me doing exactly that to
App Recipes. There is now anh1-vs-titlecheck, and I verified it fires by breaking a title on purpose.
reach forstays. At 24 files it is house idiom, and it is deliberatelyabsent from the checker.
Flagged, not fixed
Two things a reviewer should not read as oversights.
chat.md's tutorial documents an API that does not exist. It showsfunc (s *ChatState) Change(ctx *livetemplate.ActionContext) errorswitchingon
ctx.Actionwithctx.Bind(&data), and calls an undefinedcontrollerin
main. The realexamples/chat/main.gouses the controller pattern. Thatis a correctness fix, not a register one, and folding it into a prose PR
would hide it.
against
main: identical on both, so this PR adds none.Not in scope
32 pages carry an upstream
source_repoandcmd/syncoverwrites the wholefile on release, so editing them here is work that gets thrown away. They need
PRs against
livetemplate,lvt,clientandexamples— includingexamples/CONTRIBUTING.md, whose §4 tells the next author to write the READMEthat started this.
Verification
scripts/voice-check.shgreen at the committed ceilingstinkerdown validate content/— 98/98, 0 errorsmake test-e2e-localgreen, uncached, against the prod Docker image on:8084make sweep— 194/196; the 2 are the pre-existing 4.9 MBlarge-tabletimeoutsmain— 15 broken on both, 0 newsource_repo— no mirrored page touchedNote: 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