diff --git a/codex-rs/tui/src/app/event_dispatch.rs b/codex-rs/tui/src/app/event_dispatch.rs index 8ecb84b3b4..1d5771bb62 100644 --- a/codex-rs/tui/src/app/event_dispatch.rs +++ b/codex-rs/tui/src/app/event_dispatch.rs @@ -1695,7 +1695,7 @@ impl App { category, include_logs, } => { - self.chat_widget.open_feedback_note(category, include_logs); + self.chat_widget.open_feedback_note(category, include_logs, self.feedback_audience); } AppEvent::OpenFeedbackConsent { category } => { self.chat_widget.open_feedback_consent(category); diff --git a/codex-rs/tui/src/bottom_pane/feedback_note_view.rs b/codex-rs/tui/src/bottom_pane/feedback_note_view.rs index ad9c29dbd0..956c6e3e60 100644 --- a/codex-rs/tui/src/bottom_pane/feedback_note_view.rs +++ b/codex-rs/tui/src/bottom_pane/feedback_note_view.rs @@ -1,4 +1,5 @@ -//! Collects an optional feedback note and submits it with the selected log consent. +//! Collects a feedback note with audience-specific disclosure and independent log consent. +//! The editor and submission controls remain visible when the disclosure must scroll. use crossterm::event::KeyCode; use crossterm::event::KeyEvent; @@ -12,19 +13,33 @@ use ratatui::widgets::Clear; use ratatui::widgets::Paragraph; use ratatui::widgets::StatefulWidgetRef; use ratatui::widgets::Widget; +use std::cell::Cell; use std::cell::RefCell; use crate::app_event::AppEvent; use crate::app_event::FeedbackCategory; use crate::app_event_sender::AppEventSender; +use crate::key_hint; use crate::render::renderable::Renderable; +use crate::terminal_hyperlinks::mark_underlined_hyperlink; +use crate::wrapping::RtOptions; +use crate::wrapping::word_wrap_lines; use super::CancellationEvent; use super::bottom_pane_view::BottomPaneView; -use super::popup_consts::standard_popup_hint_line; +use super::feedback_view::FeedbackAudience; +use super::popup_consts::accept_cancel_hint_line; use super::textarea::TextArea; use super::textarea::TextAreaState; +const FEEDBACK_POLICY_URL: &str = "https://help.openai.com/en/articles/5722486-how-your-data-is-used-to-improve-model-performance"; +const EMPLOYEE_FEEDBACK_FAQ_URL: &str = "https://go/codex-employee-feedback-faq"; + +enum IntroContent { + Full, + DisclosureOnly, +} + /// Minimal input overlay to collect an optional feedback note, then submit it /// through the app-server-managed feedback flow. pub(crate) struct FeedbackNoteView { @@ -32,10 +47,13 @@ pub(crate) struct FeedbackNoteView { turn_id: Option, app_event_tx: AppEventSender, include_logs: bool, + feedback_audience: FeedbackAudience, // UI state textarea: TextArea, textarea_state: RefCell, + intro_scroll: Cell, + intro_page_height: Cell, complete: bool, } @@ -45,14 +63,18 @@ impl FeedbackNoteView { turn_id: Option, app_event_tx: AppEventSender, include_logs: bool, + feedback_audience: FeedbackAudience, ) -> Self { Self { category, turn_id, app_event_tx, include_logs, + feedback_audience, textarea: TextArea::new(), textarea_state: RefCell::new(TextAreaState::default()), + intro_scroll: Cell::new(0), + intro_page_height: Cell::new(1), complete: false, } } @@ -73,6 +95,26 @@ impl FeedbackNoteView { impl BottomPaneView for FeedbackNoteView { fn handle_key_event(&mut self, key_event: KeyEvent) { match key_event { + KeyEvent { + code: KeyCode::PageUp, + .. + } => { + self.intro_scroll.set( + self.intro_scroll + .get() + .saturating_sub(self.intro_page_height.get()), + ); + } + KeyEvent { + code: KeyCode::PageDown, + .. + } => { + self.intro_scroll.set( + self.intro_scroll + .get() + .saturating_add(self.intro_page_height.get()), + ); + } KeyEvent { code: KeyCode::Esc, .. } => { @@ -117,21 +159,21 @@ impl BottomPaneView for FeedbackNoteView { impl Renderable for FeedbackNoteView { fn desired_height(&self, width: u16) -> u16 { - self.intro_lines(width).len() as u16 + self.input_height(width) + 2u16 + self.intro_lines(width, IntroContent::Full).len() as u16 + self.input_height(width) + 2u16 } fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> { if area.height < 2 || area.width <= 2 { return None; } - let intro_height = self.intro_lines(area.width).len() as u16; - let text_area_height = self.input_height(area.width).saturating_sub(1); + let input_area = self.input_area(area); + let text_area_height = input_area.height.saturating_sub(1); if text_area_height == 0 { return None; } let textarea_rect = Rect { x: area.x.saturating_add(2), - y: area.y.saturating_add(intro_height).saturating_add(1), + y: input_area.y.saturating_add(1), width: area.width.saturating_sub(2), height: text_area_height, }; @@ -144,11 +186,34 @@ impl Renderable for FeedbackNoteView { return; } - let intro_lines = self.intro_lines(area.width); + // Four rows leave room for one intro line; show the disclosure before submission. + let intro_content = if area.height == 4 { + IntroContent::DisclosureOnly + } else { + IntroContent::Full + }; + let intro_lines = self.intro_lines(area.width, intro_content); + let input_area = self.input_area(area); + let intro_area = Rect { + height: input_area.y.saturating_sub(area.y), + ..area + }; let (_, placeholder) = feedback_title_and_placeholder(self.category); - let input_height = self.input_height(area.width); + let input_height = input_area.height; + let max_scroll = intro_lines + .len() + .saturating_sub(usize::from(intro_area.height)); + self.intro_scroll + .set(self.intro_scroll.get().min(max_scroll)); + self.intro_page_height + .set(usize::from(intro_area.height.max(1))); - for (offset, line) in intro_lines.iter().enumerate() { + for (offset, line) in intro_lines + .iter() + .skip(self.intro_scroll.get()) + .take(usize::from(intro_area.height)) + .enumerate() + { Paragraph::new(line.clone()).render( Rect { x: area.x, @@ -159,14 +224,13 @@ impl Renderable for FeedbackNoteView { buf, ); } + let disclosure_url = match self.feedback_audience { + FeedbackAudience::OpenAiEmployee => EMPLOYEE_FEEDBACK_FAQ_URL, + FeedbackAudience::External => FEEDBACK_POLICY_URL, + }; + mark_underlined_hyperlink(buf, intro_area, disclosure_url); // Input line - let input_area = Rect { - x: area.x, - y: area.y.saturating_add(intro_lines.len() as u16), - width: area.width, - height: input_height, - }; if input_area.width >= 2 { for row in 0..input_area.height { Paragraph::new(Line::from(vec![gutter()])).render( @@ -206,7 +270,8 @@ impl Renderable for FeedbackNoteView { } let hint_blank_y = input_area.y.saturating_add(input_height); - if hint_blank_y < area.y.saturating_add(area.height) { + let hint_spacing = u16::from(area.height > 4); + if hint_spacing > 0 && hint_blank_y < area.y.saturating_add(area.height) { let blank_area = Rect { x: area.x, y: hint_blank_y, @@ -214,11 +279,26 @@ impl Renderable for FeedbackNoteView { height: 1, }; Clear.render(blank_area, buf); + if max_scroll > 0 { + Paragraph::new(Line::from(vec![ + key_hint::plain(KeyCode::PageUp).into(), + "/".into(), + key_hint::plain(KeyCode::PageDown).into(), + " scroll disclosure".into(), + ])) + .render(blank_area, buf); + } } - let hint_y = hint_blank_y.saturating_add(1); + let hint_y = hint_blank_y.saturating_add(hint_spacing); if hint_y < area.y.saturating_add(area.height) { - Paragraph::new(standard_popup_hint_line()).render( + Paragraph::new(accept_cancel_hint_line( + Some(key_hint::plain(KeyCode::Enter).into()), + "to submit", + Some(key_hint::plain(KeyCode::Esc).into()), + "to cancel", + )) + .render( Rect { x: area.x, y: hint_y, @@ -232,15 +312,59 @@ impl Renderable for FeedbackNoteView { } impl FeedbackNoteView { + fn input_area(&self, area: Rect) -> Rect { + // Reserve the editor and submit/cancel hints before allocating disclosure rows. + let footer_height = if area.height > 4 { 2 } else { 1 }; + let input_height = self + .input_height(area.width) + .min(area.height.saturating_sub(footer_height + 1).max(2)) + .min(area.height); + let intro_height = (self.intro_lines(area.width, IntroContent::Full).len() as u16) + .min(area.height.saturating_sub(input_height + footer_height)); + Rect { + y: area.y.saturating_add(intro_height), + height: input_height, + ..area + } + } + fn input_height(&self, width: u16) -> u16 { let usable_width = width.saturating_sub(2); let text_height = self.textarea.desired_height(usable_width).clamp(1, 8); text_height.saturating_add(1).min(9) } - fn intro_lines(&self, _width: u16) -> Vec> { + fn intro_lines(&self, width: u16, content: IntroContent) -> Vec> { let (title, _) = feedback_title_and_placeholder(self.category); - vec![Line::from(vec![gutter(), title.bold()])] + let mut lines = match content { + IntroContent::Full => vec![title.bold().into(), "".into()], + IntroContent::DisclosureOnly => Vec::new(), + }; + match self.feedback_audience { + FeedbackAudience::OpenAiEmployee => { + lines.push( + "Your data may be used to improve our models and products" + .bold() + .into(), + ); + lines.push(Line::from(vec![ + "By submitting feedback, you agree that OpenAI can use your feedback for safety purposes and internal model training, as explained in more detail ".dim(), + "here".cyan().underlined(), + ". Please avoid sharing tented information under NDA, or privileged, HR-related, or sensitive personal information.".dim(), + ])); + } + FeedbackAudience::External => lines.push(Line::from(vec![ + "Your feedback can be used to improve ChatGPT. ".dim(), + "Learn more".cyan().underlined(), + ".".dim(), + ])), + } + word_wrap_lines( + lines, + RtOptions::new(usize::from(width.max(1))) + .initial_indent(Line::from(gutter())) + .subsequent_indent(Line::from(gutter())), + ) } } diff --git a/codex-rs/tui/src/bottom_pane/feedback_note_view_tests.rs b/codex-rs/tui/src/bottom_pane/feedback_note_view_tests.rs index e6926ce91a..4e50f72d78 100644 --- a/codex-rs/tui/src/bottom_pane/feedback_note_view_tests.rs +++ b/codex-rs/tui/src/bottom_pane/feedback_note_view_tests.rs @@ -18,7 +18,7 @@ fn render_buffer(area: Rect, buf: &Buffer) -> String { if symbol.is_empty() { line.push(' '); } else { - line.push_str(symbol); + line.push_str(&crate::terminal_hyperlinks::strip_osc8(symbol)); } } line.trim_end().to_string() @@ -38,7 +38,11 @@ fn make_view(category: FeedbackCategory) -> FeedbackNoteView { let (tx_raw, _rx) = tokio::sync::mpsc::unbounded_channel::(); let tx = AppEventSender::new(tx_raw); FeedbackNoteView::new( - category, /*turn_id*/ None, tx, /*include_logs*/ true, + category, + /*turn_id*/ None, + tx, + /*include_logs*/ true, + FeedbackAudience::External, ) } @@ -86,12 +90,137 @@ fn feedback_view_with_connectivity_diagnostics() { /*turn_id*/ None, tx, /*include_logs*/ false, + FeedbackAudience::External, ); let rendered = render(&view, /*width*/ 60); insta::assert_snapshot!("feedback_view_with_connectivity_diagnostics", rendered); } +#[test] +fn feedback_disclosure_matches_audience_at_normal_and_narrow_widths() { + let mut rendered = Vec::new(); + for (audience, name, url, link_label) in [ + ( + FeedbackAudience::External, + "external", + FEEDBACK_POLICY_URL, + "Learnmore", + ), + ( + FeedbackAudience::OpenAiEmployee, + "employee", + EMPLOYEE_FEEDBACK_FAQ_URL, + "here", + ), + ] { + let mut view = make_view(FeedbackCategory::Bug); + view.feedback_audience = audience; + for width in [80, 40] { + let area = Rect::new( + /*x*/ 0, + /*y*/ 0, + width, + view.desired_height(width), + ); + let mut buf = Buffer::empty(area); + view.render(area, &mut buf); + + let linked_text = buf + .content + .iter() + .filter(|cell| cell.symbol().contains(&format!("\x1b]8;;{url}\x07"))) + .map(|cell| crate::terminal_hyperlinks::strip_osc8(cell.symbol())) + .collect::(); + assert_eq!(linked_text, link_label); + rendered.push(format!( + "{name} ({width} columns)\n{}", + render_buffer(area, &buf) + )); + } + } + insta::assert_snapshot!("feedback_disclosures", rendered.join("\n\n")); +} + +#[test] +fn feedback_disclosure_scrolls_without_hiding_input_in_short_panes() { + let mut rendered = Vec::new(); + for (width, height) in [(40, 12), (80, 8)] { + let mut view = make_view(FeedbackCategory::Bug); + view.feedback_audience = FeedbackAudience::OpenAiEmployee; + view.handle_paste("A visible note.".to_string()); + let area = Rect::new(/*x*/ 0, /*y*/ 0, width, height); + for (stage, key) in [ + ("initial", None), + ("scrolled", Some(KeyCode::PageDown)), + ("restored", Some(KeyCode::PageUp)), + ] { + if let Some(key) = key { + view.handle_key_event(KeyEvent::from(key)); + } + let mut buf = Buffer::empty(area); + view.render(area, &mut buf); + let text = render_buffer(area, &buf); + assert!(text.contains("A visible note.")); + assert!(text.contains("enter to submit")); + assert!( + view.cursor_pos(area) + .is_some_and(|(x, y)| x < width && y < height) + ); + assert_eq!(view.is_complete(), false); + if stage == "scrolled" { + assert!(text.contains("sensitive personal")); + } + rendered.push(format!("{width}x{height} {stage}\n{text}")); + } + } + insta::assert_snapshot!("feedback_disclosure_short_panes", rendered.join("\n\n")); +} + +#[test] +fn feedback_disclosure_keeps_editor_visible_in_four_rows() { + let mut view = make_view(FeedbackCategory::Bug); + view.feedback_audience = FeedbackAudience::OpenAiEmployee; + view.handle_paste("A visible note.".to_string()); + let area = Rect::new( + /*x*/ 0, /*y*/ 0, /*width*/ 80, /*height*/ 4, + ); + let mut buf = Buffer::empty(area); + view.render(area, &mut buf); + + assert!( + view.cursor_pos(area) + .is_some_and(|(x, y)| area.contains((x, y).into())) + ); + let initial = render_buffer(area, &buf); + assert!(initial.contains("Your data may be used to improve our models and products")); + for _ in view.intro_lines(area.width, IntroContent::Full) { + view.handle_key_event(KeyEvent::from(KeyCode::PageDown)); + buf = Buffer::empty(area); + view.render(area, &mut buf); + } + let scrolled = render_buffer(area, &buf); + assert!(scrolled.contains("personal information.")); + insta::assert_snapshot!( + "feedback_disclosure_four_rows", + format!("{initial}\n\n{scrolled}") + ); + + let narrow_area = Rect::new( + /*x*/ 0, /*y*/ 0, /*width*/ 24, /*height*/ 4, + ); + for (audience, disclosure) in [ + (FeedbackAudience::OpenAiEmployee, "Your data"), + (FeedbackAudience::External, "Your feedback"), + ] { + let mut view = make_view(FeedbackCategory::SafetyCheck); + view.feedback_audience = audience; + let mut buf = Buffer::empty(narrow_area); + view.render(narrow_area, &mut buf); + assert!(render_buffer(narrow_area, &buf).contains(disclosure)); + } +} + #[test] fn submit_feedback_emits_submit_event_with_trimmed_note() { let (tx_raw, mut rx) = tokio::sync::mpsc::unbounded_channel::(); @@ -101,10 +230,11 @@ fn submit_feedback_emits_submit_event_with_trimmed_note() { Some("turn-123".to_string()), tx, /*include_logs*/ true, + FeedbackAudience::OpenAiEmployee, ); view.textarea.insert_str(" something broke "); - view.submit(); + view.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); let event = rx.try_recv().expect("submit feedback event"); assert!(matches!( @@ -128,6 +258,7 @@ fn submit_feedback_omits_empty_note() { /*turn_id*/ None, tx, /*include_logs*/ false, + FeedbackAudience::External, ); view.submit(); @@ -143,3 +274,21 @@ fn submit_feedback_omits_empty_note() { } )); } + +#[test] +fn feedback_note_cancel_does_not_submit() { + let (tx_raw, mut rx) = tokio::sync::mpsc::unbounded_channel::(); + let mut view = FeedbackNoteView::new( + FeedbackCategory::Bug, + /*turn_id*/ None, + AppEventSender::new(tx_raw), + /*include_logs*/ false, + FeedbackAudience::OpenAiEmployee, + ); + view.textarea.insert_str("unfinished feedback"); + + view.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)); + + assert_eq!(view.is_complete(), true); + assert!(rx.try_recv().is_err()); +} diff --git a/codex-rs/tui/src/bottom_pane/feedback_view.rs b/codex-rs/tui/src/bottom_pane/feedback_view.rs index d17c689431..0a0716c5d9 100644 --- a/codex-rs/tui/src/bottom_pane/feedback_view.rs +++ b/codex-rs/tui/src/bottom_pane/feedback_view.rs @@ -19,10 +19,9 @@ const BASE_CLI_BUG_ISSUE_URL: &str = /// Internal routing link for employee feedback follow-ups. This must not be shown to external users. const CODEX_FEEDBACK_INTERNAL_URL: &str = "http://go/codex-feedback-internal"; -/// The target audience for feedback follow-up instructions. +/// The target audience for feedback disclosure and follow-up instructions. /// -/// This is used strictly for messaging/links after feedback upload completes. It -/// must not change feedback upload behavior itself. +/// This controls displayed copy and links, not feedback upload behavior. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum FeedbackAudience { OpenAiEmployee, @@ -60,7 +59,7 @@ pub(crate) fn feedback_success_cell( let issue_url = issue_url_for_category(category, thread_id, feedback_audience); let mut lines = vec![Line::from(match issue_url.as_ref() { Some(_) if feedback_audience == FeedbackAudience::OpenAiEmployee => { - format!("{prefix} Please report this in #codex-feedback:") + format!("{prefix} You can share this in #codex-feedback:") } Some(_) => format!("{prefix} Please open an issue using the following URL:"), None => format!("{prefix} Thanks for the feedback!"), @@ -71,10 +70,15 @@ pub(crate) fn feedback_success_cell( "".into(), Line::from(vec![" ".into(), url.cyan().underlined()]), "".into(), - Line::from(" Share this and add some info about your problem:"), Line::from(vec![ - " ".into(), - format!("https://go/codex-feedback/{thread_id}").bold(), + " Sentry Feedback ID: ".into(), + thread_id.to_string().bold(), + ]), + Line::from(vec![ + " Sentry URL: ".into(), + format!("https://go/codex-feedback/{thread_id}") + .cyan() + .underlined(), ]), ]); } @@ -352,7 +356,7 @@ mod tests { if symbol.is_empty() { line.push(' '); } else { - line.push_str(symbol); + line.push_str(&crate::terminal_hyperlinks::strip_osc8(symbol)); } } line.trim_end().to_string() @@ -520,10 +524,7 @@ mod tests { ), /*width*/ 120, ); - assert_eq!( - rendered, - "• Feedback uploaded. Please report this in #codex-feedback:\n\n http://go/codex-feedback-internal\n\n Share this and add some info about your problem:\n https://go/codex-feedback/thread-2" - ); + insta::assert_snapshot!("feedback_success_employee", rendered); } #[test] diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_disclosure_four_rows.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_disclosure_four_rows.snap new file mode 100644 index 0000000000..78d4b6dc19 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_disclosure_four_rows.snap @@ -0,0 +1,13 @@ +--- +source: tui/src/bottom_pane/feedback_note_view_tests.rs +expression: "format!(\"{initial}\\n\\n{scrolled}\")" +--- +▌ Your data may be used to improve our models and products +▌ +▌ A visible note. +Press enter to submit or esc to cancel + +▌ sensitive personal information. +▌ +▌ A visible note. +Press enter to submit or esc to cancel diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_disclosure_short_panes.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_disclosure_short_panes.snap new file mode 100644 index 0000000000..c3a5555208 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_disclosure_short_panes.snap @@ -0,0 +1,75 @@ +--- +source: tui/src/bottom_pane/feedback_note_view_tests.rs +expression: "rendered.join(\"\\n\\n\")" +--- +40x12 initial +▌ Tell us more (bug) +▌ +▌ Your data may be used to improve our +▌ models and products +▌ By submitting feedback, you agree that +▌ OpenAI can use your feedback for +▌ safety purposes and internal model +▌ training, as explained in more detail +▌ +▌ A visible note. +pgup/pgdn scroll disclosure +Press enter to submit or esc to cancel + +40x12 scrolled +▌ By submitting feedback, you agree that +▌ OpenAI can use your feedback for +▌ safety purposes and internal model +▌ training, as explained in more detail +▌ here. Please avoid sharing tented +▌ information under NDA, or privileged, +▌ HR-related, or sensitive personal +▌ information. +▌ +▌ A visible note. +pgup/pgdn scroll disclosure +Press enter to submit or esc to cancel + +40x12 restored +▌ Tell us more (bug) +▌ +▌ Your data may be used to improve our +▌ models and products +▌ By submitting feedback, you agree that +▌ OpenAI can use your feedback for +▌ safety purposes and internal model +▌ training, as explained in more detail +▌ +▌ A visible note. +pgup/pgdn scroll disclosure +Press enter to submit or esc to cancel + +80x8 initial +▌ Tell us more (bug) +▌ +▌ Your data may be used to improve our models and products +▌ By submitting feedback, you agree that OpenAI can use your feedback for safety +▌ +▌ A visible note. +pgup/pgdn scroll disclosure +Press enter to submit or esc to cancel + +80x8 scrolled +▌ By submitting feedback, you agree that OpenAI can use your feedback for safety +▌ purposes and internal model training, as explained in more detail here. Please +▌ avoid sharing tented information under NDA, or privileged, HR-related, or +▌ sensitive personal information. +▌ +▌ A visible note. +pgup/pgdn scroll disclosure +Press enter to submit or esc to cancel + +80x8 restored +▌ Tell us more (bug) +▌ +▌ Your data may be used to improve our models and products +▌ By submitting feedback, you agree that OpenAI can use your feedback for safety +▌ +▌ A visible note. +pgup/pgdn scroll disclosure +Press enter to submit or esc to cancel diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_disclosures.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_disclosures.snap new file mode 100644 index 0000000000..8440474e66 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_disclosures.snap @@ -0,0 +1,53 @@ +--- +source: tui/src/bottom_pane/feedback_note_view_tests.rs +expression: "rendered.join(\"\\n\\n\")" +--- +external (80 columns) +▌ Tell us more (bug) +▌ +▌ Your feedback can be used to improve ChatGPT. Learn more. +▌ +▌ (optional) Write a short description to help us further + +Press enter to submit or esc to cancel + +external (40 columns) +▌ Tell us more (bug) +▌ +▌ Your feedback can be used to improve +▌ ChatGPT. Learn more. +▌ +▌ (optional) Write a short description t + +Press enter to submit or esc to cancel + +employee (80 columns) +▌ Tell us more (bug) +▌ +▌ Your data may be used to improve our models and products +▌ By submitting feedback, you agree that OpenAI can use your feedback for safety +▌ purposes and internal model training, as explained in more detail here. Please +▌ avoid sharing tented information under NDA, or privileged, HR-related, or +▌ sensitive personal information. +▌ +▌ (optional) Write a short description to help us further + +Press enter to submit or esc to cancel + +employee (40 columns) +▌ Tell us more (bug) +▌ +▌ Your data may be used to improve our +▌ models and products +▌ By submitting feedback, you agree that +▌ OpenAI can use your feedback for +▌ safety purposes and internal model +▌ training, as explained in more detail +▌ here. Please avoid sharing tented +▌ information under NDA, or privileged, +▌ HR-related, or sensitive personal +▌ information. +▌ +▌ (optional) Write a short description t + +Press enter to submit or esc to cancel diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_bad_result.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_bad_result.snap index cbf67c36bc..41867fcf6b 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_bad_result.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_bad_result.snap @@ -4,6 +4,8 @@ expression: rendered --- ▌ Tell us more (bad result) ▌ +▌ Your feedback can be used to improve ChatGPT. Learn more. +▌ ▌ (optional) Write a short description to help us further -Press enter to confirm or esc to go back +Press enter to submit or esc to cancel diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_bug.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_bug.snap index 468c1cb621..2183ee6105 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_bug.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_bug.snap @@ -4,6 +4,8 @@ expression: rendered --- ▌ Tell us more (bug) ▌ +▌ Your feedback can be used to improve ChatGPT. Learn more. +▌ ▌ (optional) Write a short description to help us further -Press enter to confirm or esc to go back +Press enter to submit or esc to cancel diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_good_result.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_good_result.snap index cc8559fef9..8c1f63b226 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_good_result.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_good_result.snap @@ -4,6 +4,8 @@ expression: rendered --- ▌ Tell us more (good result) ▌ +▌ Your feedback can be used to improve ChatGPT. Learn more. +▌ ▌ (optional) Write a short description to help us further -Press enter to confirm or esc to go back +Press enter to submit or esc to cancel diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_other.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_other.snap index 10100ee92a..14af47e54c 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_other.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_other.snap @@ -4,6 +4,8 @@ expression: rendered --- ▌ Tell us more (other) ▌ +▌ Your feedback can be used to improve ChatGPT. Learn more. +▌ ▌ (optional) Write a short description to help us further -Press enter to confirm or esc to go back +Press enter to submit or esc to cancel diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_safety_check.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_safety_check.snap index c108f0f27c..faee63e0e4 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_safety_check.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_safety_check.snap @@ -4,6 +4,8 @@ expression: rendered --- ▌ Tell us more (safety check) ▌ +▌ Your feedback can be used to improve ChatGPT. Learn more. +▌ ▌ (optional) Share what was refused and why it should have b -Press enter to confirm or esc to go back +Press enter to submit or esc to cancel diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_with_connectivity_diagnostics.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_with_connectivity_diagnostics.snap index 468c1cb621..2183ee6105 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_with_connectivity_diagnostics.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_note_view__tests__feedback_view_with_connectivity_diagnostics.snap @@ -4,6 +4,8 @@ expression: rendered --- ▌ Tell us more (bug) ▌ +▌ Your feedback can be used to improve ChatGPT. Learn more. +▌ ▌ (optional) Write a short description to help us further -Press enter to confirm or esc to go back +Press enter to submit or esc to cancel diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_view__tests__feedback_success_employee.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_view__tests__feedback_success_employee.snap new file mode 100644 index 0000000000..b69ba4bf39 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__feedback_view__tests__feedback_success_employee.snap @@ -0,0 +1,10 @@ +--- +source: tui/src/bottom_pane/feedback_view.rs +expression: rendered +--- +• Feedback uploaded. You can share this in #codex-feedback: + + http://go/codex-feedback-internal + + Sentry Feedback ID: thread-2 + Sentry URL: https://go/codex-feedback/thread-2 diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 85eaa61c1d..00632610c4 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -997,20 +997,14 @@ impl ChatWidget { &mut self, category: crate::app_event::FeedbackCategory, include_logs: bool, - ) { - self.show_feedback_note(category, include_logs); - } - - fn show_feedback_note( - &mut self, - category: crate::app_event::FeedbackCategory, - include_logs: bool, + feedback_audience: crate::bottom_pane::FeedbackAudience, ) { let view = crate::bottom_pane::FeedbackNoteView::new( category, self.turn_lifecycle.last_turn_id.clone(), self.app_event_tx.clone(), include_logs, + feedback_audience, ); self.bottom_pane.show_view(Box::new(view)); self.request_redraw(); diff --git a/codex-rs/tui/src/chatwidget/tests/app_server.rs b/codex-rs/tui/src/chatwidget/tests/app_server.rs index dca8e84efe..3d3c91d762 100644 --- a/codex-rs/tui/src/chatwidget/tests/app_server.rs +++ b/codex-rs/tui/src/chatwidget/tests/app_server.rs @@ -813,6 +813,7 @@ async fn live_app_server_turn_started_sets_feedback_turn_id() { chat.open_feedback_note( crate::app_event::FeedbackCategory::Bug, /*include_logs*/ false, + crate::bottom_pane::FeedbackAudience::External, ); chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));