Skip to content

fix(tui): recall accepted slash commands locally#17336

Merged
fcoury-oai merged 6 commits intomainfrom
fcoury/slash-command-history-local
Apr 11, 2026
Merged

fix(tui): recall accepted slash commands locally#17336
fcoury-oai merged 6 commits intomainfrom
fcoury/slash-command-history-local

Conversation

@fcoury-oai
Copy link
Copy Markdown
Contributor

@fcoury-oai fcoury-oai commented Apr 10, 2026

TL;DR

  • Adds recognized slash commands to the TUI's local in-session recall history.
  • This is the MVP of the whole feature: it keeps slash-command recall local only: nothing is written to persistent history, app-server history, or core history storage.
  • Treats slash commands like submitted text once they parse as a known built-in command, regardless of whether command dispatch later succeeds.

Problem

Slash commands are handled outside the normal message submission path, so they could clear the composer without becoming part of the local Up-arrow recall list. That made command-heavy workflows awkward: after running /diff, /rename Better title, /plan investigate this, or even a valid command that reports a usage error, users had to retype the command instead of recalling and editing it like a normal prompt.

The goal of this PR is to make slash commands feel like submitted input inside the current TUI session while keeping the change deliberately local. This is not persistent history yet; it only affects the composer's in-memory recall behavior.

Mental model

The composer owns draft state and local recall. When slash input parses as a recognized built-in command, the composer stages the submitted command text before returning InputResult::Command or InputResult::CommandWithArgs. ChatWidget then dispatches the command and records the staged entry once dispatch returns to the input-result path.

Command-name recognition is the only validation before local recall. A valid slash command is recallable whether it succeeds, fails with a usage error, no-ops, is unavailable while a task is running, or is skipped by command-specific logic. An unrecognized slash command is different: it is restored as a draft, surfaces the existing unrecognized-command message, and is not added to recall.

Bare commands recalled from typed text use the trimmed submitted draft. Commands selected from the popup record the canonical command text, such as /diff, rather than the partial filter text the user typed. Inline commands with arguments keep the original command invocation available locally even when their arguments are later prepared through the normal submission pipeline.

Non-goals

Persisting slash commands across sessions is intentionally out of scope. This change does not modify app-server history, core history storage, protocol events, or message submission semantics.

This does not change command availability, command side effects, popup filtering, command parsing, or the semantics of unsupported commands. It only changes whether recognized slash-command invocations are available through local Up-arrow recall after the user submits them.

Tradeoffs

The main tradeoff is that recall is based on command recognition, not command outcome. This intentionally favors a simpler user model: if the TUI accepted the input as a slash command, the user can recall and edit that input just like plain text. That means valid-but-unsuccessful invocations such as usage errors are recallable, which is useful when the next action is usually to edit and retry.

The previous accept/reject design required command dispatch to report a boolean outcome, which made the dispatcher API noisier and forced every branch to decide history behavior. This version keeps the dispatch APIs as side-effect-only methods and localizes history recording to the slash-command input path.

Inline command handling still avoids double-recording by preparing inline arguments without using the normal message-submission history path. The staged slash-command entry remains the single local recall record for the command invocation.

Architecture

ChatComposer stages a pending HistoryEntry when recognized slash-command input is promoted into an input result. The pending entry mirrors the existing local history payload shape so recall can restore text elements, local images, remote images, mention bindings, and pending paste state when those are present.

BottomPane exposes a narrow method for recording that staged command entry because it owns the composer. ChatWidget records the staged entry after dispatching a recognized command from the input-result match. Valid commands rejected before they reach ChatWidget, such as commands unavailable while a task is running, are staged and recorded in the composer path that detects the rejection.

Slash-command dispatch itself now lives in chatwidget/slash_dispatch.rs so the behavior is reviewable without adding more weight to chatwidget.rs. The extraction is behavior-preserving: the dispatch match arms stay intact, while the input flow in chatwidget.rs remains the single place that connects submitted slash-command input to dispatch.

Observability

There is no new logging because this is a local UI recall behavior and the result is directly visible through Up-arrow recall. The practical debug path is to trace Enter through ChatComposer::try_dispatch_bare_slash_command, ChatComposer::try_dispatch_slash_command_with_args, or popup Enter/Tab handling, then confirm the recognized command is staged before dispatch and recorded exactly once afterward.

If a valid command unexpectedly does not appear in recall, check whether the input path staged slash history before clearing the composer and whether it used the ChatWidget slash-dispatch wrapper. If an unrecognized command unexpectedly appears in recall, check the parser branch that should restore the draft instead of staging history.

Tests

Composer-level tests cover staging and recording for a bare typed slash command, a popup-selected command, and an inline command with arguments.

Chat-widget tests cover valid commands being recallable after normal dispatch, inline dispatch, usage errors, task-running unavailability, no-op stub dispatch, and command-specific skip behavior such as /init when an instructions file already exists. They also cover the negative case: unrecognized slash commands are not added to local recall.

@fcoury-oai fcoury-oai force-pushed the fcoury/slash-command-history-local branch 3 times, most recently from 0666664 to ec785b7 Compare April 10, 2026 17:35
Copy link
Copy Markdown
Contributor

@canvrno-oai canvrno-oai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed, tested and working.

I do wonder if we may want to add a whitelist for slash commands that are eligible for recall, as some may be less relevant under daily recall use cases.

@fcoury-oai fcoury-oai force-pushed the fcoury/slash-command-history-local branch from 7116e78 to 8323853 Compare April 11, 2026 14:33
Stage slash-command drafts when the composer dispatches them and record the draft in local recall only after ChatWidget accepts the command.

This keeps bare and inline commands reachable with Up-arrow without changing cross-session message history yet.
Explain the two-phase handoff between `ChatComposer` and
`ChatWidget` for accepted slash commands, so future command paths
know when to record or discard local recall state.

Add `.aidocs` PR narrative and risk notes to make the branch
reviewable without changing runtime behavior.
Describe slash command recall as the first local-only step toward
history support, and call out that app-server, core, protocol, and
persistent history behavior are unchanged.
Treat recognized slash commands like submitted text by recording them once they pass command-name validation, regardless of dispatch result.

This keeps the dispatcher API simple and removes the pending accept/reject history path.
Move slash command dispatch helpers into a focused chatwidget submodule.

This keeps the behavior-preserving history change easier to review while reducing the size of `chatwidget.rs`.
Ensure `/fast` inline command submissions clear the composer through the
same inline argument preparation path used by other slash commands.

Document the slash dispatch recall handoff and add coverage for the
usage-error path so local recall records the command without leaving the
submitted draft visible after Enter.
@fcoury-oai fcoury-oai force-pushed the fcoury/slash-command-history-local branch from 8323853 to 7ded899 Compare April 11, 2026 15:01
@fcoury-oai fcoury-oai merged commit 0bdeab3 into main Apr 11, 2026
21 of 22 checks passed
@fcoury-oai fcoury-oai deleted the fcoury/slash-command-history-local branch April 11, 2026 15:40
@github-actions github-actions bot locked and limited conversation to collaborators Apr 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants