diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-05 00:55:21 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-05 00:55:21 -0500 |
| commit | 83bf3cb50c88f91730656a2242141567551063ac (patch) | |
| tree | 069f0d9d52ac6ffe9c4a97a770be460b01e75cd5 /patterns/no-empty-input-as-meaningful.org | |
| parent | 3eed2895b2d23d1d8e468aee6f3dfd8122012fe4 (diff) | |
| download | rulesets-83bf3cb50c88f91730656a2242141567551063ac.tar.gz rulesets-83bf3cb50c88f91730656a2242141567551063ac.zip | |
feat(patterns): add cross-project pattern catalog (six seed patterns)
A good interaction-design pattern surfaces in one project, then gets lost, so the next project re-derives it. Pearl shipped six worked examples and the principle they share, but they sat as four raw notes in docs/design/ with nothing making them reusable.
I added a patterns/ directory, one file per pattern, each with frontmatter (name, principle, problem, tags, source, examples) and a Problem / Do / Anti-pattern / Applicability / Related body. The six seeds: one-prompt-picker-typed-prefix, transient-state-buttons, no-empty-input-as-meaningful, label-matches-behavior, default-most-common-friction-proportional, collapse-orthogonal-prompts. Patterns 3-6 cross-link as facets of one root principle: the choices the user has should all be on screen, accurately labeled, ordered by what they'll most often want, with friction sized to the cost of being wrong.
The catalog lives in rulesets because every project's agent already pulls from here. A thin claude-rules/patterns.md pointer tells the agent the catalog exists and when to consult it, so it reads one pattern on demand instead of carrying all of them. The pointer auto-installs via the Makefile RULES glob. README.org holds the root principle, the frontmatter contract, and the capture-on-landing/promote-on-review intake cadence.
The patterns are .org files with #+KEYWORD frontmatter. The pointer stays .md because the rules layer and the Makefile glob require it. Seed patterns keep their concrete Elisp examples. Generalize on the second caller, not up front.
Diffstat (limited to 'patterns/no-empty-input-as-meaningful.org')
| -rw-r--r-- | patterns/no-empty-input-as-meaningful.org | 44 |
1 files changed, 44 insertions, 0 deletions
diff --git a/patterns/no-empty-input-as-meaningful.org b/patterns/no-empty-input-as-meaningful.org new file mode 100644 index 0000000..0791c3b --- /dev/null +++ b/patterns/no-empty-input-as-meaningful.org @@ -0,0 +1,44 @@ +#+TITLE: No empty input as meaningful +#+SLUG: no-empty-input-as-meaningful +#+PRINCIPLE: If "no value" is a meaningful choice, it belongs in the candidate list, not behind an empty-input convention. +#+PROBLEM: An empty-RET-means-none idiom is an affordance the user has to know but can't see; the only hint lives in the prompt label, not where the eye is looking. +#+TAGS: completing-read prompts affordances sentinel +#+SOURCE: pearl commit 1288c2a (ad-hoc filter builder), handoff note 2026-05-28 +#+EXAMPLES: pearl ad-hoc filter builder + +* Problem + +A prompt lets the user pick a value or pick "no value." The common shortcut is to let empty input mean "none," so RET on an untouched prompt does something silently. The user has to remember the convention, and the only on-screen hint is a parenthetical in the prompt label, which sits away from the candidate list where the eye actually is. + +* Do + +Make the "no value" choice a candidate the user sees and picks. Prepend a sentinel like =[ None. ]= as the first candidate, picked by default RET, with =require-match= on so the user chooses from the list rather than free-typing. + +Before: + +#+begin_example +Team (empty for any): _ +#+end_example + +After: + +#+begin_example +Team: [ None. ] <- first candidate, default RET + Engineering + Design + ... +#+end_example + +The cost is one predicate plus one list-prepender and a defconst for the sentinel string. The predicate also treats nil and empty as the opt-out, so any caller still producing empty input is handled. The win: users stop having to know a rule that isn't on screen. The underlying rule is no hidden affordances: if it's a choice, it's in the list. + +* Anti-pattern + +"Empty input means none/any/skip." Any behavior triggered by the absence of input is invisible. A label hint is not a substitute for the choice being a visible candidate. + +* Applicability + +Every prompt across every project where "none" is a meaningful answer is a candidate. This is the base case the next three patterns sharpen. + +* Related + +The root of patterns 4-6. Pair it with [[file:label-matches-behavior.org][label-matches-behavior]]: a visible sentinel whose label is wrong is no better than a hidden idiom. |
