Products get confusing in a specific way: the person who built it knows which option to pick and what happens after the click, and none of that knowledge ever made it into the interface. New users hit a vague button, a cleverly named feature, or a choice with no stated tradeoff, and they guess or quietly leave instead of asking. This finds those spots and proposes the smallest wording change that removes the guess, without burying the UI in helper text.
Drop the folder into your agent's skills directory (Claude Code reads from ~/.claude/skills/clarity-audit/).
---
name: clarity-audit
description: Audit a UI for the places a first-time user stalls, guesses, or picks the wrong thing, then propose the copy that fixes it. Finds unlabeled actions ("Continue" to where?), clever internal feature names nobody outside the team understands, option sets with no basis for choosing, dead ends after an action completes, empty states with no on-ramp, hidden prerequisites, and leaked internal jargon. Use when the user says "clarity audit", "usability audit", "is this clear to a new user", "would someone new understand this", "this UI is confusing", "people keep asking me how to use X", "our copy is vague", "add helper text", "what does this button even do", or worries they built a product only they know how to operate. Output is a numbered list of proposed changes; the user picks which to apply. Harness-agnostic, so it works in Claude Code, Codex, Cursor, and any other agent host.
metadata:
author: Shpigford
version: 1.0.0
---
# Clarity Audit
You are auditing a UI for the **curse of knowledge**: every place the product makes sense to the person who built it and to nobody else.
The people who wrote this app know what the button does, which option to pick, and what happens after they click. None of that knowledge is in the interface. It lives in their heads. Your job is to find each spot where a user without that knowledge has to stop and guess, and to propose the smallest piece of language that removes the guess.
**The bar is "a competent person, first session, no docs, no demo, nobody sitting next to them."** Not a novice who has never used software. Not a QA engineer looking for defects. Someone smart who has simply never seen this product before, and who will quietly leave instead of asking.
**The opposite failure is real and you must respect it.** A UI drowning in tooltips, helper paragraphs, and captions under every control is worse than a terse one. It reads as insecure, it buries the signal, and it punishes the user on visit two through six hundred. Most findings should end in *fewer or better words*, not more words. If a pass produces forty new strings, the pass is wrong.
You produce a numbered list. The user picks. Then you edit.
---
## 1. Scope and mode
- If `$ARGUMENTS` names a path, a route, a screen, or a flow ("the signup flow", `src/settings/`), audit only that.
- If `$ARGUMENTS` is empty, pick the highest-value target yourself and **announce it before scanning**: the primary flow a new user hits first (signup, onboarding, first object creation, the main dashboard). A whole-app sweep in one pass produces a list too long to act on.
- Exclude: `node_modules`, `.git`, `dist`, `build`, `out`, `.next`, `.nuxt`, `vendor`, `target`, `__pycache__`, `.venv`, lock files, minified assets, tests, Storybook fixtures, and admin-only or internal-tooling screens (staff know their own jargon; that is fine).
- Say the scope out loud first so the user can redirect you before you spend the effort.
---
## 2. Get the surfaces
### Always: read the code
Find the strings a user actually reads, plus the structure around them. Use `rg` / `grep` / `find` / `glob`, which exist in every host.
| Stack | Where the copy lives |
|---|---|
| React / Next / Remix | `.tsx`/`.jsx` components, `app/**/page.tsx`, `components/**`, i18n `en.json`, `messages/*.json` |
| Vue / Nuxt | `.vue` templates, `locales/*.json`, `i18n/**` |
| Svelte | `.svelte` markup, `$lib/i18n/**` |
| Rails | `app/views/**/*.erb`/`.haml`/`.slim`, `app/components/**` (ViewComponent), `config/locales/*.yml` |
| Django | `templates/**/*.html`, `forms.py` (`label=`, `help_text=`), `locale/**/*.po` |
| Laravel | `resources/views/**/*.blade.php`, `lang/**/*.php` |
| Phoenix | `lib/**/*_html/**/*.heex`, `gettext` files |
| SwiftUI / UIKit | `Text("...")`, `Localizable.strings`, `.xcstrings` |
| Flutter | `Text('...')`, `.arb` files |
If the project uses i18n, **the locale file is the fastest complete inventory of user-facing language in the repo.** Start there, then trace keys back to where they render to learn the context. A string with no context is not auditable.
Do not audit strings in isolation. A label is only clear or unclear *next to the other things on that screen*. Read the component, not the grep hit.
### If available: look at the running app
Rendered screens beat source every time, because a screen shows what a user sees at once: the label, the three options beside it, the empty panel, the disabled button. Source review cannot see that.
Check, in order, and use the first one that works:
1. A browser automation tool the host exposes (`agent-browser`, Playwright MCP, Puppeteer, an existing screenshot script in the repo).
2. A dev server already running (`lsof -iTCP -sTCP:LISTEN -P -n | grep -E '3000|3001|4000|5173|8000|8080'`).
3. Screenshots the user pasted into the conversation. If they gave you images, those images are the audit target. Read them first, before any tooling.
If none of that is available, say so in one line and audit from source. Do not install anything, do not start services the user did not ask you to start, and do not block the audit waiting for a browser.
### Also read, if they exist
Marketing copy, the landing page, the README, and any docs site. These usually contain the plain-language explanation of a feature that never made it into the product. That sentence is frequently the exact fix, already written in the right voice.
---
## 3. The method: the cold read
This is the whole technique. Do it per screen.
**Cover the code. Read only what renders, in the order a user's eye hits it.** Then answer four questions out loud:
1. **What is this screen for?** Answerable from the screen alone, in one sentence?
2. **What does each control do?** Not "what is it called", what does it *do to my data or my account*.
3. **If there is a choice here, on what basis do I choose?** Does the screen give me the information that distinguishes the options?
4. **What happens after I act?** Where do I land, what changed, is it reversible, and what do I do next?
Any question you can only answer by reading the source is a finding. That gap is exactly the knowledge the builder has and the user does not.
Two rules that keep this honest:
- **Never audit from memory of the code you just read.** Once you know `handleBoost()` writes `priority: 2`, you cannot un-know it. Answer the four questions from the rendered text only, then check the source to confirm the gap is real and to write an accurate fix.
- **Confirm the behavior before you describe it.** A helper string that says the wrong thing is worse than no helper string. If you propose "Archiving hides the project but keeps its data", read the archive code and be sure that is true. Never invent behavior to fill a gap. If you cannot determine it, that becomes a question for the user (section 5), not a guess.Members read the full skill.
Join the Founding Club — every skill, field note, and drop while you're a member.