From 83f02f706caac31b16db93b7a21cb36d280bc099 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 9 Nov 2026 16:46:57 +0000 Subject: [PATCH] Keep fitting URLs intact in fullscreen Markdown transcripts (#60416) ## Why URLs in fullscreen Markdown transcripts could split across rows even when the full URL would fit on its own row. ## What changed Mark non-code Markdown lines containing URL-like text with the `UrlAware` wrap policy so transcript layout preserves fitting URLs on a single row. ## Testing Add a regression snapshot showing a documentation URL moving intact to the next row after introductory text. Update the conversation snapshot and copy-on-select test dimensions for the changed wrapping. GitOrigin-RevId: 4812c83483cff412406a71ae6745977c5384c7da --- ...t__tests__conversation_in_offset_rect.snap | 6 ++--- codex-rs/tui/src/markdown_render.rs | 5 ++++ .../transcript_view/copy_on_select_tests.rs | 2 +- .../tui/src/transcript_view/text_tests.rs | 23 +++++++++++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__owned_transcript__tests__conversation_in_offset_rect.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__owned_transcript__tests__conversation_in_offset_rect.snap index 58eceddd64..d18190e127 100644 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__owned_transcript__tests__conversation_in_offset_rect.snap +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__owned_transcript__tests__conversation_in_offset_rect.snap @@ -4,14 +4,14 @@ expression: normalize_snapshot_paths(screen) --- ···································· ···································· -····· ····· ·····› Second prompt ····· ····· ····· ····· ····· ·····• For the complete ····· ····· instructions see the ····· -····· manual (https:// ····· -····· example.com/docs). ····· +····· manual ····· +····· (https://example.com/doc····· +····· s). ····· ····· ····· ····· ····· ·····› preserved draft ····· diff --git a/codex-rs/tui/src/markdown_render.rs b/codex-rs/tui/src/markdown_render.rs index 69459f7243..d06cd5c05e 100644 --- a/codex-rs/tui/src/markdown_render.rs +++ b/codex-rs/tui/src/markdown_render.rs @@ -2396,6 +2396,11 @@ impl<'a, 'policy> Writer<'a, 'policy> { if let Some(mut line) = self.current_line_content.take() { let style = self.current_line_style; let mut source = crate::terminal_hyperlinks::LogicalLineSource::from_line(&line.line); + if !self.current_line_in_code_block + && crate::wrapping::text_contains_url_like(&source.text) + { + source.wrap_policy = crate::terminal_hyperlinks::LineWrapPolicy::UrlAware; + } source.copy = Some(std::sync::Arc::new(std::mem::take(&mut self.copy_line))); // NB we don't wrap code in code blocks, in order to preserve whitespace for copy/paste. if !self.current_line_in_code_block diff --git a/codex-rs/tui/src/transcript_view/copy_on_select_tests.rs b/codex-rs/tui/src/transcript_view/copy_on_select_tests.rs index 1f8bcd30c9..ab4c7affef 100644 --- a/codex-rs/tui/src/transcript_view/copy_on_select_tests.rs +++ b/codex-rs/tui/src/transcript_view/copy_on_select_tests.rs @@ -186,7 +186,7 @@ fn copy_on_select_preserves_link_activation_and_ignores_empty_drags() { copy_on_select: true, ..Default::default() }; - render(&mut view, &cells, /*width*/ 30, /*height*/ 1); + render(&mut view, &cells, /*width*/ 30, /*height*/ 2); let down = mouse(MouseEventKind::Down(MouseButton::Left), /*column*/ 2); assert!(matches!( view.handle_mouse(down, &cells), diff --git a/codex-rs/tui/src/transcript_view/text_tests.rs b/codex-rs/tui/src/transcript_view/text_tests.rs index 464bfe610c..e017b13007 100644 --- a/codex-rs/tui/src/transcript_view/text_tests.rs +++ b/codex-rs/tui/src/transcript_view/text_tests.rs @@ -298,6 +298,29 @@ fn narrow_wrapping_keeps_joined_emoji_whole() { assert_eq!(buffer[(0, 0)].symbol(), "👩‍💻"); } +#[test] +fn fullscreen_markdown_keeps_a_fitting_url_on_one_row() { + use crate::history_cell::AgentMarkdownCell; + use crate::history_cell::HistoryCell; + let cell = AgentMarkdownCell::new( + "See the documentation at https://example.com/docs/reference.".into(), + std::path::Path::new("."), + ); + let layout = TextLayout::new( + cell.retained_hyperlink_lines(/*width*/ 44, /*detailed*/ false), + /*width*/ 44, + ); + let rows = layout + .rows + .iter() + .map(|row| row.line.line.to_string()) + .collect::>(); + insta::assert_snapshot!(rows.join("\n"), @" + • See the documentation at + https://example.com/docs/reference. + "); +} + #[test] fn source_backed_markdown_copy_preserves_code_indentation() { use crate::history_cell::AgentMarkdownCell;