{"slug":"ux-writing","title":"ux-writing","summary":"Judgment rules for user-facing text and docs: CLI and diagnostic output, error and help text, README and docs structure, code comments, titles, and generated reports, decks, or exports. Use when writing or changing any user-visible string, when adding or restructuring docs or dec","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-08-31T16:21:05.779242Z","repo":{"url":"https://github.com/scarletkc/agents","stars":224,"forks":12,"license":"Apache-2.0","updatedAt":"2026-09-21T23:41:11Z"},"bodyHtml":"<hr>\n<h2>name: ux-writing\ndescription: \"Judgment rules for user-facing text and docs: CLI and diagnostic output, error and help text, README and docs structure, code comments, titles, and generated reports, decks, or exports. Use when writing or changing any user-visible string, when adding or restructuring docs or deciding which page owns a fact, when a page is about to record a version, a deployment state, or a value the code already owns, when a comment, title, or artifact could carry the reasoning or an abandoned option behind the change, when a behavior change needs its copy sites swept, or when reviewing a diff that touches copy or docs.\"\nlicense: Apache-2.0\nmetadata:\nauthor: scarletkc\nsource: <a href=\"https://github.com/scarletkc/agents\">https://github.com/scarletkc/agents</a>\nsummary: \"Review user-facing copy and documentation for clarity, consistency, facts that do not go stale, and no leftover intermediate state.\"</h2>\n<h1>UX Writing &amp; Docs</h1>\n<p>User-visible text is product behavior and carries the same quality bar as\ncode. Every rule below is distilled from a real defect caught in review, and\nkeeps its counter-example because the reasoning is the point. When in doubt,\nre-read the output as the user who just hit the problem.</p>\n<h2>Status output &amp; diagnostics</h2>\n<ul>\n<li><strong>Report effective values, not stored ones.</strong> A status display answers\n\"what will happen when I run this\", so resolve values exactly the way the\nruntime does, including environment variables and layered config.\n<em>Counter-example: a config viewer printed \"API key set: no\" while an env\nvar held the key the next run would actually use.</em></li>\n<li><strong>Diagnostics must stay truthful under failure.</strong> When one config layer\nfails to load, fall back to the most complete state that still loads,\nnever to blank defaults. A diagnostic that misreports is worse than one\nthat aborts. <em>Counter-example: a broken project-level config made a doctor\ncommand check blank defaults and report a missing API key that was in fact\nconfigured; the fake failure buried the real one.</em></li>\n<li><strong>Show deltas, not dumps.</strong> A health/diagnostic command lists what\ndeviates and who set it; the exhaustive listing belongs to the dedicated\ninspect command. Don't make one command duplicate another's job.\n<em>Counter-example: a doctor check printed fifteen \"field: origin\" lines,\nmost of them saying \"global\". One line naming the two real overrides\nreplaced the block.</em></li>\n<li><strong>Annotate at the granularity of the claim.</strong> If one sub-part of a\ncomposite value has a different source or state, say it on the sub-part;\ndon't relabel the whole. <em>Counter-example: an env-injected API key\nrelabeled an entire endpoint block \"(environment)\" although its URL and\nmodel came from a file. The fix was \"key from env\", with the block label\nunchanged.</em></li>\n<li><strong>Re-read neighboring labels after adding metadata.</strong> New suffixes collide\nwith existing value labels. <em>Counter-example: \"Embedding dimensions:\ndefault (default)\", fixed by renaming the value \"auto\".</em></li>\n<li><strong>Machine-readable output is a contract.</strong> Porcelain/TSV/JSON output never\ngains decoration, notices, or annotations; informational text goes to\nstderr or the human-format path. Absence is part of the contract, so write\nthe negative test (<code>\"(project)\" not in stdout</code>).</li>\n<li><strong>Never truncate the payload.</strong> Paths, IDs, and URLs in diagnostics must\nsurvive narrow terminals un-ellipsized (disable auto-wrap/crop for those\nlines); a truncated path cannot be copied into the next command.</li>\n</ul>\n<h2>Error messages</h2>\n<p>Every error answers three questions: what happened, where, and what to do\nnow. The strongest pattern: name the offending file or input, list the\nrejected fields, list the allowed fields, and say where the rejected setting\nbelongs instead. Fail loudly rather than degrade silently; when catching an\nexception purely to suppress a traceback, keep the message intact.</p>\n<h2>Documentation</h2>\n<ul>\n<li><strong>Each document has one responsibility, and it decides what belongs.</strong> A\npage is a durable contract, a proposal, an investigation, a TODO, a dated\nwork order, or a runbook — one of them, not several. Naming that first is\nwhat makes a canonical home decidable: a fact lives on the page whose job\nit is, and every other surface reaches it through a single specific link\ninstead of a partial retelling on each page that happens to touch it. When\ntwo pages both claim to be the detailed spec, the broader responsibility\nkeeps the shared rules and the narrower keeps only what its own surface\nadds. <em>Counter-example: an implementation plan stayed the de-facto spec\nafter shipping, so the rules lived half there and half in the architecture\ndoc; folding the stable rules into the contract and leaving the sequence in\ngit history left one page to trust.</em></li>\n<li><strong>Rationale is a genre of its own.</strong> A how-to answers what to run, a\nreference answers what exists, and why-it-was-built-this-way belongs to a\ndesign record, an ADR, or the pull request that decided it. Answering the\ndesign question inside a usage page pushes the steps the reader came for\nbelow the fold, and the argument is also the part that rots first: the\nimplementation moves on and only the guide still defends the old choice.\nAn explanation produced because someone asked once belongs in that\nanswer, not in a permanent page. <em>Counter-example: a setup guide spent\nits second paragraph on why this queue was chosen over two others; the\nqueue was replaced a release later and the paragraph outlived it.</em></li>\n<li><strong>One canonical home per fact.</strong> Details that change together (field\nlists, precedence chains, supported values) live in exactly one document;\nevery other mention links to it. Legitimate copies: artifacts distributed\nstandalone (a bundled skill file that ships without the repo), and\ngenuinely surface-specific nuance. <em>Counter-example: a seven-field\nallowlist pasted into five docs.</em></li>\n<li><strong>Restating and linking is a bug, not thoroughness.</strong> If a section\nduplicates the canonical content and then ends with \"see X for the full\ncontract\", it already is the full contract. Delete the restatement; keep\nthe link and whatever is specific to this surface.</li>\n<li><strong>Prefer the smallest sufficient edit.</strong> When revising existing text,\npreserve unaffected wording, structure, and rationale. Remove genuine\nduplication, but do not rewrite neighboring prose or compress away useful\ndistinctions without a reason. <em>Counter-example: changing one mandatory\nworkflow into an optional one rewrote several surrounding sections, then\nover-corrected by removing useful context; a few local edits were enough.</em></li>\n<li><strong>Insertion respects adjacency.</strong> Before adding a section, check what the\nsurrounding paragraphs attach to. <em>Counter-example: a new section landed\nbetween a flags table and its output-format footnote, orphaning the\nfootnote in the wrong chapter.</em></li>\n<li><strong>Adjectives need evidence.</strong> \"Recommended\", \"faster\", \"better\" come from\nyour own benchmarks, not optimism. <em>Counter-example: a feature was about\nto ship commented \"# recommended\" while the project's own eval showed it\nlosing to the default on strong models. It shipped as \"optional\".</em></li>\n<li><strong>Every README section has one job.</strong> Positioning sections (\"Why X?\")\ndon't accumulate feature bullets; quick-starts don't explain architecture.\nA README stays lean and links into the docs; detail accumulating there\nusually means it left its canonical home.</li>\n<li><strong>Order a page by what the reader needs first, and split when it stops\nbeing one task.</strong> Open with scope and the authoritative entry points, then\nthe common rules and the main path, and only then exceptions, recovery,\nand change checks. An overview layer summarizes stable semantics and links\ndown; it does not carry field tables, full payloads, or current numbers to\nbuy self-containment. When a page starts demanding that the reader\nunderstand several unrelated tasks, or whole chapters serve only two\nmaintainers, that is the signal to split it — and the split leaves behind\none line of purpose plus the link, never a second copy of the fact.\n<em>Counter-example: a getting-started page opened with the full option\nreference, so the three commands a first-time reader needed sat two\nscreens below it.</em></li>\n<li><strong>Reminders name the most-forgotten item only.</strong> A guideline that\nenumerates every artifact reads as noise and gets skipped whole. \"Update\nwhichever docs the change affects; the bundled skill is the easiest to\nforget\" beats a list of six file types.</li>\n</ul>\n<h2>Facts that go stale</h2>\n<p>Docs are edited on a human cadence, while some facts change on every commit,\ndeploy, or restart. Writing one of those into a long-lived page is not a\nmaintenance burden, it is a defect on a delay: the page turns wrong on its\nown, and nothing fails when it does. Record where the current answer is\nread, not the answer. This is \"report effective values, not stored ones\"\napplied to prose.</p>\n<ul>\n<li><strong>Never snapshot a value that moves faster than the doc.</strong> Long-lived\npages (README, architecture notes, runbooks, domain docs) carry the stable\nmaterial: intent, invariants, boundaries, procedures, failure handling.\nVersion and protocol numbers, image tags, build IDs, deployed commit\nhashes, object and migration counts, expiry dates, and \"currently live /\nnot yet shipped\" claims all change without anyone re-reading the page that\nrepeats them. Those belong in a changelog, in git history, or on the\nrelease ticket, where carrying a date is the point. The rule forbids the\nhand-maintained second copy, not the table: when a page genuinely has to\nshow current values, generate it from the authoritative source at build\ntime so it cannot drift silently. <em>Counter-example: a\nrunbook opened with \"production currently runs 2.3.1\"; four releases later\nan on-call engineer trusted the line and worked through the wrong\nversion's changelog.</em></li>\n<li><strong>A pointer names a symbol, not a repository.</strong> The canonical home for a\nfact is often code rather than a doc, and then the doc's job is to say\nwhere to read it instead of copying the value or the whole field table.\nMake the pointer land: a specific file plus a searchable symbol, function,\ndata key, or heading. \"See the source\", a repo-root link, or a directory\nleaves the reader to re-derive what the sentence promised. If no single\nsymbol owns the fact, that is a code problem surfacing as a doc problem;\nfix the boundary instead of papering over it with a copied table. A\ndirectory is a fair target in two cases only: the fact emerges from an\nordered set with no single-file truth (migrations replayed in sequence),\nor the directory is a catalog some loader enumerates (locales, plugins,\nmaps). Both still owe a searchable selection key — the naming convention,\nthe loader function, the object name.\n<em>Counter-example: \"protocol versions are defined in the networking layer\"\nsent every reader grepping six files, and became a link to\n<code>PROTOCOL_VERSION</code> in <code>net/constants.py</code>.</em></li>\n<li><strong>A doc cannot observe the runtime.</strong> The repository answers how a commit\nis meant to behave; only the running system knows which commit is live,\nwhat is healthy, and which artifact is being served. Docs record the\ncommand or console that answers those questions, never the answer, and\nnever promote merged code to \"deployed\". A successful deploy report is\nevidence on that release's ticket; copying it into a doc converts a\none-time result into a standing hand-sync obligation. <em>Counter-example: a\n\"current environment\" table listing service versions was updated by hand\nafter every deploy, until the deploy where it wasn't, and nothing in CI\ncould notice.</em></li>\n</ul>\n<h2>The final state, not the path to it</h2>\n<p>A deliverable is read by someone who was not in the room while it was made.\nAnything that only holds against the conversation behind it — an option\nthat was considered and dropped, a scope that was corrected, an instruction\nthe requester gave ten minutes ago — reads as noise at best, and at worst\nas a claim about the product. Session context expires faster than the\nartifact carrying it, so this is \"facts that go stale\" applied to the\nconversation rather than to time. The test for any line: does it hold for a\nreader who has never seen that conversation? \"Without the bulk-download\npanel\" does not, because nobody expected one. \"Not <code>json.dumps</code> here, the\npayload has to keep key order for the signature check\" does.</p>\n<ul>\n<li><strong>State what the code does, not what it nearly did.</strong> Titles, summaries,\nand comments describe the shipped behavior; intermediate attempts,\nabandoned options, and negative scope belong to the discussion that\nproduced them, which git history and the review thread already keep.\n<em>Counter-example: a requested cut left the pull request titled \"Add\nexport button (without the bulk-download panel)\", so every reader had to\nunderstand a panel that never existed before reading the one that did.</em></li>\n<li><strong>A comment carries the non-obvious reason only.</strong> What earns the lines\nis a constraint the next reader cannot recover from the code: an ordering\nrequirement, an upstream bug, a platform quirk. A \"why not X\" line\nqualifies when X is what that reader would reach for anyway, not when X\nis merely what this conversation happened to try and discard. Restating\nwhat the code plainly says, or defending it against an alternative nobody\nwould propose, spends attention now and becomes a lie when the code\naround it moves. <em>Counter-example: a helper kept eight lines on why it\nheld no cache, written the moment a reviewer asked for the cache to go;\ntwo rewrites later the paragraph was the only trace of either.</em></li>\n<li><strong>Evidence serves the reader's decision, not the author's doubt.</strong> A\nclaim the reader has to act on — this one is faster, this default is\nsafe, use this over that — owes its basis, and \"adjectives need evidence\"\nabove says where the basis comes from. A statement of what the code does\nowes nothing, because nobody is being asked to believe anything: a cited\nstandard, a benchmark number, or an appeal to consensus attached to it is\nanswering a challenge that was never made. The test is whether removing\nthe line changes what the reader can decide. Cited figures also age\nfaster than the sentence carrying them, and nobody comes back to\nre-measure. <em>Counter-example: a config page defended its default with\n\"benchmarks show a 40% improvement\", measured two majors earlier against\na code path that no longer existed; support was still quoting the\nnumber.</em></li>\n<li><strong>A deliverable does not narrate its own production.</strong> Generated reports,\ndecks, exports, and screens are product content: they carry findings,\nvalues, and instructions, never the implementation notes, method\nrationale, or next steps of whoever produced them. \"This page\ndemonstrates\", \"we could also\", and \"implemented as\" are the producer's\nvoice leaking into the product, and the exceptions are narrow — copy that\ngenuinely is help text or an empty state, and documents explicitly asked\nto record their own methodology. Reasoning has its own homes: the reply\nto the requester, the commit message, the pull request body, a planning\nfile. <em>Counter-example: a generated status deck opened on a slide titled\n\"Approach and next steps for this report\", ahead of the numbers it had\nbeen asked for.</em></li>\n</ul>\n<h2>Sync sweep for behavior changes</h2>\n<p>A behavior change is unfinished until its copy sites agree. Grep for the old\nwording across, in rough order of forgettability:</p>\n<ol>\n<li><code>--help</code> option strings, the most-missed site: a flag's help kept saying\n\"show current configuration\" after the command learned origin labels,</li>\n<li>centralized message/string modules and command docstrings,</li>\n<li>README and docs pages,</li>\n<li>bundled skill files, plugin metadata, MCP tool descriptions (these are UX\nfor agents, and the same rules apply),</li>\n<li>roadmap or status notes describing the old behavior.</li>\n</ol>\n<p>Then re-resolve the links involved: a renamed heading breaks every anchor\naimed at it, a moved file breaks the relative paths pointing at it, and\nneither announces itself in a normal test run.</p>\n<h2>Testing copy</h2>\n<ul>\n<li>Assert flattened text or behavior, not console formatting: consoles wrap\n(~80 columns under test runners), so multi-word substrings split across\nlines. Flatten with <code>\" \".join(output.split())</code> before substring\nassertions.</li>\n<li>Color env leakage: <code>FORCE_COLOR</code> / <code>COLORTERM</code> in the invoking shell make\nrich consoles emit ANSI into captured output; clear them for test runs.</li>\n<li>For machine formats, assert what must be absent, not only what must be\npresent.</li>\n</ul>\n","files":[{"path":"SKILL.md","sizeBytes":16614,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-08-31T16:23:21.871198Z","sha256":"F76328365273221500291CC42DD2A103BC8222B0747F68586E074E409F106288","sizeBytes":7320},"review":null,"source":{"repositoryUrl":"https://github.com/scarletkc/agents","path":"skills/ux-writing","license":"Apache-2.0","commit":"eb55005652d5708f369bde008cc48c71159f9e95","subtreeSha":"5E3383A3C9A6CF53FD2FB226B9B5625D7BF9CC75CD0940887ACF9CCB347B00C9","lastSyncedAt":"2026-10-03T15:23:31.496051Z"},"reviewedAt":"2026-08-31T16:27:43.493068Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/scarletkc/agents/tree/main/skills/ux-writing"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install scarletkc-agents@llmmart"},{"target":"git","command":"git clone https://github.com/scarletkc/agents.git"}]}