Requirement
Every error message answers, in order: what happened, what it means for the user, and what to do next. Calm and helpful in tone. Raw error codes never appear as the primary message (they may appear as secondary detail for support).
Rationale
Kind at Surface: gentle error states with clear next steps, never technical error codes or dismissive generic errors. An error moment is where a tired user decides whether this tool is on their side.
Passes when
- "We could not save your notes — the connection dropped. Your draft is kept on this device; try again when you're back online."
- A failed action keeps the user's entered data and says so.
- Where a code aids support, it is secondary: "…contact support and quote ref 4031."
Fails when
- "Error 500" / "Something went wrong" as the whole message.
- The message blames the user ("You entered invalid data") instead of stating the problem ("We don't recognise this date format — use DD/MM/YYYY").
- The message states what happened but not what to do next.
- An error discards the user's input silently.
How to verify
Deterministic half — checks/content-lint.py: find error-state strings, flag
raw codes appearing as primary copy. The lint half flags a raw code only where it can
tell a code from a name: a bare string literal, or a Markdown prose line. It does not
flag one in rendered text, a prop value, or a template segment, where an all-caps
token is as likely a wordmark, a badge, or an acronym. It also never reads a compared
value (tag === "INPUT") as copy. Judgment half — the evaluator quotes each error
message and grades it against the three questions, using the tone-by-context table in
the content skill (error tone: calm, helpful).