Keep the composer editable during TUI startup (#38642)

## Why

Configuration and app-server initialization can take time before the main TUI is ready, leaving users unable to begin drafting a prompt.

## What changed

- Show a provisional composer while startup work runs and carry its text, cursor position, paste state, and attachments into the initialized chat.
- Limit the provisional composer to safe editing and cancellation, and quarantine input around session pickers, approvals, and other actionable startup screens.
- Preserve configuration validation before terminal checks and recover terminal state cleanly from startup failures and caught panics.

## Testing

- Cover startup editing, multiline and large pastes, keymaps, cancellation, session-picker handoff, approval boundaries, and draft restoration.
- Verify non-interactive launches report configuration errors before terminal errors.

GitOrigin-RevId: fe04a85cfbbcff21ff87fa81fd17474827858575
This commit is contained in:
Charlie Marsh
2026-08-14 20:08:49 +00:00
committed by copyberry
parent 58d2daba45
commit a0bed4be21
26 changed files with 4348 additions and 1151 deletions

View File

@@ -38,6 +38,82 @@ fn strict_config_rejects_unknown_config_override() -> Result<()> {
Ok(())
}
#[test]
fn interactive_validates_config_before_requiring_terminal() -> Result<()> {
let cases: &[(&[&str], &str, &str, &str)] = &[
(&[], "config.toml", "model = [", "Error loading config.toml"),
(
&["--strict-config"],
"config.toml",
"unknown_key = true",
"unknown configuration field",
),
(
&["--strict-config", "-c", "foo=bar"],
"config.toml",
"",
"unknown configuration field",
),
(
&["--profile", "work"],
"work.config.toml",
"model = [",
"work.config.toml",
),
(
&["-c", "model_provider=\"missing\""],
"config.toml",
"",
"Model provider `missing` not found",
),
(&[], "config.toml", "", "stdin is not a terminal"),
];
for &(args, config_file, contents, expected_error) in cases {
let codex_home = TempDir::new()?;
std::fs::write(codex_home.path().join(config_file), contents)?;
let mut cmd = codex_command(codex_home.path())?;
cmd.env("TERM", "xterm-256color")
.current_dir(codex_home.path())
.args(args)
.assert()
.failure()
.stderr(contains(expected_error));
}
Ok(())
}
#[test]
fn interactive_remote_default_preserves_remote_working_directory_before_requiring_terminal()
-> Result<()> {
let codex_home = TempDir::new()?;
std::fs::write(codex_home.path().join("config.toml"), "")?;
std::fs::write(
codex_home.path().join("environments.toml"),
r#"default = "remote"
include_local = false
[[environments]]
id = "remote"
url = "ws://127.0.0.1:4512"
"#,
)?;
let remote_only_cwd = codex_home.path().join("remote-only-working-directory");
let mut cmd = codex_command(codex_home.path())?;
cmd.env("TERM", "xterm-256color")
.current_dir(codex_home.path())
.arg("--cd")
.arg(remote_only_cwd)
.assert()
.failure()
.stderr(contains("stdin is not a terminal"));
Ok(())
}
#[test]
fn strict_config_is_not_supported_for_cloud_command() -> Result<()> {
let codex_home = TempDir::new()?;