From 9ef7712d06486f75a71fa1038529bdaca7470536 Mon Sep 17 00:00:00 2001 From: Richard Lee Date: Wed, 15 Apr 2026 13:54:55 -0700 Subject: [PATCH] Match usage-limit TUI copy --- codex-rs/tui/src/app.rs | 3 +- codex-rs/tui/src/chatwidget.rs | 79 ++++++++++++------ ...kspace_member_credits_depleted_prompt.snap | 4 +- ...__workspace_member_usage_limit_prompt.snap | 4 +- ...er_credits_nudge_completion_feedback.snap} | 0 ..._workspace_owner_limit_state_messages.snap | 8 ++ ...usage_limit_nudge_completion_feedback.snap | 11 +++ codex-rs/tui/src/chatwidget/tests/helpers.rs | 2 +- .../src/chatwidget/tests/status_and_layout.rs | 81 ++++++++++++++++++- 9 files changed, 158 insertions(+), 34 deletions(-) rename codex-rs/tui/src/chatwidget/snapshots/{codex_tui__chatwidget__tests__workspace_owner_nudge_completion_feedback.snap => codex_tui__chatwidget__tests__workspace_owner_credits_nudge_completion_feedback.snap} (100%) create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_owner_limit_state_messages.snap create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_owner_usage_limit_nudge_completion_feedback.snap diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 578d797428..cfe288d4ee 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -4601,7 +4601,8 @@ impl App { self.refresh_rate_limits(app_server, origin); } AppEvent::SendAddCreditsNudgeEmail { credit_type } => { - self.chat_widget.start_add_credits_nudge_email_request(); + self.chat_widget + .start_add_credits_nudge_email_request(credit_type); self.send_add_credits_nudge_email(app_server, credit_type); } AppEvent::AddCreditsNudgeEmailFinished { result } => { diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 6f9abc7d36..4f4cf60d8b 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -776,7 +776,7 @@ pub(crate) struct ChatWidget { codex_rate_limit_reached_type: Option, rate_limit_warnings: RateLimitWarningState, rate_limit_switch_prompt: RateLimitSwitchPromptState, - add_credits_nudge_email_in_flight: bool, + add_credits_nudge_email_in_flight: Option, adaptive_chunking: AdaptiveChunkingPolicy, // Stream lifecycle controller stream_controller: Option, @@ -2806,20 +2806,30 @@ impl ChatWidget { } fn on_rate_limit_error(&mut self, message: String) { - self.on_error(message); match self.codex_rate_limit_reached_type { + Some(RateLimitReachedType::WorkspaceOwnerCreditsDepleted) => { + self.on_error( + "You're out of credits. Your workspace is out of credits. Add credits to continue using Codex." + .to_string(), + ); + } + Some(RateLimitReachedType::WorkspaceOwnerUsageLimitReached) => { + self.on_error( + "Usage limit reached. You've reached your usage limit. Increase your limits to continue using codex." + .to_string(), + ); + } Some(RateLimitReachedType::WorkspaceMemberCreditsDepleted) => { + self.on_error(message); self.open_workspace_owner_nudge_prompt(AddCreditsNudgeCreditType::Credits); } Some(RateLimitReachedType::WorkspaceMemberUsageLimitReached) => { + self.on_error(message); self.open_workspace_owner_nudge_prompt(AddCreditsNudgeCreditType::UsageLimit); } - Some( - RateLimitReachedType::RateLimitReached - | RateLimitReachedType::WorkspaceOwnerCreditsDepleted - | RateLimitReachedType::WorkspaceOwnerUsageLimitReached, - ) - | None => {} + Some(RateLimitReachedType::RateLimitReached) | None => { + self.on_error(message); + } } } @@ -4833,7 +4843,7 @@ impl ChatWidget { codex_rate_limit_reached_type: None, rate_limit_warnings: RateLimitWarningState::default(), rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), - add_credits_nudge_email_in_flight: false, + add_credits_nudge_email_in_flight: None, adaptive_chunking: AdaptiveChunkingPolicy::default(), stream_controller: None, plan_stream_controller: None, @@ -7491,17 +7501,19 @@ impl ChatWidget { } fn open_workspace_owner_nudge_prompt(&mut self, credit_type: AddCreditsNudgeCreditType) { - if self.add_credits_nudge_email_in_flight { + if self.add_credits_nudge_email_in_flight.is_some() { return; } - let prompt = match credit_type { - AddCreditsNudgeCreditType::Credits => { - "Your workspace is out of credits. Notify your workspace owner?" - } - AddCreditsNudgeCreditType::UsageLimit => { - "Your workspace usage limit has been reached. Request an increase from your workspace owner?" - } + let (title, prompt) = match credit_type { + AddCreditsNudgeCreditType::Credits => ( + "You've reached your workspace credit limit", + "Your workspace is out of credits. Ask your workspace owner to add more. Notify owner?", + ), + AddCreditsNudgeCreditType::UsageLimit => ( + "Usage limit reached", + "Request a limit increase from your owner to continue using codex. Request increase?", + ), }; let send_actions: Vec = vec![Box::new(move |tx| { tx.send(AppEvent::SendAddCreditsNudgeEmail { credit_type }); @@ -7524,7 +7536,7 @@ impl ChatWidget { ]; self.bottom_pane.show_selection_view(SelectionViewParams { - title: Some("Workspace limit reached".to_string()), + title: Some(title.to_string()), subtitle: Some(prompt.to_string()), footer_hint: Some(standard_popup_hint_line()), items, @@ -7533,23 +7545,40 @@ impl ChatWidget { }); } - pub(crate) fn start_add_credits_nudge_email_request(&mut self) { - self.add_credits_nudge_email_in_flight = true; + pub(crate) fn start_add_credits_nudge_email_request( + &mut self, + credit_type: AddCreditsNudgeCreditType, + ) { + self.add_credits_nudge_email_in_flight = Some(credit_type); } pub(crate) fn finish_add_credits_nudge_email_request( &mut self, result: AddCreditsNudgeEmailResult, ) { - self.add_credits_nudge_email_in_flight = false; - let message = match result { - AddCreditsNudgeEmailResult::Sent => "Workspace owner notified.", - AddCreditsNudgeEmailResult::CooldownActive => { + let credit_type = self + .add_credits_nudge_email_in_flight + .take() + .unwrap_or(AddCreditsNudgeCreditType::Credits); + let message = match (credit_type, result) { + (AddCreditsNudgeCreditType::Credits, AddCreditsNudgeEmailResult::Sent) => { + "Workspace owner notified." + } + (AddCreditsNudgeCreditType::Credits, AddCreditsNudgeEmailResult::CooldownActive) => { "Workspace owner was already notified recently." } - AddCreditsNudgeEmailResult::Failed { .. } => { + (AddCreditsNudgeCreditType::Credits, AddCreditsNudgeEmailResult::Failed { .. }) => { "Could not notify your workspace owner. Please try again." } + (AddCreditsNudgeCreditType::UsageLimit, AddCreditsNudgeEmailResult::Sent) => { + "Limit increase requested." + } + (AddCreditsNudgeCreditType::UsageLimit, AddCreditsNudgeEmailResult::CooldownActive) => { + "A limit increase was already requested recently." + } + (AddCreditsNudgeCreditType::UsageLimit, AddCreditsNudgeEmailResult::Failed { .. }) => { + "Could not request a limit increase. Please try again." + } }; self.add_to_history(history_cell::new_info_event( message.to_string(), diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_member_credits_depleted_prompt.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_member_credits_depleted_prompt.snap index 8997121fdb..283dcd5f0f 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_member_credits_depleted_prompt.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_member_credits_depleted_prompt.snap @@ -2,8 +2,8 @@ source: tui/src/chatwidget/tests/status_and_layout.rs expression: popup --- - Workspace limit reached - Your workspace is out of credits. Notify your workspace owner? + You've reached your workspace credit limit + Your workspace is out of credits. Ask your workspace owner to add more. Notify owner? 1. Yes (y) › 2. No (default) (n) diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_member_usage_limit_prompt.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_member_usage_limit_prompt.snap index cf2044db30..ee8322ab7f 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_member_usage_limit_prompt.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_member_usage_limit_prompt.snap @@ -2,8 +2,8 @@ source: tui/src/chatwidget/tests/status_and_layout.rs expression: popup --- - Workspace limit reached - Your workspace usage limit has been reached. Request an increase from your workspace owner? + Usage limit reached + Request a limit increase from your owner to continue using codex. Request increase? 1. Yes (y) › 2. No (default) (n) diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_owner_nudge_completion_feedback.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_owner_credits_nudge_completion_feedback.snap similarity index 100% rename from codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_owner_nudge_completion_feedback.snap rename to codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_owner_credits_nudge_completion_feedback.snap diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_owner_limit_state_messages.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_owner_limit_state_messages.snap new file mode 100644 index 0000000000..b3c1022a07 --- /dev/null +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_owner_limit_state_messages.snap @@ -0,0 +1,8 @@ +--- +source: tui/src/chatwidget/tests/status_and_layout.rs +expression: "rendered_cases.join(\"\\n---\\n\")" +--- +■ You're out of credits. Your workspace is out of credits. Add credits to continue using Codex. + +--- +■ Usage limit reached. You've reached your usage limit. Increase your limits to continue using codex. diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_owner_usage_limit_nudge_completion_feedback.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_owner_usage_limit_nudge_completion_feedback.snap new file mode 100644 index 0000000000..8091deebf4 --- /dev/null +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__workspace_owner_usage_limit_nudge_completion_feedback.snap @@ -0,0 +1,11 @@ +--- +source: tui/src/chatwidget/tests/status_and_layout.rs +expression: "rendered_cases.join(\"\\n---\\n\")" +--- +• Limit increase requested. + +--- +• A limit increase was already requested recently. + +--- +• Could not request a limit increase. Please try again. diff --git a/codex-rs/tui/src/chatwidget/tests/helpers.rs b/codex-rs/tui/src/chatwidget/tests/helpers.rs index d356eb8bf2..c504d39d5a 100644 --- a/codex-rs/tui/src/chatwidget/tests/helpers.rs +++ b/codex-rs/tui/src/chatwidget/tests/helpers.rs @@ -203,7 +203,7 @@ pub(super) async fn make_chatwidget_manual( codex_rate_limit_reached_type: None, rate_limit_warnings: RateLimitWarningState::default(), rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), - add_credits_nudge_email_in_flight: false, + add_credits_nudge_email_in_flight: None, adaptive_chunking: crate::streaming::chunking::AdaptiveChunkingPolicy::default(), stream_controller: None, plan_stream_controller: None, diff --git a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs index dccff1609f..30b20a5a74 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs @@ -540,7 +540,7 @@ async fn workspace_owner_limit_states_do_not_prompt_for_owner_nudge() { RateLimitReachedType::WorkspaceOwnerUsageLimitReached, RateLimitReachedType::RateLimitReached, ] { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; let mut limits = snapshot(/*percent*/ 100.0); limits.rate_limit_reached_type = Some(limit_type); chat.on_rate_limit_snapshot(Some(limits)); @@ -548,9 +548,45 @@ async fn workspace_owner_limit_states_do_not_prompt_for_owner_nudge() { chat.on_rate_limit_error("Usage limit reached.".to_string()); let popup = render_bottom_popup(&chat, /*width*/ 90); assert!(!popup.contains("workspace owner")); + assert_no_owner_nudge_or_rate_limit_refresh(&mut rx); } } +#[tokio::test] +async fn workspace_owner_limit_states_render_state_specific_messages() { + let cases = [ + ( + RateLimitReachedType::WorkspaceOwnerCreditsDepleted, + "You're out of credits. Your workspace is out of credits. Add credits to continue using Codex.", + ), + ( + RateLimitReachedType::WorkspaceOwnerUsageLimitReached, + "Usage limit reached. You've reached your usage limit. Increase your limits to continue using codex.", + ), + ]; + + let mut rendered_cases = Vec::new(); + for (limit_type, expected) in cases { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + let mut limits = snapshot(/*percent*/ 100.0); + limits.rate_limit_reached_type = Some(limit_type); + chat.on_rate_limit_snapshot(Some(limits)); + + chat.on_rate_limit_error("Usage limit reached.".to_string()); + let rendered = drain_insert_history(&mut rx) + .into_iter() + .map(|lines| lines_to_single_string(&lines)) + .collect::(); + assert!(rendered.contains(expected), "rendered: {rendered}"); + rendered_cases.push(rendered); + } + + assert_chatwidget_snapshot!( + "workspace_owner_limit_state_messages", + rendered_cases.join("\n---\n") + ); +} + #[tokio::test] async fn missing_rate_limit_reached_type_does_not_prompt_or_refresh() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; @@ -576,7 +612,7 @@ async fn workspace_owner_nudge_default_no_dismisses_without_sending() { } #[tokio::test] -async fn workspace_owner_nudge_completion_renders_feedback() { +async fn workspace_owner_credits_nudge_completion_renders_feedback() { let cases = [ ( AddCreditsNudgeEmailResult::Sent, @@ -597,6 +633,7 @@ async fn workspace_owner_nudge_completion_renders_feedback() { let mut rendered_cases = Vec::new(); for (result, expected) in cases { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.start_add_credits_nudge_email_request(AddCreditsNudgeCreditType::Credits); chat.finish_add_credits_nudge_email_request(result); let rendered = drain_insert_history(&mut rx) .into_iter() @@ -607,7 +644,45 @@ async fn workspace_owner_nudge_completion_renders_feedback() { } assert_chatwidget_snapshot!( - "workspace_owner_nudge_completion_feedback", + "workspace_owner_credits_nudge_completion_feedback", + rendered_cases.join("\n---\n") + ); +} + +#[tokio::test] +async fn workspace_owner_usage_limit_nudge_completion_renders_feedback() { + let cases = [ + ( + AddCreditsNudgeEmailResult::Sent, + "Limit increase requested.", + ), + ( + AddCreditsNudgeEmailResult::CooldownActive, + "A limit increase was already requested recently.", + ), + ( + AddCreditsNudgeEmailResult::Failed { + message: "request failed".to_string(), + }, + "Could not request a limit increase. Please try again.", + ), + ]; + + let mut rendered_cases = Vec::new(); + for (result, expected) in cases { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.start_add_credits_nudge_email_request(AddCreditsNudgeCreditType::UsageLimit); + chat.finish_add_credits_nudge_email_request(result); + let rendered = drain_insert_history(&mut rx) + .into_iter() + .map(|lines| lines_to_single_string(&lines)) + .collect::(); + assert!(rendered.contains(expected), "rendered: {rendered}"); + rendered_cases.push(rendered); + } + + assert_chatwidget_snapshot!( + "workspace_owner_usage_limit_nudge_completion_feedback", rendered_cases.join("\n---\n") ); }