feat: update splash (#3631)

- Update splash styling.
- Add center truncation for long paths.
  (Uses new `center_truncate_path` utility.)
- Update the suggested commands.


## New splash
<img width="560" height="326" alt="image"
src="https://github.com/user-attachments/assets/b80d7075-f376-4019-a464-b96a78b0676d"
/>

## Example with truncation:
<img width="524" height="317" alt="image"
src="https://github.com/user-attachments/assets/b023c5cc-0bf0-4d21-9b98-bfea85546eda"
/>
This commit is contained in:
ae
2025-09-15 06:44:40 -07:00
committed by GitHub
parent fdf4a68646
commit 9baa5c33da
6 changed files with 379 additions and 64 deletions

View File

@@ -574,7 +574,7 @@ impl HistoryCell for CompletedMcpToolCallWithImageOutput {
}
const TOOL_CALL_MAX_LINES: usize = 5;
const SESSION_HEADER_MAX_INNER_WIDTH: usize = 70;
const SESSION_HEADER_MAX_INNER_WIDTH: usize = 56; // Just an eyeballed value
fn title_case(s: &str) -> String {
if s.is_empty() {
@@ -628,24 +628,29 @@ pub(crate) fn new_session_info(
// Help lines below the header (new copy and list)
let help_lines: Vec<Line<'static>> = vec![
"Describe a task to get started or try one of the following commands:"
" To get started, describe a task or try one of these commands:"
.dim()
.into(),
Line::from("".dim()),
Line::from(""),
Line::from(vec![
"1. ".into(),
"/status".bold(),
" - show current session configuration and token usage".dim(),
" ".into(),
"/init".into(),
" - create an AGENTS.md file with instructions for Codex".dim(),
]),
Line::from(vec![
"2. ".into(),
"/compact".bold(),
" - compact the chat history to avoid context limits".dim(),
" ".into(),
"/status".into(),
" - show current session configuration".dim(),
]),
Line::from(vec![
"3. ".into(),
"/prompts".bold(),
" - explore starter prompts to get to know Codex".dim(),
" ".into(),
"/approvals".into(),
" - choose what Codex can do without approval".dim(),
]),
Line::from(vec![
" ".into(),
"/model".into(),
" - choose what model and reasoning effort to use".dim(),
]),
];
@@ -715,16 +720,31 @@ impl SessionHeaderHistoryCell {
}
}
fn format_directory(&self) -> String {
if let Some(rel) = relativize_to_home(&self.directory) {
fn format_directory(&self, max_width: Option<usize>) -> String {
Self::format_directory_inner(&self.directory, max_width)
}
fn format_directory_inner(directory: &Path, max_width: Option<usize>) -> String {
let formatted = if let Some(rel) = relativize_to_home(directory) {
if rel.as_os_str().is_empty() {
"~".to_string()
} else {
format!("~{}{}", std::path::MAIN_SEPARATOR, rel.display())
}
} else {
self.directory.display().to_string()
directory.display().to_string()
};
if let Some(max_width) = max_width {
if max_width == 0 {
return String::new();
}
if UnicodeWidthStr::width(formatted.as_str()) > max_width {
return crate::text_formatting::center_truncate_path(&formatted, max_width);
}
}
formatted
}
fn reasoning_label(&self) -> Option<&'static str> {
@@ -753,79 +773,93 @@ impl HistoryCell for SessionHeaderHistoryCell {
top.push('╭');
top.push_str(&"".repeat(inner_width));
top.push('╮');
out.push(Line::from(top));
out.push(Line::from(top.dim()));
// Title line rendered inside the box: " >_ OpenAI Codex (vX)"
let title_text = format!(" >_ OpenAI Codex (v{})", self.version);
let title_w = UnicodeWidthStr::width(title_text.as_str());
let pad_w = inner_width.saturating_sub(title_w);
let mut title_spans: Vec<Span<'static>> = vec![
"".into(),
" ".into(),
">_ ".into(),
"OpenAI Codex".bold(),
" ".into(),
format!("(v{})", self.version).dim(),
Span::from("").dim(),
Span::from(" ").dim(),
Span::from(">_ ").dim(),
Span::from("OpenAI Codex").bold(),
Span::from(" ").dim(),
Span::from(format!("(v{})", self.version)).dim(),
];
if pad_w > 0 {
title_spans.push(" ".repeat(pad_w).into());
title_spans.push(Span::from(" ".repeat(pad_w)).dim());
}
title_spans.push("".into());
title_spans.push(Span::from("").dim());
out.push(Line::from(title_spans));
// Spacer row between title and details
out.push(Line::from(vec![
"".into(),
" ".repeat(inner_width).into(),
"".into(),
Span::from(format!("{}", " ".repeat(inner_width))).dim(),
]));
// Model line: " Model: <model> <reasoning_label> (change with /model)"
const CHANGE_MODEL_HINT: &str = "(change with /model)";
// Model line: " model: <model> <reasoning_label> (change with /model)"
const CHANGE_MODEL_HINT_COMMAND: &str = "/model";
const CHANGE_MODEL_HINT_EXPLANATION: &str = " to change";
const DIR_LABEL: &str = "directory:";
let label_width = DIR_LABEL.len();
let model_label = format!(
"{model_label:<label_width$}",
model_label = "model:",
label_width = label_width
);
let reasoning_label = self.reasoning_label();
let mut model_text = format!(" Model: {}", self.model);
let mut model_value_for_width = self.model.clone();
if let Some(reasoning) = reasoning_label {
model_text.push(' ');
model_text.push_str(reasoning);
model_value_for_width.push(' ');
model_value_for_width.push_str(reasoning);
}
model_text.push(' ');
model_text.push_str(CHANGE_MODEL_HINT);
let model_w = UnicodeWidthStr::width(model_text.as_str());
let model_text_for_width_calc = format!(
" {model_label} {model_value_for_width} {CHANGE_MODEL_HINT_COMMAND}{CHANGE_MODEL_HINT_EXPLANATION}",
);
let model_w = UnicodeWidthStr::width(model_text_for_width_calc.as_str());
let pad_w = inner_width.saturating_sub(model_w);
let mut spans: Vec<Span<'static>> = vec![
"".into(),
" ".into(),
"Model: ".bold(),
self.model.clone().into(),
Span::from(format!("{model_label} ")).dim(),
Span::from(self.model.clone()),
];
if let Some(reasoning) = reasoning_label {
spans.push(" ".into());
spans.push(reasoning.into());
spans.push(Span::from(" "));
spans.push(Span::from(reasoning));
}
spans.push(" ".into());
spans.push(CHANGE_MODEL_HINT.dim());
spans.push(Span::from(" ").dim());
spans.push(Span::from(CHANGE_MODEL_HINT_COMMAND).cyan());
spans.push(Span::from(CHANGE_MODEL_HINT_EXPLANATION).dim());
if pad_w > 0 {
spans.push(" ".repeat(pad_w).into());
spans.push(Span::from(" ".repeat(pad_w)).dim());
}
spans.push("".into());
spans.push(Span::from("").dim());
out.push(Line::from(spans));
// Directory line: " Directory: <cwd>"
let dir = self.format_directory();
let dir_text = format!(" Directory: {dir}");
let dir_label = format!("{DIR_LABEL:<label_width$}");
let dir_prefix = format!(" {dir_label} ");
let dir_max_width = inner_width.saturating_sub(UnicodeWidthStr::width(dir_prefix.as_str()));
let dir = self.format_directory(Some(dir_max_width));
let dir_text = format!(" {dir_label} {dir}");
let dir_w = UnicodeWidthStr::width(dir_text.as_str());
let pad_w = inner_width.saturating_sub(dir_w);
let mut spans: Vec<Span<'static>> =
vec!["".into(), " ".into(), "Directory: ".bold(), dir.into()];
let mut spans: Vec<Span<'static>> = vec![
Span::from("").dim(),
Span::from(" ").dim(),
Span::from(dir_label).dim(),
Span::from(" ").dim(),
Span::from(dir),
];
if pad_w > 0 {
spans.push(" ".repeat(pad_w).into());
spans.push(Span::from(" ".repeat(pad_w)).dim());
}
spans.push("".into());
spans.push(Span::from("").dim());
out.push(Line::from(spans));
// Bottom border
let bottom = format!("{}", "".repeat(inner_width));
out.push(Line::from(bottom));
out.push(Line::from(bottom.dim()));
out
}
@@ -1523,6 +1557,7 @@ mod tests {
use codex_core::config::Config;
use codex_core::config::ConfigOverrides;
use codex_core::config::ConfigToml;
use dirs::home_dir;
fn test_config() -> Config {
Config::load_from_base_config_with_overrides(
@@ -1561,11 +1596,35 @@ mod tests {
let lines = render_lines(&cell.display_lines(80));
let model_line = lines
.into_iter()
.find(|line| line.contains("Model:"))
.find(|line| line.contains("model:"))
.expect("model line");
assert!(model_line.contains("Model: gpt-4o high"));
assert!(model_line.contains("(change with /model)"));
assert!(model_line.contains("gpt-4o high"));
assert!(model_line.contains("/model to change"));
}
#[test]
fn session_header_directory_center_truncates() {
let mut dir = home_dir().expect("home directory");
for part in ["hello", "the", "fox", "is", "very", "fast"] {
dir.push(part);
}
let formatted = SessionHeaderHistoryCell::format_directory_inner(&dir, Some(24));
let sep = std::path::MAIN_SEPARATOR;
let expected = format!("~{sep}hello{sep}the{sep}{sep}very{sep}fast");
assert_eq!(formatted, expected);
}
#[test]
fn session_header_directory_front_truncates_long_segment() {
let mut dir = home_dir().expect("home directory");
dir.push("supercalifragilisticexpialidocious");
let formatted = SessionHeaderHistoryCell::format_directory_inner(&dir, Some(18));
let sep = std::path::MAIN_SEPARATOR;
let expected = format!("~{sep}…cexpialidocious");
assert_eq!(formatted, expected);
}
#[test]