Skip to content

fix(tui): submit file-defined slash commands on the first enter - #51714

Open
argszero wants to merge 1 commit into
anomalyco:devfrom
argszero:file-command-enter
Open

argszero wants to merge 1 commit into
anomalyco:devfrom
argszero:file-command-enter

Conversation

@argszero

Copy link
Copy Markdown

Issue for this PR

Closes #50962

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

A file-defined slash command needs two Enter presses to run, while a built-in command runs on the first one. Typing /good and pressing Enter only shifts the cursor by one space; a second Enter is what actually submits.

Why. Enter is bound twice. packages/tui/src/config/keybind.ts gives return to both prompt.autocomplete.select (:217) and input_submit (:163), and the autocomplete registers its bindings with enabled: () => Boolean(store.visible) (packages/tui/src/component/prompt/autocomplete.tsx:583). While the slash list is open, Enter therefore always means "select", and submit never sees the key.

What the selection then does is what makes the two paths differ. A built-in command comes from useCommandSlashes() and its onSelect is keymap.dispatchCommand(name), so it executes immediately (packages/tui/src/keymap.tsx:286). A file-defined command comes from sync.data.command, and its onSelect only rewrites the input to "/" + name + " " and moves the cursor (autocomplete.tsx:456-462). So for a file-defined command the first Enter selects, and the command waits for a second Enter.

The fix. Collapse the list once the typed text already names a whole command, so the next Enter reaches the input's own submit path (prompt/index.tsx:1391-1395 <textarea onSubmit> -> submit() -> submitInner()), which dispatches the command through sdk.client.session.command. No new prop, no keymap change.

isCompleteCommand compares the typed text against the trimmed display of the offered commands. The trimming matters: the list pads every display to the width of the longest entry before rendering (autocomplete.tsx:468-473). Aliases count too, matching what the fuzzer searches.

Note this uses setStore("visible", false) rather than the existing hide(): hide() is for a half-typed trigger and deletes the input text when it does not end in a space, which would have discarded the command the user just finished typing.

Typing another character reopens the list, because the reopen branch below only requires the text before the cursor to start with / and contain no whitespace (autocomplete.tsx:716), so /good -> /good- still lists /good-thing.

Known limits, so the reviewer can weigh them:

  • Once the typed text exactly equals a command name the list disappears, so browsing from /good to /good-thing with the arrow keys takes one more keystroke to bring the list back. Confirm-and-prefix-browsing are in tension here; I preferred the reported case (type the name, press Enter).
  • Selecting an entry from the list with the arrow keys still inserts /name and waits for a following Enter, exactly as before. Collapsing the list cannot change that path, because the selection itself is what inserts the text. If you would rather have a selected file-defined command submit immediately (like a built-in, which executes on selection), that is a different change and I can do it on top.
  • The template body that reaches the agent after the command runs is what executing a file-defined command does: its template becomes the prompt. This PR only changes how many Enter presses it takes to get there, not what runs.

How did you verify your code works?

  • cd packages/tui && bun test test/component/autocomplete.test.ts — 5 pass, 0 fail. The new isCompleteCommand tests pin the behaviour that decides this fix: a name that differs from display only by list padding matches, an alias matches, and a prefix (/goo), a name with arguments (/good clean up), unrelated text and an empty list do not, so the list stays open while the name is still being typed.
  • cd packages/tui && bun test — 199 pass, 1 skip, 0 fail across 46 files, so nothing else in the package changed behaviour.
  • cd packages/tui && bun run typecheck, bun run lint (oxlint) and bunx prettier --check are clean on both files. The two oxlint consistent-return warnings on this file also reproduce on the unmodified file, so they are pre-existing.
  • Punched by hand as far as the code goes: the Enter binding is gated on store.visible, so once the list is collapsed that binding is inactive and the key falls through to the textarea's onSubmit, which is the path a successful submit already takes.

I did not add an end-to-end render test that presses Enter and asserts the command dispatched: mounting Prompt needs the full SDK/session/editor provider stack, and I did not want to introduce that harness inside this fix. The predicate is unit-tested and the fall-through is by construction; say the word if you want the integration test as well.

Screenshots / recordings

Not applicable, this is TUI key handling rather than a visual change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TUI: client.tui.showToast() from command.execute.before corrupts input box

1 participant