diff --git a/nori-rs/tui/src/chatwidget/helpers.rs b/nori-rs/tui/src/chatwidget/helpers.rs index 1ef9d3ac..e064bb80 100644 --- a/nori-rs/tui/src/chatwidget/helpers.rs +++ b/nori-rs/tui/src/chatwidget/helpers.rs @@ -75,6 +75,13 @@ impl ChatWidget { ), ); } + } else if !next_snapshot.is_empty() { + self.add_to_history( + crate::nori::session_config_history::new_agent_options_initial_history_cell( + self.bottom_pane.agent_display_name(), + config_options, + ), + ); } self.acp_config_option_snapshot = Some(next_snapshot); diff --git a/nori-rs/tui/src/chatwidget/tests/part3.rs b/nori-rs/tui/src/chatwidget/tests/part3.rs index c41c90c4..63d3cfe6 100644 --- a/nori-rs/tui/src/chatwidget/tests/part3.rs +++ b/nori-rs/tui/src/chatwidget/tests/part3.rs @@ -469,10 +469,10 @@ fn session_config_update_history_shows_only_changed_values_after_baseline() { ], }, )); - assert!( - drain_insert_history(&mut rx).is_empty(), - "the first config snapshot should establish a baseline without history noise" - ); + // Drain (and discard) the initial-snapshot banner so the snapshot below + // captures only the subsequent change-diff cell. The banner is covered + // by `session_config_update_history_renders_startup_banner_on_first_snapshot`. + let _ = drain_insert_history(&mut rx); chat.handle_client_event(nori_protocol::ClientEvent::SessionConfigUpdate( nori_protocol::SessionConfigUpdate { @@ -509,6 +509,46 @@ fn session_config_update_history_shows_only_changed_values_after_baseline() { assert_snapshot!("session_config_update_changed_values_history", rendered); } +#[test] +fn session_config_update_history_renders_startup_banner_on_first_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(); + chat.set_agent("claude-code"); + + chat.handle_client_event(nori_protocol::ClientEvent::SessionConfigUpdate( + nori_protocol::SessionConfigUpdate { + config_options: vec![ + select_config_option( + "mode", + "Mode", + "default", + &[("default", "Default"), ("plan", "Plan")], + ), + select_config_option( + "model", + "Model", + "opus-4-6", + &[("opus-4-6", "Opus 4.6"), ("sonnet-4-6", "Sonnet 4.6")], + ), + select_config_option( + "effort", + "Effort", + "medium", + &[("medium", "Medium"), ("high", "High")], + ), + ], + }, + )); + + let cells = drain_insert_history(&mut rx); + let rendered = cells + .iter() + .map(|lines| lines_to_single_string(lines)) + .collect::>() + .join("\n"); + + assert_snapshot!("session_config_update_startup_banner", rendered); +} + #[test] fn session_config_set_history_uses_final_agent_named_message() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(); diff --git a/nori-rs/tui/src/chatwidget/tests/snapshots/nori_tui__chatwidget__tests__part3__session_config_update_startup_banner.snap b/nori-rs/tui/src/chatwidget/tests/snapshots/nori_tui__chatwidget__tests__part3__session_config_update_startup_banner.snap new file mode 100644 index 00000000..a2a8a69b --- /dev/null +++ b/nori-rs/tui/src/chatwidget/tests/snapshots/nori_tui__chatwidget__tests__part3__session_config_update_startup_banner.snap @@ -0,0 +1,5 @@ +--- +source: tui/src/chatwidget/tests/part3.rs +expression: rendered +--- +• Claude Code options: Mode=Default, Model=Opus 4.6, Effort=Medium (/config to change) diff --git a/nori-rs/tui/src/nori/session_config_history.rs b/nori-rs/tui/src/nori/session_config_history.rs index e76bdf00..20da90d8 100644 --- a/nori-rs/tui/src/nori/session_config_history.rs +++ b/nori-rs/tui/src/nori/session_config_history.rs @@ -39,6 +39,33 @@ pub(crate) fn changed_values( .collect() } +/// Initial-snapshot banner shown the first time the agent announces its +/// session config options. Lists every option's current value and surfaces +/// the `/config` affordance so the user knows how to change them. +pub(crate) fn new_agent_options_initial_history_cell( + agent_display_name: &str, + config_options: &[acp::SessionConfigOption], +) -> PlainHistoryCell { + let agent_display_name = if agent_display_name.is_empty() { + "Agent" + } else { + agent_display_name + }; + let values: Vec = + config_options.iter().filter_map(display_value).collect(); + + let mut line = vec!["• ".dim(), format!("{agent_display_name} options: ").into()]; + for (index, value) in values.iter().enumerate() { + if index > 0 { + line.push(", ".into()); + } + line.push(format!("{}={}", value.name, value.value).cyan().bold()); + } + line.push(" (/config to change)".dim()); + + PlainHistoryCell::new(vec![Line::from(line)]) +} + pub(crate) fn new_agent_options_history_cell( agent_display_name: &str, changes: &[SessionConfigDisplayValue],