From 96f0a5f19672a4ed0eeba3a511a4ff30bcfbd61b Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Thu, 28 May 2026 01:29:15 -0500 Subject: chore(intake): file pearl pattern-catalog and codex runtime spec as TODOs Moved three inbox notes into docs/design/ so the task body links survive: pearl's two pattern-catalog handoffs and codex's v0 generic-agent-runtime spec. Added two corresponding TODOs under Rulesets Open Work, both [#C]. --- .../2026-05-27-pattern-catalog-pearl-notes.org | 50 +++ .../2026-05-28-generic-agent-runtime-spec.org | 471 +++++++++++++++++++++ .../2026-05-28-pattern-catalog-no-empty-input.org | 59 +++ 3 files changed, 580 insertions(+) create mode 100644 docs/design/2026-05-27-pattern-catalog-pearl-notes.org create mode 100644 docs/design/2026-05-28-generic-agent-runtime-spec.org create mode 100644 docs/design/2026-05-28-pattern-catalog-no-empty-input.org (limited to 'docs') diff --git a/docs/design/2026-05-27-pattern-catalog-pearl-notes.org b/docs/design/2026-05-27-pattern-catalog-pearl-notes.org new file mode 100644 index 0000000..f6d4a8c --- /dev/null +++ b/docs/design/2026-05-27-pattern-catalog-pearl-notes.org @@ -0,0 +1,50 @@ +#+TITLE: UI pattern standardization across projects +#+DATE: [2026-05-27 Wed] +#+SOURCE: pearl issue-sources Phase-5 verify walk-through + +* Context + +Surfaced during pearl issue-sources verify on 2026-05-27. A pattern landed in pearl that Craig liked and asked how to standardize across projects so the next project benefits. This is the cross-project meta-question he wanted filed somewhere his agenda would catch it. + +* Two patterns worth naming + +** "All info you need, in one completing-read" + +What pearl's =pearl-pick-source= does: every fetch source (Linear favorites + saved queries) goes into one =completing-read= with a typed prefix and the name. The candidate string reads as =[view] Active bugs= / =[project] Platform= / =[saved] My open=, sorted by Linear's =sortOrder= then alphabetically. The user sees kind, identity, and natural rank in one scan, picks once, and is done. No second prompt to disambiguate, no mode switch, no menu chain. + +Cost was a small helper that maps source-plist to display string. The win was the picker collapsing to one decision rather than three. + +The pattern generalizes anywhere a project has N candidates and the *kind* of candidate matters as much as the name. Pearl reaches for it again in the planned ad-hoc builder rewrite and in =pearl-refine-current-source=. + +** The "magit transient" pattern (Craig's specific reference) + +A bottom-popup screen with toggleable button rows showing current state, with key bindings printed inline, and a single keybinding to summon it. The reference is magit's =transient.el= library, which is actual library code rather than just a UI convention. Craig described the music-player UX equivalent. The win is "all the levers in one place, with affordances visible, and one chord to get there from anywhere." + +Pearl uses transient lightly today (=pearl-menu= behind =C-; L m=) but doesn't yet exploit toggleable state-indicator buttons. The ad-hoc builder rewrite would be a natural place. + +* The meta-question + +Craig's actual question isn't about either pattern. It's: *how do good patterns travel from project A to project B?* + +Three angles worth thinking through: + +1. *A pattern catalog.* A written record of patterns that worked, with the context that made each one work. Lives somewhere durable. Without it, every project re-derives the same patterns from scratch and the discoveries don't compound. + +2. *A surfacing mechanism.* Even with a catalog, how does the right pattern show up at the right moment? "You're about to write a multi-prompt completing-read chain. Consider the one-prompt + typed-prefix shape instead." A passive catalog only helps if Craig or the agent thinks to consult it. Active surfacing is the harder problem. + +3. *Where patterns live.* Three plausible homes: this rulesets repo (next to existing rules + skills), a Linear-style document workspace, or per-project notes that cross-link. Each shapes who reads them and when. Rulesets has the agent-visibility advantage. Every session loads it. + +* My read on spec scope + +Brainstorm-level, not implementation-ready. The questions above aren't one-line decisions: + +- Does the catalog get a structured format (one pattern per file, frontmatter, slug) or a free-form doc? +- Is the surfacing mechanism agent-driven (the model spots the pattern opportunity) or human-driven (Craig grep-searches when he feels stuck)? +- Does the catalog include anti-patterns (things that didn't work, with the reason) or only what worked? +- How do patterns get into the catalog? Every time one lands, or batch reviews on cadence? + +A one-page spec along the lines of pearl's =docs/issue-sources-spec.org= (problem + design + open questions + acceptance) would land the shape before any implementation. Recommend doing that before writing any catalog file or any agent prompt change. + +* What I might be pulled in for + +Craig flagged he'd work on this with the rulesets project and that spec-reviews might come back this way. Standing by for those. diff --git a/docs/design/2026-05-28-generic-agent-runtime-spec.org b/docs/design/2026-05-28-generic-agent-runtime-spec.org new file mode 100644 index 0000000..8c16043 --- /dev/null +++ b/docs/design/2026-05-28-generic-agent-runtime-spec.org @@ -0,0 +1,471 @@ +#+TITLE: Spec: Generic Agent Runtime Support for rulesets +#+AUTHOR: Codex +#+DATE: 2026-05-28 +#+STARTUP: showall + +* Introductory note + +Craig asked for a design pass on making =rulesets= generic rather than +Claude-Code-specific. The motivating case is offline operation: if he is on a +laptop without network, a local LLM should still be able to use the same project +structure, workflows, memory, and cross-agent conventions. The design also needs +to support two different LLMs running in the same project at the same time, +without trampling each other's live session state. + +I read the current =rulesets= checkout and found that the reusable core is +already there: =.ai/= workflows, scripts, cross-agent comms, inboxes, and +project startup structure are not inherently Claude-specific. The Claude +assumptions live mostly in naming, install destinations, launcher behavior, +per-language bundle layout, hook APIs, and a single active +=.ai/session-context.org= file. + +Hardware notes: + +- This machine is the high-end local-LLM target: AMD Ryzen AI Max+ 395, 128 GiB + RAM, Radeon 8060S / Strix Halo unified memory. For offline agentic coding, I + recommend installing =Qwen3-Coder-30B-A3B-Instruct-GGUF= as the default local + coding model, preferably =Q6_K= on this machine and =Q4_K_M= as the compatibility + quant. It is code-specialized, Apache-2.0, and its GGUF files fit comfortably. + For a stronger general fallback on this machine, also install + =Qwen3-Next-80B-A3B-Instruct-GGUF= =Q4_K_M=; it is not as code-specialized but + gives a much larger model with long context and still fits the 128 GiB system. +- =velox= hardware from =ssh velox inxi -C -G -m -S --filter=: Intel i7-1370P, + 64 GiB DDR4, Intel Iris Xe integrated graphics. For that machine, the strongest + model I would recommend as normal offline coding stock is + =Qwen3-Coder-30B-A3B-Instruct-GGUF= =Q4_K_M=. It should fit in RAM with room for + context, but expect CPU-class latency. Also install an 8B fallback for quick + edits and low-latency triage. + +Suggested archsetup handoff: ask =archsetup= to install the runtime stack +(=llama.cpp= with Vulkan/CPU support, optionally =ollama= as a simple manager), +create a shared model cache, and prefetch the model set above during normal +machine setup when network is available. + +Sources checked: + +- [[https://huggingface.co/Qwen/Qwen3-Next-80B-A3B-Instruct-GGUF][Qwen3-Next-80B-A3B-Instruct-GGUF model card]]: Q4_K_M is 48.4 GB, native + context length is 262,144 tokens, Apache-2.0. +- [[https://huggingface.co/tensorblock/Qwen_Qwen3-Coder-30B-A3B-Instruct-GGUF][Qwen3-Coder-30B-A3B-Instruct GGUF quant listing]]: Q4_K_M is 18.557 GB, + Q5_K_M is 21.726 GB, Q6_K is 25.093 GB. +- [[https://huggingface.co/Qwen/Qwen3-Next-80B-A3B-Instruct-GGUF][Qwen3-Next model overview]]: 80B total parameters, 3B active, GGUF support via + =llama.cpp= / =llama-cpp-python=. +- [[https://en.wikipedia.org/wiki/Llama.cpp][llama.cpp overview]]: supports Vulkan, HIP/ROCm, OpenCL, CPU, and other + backends. For this hardware class, keep the implementation backend-swappable. + +* Status + +Draft v0. This is not an implementation plan yet; it is a product/architecture +spec for the next =rulesets= refactor. + +* Problem + +=rulesets= is named and wired as a Claude Code rules distribution: + +- Global install targets =~/.claude/skills=, =~/.claude/rules=, + =~/.claude/hooks=, and =~/.claude/settings.json=. +- Per-project language bundles copy into =.claude/= and seed =CLAUDE.md=. +- The launcher =claude-templates/bin/ai= hard-codes =CLAUDE_CMD=claude= and + requires the =claude= binary. +- Template documentation says "Claude" throughout =protocols.org=, + =startup.org=, and the README. +- Hook scripts and settings assume Claude Code's hook protocol and + =$CLAUDE_PROJECT_DIR=. +- The active session file is a singleton =.ai/session-context.org=, which is + unsafe when two agents operate in the same project simultaneously. + +The result: the good project structure is portable in principle but not in +practice. A local offline model can read files, but there is no generic runtime +contract that tells it where to load rules from, where to record live state, how +to avoid another agent's context file, or how to use the same launcher and +project discovery flow. + +* Goals + +- Preserve =.ai/= as the project-neutral workflow, memory, scripts, inbox, and + cross-agent layer. +- Support multiple runtimes: + - Claude Code as the existing adapter. + - Codex/OpenAI-compatible hosted agents. + - Local OpenAI-compatible agents backed by =llama.cpp= / =ollama= / LM Studio. +- Allow two or more agents to work in the same project concurrently without + sharing a live session-context file. +- Keep current Claude workflows working during migration. +- Make language bundles and team overlays installable for more than one runtime. +- Make offline use a first-class path: rules, workflows, launcher, model cache, + and local endpoint all work with no network after setup. + +* Non-goals for v1 + +- No attempt to make every Claude hook feature work identically in every runtime. + Runtimes expose different hook/event APIs. +- No automatic prompt translation that rewrites every rule into every vendor's + preferred style. V1 should install common rules plus small runtime adapters. +- No local model benchmarking harness. Pick sensible defaults and make the model + inventory configurable. +- No forced rename of existing =.claude/= installations in existing projects. + Compatibility matters. + +* Current-state findings + +** Project-neutral pieces + +These can remain conceptually unchanged: + +- =.ai/protocols.org= as the behavioral entry point. +- =.ai/workflows/= and =.ai/scripts/= as synced canonical project tooling. +- =.ai/project-workflows/= and =.ai/project-scripts/= as project-owned extension + points. +- =inbox/= and =inbox/from-agents/= as human and agent inboxes. +- Cross-agent message protocol and scripts. They say "agent" already and are + mostly model-neutral. + +** Claude-specific pieces + +Observed files and assumptions: + +- =README.org= describes "Claude Code skills, rules, and per-language project + bundles." +- =Makefile= uses =SKILLS_DIR=$(HOME)/.claude/skills=, + =RULES_DIR=$(HOME)/.claude/rules=, =HOOKS_DIR=$(HOME)/.claude/hooks=, and + installs =.claude= config. +- =Makefile deps= installs =@anthropic-ai/claude-code= and checks =claude=. +- =scripts/install-lang.sh= copies common rules into =PROJECT/.claude/rules=, + copies language-specific =claude/= directories, and seeds =CLAUDE.md=. +- =scripts/sync-language-bundle.sh= fingerprints bundles by + =PROJECT/.claude/rules= files. +- =scripts/install-team.sh= installs team overlays into =PROJECT/.claude/rules=. +- =scripts/audit.sh= calls the canonical source =claude-templates/.ai=. +- =claude-templates/bin/ai= requires =claude= and launches + =claude ""= in tmux. +- =languages/elisp/CLAUDE.md= is the project instruction template. +- =languages/elisp/claude/settings.json= uses Claude Code hooks and + =$CLAUDE_PROJECT_DIR=. + +* Proposed model + +** Vocabulary + +- *Core* — runtime-neutral rules, workflows, scripts, and project conventions. +- *Runtime* — an agent implementation: =claude=, =codex=, =local-openai=, + =aider-local=, etc. +- *Runtime adapter* — install paths, hook wiring, command template, instruction + filename, and limitations for one runtime. +- *Agent instance* — one live process/session in one project, identified by + runtime + host + project + unique suffix. + +** Directory model + +Keep =.ai/= as the stable project-local core. + +Change active session state from a singleton: + +#+begin_example +.ai/session-context.org +#+end_example + +to an active-session directory: + +#+begin_example +.ai/session-context.d/ + .org +.ai/sessions/ + YYYY-MM-DD-HH-MM--.org +#+end_example + +Recommended =agent-id= shape: + +#+begin_example +... +#+end_example + +Examples: + +#+begin_example +pearl.org-drill.claude.a83f +pearl.org-drill.local-qwen30b.19ca +velox.archsetup.local-qwen30b.7712 +#+end_example + +Compatibility rule: if exactly one active context exists, tools may expose a +temporary =.ai/session-context.org= symlink or legacy copy for old workflows. +New workflows should read/write by =AI_AGENT_ID=. + +** Runtime manifest + +Add a repository-level runtime manifest: + +#+begin_example +runtimes/ + claude.toml + codex.toml + local-openai.toml +#+end_example + +Each runtime defines: + +#+begin_src toml +id = "local-openai" +display_name = "Local OpenAI-compatible agent" +command = "aider" +args = ["--model", "openai/qwen-local", "--openai-api-base", "http://127.0.0.1:11434/v1"] +requires_network = false +project_instruction_files = ["AGENTS.md", ".ai/protocols.org"] +global_install_root = "~/.config/rulesets/runtimes/local-openai" +project_install_dir = ".agents/local-openai" +supports_hooks = "wrapper" +supports_mcp = false +supports_subagents = false +#+end_src + +The manifest lets the launcher and install scripts reason about a runtime +without hard-coding Claude paths. + +** Source layout + +Refactor source directories toward: + +#+begin_example +agent-rules/ # former claude-rules; runtime-neutral where possible +skills/ # skills with runtime support metadata +ai-templates/.ai/ # former claude-templates/.ai +runtimes/claude/ # Claude adapter +runtimes/codex/ # Codex adapter +runtimes/local-openai/ # local model adapter +languages/elisp/common/ # common language bundle material +languages/elisp/runtimes/claude/ +languages/elisp/runtimes/local-openai/ +teams/deepsat/common/ +teams/deepsat/runtimes/claude/ +#+end_example + +Do not require a big-bang rename. V1 can support aliases: + +- =claude-rules/= remains as a compatibility symlink or wrapper around + =agent-rules/=. +- =claude-templates/= remains as an alias for =ai-templates/= until all startup + workflows are updated. +- =languages//claude/= remains supported by the Claude adapter. + +** Install behavior + +Replace "install Claude tooling" with "install runtime adapter": + +#+begin_example +make install-runtime RUNTIME=claude +make install-runtime RUNTIME=local-openai +make install-lang LANG=elisp PROJECT=~/code/foo RUNTIME=claude +make install-lang LANG=elisp PROJECT=~/code/foo RUNTIME=local-openai +#+end_example + +Claude adapter: + +- Global: =~/.claude/skills=, =~/.claude/rules=, =~/.claude/hooks=. +- Project: =.claude/= and =CLAUDE.md=. +- Hook API: Claude Code =settings.json=. + +Local OpenAI adapter: + +- Global: =~/.config/rulesets/local-openai/= and model server config. +- Project: =.agents/local-openai/= plus =AGENTS.md= or + =.ai/runtime/local-openai/instructions.md=. +- Hook API: wrapper-level checks only. If the local CLI has no hook protocol, + hooks become documented commands or wrapper pre/post actions. + +Codex adapter: + +- Project instruction file should be =AGENTS.md= where supported. +- Runtime-specific config lives under =.agents/codex/= or the tool's native + config path. + +** Launcher behavior + +Refactor =claude-templates/bin/ai= into a generic launcher, still named =ai=: + +#+begin_example +ai # choose project and default runtime +ai --runtime claude . +ai --runtime local-openai . +ai --runtime local-qwen30b ~/code/org-drill +ai --attach +ai --list-runtimes +#+end_example + +Launcher responsibilities: + +- Discover projects by =.ai/protocols.org=, not by "Claude-template project." +- Select runtime from: + - explicit =--runtime=, + - project default in =.ai/runtime.toml=, + - host default in =~/.config/rulesets/runtime.toml=. +- Create =AI_AGENT_ID= before launch. +- Export: + - =AI_AGENT_ID= + - =AI_RUNTIME= + - =AI_PROJECT_DIR= + - =AI_SESSION_CONTEXT=.ai/session-context.d/$AI_AGENT_ID.org= +- Use tmux window names that include runtime when needed: + - =org-drill= if only one agent for the project. + - =org-drill:claude= and =org-drill:local-qwen30b= if multiple agents exist. +- Pass a runtime-appropriate opening instruction: + - Claude: current command-line prompt. + - Local agent: prompt file or initial message that says to read + =.ai/protocols.org= and use =AI_SESSION_CONTEXT=. + +** Session-context contract + +Every runtime must obey: + +- Never write the legacy singleton when =AI_SESSION_CONTEXT= is set. +- Create the context file lazily on the first state-mutating turn. +- Archive to =.ai/sessions/= with the =agent-id= in the filename. +- Include runtime and model metadata in frontmatter: + +#+begin_example +#+TITLE: Session context +#+AGENT_ID: pearl.org-drill.local-qwen30b.19ca +#+RUNTIME: local-openai +#+MODEL: Qwen3-Coder-30B-A3B-Instruct-Q6_K +#+HOST: pearl +#+STARTED: 2026-05-28T... +#+end_example + +Startup workflow changes: + +- Check =.ai/session-context.d/*.org=, not only =.ai/session-context.org=. +- If the current =AI_AGENT_ID= has a live file, recover it. +- If other active files exist, surface them as "other active agents" but do not + read them wholesale unless needed. This prevents context contamination. + +** Cross-agent updates + +The existing cross-agent protocol can stay, but add optional fields: + +#+begin_example +#+SENDER_AGENT_ID: pearl.org-drill.claude.a83f +#+SENDER_RUNTIME: claude +#+TARGET_AGENT_ID: pearl.org-drill.local-qwen30b.19ca +#+TARGET_RUNTIME: local-openai +#+MODEL: Qwen3-Coder-30B-A3B-Instruct-Q6_K +#+end_example + +Destination syntax can remain =machine.project= for project-level delivery. +Add =machine.project.agent-id= as an optional targeted form when two agents in +the same project are both active. + +Receivers should ignore messages targeted at another =TARGET_AGENT_ID= unless +the user explicitly asks them to take over. + +** Hook and validation strategy + +V1 should not pretend all runtimes have Claude's hooks. + +Define hook levels: + +| Level | Meaning | +|-------+---------| +| =native= | Runtime has an event/hook API; install native config. | +| =wrapper= | =ai= launcher or helper scripts run checks around common actions. | +| =manual= | Rules document the verification commands; no enforcement. | + +Language bundles should declare which hooks are required and which are advisory. +For local runtimes, start with =manual= plus project-level test commands. Add +=wrapper= only where the local agent CLI can route edits through a known command. + +** Local model runtime + +Install a host-level local model service: + +- Preferred low-level runtime: =llama.cpp= server with OpenAI-compatible API. +- Optional manager: =ollama= for simpler model lifecycle where its model catalog + is enough. +- Model cache: =~/.local/share/llm/models= or =/srv/models/llm=. +- Ports: + - =127.0.0.1:11434= for =ollama= if installed. + - =127.0.0.1:8081= for =llama-server= default coding model. + - =127.0.0.1:8082= for larger/general model when running simultaneously. + +Host model recommendations: + +| Host | Hardware | Default offline coding model | Larger/secondary model | +|------+----------+------------------------------+------------------------| +| current high-end machine | Ryzen AI Max+ 395, 128 GiB unified RAM, Radeon 8060S | =Qwen3-Coder-30B-A3B-Instruct-GGUF Q6_K= | =Qwen3-Next-80B-A3B-Instruct-GGUF Q4_K_M= | +| velox | i7-1370P, 64 GiB RAM, Intel Iris Xe | =Qwen3-Coder-30B-A3B-Instruct-GGUF Q4_K_M= | 8B fallback for speed | + +Rationale: + +- The Qwen3-Coder 30B GGUF sizes leave enough headroom for context and a second + agent on both machines. +- The high-end machine can also carry Qwen3-Next 80B Q4_K_M at 48.4 GB, useful + for long-context planning or general reasoning offline. +- =velox= is memory-capable but GPU-limited; Qwen3-Coder 30B Q4_K_M is the + strongest practical coding default before latency becomes the dominant pain. + +* Migration plan + +** Phase 1: Add runtime identity without renaming everything + +- Teach =ai= launcher to set =AI_AGENT_ID=, =AI_RUNTIME=, =AI_PROJECT_DIR=, and + =AI_SESSION_CONTEXT=. +- Update startup/wrap-up workflows to prefer =AI_SESSION_CONTEXT=. +- Keep legacy =.ai/session-context.org= fallback. +- Add tests for two simultaneous session-context files. + +** Phase 2: Introduce runtime manifests and generic install commands + +- Add =runtimes/claude.toml= and make current install behavior data-driven. +- Add =runtimes/local-openai.toml= with command templates. +- Add =make install-runtime= and keep =make install= as Claude-compatible alias. + +** Phase 3: Split common language bundles from runtime adapters + +- Move runtime-neutral language rules into =languages//common=. +- Keep Claude-specific settings/hooks under =languages//runtimes/claude=. +- Add local-openai adapter docs/instructions for at least elisp. + +** Phase 4: Rename user-facing docs + +- Rename =claude-templates= to =ai-templates= after compatibility aliases exist. +- Rename =claude-rules= to =agent-rules= after scripts no longer hard-code it. +- Update docs from "Claude should" to "the active agent should" where the rule is + runtime-neutral. +- Keep a short Claude adapter README for Claude-only behavior. + +** Phase 5: Local model install handoff + +- Send archsetup an inbox note requesting local model runtime support. +- After archsetup lands it, teach =rulesets doctor= to verify: + - =llama-server= or =ollama= installed. + - configured model files exist. + - configured OpenAI-compatible endpoint can answer a smoke prompt. + +* Test strategy + +- Unit-test launcher runtime selection and =AI_AGENT_ID= generation. +- Unit-test session-context path generation and archival names. +- Integration-test two fake runtimes launching the same project into distinct + context files. +- Test =sync-language-bundle.sh= compatibility for legacy Claude bundles. +- Test install-lang for: + - =RUNTIME=claude= writes =.claude/= and =CLAUDE.md=. + - =RUNTIME=local-openai= writes =.agents/local-openai/= and does not touch + =.claude/=. +- Test startup workflow examples or scripts so they look for + =session-context.d= without breaking old projects. +- Test cross-agent targeted messages with =TARGET_AGENT_ID=. + +* Open decisions + +- What should the generic project instruction file be: =AGENTS.md=, + =AI.md=, or runtime-specific only? +- Should =.ai/session-context.org= become a symlink to the current agent's file, + or should it disappear after migration? +- Should =rulesets= standardize on =llama.cpp= only, or support =ollama= as the + default beginner-friendly local runtime? +- Which local agent CLI should be the first supported offline editor: + =aider=, =opencode=, a simple custom wrapper, or something else? + +* Recommended next step + +Start with Phase 1 only. The singleton session-context file is the immediate +correctness issue for simultaneous agents, and it can be fixed without renaming +the whole repository or disrupting current Claude installs. diff --git a/docs/design/2026-05-28-pattern-catalog-no-empty-input.org b/docs/design/2026-05-28-pattern-catalog-no-empty-input.org new file mode 100644 index 0000000..d2b19c8 --- /dev/null +++ b/docs/design/2026-05-28-pattern-catalog-no-empty-input.org @@ -0,0 +1,59 @@ +#+TITLE: Pattern catalog — "no empty input as meaningful" (third worked example) +#+DATE: [2026-05-28 Thu] +#+SOURCE: pearl issue-sources verify, follow-up to earlier UI-patterns handoff + +* Follow-up note + +A third worked example landed in pearl on 2026-05-28, shipped as commit =1288c2a=. Adding it here so the rulesets catalog discussion has three concrete examples to ground design questions in. + +* Pattern: "no empty input as meaningful" + +Whenever a prompt offers the user a choice between picking a value and picking "no value," the no-value option belongs in the candidate list, not behind empty input. + +** Before + +The pearl ad-hoc filter builder had five sequential =completing-read= prompts (team, state, project, labels, assignee). Four of them relied on the "empty input means no constraint" convention, with a parenthetical hint in the label: + +#+begin_example +Team (empty for any): _ +State (empty for any): _ +Project (empty for any): _ +Labels (comma-separated, empty for none): _ +#+end_example + +The user had to remember the convention. RET on an unselected prompt did something silently. The label hint was the only on-screen affordance, and it lived in the prompt rather than the candidate list, where the eye was actually looking. + +** After + +#+begin_example +Team: [ None. ] <-- first candidate, picked by default RET + Engineering + Design + Marketing + ... +#+end_example + +Same logical behavior. The "no constraint" choice is now a candidate the user sees and picks, not a convention they have to recall. =require-match= is on, so the user picks from the list rather than free-typing. + +** What changed in code + +Two helpers (one predicate, one list-prepender) and a defconst hold the sentinel string. Each =completing-read= wraps its candidate list with the prepender and treats the sentinel as the same logical opt-out empty input was before. Trivial back-compat: the predicate also returns true for nil and empty string, so any caller that still produces an empty answer (e.g. =completing-read-multiple= returning nothing) is handled. + +** Why it matters for the catalog + +The principle is small but has a lot of surface area: every prompt across every project where "none" is a meaningful choice is a candidate. The cost is one helper per project (or one shared helper); the win is users stop having to know rules that aren't on screen. + +Worth thinking about as a catalog entry alongside the earlier two examples (one-prompt picker with typed prefix, magit-transient state-buttons). All three share the same underlying principle: *no hidden affordances; if it's a choice, it's in the list.* + +* What the catalog might need to capture for each pattern + +Surfacing this third example clarifies what a catalog entry probably needs to carry. Tentative shape: + +- One-sentence statement of the principle +- The before / after the pattern replaced +- The shape of the code change (small helpers, single source of truth) +- Where the pattern applies (the surface area predicate) +- Anti-pattern variants to avoid +- A link or grep target for an in-the-wild example + +If that shape stabilizes, the catalog has a chance of staying scannable as it grows. -- cgit v1.2.3