Use availability NUX messages in tui

This commit is contained in:
Ahmed Ibrahim
2026-02-26 19:20:05 -08:00
parent 1b5e47a5d4
commit 0667409ec6
6 changed files with 43 additions and 63 deletions
+12 -11
View File
@@ -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<String, McpServerConfig>),
/// 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);
}
+3 -3
View File
@@ -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
{
+1 -1
View File
@@ -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.
+6 -6
View File
@@ -1179,31 +1179,31 @@ impl ChatWidget {
displayed_startup_tips,
);
self.apply_session_info_cell(session_info_cell);
let displayed_availability_nux_ids: Vec<String> = if self.show_welcome_banner {
let displayed_availability_nux_messages: Vec<String> = 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 {
+4 -3
View File
@@ -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.")
);
}
+17 -39
View File
@@ -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<String, u32>,
) -> Vec<StartupTip> {
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;