Files
codex/codex-rs/tui/src/startup_draft_layout_tests.rs
T
Eric Traut 7b88d09d61 Decouple TUI startup presentation from execution configuration (#49875)
## Why

Startup presentation needs client preferences before thread initialization establishes effective execution permissions. The startup header should not infer permission state from local configuration.

## What changed

- Load `LocalSettings` directly from bootstrap TOML, preserving preference defaults and overrides, accessibility settings, and audio settings that exclude project layers.
- Use those settings for first-frame presentation and pass `LocalSettings` plus the working directory to the startup draft instead of a full `Config`.
- Stop showing the YOLO indicator in the startup header while execution permissions are unknown.

## Testing

Update tests to compare bootstrap settings with resolved configuration, verify first-frame keybindings and paste preferences with an unresolved permission-profile parent, and snapshot the configured startup header. Adapt startup editor tests to the settings-based interface.

GitOrigin-RevId: f33cf8f42c53a9ddcbe0273491b5a849da9e6808
2026-10-01 05:18:04 +00:00

134 lines
4.8 KiB
Rust

//! Verify provisional composer geometry while startup owns the terminal.
use super::*;
#[test]
fn owned_startup_keeps_the_live_bottom_geometry() {
let mut pump = crate::startup_draft::tests::quiet_startup_test_pump();
pump.bottom_pane.set_status_line_enabled(/*enabled*/ true);
pump.bottom_pane
.set_composer_text("first line\nsecond line".into(), Vec::new(), Vec::new());
pump.bottom_pane.set_footer_hint_override(Some(vec![
("Waiting for startup".into(), String::new()),
("esc".into(), "cancel".into()),
]));
let layout = OwnedStartupLayout::new(&pump);
let area = Rect::new(
/*x*/ 0, /*y*/ 0, /*width*/ 48, /*height*/ 16,
);
let mut buffer = Buffer::empty(area);
layout.render(area, &mut buffer);
let frame = (area.y..area.bottom())
.map(|y| {
(area.x..area.right())
.map(|x| buffer[(x, y)].symbol())
.collect::<String>()
})
.collect::<Vec<_>>()
.join("\n");
insta::assert_snapshot!(
"owned_startup_layout",
format!("cursor={:?}\n{frame}", layout.cursor_pos(area))
.replace(crate::version::CODEX_CLI_VERSION, "<VERSION>")
);
}
#[test]
fn new_startup_decoration_tracks_draft_and_session_action() {
let mut pump = crate::startup_draft::tests::quiet_startup_test_pump();
let area = Rect::new(
/*x*/ 0, /*y*/ 0, /*width*/ 80, /*height*/ 32,
);
let mut blossom_visible = Vec::new();
for (draft, action, pending) in [
("", StartupDraftSessionAction::New, false),
(" ", StartupDraftSessionAction::New, false),
("", StartupDraftSessionAction::New, false),
("", StartupDraftSessionAction::New, true),
("", StartupDraftSessionAction::Resume, false),
("", StartupDraftSessionAction::Fork, false),
("", StartupDraftSessionAction::NewFromCommandCenter, false),
] {
pump.session_action = action;
pump.submission_pending = pending;
pump.bottom_pane
.set_composer_text(draft.into(), Vec::new(), Vec::new());
let layout = OwnedStartupLayout::new(&pump);
let mut buffer = Buffer::empty(area);
layout.render(area, &mut buffer);
blossom_visible.push(buffer.content.iter().any(|cell| {
cell.symbol()
.chars()
.any(|ch| ('\u{2801}'..='\u{28ff}').contains(&ch))
}));
}
assert_eq!(
blossom_visible,
[true, false, true, false, false, false, true]
);
}
#[tokio::test]
async fn configured_welcome_opt_out_disables_blossom_and_clicks() -> anyhow::Result<()> {
use clap::Parser;
use codex_config::LoaderOverrides;
use crossterm::event::KeyModifiers;
use crossterm::event::MouseButton;
use crossterm::event::MouseEvent;
use crossterm::event::MouseEventKind;
let codex_home = tempfile::tempdir()?;
let cli = crate::Cli::try_parse_from(["codex"])?;
let area = Rect::new(
/*x*/ 0, /*y*/ 0, /*width*/ 80, /*height*/ 44,
);
let click = MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
column: 40,
row: 22,
modifiers: KeyModifiers::NONE,
};
for (settings, enabled) in [
("", true),
("[tui]\nanimations = false\n", false),
("[tui.effects]\nwelcome = false\n", false),
(
"[tui]\nanimations = false\n[tui.effects]\nwelcome = false\n",
false,
),
] {
std::fs::write(codex_home.path().join("config.toml"), settings)?;
let presentation = crate::startup_presentation::load(
&cli,
codex_home.path(),
LoaderOverrides {
ignore_project_config: true,
..LoaderOverrides::without_managed_config_for_tests()
},
Vec::new(),
/*config_cwd*/ None,
)
.await?;
let mut pump = crate::startup_draft::tests::quiet_startup_test_pump();
// The provisional frame uses the bootstrap value; subsequent frames use effective config.
pump.motion = presentation.screen.welcome_motion;
for effective in [false, true] {
if effective {
pump.apply_settings(&presentation.local_settings, codex_home.path());
}
let mut buffer = Buffer::empty(area);
OwnedStartupLayout::new(&pump).render(area, &mut buffer);
let visible = buffer.content.iter().any(|cell| {
cell.symbol()
.chars()
.any(|ch| ('\u{2801}'..='\u{28ff}').contains(&ch))
});
assert_eq!(
visible, enabled,
"settings: {settings}, effective: {effective}"
);
assert_eq!(pump.blossom.borrow_mut().handle_mouse(click), enabled);
}
}
Ok(())
}