The copy rules are checked two different ways, and some sentences a person reads are checked by neither #220

Open
opened 2026-10-12 02:54:15 +00:00 by cgalo5758 · 0 comments
Owner

What a contributor runs into

The console's copy rules (no em dash in copy, no filler phrase such as "Please" or "you can", a hint no longer than one short line) are checked in two places, and the two do not agree.

  • The project lint (go run . lint, the copy checks in internal/lint) refuses an em dash with a letter on either side and lets a lone "—" through, because a lone dash is the empty-value marker. The form registry's check of every form declaration (forms.CheckInvariants) refuses any em dash, the lone marker included, in eleven of a declaration's copy strings, and does not look at table column headings or a static row's value. So "1 — 2" or a lone "—" as a label passes the lint and fails the registry, and a dash in a column heading is caught only when the heading is a string literal in a package the lint reads.
  • Only the lint refuses filler phrases. AGENTS.md says the form registry refuses them too; it does not.
  • The lint reads a Go string literal as it is written, so an em dash written as a Unicode escape between two words passes. It reads template text as raw bytes and attribute values decoded, so — between two words passes in text and fails in an attribute, and you can passes.
  • The lint reads Go strings only in internal/server, internal/web and each integration's web package. Other packages also return sentences a person reads: the form library's answer to a form it cannot read, and the domains registry's refusal of a taken name and its retry limit. No check reads those.
  • The form registry counts a hint's length in bytes; the rule says 100 characters.

Neither check refuses a string the console holds today, and the two give the same verdict on every string they both read.

Why it costs

A contributor who follows one statement of the rule can write copy that the other check refuses, or copy that no check reads. The rule is written in four places with three wordings: the UI quality gate spec ("an em dash with a letter on either side"), the form library spec ("no em dash in any string"), docs/design-system.md (no em dashes, the lone marker exempt) and AGENTS.md.

Where

internal/lint (the copy checks, and the list of packages its Go pass reads); internal/forms/invariants.go; AGENTS.md; openspec/specs/ui-quality-gate, openspec/specs/form-library and openspec/specs/ui-vocabulary; docs/design-system.md.

Done when

  • One em-dash rule is chosen and written once. Two candidates leave today's copy as it is: refuse an em dash with a letter beside it and exempt the lone marker (what the lint checks now), or refuse any em dash except in a value that is only the dash.
  • One function, in a package that imports only the standard library, answers that rule and the filler phrases; the lint and the form registry both call it, each keeping its own message.
  • The form registry checks every copy string of a declaration with it, column headings and static values included.
  • The lint's Go pass reads every package that returns text a person reads.
  • An escaped dash and a character entity are read as the character they stand for.
  • The hint limit counts characters.
  • AGENTS.md, the three specs and the design system state the one rule.

Related: #169 asks for a guide to writing the copy that stays; #63 notes that each lint rule picks its own directories.

### What a contributor runs into The console's copy rules (no em dash in copy, no filler phrase such as "Please" or "you can", a hint no longer than one short line) are checked in two places, and the two do not agree. - The project lint (`go run . lint`, the copy checks in `internal/lint`) refuses an em dash with a letter on either side and lets a lone "—" through, because a lone dash is the empty-value marker. The form registry's check of every form declaration (`forms.CheckInvariants`) refuses any em dash, the lone marker included, in eleven of a declaration's copy strings, and does not look at table column headings or a static row's value. So "1 — 2" or a lone "—" as a label passes the lint and fails the registry, and a dash in a column heading is caught only when the heading is a string literal in a package the lint reads. - Only the lint refuses filler phrases. `AGENTS.md` says the form registry refuses them too; it does not. - The lint reads a Go string literal as it is written, so an em dash written as a Unicode escape between two words passes. It reads template text as raw bytes and attribute values decoded, so `—` between two words passes in text and fails in an attribute, and `you can` passes. - The lint reads Go strings only in `internal/server`, `internal/web` and each integration's `web` package. Other packages also return sentences a person reads: the form library's answer to a form it cannot read, and the domains registry's refusal of a taken name and its retry limit. No check reads those. - The form registry counts a hint's length in bytes; the rule says 100 characters. Neither check refuses a string the console holds today, and the two give the same verdict on every string they both read. ### Why it costs A contributor who follows one statement of the rule can write copy that the other check refuses, or copy that no check reads. The rule is written in four places with three wordings: the UI quality gate spec ("an em dash with a letter on either side"), the form library spec ("no em dash in any string"), `docs/design-system.md` (no em dashes, the lone marker exempt) and `AGENTS.md`. ### Where `internal/lint` (the copy checks, and the list of packages its Go pass reads); `internal/forms/invariants.go`; `AGENTS.md`; `openspec/specs/ui-quality-gate`, `openspec/specs/form-library` and `openspec/specs/ui-vocabulary`; `docs/design-system.md`. ### Done when - One em-dash rule is chosen and written once. Two candidates leave today's copy as it is: refuse an em dash with a letter beside it and exempt the lone marker (what the lint checks now), or refuse any em dash except in a value that is only the dash. - One function, in a package that imports only the standard library, answers that rule and the filler phrases; the lint and the form registry both call it, each keeping its own message. - The form registry checks every copy string of a declaration with it, column headings and static values included. - The lint's Go pass reads every package that returns text a person reads. - An escaped dash and a character entity are read as the character they stand for. - The hint limit counts characters. - `AGENTS.md`, the three specs and the design system state the one rule. Related: #169 asks for a guide to writing the copy that stays; #63 notes that each lint rule picks its own directories.
cgalo5758 added the
kind
debt
area/operator-uiarea/member-uiarea/testing
labels 2026-10-12 02:54:15 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wiki-cafe/member-console#220