Fix /goal usage text for control commands (#26551)

## Why

The TUI's `/goal` usage text only advertised the objective form even
though `/goal clear`, `/goal edit`, `/goal pause`, and `/goal resume`
are implemented. This made the lifecycle controls difficult to discover
and allowed the duplicated help text to drift from actual behavior.

Fixes #25530.

## What changed

- Show the complete `/goal [<objective>|clear|edit|pause|resume]` syntax
in usage messages.
- Share one usage string across slash-command dispatch and goal-related
app messages.
- Add inline snapshot coverage for the control-command usage path.
This commit is contained in:
Eric Traut
2026-06-05 08:32:53 -07:00
committed by GitHub
Unverified
parent e781816ead
commit fb0993dd3b
4 changed files with 21 additions and 3 deletions
+3 -2
View File
@@ -6,6 +6,7 @@ use crate::bottom_pane::SelectionAction;
use crate::bottom_pane::SelectionItem;
use crate::bottom_pane::SelectionViewParams;
use crate::bottom_pane::popup_consts::standard_popup_hint_line;
use crate::goal_display::GOAL_USAGE;
use crate::goal_display::goal_status_label;
use crate::goal_display::goal_usage_summary;
use codex_app_server_protocol::ThreadGoal;
@@ -39,7 +40,7 @@ impl App {
let Some(goal) = response.goal else {
self.chat_widget.add_info_message(
"Usage: /goal <objective>".to_string(),
GOAL_USAGE.to_string(),
Some("No goal is currently set.".to_string()),
);
return;
@@ -280,7 +281,7 @@ impl App {
self.chat_widget
.add_error_message("No goal is currently set.".to_string());
self.chat_widget.add_info_message(
"Usage: /goal <objective>".to_string(),
GOAL_USAGE.to_string(),
Some("Create a goal before editing it.".to_string()),
);
}
@@ -13,6 +13,7 @@ use crate::bottom_pane::slash_commands::BuiltinCommandFlags;
use crate::bottom_pane::slash_commands::ServiceTierCommand;
use crate::bottom_pane::slash_commands::SlashCommandItem;
use crate::bottom_pane::slash_commands::find_slash_command;
use crate::goal_display::GOAL_USAGE;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum SlashCommandDispatchSource {
@@ -32,7 +33,6 @@ struct PreparedSlashCommandArgs {
const SIDE_STARTING_CONTEXT_LABEL: &str = "Side starting...";
const SIDE_SLASH_COMMAND_UNAVAILABLE_HINT: &str =
"Press Ctrl+C to return to the main thread first.";
const GOAL_USAGE: &str = "Usage: /goal <objective>";
const GOAL_USAGE_HINT: &str = "Example: /goal improve benchmark coverage";
const RAW_USAGE: &str = "Usage: /raw [on|off]";
@@ -784,6 +784,21 @@ async fn goal_control_slash_commands_emit_goal_events() {
}
}
#[tokio::test]
async fn goal_control_slash_command_without_thread_shows_full_usage() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.set_feature_enabled(Feature::Goals, /*enabled*/ true);
submit_composer_text(&mut chat, "/goal pause");
let cells = drain_insert_history(&mut rx);
assert_eq!(cells.len(), 1, "expected goal usage message");
insta::assert_snapshot!(
lines_to_single_string(&cells[0]),
@"• Usage: /goal [<objective>|clear|edit|pause|resume] The session must start before you can change a goal."
);
}
#[tokio::test]
async fn goal_edit_slash_command_opens_goal_editor() {
for thread_id in [Some(ThreadId::new()), None] {
+2
View File
@@ -2,6 +2,8 @@ use crate::status::format_tokens_compact;
use codex_app_server_protocol::ThreadGoal;
use codex_app_server_protocol::ThreadGoalStatus;
pub(crate) const GOAL_USAGE: &str = "Usage: /goal [<objective>|clear|edit|pause|resume]";
pub(crate) fn format_goal_elapsed_seconds(seconds: i64) -> String {
let seconds = seconds.max(0) as u64;
if seconds < 60 {