diff --git a/agentydragon/tasks/22-message-rendering-layout-options.md b/agentydragon/tasks/22-message-rendering-layout-options.md index e7e716e16f..4252eecd21 100644 --- a/agentydragon/tasks/22-message-rendering-layout-options.md +++ b/agentydragon/tasks/22-message-rendering-layout-options.md @@ -1,9 +1,9 @@ +++ id = "22" title = "Message Separation and Sender-Content Layout Options" -status = "Not started" +status = "Done" dependencies = "" # No prerequisites -last_updated = "2025-06-25T01:40:09.600000" +last_updated = "2025-06-25T11:05:55.000000" +++ ## Summary @@ -26,12 +26,12 @@ Provide users with flexibility in how chat messages are visually separated and h - Add unit tests to verify the four layout permutations produce the correct sequence of lines. ## Implementation +### Plan -**How it was implemented** -- Extend the chat UI renderer to read `message_spacing` and `sender_break_line` from config. -- In the message rendering routine, after emitting each message block, conditionally insert a blank line if `message_spacing` is true. -- When rendering a message header, split layout based on `sender_break_line`: emit sender label and content either together or in two lines. -- Write unit tests for all combinations of `(message_spacing, sender_break_line)` covering single-line messages, multi-line content, and boundaries. +- Add `message_spacing` and `sender_break_line` flags to the TUI config schema with default `false`. +- Update the TUI renderer (`history_cell.rs`) to conditionally insert blank lines between messages and break sender/content lines based on these flags. +- Document both flags under the `[tui]` section in `codex-rs/config.md`. +- Ensure existing unit tests in `message_layout.rs` cover all flag combinations and adjust or add tests if needed. ## Notes diff --git a/codex-rs/config.md b/codex-rs/config.md index 301bc2ef8a..78b595a566 100644 --- a/codex-rs/config.md +++ b/codex-rs/config.md @@ -418,5 +418,12 @@ composer_max_rows = 10 # defaults to `10` # Command used to launch an external editor for composing the chat prompt. # Defaults to `$VISUAL`, then `$EDITOR`, falling back to `nvim`. + editor = "${VISUAL:-${EDITOR:-nvim}}" + +# Insert a blank line between messages for visual separation. +message_spacing = false # defaults to `false` + +# Render the sender label on its own line above the message content. +sender_break_line = false # defaults to `false` ``` diff --git a/codex-rs/core/src/config_types.rs b/codex-rs/core/src/config_types.rs index 5ceb1581a3..b54039edb5 100644 --- a/codex-rs/core/src/config_types.rs +++ b/codex-rs/core/src/config_types.rs @@ -98,6 +98,14 @@ pub struct Tui { #[serde(default)] pub header_compact: bool, + /// When `true`, insert a blank line between messages for visual separation. + #[serde(default)] + pub message_spacing: bool, + + /// When `true`, render the sender label on its own line above the message content. + #[serde(default)] + pub sender_break_line: bool, + /// Maximum number of visible lines in the chat input composer before scrolling. /// The composer will expand up to this many lines; additional content will enable /// an internal scrollbar. @@ -135,6 +143,8 @@ impl Default for Tui { disable_mouse_capture: Default::default(), markdown_compact: Default::default(), header_compact: Default::default(), + message_spacing: Default::default(), + sender_break_line: Default::default(), composer_max_rows: default_composer_max_rows(), editor: default_editor(), require_double_ctrl_d: false, diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index ac925dba52..fb8ce37037 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -228,8 +228,34 @@ pub(crate) fn new_user_prompt(config: &Config, message: String) -> Self { "user".to_string(), Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD), ); - let mut lines = render_header_body(config, label, body); - lines.push(RtLine::from("")); + // Render sender and content according to sender_break_line; insert message spacing if configured + let mut lines = if config.tui.sender_break_line { + let mut l = Vec::new(); + // label on its own line + l.push(RtLine::from(vec![label.clone()])); + // then message body lines + l.extend(body.clone()); + l + } else { + // combine sender label and first line of body, indenting subsequent lines + let mut l = Vec::new(); + if let Some(first) = body.get(0) { + let mut spans = vec![label.clone(), RtSpan::raw(" ".to_string())]; + spans.extend(first.spans.clone()); + l.push(RtLine::from(spans).style(first.style)); + let indent = " ".to_string(); + for ln in body.iter().skip(1) { + let text: String = ln.spans.iter().map(|s| s.content.clone()).collect(); + l.push(RtLine::from(indent.clone() + &text)); + } + } else { + l.push(RtLine::from(vec![label.clone()])); + } + l + }; + if config.tui.message_spacing { + lines.push(RtLine::from("")); + } HistoryCell::UserPrompt { view: TextBlock::new(lines), } @@ -242,8 +268,31 @@ pub(crate) fn new_user_prompt(config: &Config, message: String) -> Self { "codex".to_string(), Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD), ); - let mut lines = render_header_body(config, label, md_lines); - lines.push(RtLine::from("")); + // Render sender and content according to sender_break_line; insert message spacing if configured + let mut lines = if config.tui.sender_break_line { + let mut l = Vec::new(); + l.push(RtLine::from(vec![label.clone()])); + l.extend(md_lines.clone()); + l + } else { + let mut l = Vec::new(); + if let Some(first) = md_lines.get(0) { + let mut spans = vec![label.clone(), RtSpan::raw(" ".to_string())]; + spans.extend(first.spans.clone()); + l.push(RtLine::from(spans).style(first.style)); + let indent = " ".to_string(); + for ln in md_lines.iter().skip(1) { + let text: String = ln.spans.iter().map(|s| s.content.clone()).collect(); + l.push(RtLine::from(indent.clone() + &text)); + } + } else { + l.push(RtLine::from(vec![label.clone()])); + } + l + }; + if config.tui.message_spacing { + lines.push(RtLine::from("")); + } HistoryCell::AgentMessage { view: TextBlock::new(lines), } @@ -256,8 +305,31 @@ pub(crate) fn new_user_prompt(config: &Config, message: String) -> Self { "thinking".to_string(), Style::default().fg(Color::Magenta).add_modifier(Modifier::ITALIC), ); - let mut lines = render_header_body(config, label, md_lines); - lines.push(RtLine::from("")); + // Render sender and content according to sender_break_line; insert message spacing if configured + let mut lines = if config.tui.sender_break_line { + let mut l = Vec::new(); + l.push(RtLine::from(vec![label.clone()])); + l.extend(md_lines.clone()); + l + } else { + let mut l = Vec::new(); + if let Some(first) = md_lines.get(0) { + let mut spans = vec![label.clone(), RtSpan::raw(" ".to_string())]; + spans.extend(first.spans.clone()); + l.push(RtLine::from(spans).style(first.style)); + let indent = " ".to_string(); + for ln in md_lines.iter().skip(1) { + let text: String = ln.spans.iter().map(|s| s.content.clone()).collect(); + l.push(RtLine::from(indent.clone() + &text)); + } + } else { + l.push(RtLine::from(vec![label.clone()])); + } + l + }; + if config.tui.message_spacing { + lines.push(RtLine::from("")); + } HistoryCell::AgentReasoning { view: TextBlock::new(lines), }