Composer autocomplete gets tiered ranking; Tab completes, Enter always sends, click sends unless hinted — partially reversing ADR-0031's popover-Enter-accept precedence¶
The slash-command popover's matcher is a plain case-insensitive substring test
across a command's name, description, and arg_hint, results stay in
catalog order (builtins pinned, then project skills alphabetical), and the
highlighted row is always index 0. Typing /triage therefore highlights
setup-matt-pocock-skills instead of triage — its description happens to
contain the word "triage" and it sorts ahead of triage in catalog order —
and accepting the highlight inserts the wrong skill. Discovery-via-description
is a real, wanted behaviour (a user typing a topic word should still surface a
relevant skill), the bug is only that it can outrank an exact or prefix match
on the command's own name.
Separately, ADR-0031 pinned "Slash-command popover precedence is unchanged":
while the popover is open, Enter (like Tab) accepts the highlighted command
and the send gate is only reached once the popover is closed. That was a
deliberate carry-forward at the time, but in practice it makes sending any
slash command a two-step gesture — Enter inserts /name, and a second
Enter is needed to send, even for a zero-argument command like /clear. Both
complaints were raised and resolved in the same grill session (issue #122,
2026-07-10).
This ADR is a pure frontend diff — no colour/type/radius/shadow change, no
backend/API/SSE/message-schema/provider/migration change, the same class of
change as ADR-0019/0022/0029/0031. It partially reverses ADR-0031's
"Slash-command popover precedence is unchanged" clause (itself inherited from
issue #70): Enter no longer accepts the highlighted command under any
circumstance. Every other ADR-0031 decision — the fine-pointer
matchMedia-gated bare-Enter-sends rule, the IME guard (isComposing /
keyCode === 229), Shift+Enter and Alt+Enter staying an explicit newline,
Cmd/Ctrl+Enter sending everywhere regardless of pointer type or agent state,
and the empty-composer no-send-but-preventDefault rule — stands exactly as
pinned. isComposerSend itself is untouched; this ADR only removes the
popover-acceptance branch that used to run ahead of it.
The decisions, pinned (settled with the maintainer 2026-07-10 via /grill-me — do not re-litigate):
- Tiered ranking in
acMatches. Matches are ranked into four tiers, evaluated in order, with catalog order preserved as a stable sort within a tier and the highlight always staying at index 0 of the ranked list: - exact name match, 2. name prefix match, 3. name substring match,
- description/arg_hint substring match. Query
/triagenow highlightstriage(tier 1) withsetup-matt-pocock-skills(tier 4) still present below it — discovery via description still works, it just never outranks a match on the command's own name. - Tab is the only completion gesture. It inserts
/namefor the highlighted suggestion, keeps focus in the input, and dismisses the popover until the next keystroke re-opens it. Nothing else completes a suggestion. - Enter always sends the input as-is. With the popover open or closed,
Enter falls straight through to the existing
isComposerSendrules (bare Enter on a fine pointer, Cmd/Ctrl+Enter everywhere, IME guard, Shift/Alt+Enter passthrough) and never accepts or auto-completes the highlighted row. This is the deliberate reversal: issue #70 / ADR-0031 had popover-Enter-accept run ahead of the send gate; that branch is now removed fromonKeyDownentirely. - Click on a suggestion sends immediately when it has no
arg_hint; when anarg_hintis present, click instead completes to/nameand focuses the input so the argument can be typed.arg_hintpresence is the one "this command expects more text" signal the popover has, and it now drives both the ranking-independent completion choice and the click behaviour. - ArrowUp/Down navigate, Escape dismisses — unchanged. Neither gesture sends or completes; they only move or clear the highlight.
- No data-model, backend, or
SKILL.mdfrontmatter changes. No newCommandSpecfield, no backend change, no vendored-skill frontmatter edit —arg_hintis read as it already exists today. Hints can be added per-skill later as pain arises, but that is a separate, future decision.
Status¶
Accepted. Partially reverses ADR-0031 (and, through it, the issue #70
decision ADR-0031 had carried forward): Enter no longer accepts a highlighted
slash-command suggestion under any circumstance, popover open or not. Every
other ADR-0031 decision — the fine-pointer bare-Enter gate, the IME guard,
Shift/Alt+Enter newline, Cmd/Ctrl+Enter sending everywhere, and the
empty-composer guard — remains in force, unamended. A pure SPA diff:
web/src/routes/RunChat.tsx (acMatches, onKeyDown, acceptCommand, and
the popover row click handler) and web/src/routes/RunChat.test.tsx. No
Go, API, SSE, message-schema, provider, or migration change.
Considered options¶
- Fuzzy matching (e.g. subsequence or edit-distance scoring) instead of tiered substring ranking. Rejected: out of scope for issue #122 — the reported bug is that a worse substring match outranks a better one, not that substring matching itself is too strict; tiering plain substring results already fixes the reported case with far less surface area than a fuzzy-scoring engine.
- A new
CommandSpec/frontmatter field such assendableto declare whether a command takes arguments. Rejected:arg_hint's presence already encodes exactly that signal today, and adding a second field for the same fact would be a data-model change this ADR explicitly keeps out of scope. - Keep popover-Enter-accept precedence (ADR-0031/#70's status quo) and only
fix ranking. Rejected: ranking alone doesn't address the second complaint
— sending any slash command, even a correctly-highlighted zero-argument one
like
/clear, would still take two Enters. - Extend the same autocomplete/gesture model to the NewRun spawn composer. Rejected as out of scope: that composer has no autocomplete today, and adding one would first need a catalog source to draw from before a run even exists — a separate piece of work.
Consequences¶
RunChat.tsx'sacMatchesis rewritten to rank into the four tiers above instead of a single substring test, with catalog order used only as the within-tier tiebreaker.RunChat.tsx'sonKeyDowndrops the popover Enter/Tab-accept branch entirely; Tab gets its own handler that completes-only (no send), and Enter falls straight through toisComposerSendunconditionally. ArrowUp/Down and Escape are unchanged.acceptCommandis split into a complete-only path (used by Tab and by click-with-arg_hint) and a send path (used by click-without-arg_hint); neither path is reachable from Enter any more.- The popover row click handler now checks
arg_hintto decide whether to send immediately or complete-and-focus. RunChat.test.tsx's "Slash-command autocomplete" block is updated: the old Enter-accepts test and the #70 Enter-precedence test are rewritten for the new contract (Enter sends the raw input, complete or partial, popover open or not), and new tests cover tiered ranking (exact/prefix beats a description match), Tab-completes-without-sending, click-sends when there is noarg_hint, and click-completes-and-focuses when there is one.- Accepted consequence: partial input like
/trifollowed by Enter sends the literal text/tri— completing first with Tab is the user's own responsibility, the same way a typo isn't auto-corrected before sending. - Accepted consequence: clicking
/clearexecutes it in a single click, while the Enter path still requires the full command text/clearto be present in the input — typing (or completing to) the whole word is itself the confirmation step that a one-click affordance skips. - Accepted consequence: clicking
/land-prsends it bare today (it declares noarg_hint); the skill itself prompts for the PR number rather than the composer collecting it first.