feat(chat): slash autocomplete — tiered ranking, Tab completes / Enter sends input / click sends #122

Closed
opened 2026-07-10 21:42:58 +02:00 by dominik.polakovics · 2 comments

Problem

Two composer-autocomplete complaints (grill session 2026-07-10):

  1. Wrong skill wins. Matching is a plain case-insensitive substring test across name, description, and arg_hint, results stay in catalog order (builtins pinned, then project skills alphabetical), and the highlight is always index 0. Typing /triage therefore highlights setup-matt-pocock-skills — its description contains the word "triage" and it sorts before triage. Enter accepts the highlighted row → wrong skill inserted.
  2. Two-step send is slow. Enter with the popover open only accepts (/name inserted); a second Enter is needed to send, even for commands that take no arguments.

Agreed design

1. Tiered ranking (acMatches)

Rank matches by quality, stable catalog order within each tier, highlight stays index 0:

  1. exact name match
  2. name prefix match
  3. name substring match
  4. description / arg_hint substring match

Query "triage" → /triage first, setup-matt-pocock-skills below it (discovery via description still works, it just never outranks a name match).

2. Gesture model

  • Tab — completes the highlighted suggestion to /name (only completion gesture).
  • Enter — sends whatever is currently in the input, as-is. It never accepts/auto-completes the highlight, even with the popover open. It falls through to the existing isComposerSend rules (fine-pointer bare Enter sends, Shift/Alt+Enter newline, IME guard, Cmd/Ctrl+Enter always sends). ⚠️ This deliberately reverses the issue #70 decision that popover-Enter-accept takes precedence over send.
  • Click on a suggestion — sends that command immediately if it has no arg_hint; if arg_hint is present, click completes to /name and focuses the input so the argument can be typed. arg_hint presence is the one "expects more text" signal.
  • ArrowUp/Down navigate, Escape dismisses — unchanged.

3. No data-model or frontmatter changes

No new CommandSpec fields, no backend changes, no SKILL.md edits (repo skills are vendored via setup-matt-pocock-skills; hints can be added per-skill later as pain arises).

Accepted consequences

  • Partial input like /tri + Enter sends the literal text /tri (user's responsibility; Tab first to complete).
  • Clicking /clear executes it immediately (one click). Enter-path still requires the full text /clear in the input, which is its own confirmation.
  • Clicking /land-pr sends it bare (no hint declared today); the skill asks for the PR number itself.

Out of scope

  • NewRun spawn composer (has no autocomplete; would need a catalog source before a run exists).
  • Fuzzy matching.

Where

  • web/src/routes/RunChat.tsxComposer: acMatches (~1523-1530, ranking), onKeyDown (~1561-1590, drop Enter-accept branch, keep Tab/arrows/Escape), acceptCommand (~1540-1548, split complete vs send paths), popover row click handler (~1656-1682).
  • web/src/routes/RunChat.test.tsx — "Slash-command autocomplete" block (~2664-2806): the Enter-accepts test (~2725) and the #70 Enter-precedence test (~2773) must be rewritten to the new model; add ranking tests (exact/prefix beats description match), Tab-completes, Enter-sends-raw-input, click-sends-no-hint vs click-completes-hinted.

Test plan

  • Ranking: catalog with setup-matt-pocock-skills (description containing "triage") + triage → query "triage" highlights /triage; query "tri" ditto (prefix tier).
  • Tab completes highlighted to /name , does not send.
  • Enter with popover open sends the raw input text (both partial /tri and completed /triage).
  • Click on no-hint command posts it as a reply; click on hinted command inserts /name and does not post.
  • Escape/arrow behavior and Cmd/Ctrl+Enter unchanged.
## Problem Two composer-autocomplete complaints (grill session 2026-07-10): 1. **Wrong skill wins.** Matching is a plain case-insensitive substring test across `name`, `description`, and `arg_hint`, results stay in catalog order (builtins pinned, then project skills alphabetical), and the highlight is always index 0. Typing `/triage` therefore highlights `setup-matt-pocock-skills` — its *description* contains the word "triage" and it sorts before `triage`. Enter accepts the highlighted row → wrong skill inserted. 2. **Two-step send is slow.** Enter with the popover open only accepts (`/name ` inserted); a second Enter is needed to send, even for commands that take no arguments. ## Agreed design ### 1. Tiered ranking (`acMatches`) Rank matches by quality, stable catalog order within each tier, highlight stays index 0: 1. exact name match 2. name prefix match 3. name substring match 4. description / arg_hint substring match Query "triage" → `/triage` first, `setup-matt-pocock-skills` below it (discovery via description still works, it just never outranks a name match). ### 2. Gesture model - **Tab** — completes the highlighted suggestion to `/name ` (only completion gesture). - **Enter** — sends **whatever is currently in the input, as-is**. It never accepts/auto-completes the highlight, even with the popover open. It falls through to the existing `isComposerSend` rules (fine-pointer bare Enter sends, Shift/Alt+Enter newline, IME guard, Cmd/Ctrl+Enter always sends). ⚠️ This deliberately reverses the issue #70 decision that popover-Enter-accept takes precedence over send. - **Click on a suggestion** — sends that command immediately **if it has no `arg_hint`**; if `arg_hint` is present, click completes to `/name ` and focuses the input so the argument can be typed. `arg_hint` presence is the one "expects more text" signal. - ArrowUp/Down navigate, Escape dismisses — unchanged. ### 3. No data-model or frontmatter changes No new `CommandSpec` fields, no backend changes, no `SKILL.md` edits (repo skills are vendored via setup-matt-pocock-skills; hints can be added per-skill later as pain arises). ### Accepted consequences - Partial input like `/tri` + Enter sends the literal text `/tri` (user's responsibility; Tab first to complete). - Clicking `/clear` executes it immediately (one click). Enter-path still requires the full text `/clear` in the input, which is its own confirmation. - Clicking `/land-pr` sends it bare (no hint declared today); the skill asks for the PR number itself. ### Out of scope - NewRun spawn composer (has no autocomplete; would need a catalog source before a run exists). - Fuzzy matching. ## Where - `web/src/routes/RunChat.tsx` — `Composer`: `acMatches` (~1523-1530, ranking), `onKeyDown` (~1561-1590, drop Enter-accept branch, keep Tab/arrows/Escape), `acceptCommand` (~1540-1548, split complete vs send paths), popover row click handler (~1656-1682). - `web/src/routes/RunChat.test.tsx` — "Slash-command autocomplete" block (~2664-2806): the Enter-accepts test (~2725) and the #70 Enter-precedence test (~2773) must be rewritten to the new model; add ranking tests (exact/prefix beats description match), Tab-completes, Enter-sends-raw-input, click-sends-no-hint vs click-completes-hinted. ## Test plan - Ranking: catalog with `setup-matt-pocock-skills` (description containing "triage") + `triage` → query "triage" highlights `/triage`; query "tri" ditto (prefix tier). - Tab completes highlighted to `/name `, does not send. - Enter with popover open sends the raw input text (both partial `/tri` and completed `/triage`). - Click on no-hint command posts it as a reply; click on hinted command inserts `/name ` and does not post. - Escape/arrow behavior and Cmd/Ctrl+Enter unchanged.
Author
Owner

This was generated by AI while landing a PR.

Land audit — PR #123main: PASS

  • CI: ci / native success (Successful in 5m26s, run #128) — relied on as the vouching signal; not re-run. PR body reports 594 tests / 37 files, eslint, tsc, prettier and npm run build all green.
  • Convention: title is Conventional Commits (feat(chat): …). ✓
  • AFK contract: head afk/122 carries a working Closes #122. ✓
  • Conflicts: merges cleanly against current main (d8aafcf) — 0 conflict markers.
  • Diff review (3 files, +301/-43): matches the agreed design point-for-point —
    • acMatches now tiers matches (0 exact name · 1 name prefix · 2 name substring · 3 description/arg-hint) with a spec-stable sort, so catalog order survives within a tier and a description-only match can never outrank the command named after it.
    • Popover branch: only Tab completes (completeCommand/name ); the Enter-accept branch is dropped, so Enter falls through to the existing isComposerSend gate and posts the box as typed. Arrows/Escape/Cmd+Ctrl-Enter unchanged.
    • Row click sends the command outright, or completes to /name when it declares an arg_hint; send() gained an optional body arg so click-send and Enter-send share one POST + clear-on-success.
    • ADR-0041 records the reversal of ADR-0031's popover-Enter-precedence clause; no data-model/backend/SKILL.md changes.
    • Tests rewritten to the new contract: tiered-ranking regression, Tab-completes-without-sending, Enter-sends-raw (partial + completed), non-fine-pointer + Cmd/Ctrl+Enter guards, click-sends vs click-completes.

Verdict: PASS — ready to merge on your go-ahead.

> *This was generated by AI while landing a PR.* **Land audit — PR #123 → `main`: PASS** - **CI**: `ci / native` **success** (Successful in 5m26s, run #128) — relied on as the vouching signal; not re-run. PR body reports 594 tests / 37 files, eslint, tsc, prettier and `npm run build` all green. - **Convention**: title is Conventional Commits (`feat(chat): …`). ✓ - **AFK contract**: head `afk/122` carries a working `Closes #122`. ✓ - **Conflicts**: merges cleanly against current `main` (`d8aafcf`) — 0 conflict markers. - **Diff review** (3 files, +301/-43): matches the agreed design point-for-point — - `acMatches` now tiers matches (0 exact name · 1 name prefix · 2 name substring · 3 description/arg-hint) with a spec-stable `sort`, so catalog order survives within a tier and a description-only match can never outrank the command named after it. - Popover branch: only `Tab` completes (`completeCommand` → `/name `); the Enter-accept branch is dropped, so Enter falls through to the existing `isComposerSend` gate and posts the box as typed. Arrows/Escape/Cmd+Ctrl-Enter unchanged. - Row click sends the command outright, or completes to `/name ` when it declares an `arg_hint`; `send()` gained an optional `body` arg so click-send and Enter-send share one POST + clear-on-success. - **ADR-0041** records the reversal of ADR-0031's popover-Enter-precedence clause; no data-model/backend/SKILL.md changes. - Tests rewritten to the new contract: tiered-ranking regression, Tab-completes-without-sending, Enter-sends-raw (partial + completed), non-fine-pointer + Cmd/Ctrl+Enter guards, click-sends vs click-completes. Verdict: **PASS** — ready to merge on your go-ahead.
Author
Owner

This was generated by AI while landing a PR.

Landing audit — PR #123Closes #122: verdict PASS

  • CI: ci / native green (success in 5m26s, run #128) — relied on, not re-run. Body reports 594 tests / 37 files, eslint, tsc, prettier, npm run build all pass.
  • Conventions: title is Conventional Commits (feat(chat): …); AFK contract satisfied — head afk/122 carries a working Closes #122.
  • Mergeability: clean — git merge-tree against current origin/main (67d7dfa) produces no conflict despite both sides touching RunChat.tsx; the edits are disjoint.
  • Diff review (RunChat.tsx, RunChat.test.tsx, new docs/adr/0041-composer-autocomplete-tiered-ranking-and-gesture-model.md):
    • acMatches tiers correctly (0 exact → 1 prefix → 2 substring → 3 desc/hint), .sort() is stable so catalog order survives within a tier; a bare / prefix-matches every name → full catalog unchanged.
    • Tab is the sole completion gesture; Enter falls through to the existing isComposerSend send gate (ADR-0041 reversal of ADR-0031's popover clause) and posts the box as typed.
    • Click sends outright when no arg_hint, else completes to /name and refocuses; send(body?) shares one POST + clear-on-success, and clear-on-success collapses the popover.
    • Tests cover every branch: tiered ranking, Tab-completes, Enter-sends-raw (partial + completed), non-fine-pointer guard, Cmd/Ctrl+Enter, click-sends, click-completes-hinted.

No blockers. Awaiting free-text merge go-ahead.

> *This was generated by AI while landing a PR.* **Landing audit — PR #123 → `Closes #122`: verdict PASS** - **CI**: `ci / native` green (success in 5m26s, run #128) — relied on, not re-run. Body reports 594 tests / 37 files, eslint, tsc, prettier, `npm run build` all pass. - **Conventions**: title is Conventional Commits (`feat(chat): …`); AFK contract satisfied — head `afk/122` carries a working `Closes #122`. - **Mergeability**: clean — `git merge-tree` against current `origin/main` (67d7dfa) produces no conflict despite both sides touching `RunChat.tsx`; the edits are disjoint. - **Diff review** (`RunChat.tsx`, `RunChat.test.tsx`, new `docs/adr/0041-composer-autocomplete-tiered-ranking-and-gesture-model.md`): - `acMatches` tiers correctly (0 exact → 1 prefix → 2 substring → 3 desc/hint), `.sort()` is stable so catalog order survives within a tier; a bare `/` prefix-matches every name → full catalog unchanged. - Tab is the sole completion gesture; Enter falls through to the existing `isComposerSend` send gate (ADR-0041 reversal of ADR-0031's popover clause) and posts the box as typed. - Click sends outright when no `arg_hint`, else completes to `/name ` and refocuses; `send(body?)` shares one POST + clear-on-success, and clear-on-success collapses the popover. - Tests cover every branch: tiered ranking, Tab-completes, Enter-sends-raw (partial + completed), non-fine-pointer guard, Cmd/Ctrl+Enter, click-sends, click-completes-hinted. No blockers. Awaiting free-text merge go-ahead.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab#122
No description provided.