Skip to content

S9 — plain language: plain meaning first, term after in parentheses - #93

Merged
matthewpmunger merged 3 commits into
mainfrom
chunk-s9
Aug 26, 2026
Merged

S9 — plain language: plain meaning first, term after in parentheses#93
matthewpmunger merged 3 commits into
mainfrom
chunk-s9

Conversation

@matthewpmunger

Copy link
Copy Markdown
Collaborator

Base commit

  • Base sha: 95d77a98e77cafef99492d0964f5fb92e599a510
  • Checks at that sha: greenlint 0, typecheck 0, 104 files / 1335 tests, build OK. origin/main was identical, and REPAIRS.md had nothing open.

What & why

S9 — plain language. Option 11b: plain meaning first, technical term after it in parentheses. Tooltips and glossary links were both considered and rejected, and the reason is where this copy is read: a digest arrives in email, a fix is pasted into a ticket, a case is screenshotted into Slack. None of those carry a hover target or a link back into the app, so the meaning goes in the sentence.

Two groups, two treatments. Group A — internal names, vendor codenames, and Lighthouse rule ids — is rewritten out of reader-facing copy entirely. Group B — industry-standard measurements and web standards — keeps the term, because renaming "cumulative layout shift" would cut the reader off from every other tool that measures it, and introduces it with its plain meaning.

Changes

The glossary is retired, not rewritten.

  • src/lib/guide.ts and its test are deleted. The module had no importer in src/ besides its own test, so the glossary was already unreachable.
  • /guide now redirects to /issues, alongside the other retired destinations. It used to aim at /settings#reference — an anchor that never existed and now never will, because the operational content is distributed beside the controls it explains.
  • The operational half moved into Settings as short lines: what a run does and how often, in the Digest group's help where the reader meets the phrase "per run"; and a new "Always on" block in Connected systems, keyed by the registry's evidence sources so a system added to the ledger cannot arrive unexplained. Each line carries its own cadence — nightly for the lab test and the agent checks, weekly for the visitor figures — because one screen-level claim about "how often" would be false for whichever system it did not describe.
  • Ora's row keeps its public-scan disclosure, which was the one thing in the glossary that was an obligation rather than a definition.

The copy sweep (~25 files)

  • Lighthouse audit ids no longer reach a reader anywhere. The keys are untouched — they are how a reading is matched to the report it came from.
  • The metric chip led with three letters (TBT · 30%) and buried the only part a reader can use. metricDisplay() puts the plain word first.
  • The lab/field comparison called its two sides "Lab" and "Visitor" — words that only mean that to us. It now says "the nightly test" and "real visitors". The acronyms stay on the metric rows, because the responsiveness row deliberately compares two different measurements and stripping the names would hide that.
  • The page-detail history table introduces every measurement in its caption, once, so its four-character column headings are a second mention rather than the reader's first. A11y is gone; it was a numeronym.
  • PSI is gone from copy — our shorthand for a service nobody else calls that.
  • The 20 agent-readiness checks now have display labels separate from their names. A separate map rather than a rename, because those names are keys: an exclusion is stored as group + name, the scanner's results are keyed by check name, and the issue families refer to checks by name. Renaming them would have stranded every exclusion a site has recorded — a migration, not a copy change. "DNS for AI Discovery (DNS-AID)" was already written this way and is the shape the other nineteen now follow.
  • The demo data's case titles were Lighthouse audit titles carried through verbatim, which the IssueCase contract explicitly forbids: "one plain sentence… Never an audit title."

Registry v10 renames evidence_source.kitesurf's label from the codename Kitesurf to Rendered page. That codename was the only name a reader ever saw for that system — squarely the term-as-jargon this chunk forbids. The key is unchanged, so no stored reading migrates. Ora stays: it is a vendor the user opts into by name. See Notes — this is the one call in here that is arguably product's rather than mine.

Three literal-sets consolidated into named exported maps (LAB_FIELD_HEADLINE, VISITOR_CONFIDENCE_LABEL, CULPRIT_GROUP_LABELS). Each was a set of strings inside one if/else, asserted by a second set of literals in a test — a rewording had to be made twice and agreed nowhere (rule 20). The tests now assert against the map, so they check which conclusion was reached rather than how it was spelled (rule 21).

plain-language.test.ts asserts the ordering, not the punctuation: no reader-facing string may open with an industry term, and no internal shorthand or rule id may appear in one at all. Checking for a parenthesis would have passed "CLS (cumulative layout shift) 0.24", which is the same defect wearing the pattern's clothes. Proved by injection — three deliberate violations shaped like the brief's own examples, each caught by a different assertion. It deliberately does not check "one appositive per term per screen": a screen is not something a test can see, and that half is a review call.

Verification

  • npm run build passes
  • npm run lint passes
  • npm run typecheck passes
  • npm test -- --run passes — 104 files / 1333 tests
  • Drove the affected route(s) in the browser (dev server on :3100) — partially; see Notes
  • Any repair of shared breakage is claimed in REPAIRS.md and kept in its own commit — n/a, no repair here

Notes

Two premises in the brief were wrong at this base sha, and one of them costs a VERIFY item.

1. banned_global.allowlist cannot be emptied by S9. The brief said guide.ts "should be the last, but verify rather than assume." It is not. Linting each allowlisted file with the rule forced on:

entry owner still live?
src/lib/guide.ts S9 cleared with the file ✅
src/components/bits.tsx F2 yes — renders "Verifying recovery: …" and "Returned after a confirmed resolution"
src/components/store.tsx S2 yes — renders "cleared from Inbox"

Both are left alone. store.tsx especially: #84 (S5, open) edits that same file and deletes the adjacent "Tasks board" string but not the Inbox one, and does not remove the entry. Editing it here is precisely the non-overlapping-merge collision AGENTS.md and REPAIRS.md exist to prevent. So the brief's "allowlist is empty and lint passes with no exemptions" is not met, and cannot be by this chunk.

A trap for whoever clears the last entry: eslint.config.mjs spreads ALLOWLISTED_FILES into files:, and ESLint rejects files: [] with "Expected value to be a non-empty array." The last removal must delete the whole exemption block, not just empty the list.

2. Two locked examples describe copy that does not exist here. "Structured data absent on 2 templates" and "robots.txt disallows GPTBot" have no counterpart in the tree — GPTBot appears only inside a regex in agentReadiness.ts. Consistent with "these eight are the pattern, not a sample", so the shape was applied rather than the strings invented.

Deliberate exemptions, both recorded in the test. /admin's unmapped-audit screen keeps its audit IDs — it is app-admin-only and mapping those ids to the remediation catalogue is the screen's entire job. And robots.txt / sitemap.xml are not treated as terms needing an appositive: the decided copy is the argument, since "Your robots.txt file tells ChatGPT's crawler not to read this site" opens with the filename and needs no gloss. A file on the reader's own site is something they can go and look at.

Browser verification is partial, and here is exactly what was and was not done. The new Settings block was rendered against the compiled token layer at 1280px — two columns, every system name on one line, no clipping, no overflow — and its media query collapses to one column below 620px. The integrated Settings screen was not driven in a browser: port 3100 was held by a dev server running from another checkout, and the production build correctly refuses the dev-session bypass. Worth a reviewer's eye on /settings and /pages/[id].

Overlap with #84 (S5, open). Both branches touch webflowPerformance.ts, its test, bits.tsx, pages-content.tsx and seed.ts. A trial merge of the two was run locally and the combined result is reported in a comment below.

🤖 Generated with Claude Code

matthewpmunger and others added 3 commits August 26, 2026 15:32
The Guide is deleted rather than rewritten. A term a reader has to look up is
a term the copy should have explained where they met it, and most of this
product's copy is read outside the app — in digests, tickets and screenshots —
where no glossary is reachable at all.

- `src/lib/guide.ts` and its test are gone. The file had no importer in `src/`
  besides its own test, so the glossary was already unreachable; what it
  carried that still matters has moved.
- `/guide` now redirects to `/issues`, alongside the other retired
  destinations. It used to aim at `/settings#reference`, an anchor that never
  existed and now never will: the operational content is distributed beside the
  controls it explains, so there is no single section to land on.
- The operational half moves to Settings as short lines beside the controls
  they explain. What a run does and how often it happens is said in the Digest
  group's help, where the reader meets the phrase "per run". What each system
  contributes is a new "Always on" block in Connected systems, keyed by the
  registry's evidence sources so a system added to the ledger cannot arrive
  unexplained, and carrying its own cadence — nightly for the lab test and the
  agent checks, weekly for the visitor figures. One screen-level claim about
  "how often" would have been false for whichever system it did not describe.
- The Ora row keeps its public-scan disclosure, which was the one thing in the
  glossary that was an obligation rather than a definition.
- Registry v10 renames `evidence_source.kitesurf`'s label from the codename
  "Kitesurf" to "Rendered page". The codename was the only name a reader ever
  saw for that system, which is precisely the term-as-jargon this chunk
  forbids. The KEY is unchanged, so no stored reading migrates.
- `systemLabel` in `agentIssueCases.ts` now reads the ledger's labels instead
  of holding its own three strings — one of which, "Page Watch HTTP", was a
  spelling the registry never named (rule 20). Its test asserts against the
  registry rather than a literal (rule 21).

`banned_global.allowlist` loses its `src/lib/guide.ts` entry with the file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pattern, applied to the screens that carry measurements: plain meaning
first, industry term after it in parentheses. Internal shorthand is gone.

`plain-language.ts` states the pattern and the two groups it applies to, and
`appositive()` builds an introduction so the ordering is structural rather than
remembered.

What changed and why:

- Lighthouse audit ids no longer reach a reader anywhere. `culpritEvidence`'s
  titles were half-translated already ("DOM structure", "Image encoding
  waste"); they now say what the finding is ("Deeply nested elements", "Images
  heavier than they need to be"). The keys are untouched — they are how a
  reading is matched to the report it came from.
- The evidence facts said "Main-thread time" and "Blocking time", which name
  the browser's internals rather than the reader's problem. They now say what
  the number cost: "Time the browser spent running it", "Time the page could
  not respond".
- The metric chip led with three letters — "TBT · 30%" — and buried the only
  part a reader can use. `metricDisplay` puts the plain word first and keeps the
  acronym after it. The spelled-out term is introduced once per screen in the
  prose beside the chips, never inside them.
- The score-card tooltips listed their measurements as bare acronyms. Each is
  now introduced by what it measures. The tooltip is not the pattern and must
  not become it — the comment says so — but its own copy still has to be plain.
- The lab/field comparison called its two sides "Lab" and "Visitor", words that
  only mean that to us. It now says "the nightly test" and "real visitors"
  throughout. The acronyms stay on the metric rows, because the responsiveness
  row deliberately compares two DIFFERENT measurements and stripping the names
  would hide that.
- The page-detail history table introduces every measurement in its caption,
  once, so the four-character column headings are a second mention rather than
  the reader's first. "A11y" is gone; it was a numeronym.
- "PSI" is gone from copy. It is our shorthand for a service nobody else calls
  that, and the device is the part a reader can act on.

Three headline/label sets moved into named exported maps —
`LAB_FIELD_HEADLINE`, `VISITOR_CONFIDENCE_LABEL`, `CULPRIT_GROUP_LABELS`.
Each was a set of literals inside one if/else, asserted by a second set of
literals in a test: a rewording had to be made twice and agreed nowhere
(rule 20). The tests now assert against the map, so they check which
conclusion was reached rather than how it was spelled (rule 21).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ring

The rest of the sweep, plus the test the pattern needed.

`plain-language.test.ts` asserts the ORDERING rather than the punctuation: no
reader-facing string may open with an industry term, no internal shorthand or
rule id may appear in one at all. Checking for a parenthesis would have passed
"CLS (cumulative layout shift) 0.24", which is the same defect wearing the
pattern's clothes. It was proved by injection — three deliberate violations
shaped like the brief's own examples, each caught by a different assertion.

It does NOT check "one appositive per term per screen". A screen is not
something a test can see, and the brief says that half is a review call.

Copy:

- The 20 agent-readiness checks now have display labels separate from their
  names. A separate map rather than a rename, because those names are KEYS: an
  exclusion is stored as `group + name`, the scanner's results are keyed by
  check name, and the issue families refer to checks by name. Renaming them
  would have stranded every exclusion a site has recorded — a migration, not a
  copy change. "DNS for AI Discovery (DNS-AID)" was already written this way
  and is the shape the other nineteen now follow.
- The agent-access cause lines said "origin" and "parse" — our word for a site,
  and what a program does to a document. S4 locked them; S9 re-decides them,
  and the test's lock moved with the decision.
- Six issue titles opened with an acronym ("MCP resources are missing…",
  "API errors are not machine-readable"). Each now leads with what the reader
  loses and names the standard after it.
- The demo data's case titles were Lighthouse audit titles carried through
  verbatim — "Reduce unused JavaScript", "Properly size images" — which the
  `IssueCase` contract explicitly forbids: "one plain sentence… Never an audit
  title." The mock provider in `psi.ts` carried four more, and reaches demo
  mode as real copy.
- "PSI" is gone from the last of the copy, including the collection-status
  lines, the chart's excluded-reading label, and the demo digest note.
- `robots.txt` and `sitemap.xml` are deliberately NOT treated as terms needing
  an appositive. The decided copy is the argument: "Your robots.txt file tells
  ChatGPT's crawler not to read this site" opens with the filename and needs no
  gloss, because a file on the reader's own site is something they can go and
  look at.

Verified: lint, typecheck, 1333 tests and build all pass. The new Settings
block was rendered against the compiled token layer at 1280px — two columns,
no name wrapping, no clipping, no overflow — and its media query collapses to
one column below 620px.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@matthewpmunger

Copy link
Copy Markdown
Collaborator Author

Trial merge against #84 (S5)

Ran locally, because overlapping-but-non-conflicting merges are this repo's documented failure mode. Three files are touched by both branches:

  • src/lib/webflowPerformance.ts
  • src/lib/__tests__/webflow-performance.test.ts
  • src/app/(app)/pages/pages-content.tsx

The pair I was worried about auto-merges correctly. S9 adds a CULPRIT_GROUP_LABELS export and a test import of it; S5 removes triageActionLabel and its assertions. Both survive: the merged module exports all nine symbols the merged test imports, and triageActionLabel is at zero occurrences on both sides. No silent breakage in the combination.

git merge reports three conflicts, and two of them are not S9's:

file conflict is note
pages-content.tsx S9 ↔ S5 one import line. S9 adds metricDisplay, S5 drops triageActionLabel; the union is the resolution
case-detail.tsx S5 ↔ main pre-existing
issue-case.ts S5 ↔ main pre-existing

chunk-s5 is based on 728eb6c, ten commits behind main — it predates S4's merge (#88), the merge-order guardrails (#89), the DECISIONS.md repair (#91) and the watchlist headings (#92). Those latter two conflicts exist against main today, with or without S9.

So the order costs #84 one trivial import union on top of a rebase it already needs. Nothing here requires S9 to change, and nothing requires #84 to wait — but per AGENTS.md, #84 should re-run the checks at its new base rather than inherit this account of them.

One thing #84 may want to pick up while rebasing: store.tsx stays on banned_global.allowlist after S5 merges. #84 deletes "Saved to Tasks — track it on the Tasks board" but not "Ignored — cleared from Inbox, still listed on the page" a few lines below, and does not remove the allowlist entry.

🤖 Generated with Claude Code

@matthewpmunger
matthewpmunger merged commit 07834fa into main Aug 26, 2026
6 checks passed
@matthewpmunger
matthewpmunger deleted the chunk-s9 branch August 26, 2026 21:08
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