diff --git a/codex-rs/core/src/config/edit.rs b/codex-rs/core/src/config/edit.rs index d9b376106b..912adba5c7 100644 --- a/codex-rs/core/src/config/edit.rs +++ b/codex-rs/core/src/config/edit.rs @@ -39,8 +39,8 @@ pub enum ConfigEdit { SetNoticeHideModelMigrationPrompt(String, bool), /// Record that a migration prompt was shown for an old->new model mapping. RecordModelMigrationSeen { from: String, to: String }, - /// Record that an availability NUX was displayed. - RecordAvailabilityNuxDisplay { id: String }, + /// Record that an availability NUX message was displayed. + RecordAvailabilityNuxDisplay { message: String }, /// Replace the entire `[mcp_servers]` table. ReplaceMcpServers(BTreeMap), /// Set or clear a skill config entry under `[[skills.config]]`. @@ -333,7 +333,7 @@ impl ConfigDocument { &[Notice::TABLE_KEY, "model_migrations", from.as_str()], value(to.clone()), )), - ConfigEdit::RecordAvailabilityNuxDisplay { id } => { + ConfigEdit::RecordAvailabilityNuxDisplay { message } => { let resolved = self.scoped_segments( Scope::Global, &[Notice::TABLE_KEY, "availability_nux_display_counts"], @@ -342,10 +342,10 @@ impl ConfigDocument { return Ok(false); }; let current = table - .get(id.as_str()) + .get(message.as_str()) .and_then(toml_edit::Item::as_integer) .unwrap_or(0); - table[id.as_str()] = value(current + 1); + table[message.as_str()] = value(current + 1); Ok(true) } ConfigEdit::SetWindowsWslSetupAcknowledged(acknowledged) => Ok(self.write_value( @@ -805,9 +805,10 @@ impl ConfigEditsBuilder { self } - pub fn record_availability_nux_display(mut self, id: &str) -> Self { - self.edits - .push(ConfigEdit::RecordAvailabilityNuxDisplay { id: id.to_string() }); + pub fn record_availability_nux_display(mut self, message: &str) -> Self { + self.edits.push(ConfigEdit::RecordAvailabilityNuxDisplay { + message: message.to_string(), + }); self } @@ -1467,7 +1468,7 @@ gpt-5 = "gpt-5.1" existing = "value" [notice.availability_nux_display_counts] -try_spark = 1 +"*New* Spark is now available to you." = 1 "#, ) .expect("seed"); @@ -1475,7 +1476,7 @@ try_spark = 1 codex_home, None, &[ConfigEdit::RecordAvailabilityNuxDisplay { - id: "try_spark".to_string(), + message: "*New* Spark is now available to you.".to_string(), }], ) .expect("persist"); @@ -1486,7 +1487,7 @@ try_spark = 1 existing = "value" [notice.availability_nux_display_counts] -try_spark = 2 +"*New* Spark is now available to you." = 2 "#; assert_eq!(contents, expected); } diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 6e1e24b8ad..1dee7f78e1 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -2743,16 +2743,16 @@ impl App { )); } } - AppEvent::PersistAvailabilityNuxDisplayed { id } => { + AppEvent::PersistAvailabilityNuxDisplayed { message } => { let count = self .config .notices .availability_nux_display_counts - .entry(id.clone()) + .entry(message.clone()) .or_default(); *count += 1; if let Err(err) = ConfigEditsBuilder::new(&self.config.codex_home) - .record_availability_nux_display(id.as_str()) + .record_availability_nux_display(message.as_str()) .apply() .await { diff --git a/codex-rs/tui/src/app_event.rs b/codex-rs/tui/src/app_event.rs index e3e06582d5..262299fc37 100644 --- a/codex-rs/tui/src/app_event.rs +++ b/codex-rs/tui/src/app_event.rs @@ -337,7 +337,7 @@ pub(crate) enum AppEvent { /// Persist that an availability NUX was displayed. PersistAvailabilityNuxDisplayed { - id: String, + message: String, }, /// Skip the next world-writable scan (one-shot) after a user-confirmed continue. diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 78cce24bea..f420d03ac9 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -1179,31 +1179,31 @@ impl ChatWidget { displayed_startup_tips, ); self.apply_session_info_cell(session_info_cell); - let displayed_availability_nux_ids: Vec = if self.show_welcome_banner { + let displayed_availability_nux_messages: Vec = if self.show_welcome_banner { startup_tips .first_session_tips .iter() - .filter_map(StartupTip::availability_nux_id) + .filter_map(StartupTip::availability_nux_message) .map(str::to_string) .collect() } else { startup_tips .selected_tip .iter() - .filter_map(StartupTip::availability_nux_id) + .filter_map(StartupTip::availability_nux_message) .map(str::to_string) .collect() }; - for id in displayed_availability_nux_ids { + for message in displayed_availability_nux_messages { let count = self .config .notices .availability_nux_display_counts - .entry(id.clone()) + .entry(message.clone()) .or_default(); *count += 1; self.app_event_tx - .send(AppEvent::PersistAvailabilityNuxDisplayed { id }); + .send(AppEvent::PersistAvailabilityNuxDisplayed { message }); } if let Some(messages) = initial_messages { diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 1f0bd2b0f9..180c7b346a 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -1849,7 +1849,6 @@ async fn session_configured_first_session_shows_model_nux_and_increments_display model_info.display_name = "GPT Test".to_string(); model_info.description = Some("Fast, high-reliability coding model.".to_string()); model_info.availability_nux = Some(ModelAvailabilityNux { - id: "try_spark".to_string(), message: "*New* You're using **GPT Test**. Fast, high-reliability coding model. Use /model to compare or switch anytime." .to_string(), @@ -1893,7 +1892,9 @@ async fn session_configured_first_session_shows_model_nux_and_increments_display chat.config .notices .availability_nux_display_counts - .get("try_spark") + .get( + "*New* You're using **GPT Test**. Fast, high-reliability coding model. Use /model to compare or switch anytime.", + ) ); } @@ -1937,7 +1938,7 @@ async fn session_configured_ineligible_model_does_not_increment_display_count() chat.config .notices .availability_nux_display_counts - .get("try_spark") + .get("*New* Spark is now available to you.") ); } diff --git a/codex-rs/tui/src/tooltips.rs b/codex-rs/tui/src/tooltips.rs index f1a963a48f..471b04698c 100644 --- a/codex-rs/tui/src/tooltips.rs +++ b/codex-rs/tui/src/tooltips.rs @@ -53,20 +53,20 @@ fn experimental_tooltips() -> Vec<&'static str> { #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum StartupTip { Generic(String), - AvailabilityNux { id: String, message: String }, + AvailabilityNux { message: String }, } impl StartupTip { pub(crate) fn message(&self) -> String { match self { - Self::Generic(message) | Self::AvailabilityNux { message, .. } => message.clone(), + Self::Generic(message) | Self::AvailabilityNux { message } => message.clone(), } } - pub(crate) fn availability_nux_id(&self) -> Option<&str> { + pub(crate) fn availability_nux_message(&self) -> Option<&str> { match self { Self::Generic(_) => None, - Self::AvailabilityNux { id, .. } => Some(id.as_str()), + Self::AvailabilityNux { message } => Some(message.as_str()), } } } @@ -188,26 +188,25 @@ fn availability_nux_tips( models: &[ModelPreset], availability_nux_display_counts: &BTreeMap, ) -> Vec { - let mut seen_ids = HashSet::new(); + let mut seen_messages = HashSet::new(); models .iter() .filter_map(|preset| preset.availability_nux.as_ref()) .filter(|availability_nux| { availability_nux_display_counts - .get(&availability_nux.id) + .get(&availability_nux.message) .copied() .unwrap_or(0) < 4 }) - .filter(|availability_nux| seen_ids.insert(availability_nux.id.clone())) + .filter(|availability_nux| seen_messages.insert(availability_nux.message.clone())) .map(startup_tip_from_availability_nux) .collect() } fn startup_tip_from_availability_nux(availability_nux: &ModelAvailabilityNux) -> StartupTip { StartupTip::AvailabilityNux { - id: availability_nux.id.clone(), message: availability_nux.message.clone(), } } @@ -373,7 +372,7 @@ mod tests { use rand::SeedableRng; use rand::rngs::StdRng; - fn model_preset(availability_nux: Option<(&str, &str)>, description: &str) -> ModelPreset { + fn model_preset(availability_nux: Option<&str>, description: &str) -> ModelPreset { ModelPreset { id: "gpt-test".to_string(), model: "gpt-test".to_string(), @@ -385,8 +384,7 @@ mod tests { is_default: false, upgrade: None, show_in_picker: true, - availability_nux: availability_nux.map(|(id, message)| ModelAvailabilityNux { - id: id.to_string(), + availability_nux: availability_nux.map(|message| ModelAvailabilityNux { message: message.to_string(), }), supported_in_api: true, @@ -514,7 +512,7 @@ content = "This is a test announcement" #[test] fn first_session_eligible_model_returns_all_availability_nux_tips() { let preset = model_preset( - Some(("try_spark", "*New* Spark is now available to you.")), + Some("*New* Spark is now available to you."), "Fast, high-reliability coding model.", ); let mut rng = StdRng::seed_from_u64(1); @@ -531,7 +529,6 @@ content = "This is a test announcement" assert_eq!( StartupTips { first_session_tips: vec![StartupTip::AvailabilityNux { - id: "try_spark".to_string(), message: "*New* Spark is now available to you.".to_string(), }], selected_tip: None, @@ -558,21 +555,14 @@ content = "This is a test announcement" } #[test] - fn first_session_dedupes_multiple_availability_nuxes_by_id() { + fn first_session_dedupes_multiple_availability_nuxes_by_message() { let mut rng = StdRng::seed_from_u64(1); let models = vec![ - model_preset( - Some(("try_spark", "*New* Spark is now available to you.")), - "", - ), - model_preset( - Some(("try_spark", "*New* Spark is now available to you.")), - "", - ), + model_preset(Some("*New* Spark is now available to you."), ""), + model_preset(Some("*New* Spark is now available to you."), ""), ModelPreset { model: "gpt-other".to_string(), availability_nux: Some(ModelAvailabilityNux { - id: "try_canvas".to_string(), message: "*New* Canvas is now available to you.".to_string(), }), ..model_preset(None, "") @@ -592,11 +582,9 @@ content = "This is a test announcement" StartupTips { first_session_tips: vec![ StartupTip::AvailabilityNux { - id: "try_spark".to_string(), message: "*New* Spark is now available to you.".to_string(), }, StartupTip::AvailabilityNux { - id: "try_canvas".to_string(), message: "*New* Canvas is now available to you.".to_string(), }, ], @@ -608,10 +596,7 @@ content = "This is a test announcement" #[test] fn later_session_can_select_availability_nux_from_weighted_pool() { - let preset = model_preset( - Some(("try_spark", "*New* Spark is now available to you.")), - "", - ); + let preset = model_preset(Some("*New* Spark is now available to you."), ""); let mut rng = StdRng::seed_from_u64(5); let tips = get_startup_tips_with_rng( @@ -627,7 +612,6 @@ content = "This is a test announcement" StartupTips { first_session_tips: Vec::new(), selected_tip: Some(StartupTip::AvailabilityNux { - id: "try_spark".to_string(), message: "*New* Spark is now available to you.".to_string(), }), }, @@ -637,11 +621,8 @@ content = "This is a test announcement" #[test] fn later_session_count_limit_disables_availability_nux() { - let preset = model_preset( - Some(("try_spark", "*New* Spark is now available to you.")), - "", - ); - let counts = BTreeMap::from([("try_spark".to_string(), 4)]); + let preset = model_preset(Some("*New* Spark is now available to you."), ""); + let counts = BTreeMap::from([("*New* Spark is now available to you.".to_string(), 4)]); let mut rng = StdRng::seed_from_u64(5); let tips = get_startup_tips_with_rng( @@ -664,10 +645,7 @@ content = "This is a test announcement" #[test] fn later_session_eligible_model_includes_announcement_and_generic_candidates() { - let preset = model_preset( - Some(("try_spark", "*New* Spark is now available to you.")), - "", - ); + let preset = model_preset(Some("*New* Spark is now available to you."), ""); let mut saw_announcement = false; let mut saw_plan_tip = false; let mut saw_random_tip = false;