Agent docs

13 · When a solution is unsuitable

How to tell a solution does not fit, and how request-versus-reality drift means rethink, not one-shot.

A request arrives with a shape already in it: "add a toggle", "make the error sit under the tabs", "give the menu a theme row". Most of the time the shape is right and the build is the work. Some of the time the shape is the request's guess, and building it exactly as typed ships a duplicate, an override, or a control nobody asked for. This doc is the list of tells that a solution does not fit, and the discipline that replaces a one-shot build when the request and the screen disagree. Every tell below is one that has already happened in this repo, cited to where it happened.

Audience: an agent (or person) about to build from a request, before the first edit. Read with the generated do-nots page (docs/DO-NOTS.md) and the "Before you adopt this" checklist on the contract docs (packages/ui/docs/app-shell.md:52).

A. Tells that a solution is unsuitable#

1. It duplicates something a parent already provides. The shell, a layout, a global provider, or the kit already renders it, and the new copy would be the second one. The recorded case: a floating bottom-right theme toggle shipped on the playground landing while the navbar already carried one, and the owner named it an anti-pattern (docs/plan/55-showcase-feedback-3.md:131). The same shape in code is a local Select living beside the kit's, with call sites split between them (docs/SCRIPTURE.md:257). The tell is a search you did not run: grep the shell and the kit for the noun in the request before you build it.

2. It only fits with an override. A kit component needs a selector override, a wrapper hack, or a !important to sit where the page wants it. The recorded case: the compact dropzone's error paragraph appended to the right of the row instead of below it, and the pattern page fixed it with a selector override while the kit change waited for the owner (docs/plan/55-showcase-feedback-3.md:107). An override on a kit primitive is a report against the primitive's contract, not a finished fix; write the override, file the kit gap in the same breath, and say which one the reader is looking at.

3. It invents a seat neither shipping app has. A fixture or a pattern grows a control because it seemed plausible, not because a screen ships it. The recorded case: an account-menu fixture drew a "Theme" row with a switch glyph that did nothing, in a menu neither walmart-mvp nor speedway carries a theme control in, one hour after the owner ruled the navbar toggle is the only one. The rule that catches it is the pattern section's own bar: a composite is proven on a shipping screen and rebuilt here from kit parts, so a slot with no shipping site is a question, not a feature.

4. It rests on a structural assumption you did not check. A sticky table header resolves against the nearest scroll container, so any wrapper between the header and the real scrollport pins it to a box that never moves (docs/design-language/06-tables.md:42). A kit pin behind the version a prop landed in typechecks and fails at runtime (docs/app-patterns/12-primitives-and-rules.md:54). A generator that assumed one paragraph per source line dropped every canon headline whose period was followed by a closing bold marker (scripts/build-do-nots.mjs:104). None of these fail where the assumption was made; they fail two layers away, which is why the assumption has to be named before the build, not found after it.

5. It works in the state you tested and no other. A surface verified in one theme, one viewport, or the happy path only. This repo's bar for done is the running dev app in both themes, dark being the literal theme name versable-dark, because a light-only sign-off has shipped a broken dark theme here more than once (CLAUDE.md:14). "It renders" is a claim about one state.

6. It answers a number or a claim from a partial read. A sed -n 12,50p cut a canon table mid-way and produced "three rows" where there were eight; an adversary caught it, the arithmetic that had called the docs thin did not survive the recount (docs/plan/57-sitting-1-answers.md:34). A solution built on a count is only as good as the read behind the count. Read the whole table, then decide.

B. Request-versus-reality drift#

Drift is the gap between what the request says and what the screen, the kit, or the canon already holds. It is a signal, and the response it asks for is a closer look, not a faster build.

The request names a thing that exists under another name. "Add a copy affordance" when CopyButton exists; "a pill for the file name" when FieldChip tone="file" exists. The move is one grep of the kit's index and the do-nots page before the first edit; if the thing exists, the request is a pointer to it, and the answer is a link or a one-line adoption.

The request's example is one instance of a class. "Remove that parenthetical" usually means every parenthetical of that kind; "fix the toggle on the landing" meant the whole class of duplicated chrome, which is why O3 became a do-nots lens and a checklist rather than one deletion (docs/plan/57-sitting-1-answers.md:35). Fix the class, and say so, so the requester can scope you back if they meant the instance.

The request contradicts a ruling or a canon principle. Build the intent, not the wording, and say the contradiction in one sentence first: "the canon seats the theme toggle in the topbar; I am adding it there rather than floating". Silent compliance ships the requester's slip; silent substitution ships yours. One sentence, then the build.

The request would be answered by a doc that exists but cannot be found. The floating-toggle case was covered by the app-shell contract and by canon doc 8, and it shipped anyway, because a builder who never opens the shell's contract never meets the rule. Findability, not existence, was the gap (docs/DO-NOTS.md:9). When a request re-asks something the docs already settle, the deliverable is an entrance to the doc, not a second copy of it.

The build keeps needing a third exception. One override is a fix, two is a pattern, three is the wrong primitive. When the third exception arrives, stop building around the component and write down which contract is short; the fix belongs upstream and rides the owner's next kit bump.

C. The discipline that replaces the one-shot#

  1. Before the first edit, name the request's shape in one line and grep for the noun: the shell, the kit index (packages/ui/src/index.ts), the do-nots page. If it exists, adopt or link; if a rule names it, cite the rule.
  2. If the request and the screen disagree, say the drift in one sentence with the citation, then build the intent. Do not ask a question a grep answers.
  3. Build the smallest change that fits the contract; when it does not fit without an override, ship the override and file the kit gap beside it.
  4. Exercise the result in every state that matters (both themes, the empty and error states, the viewport the request came from) before calling it done.
  5. When you catch a tell from section A in your own build, treat it as the deliverable's first line, not a footnote: what fit, what did not, and what was left for the owner.

The scripture's acknowledged failure modes (docs/SCRIPTURE.md:236) are the long form of this list; this doc is the version to read on the way in.

@versable-git/ui · reference, canon, and method, read in place