diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8ac17698..72f7f9eb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Changed
-- The TUI runs **inline** by default (`alternate_screen` never). It does not enter the alternate screen on start; the real shell prompt stays in scrollback above the app. Opt in with `cortex --alternate-screen` or `[tui] alternate_screen = true`.
+- The TUI enters the **alternate screen** by default (`alternate_screen` always). Interactive launch takes the full viewport. Opt out with `cortex --no-alternate-screen` or `[tui] alternate_screen = false` to stay inline.
- Empty-session splash is `Welcome to Cortex, the coding agent CLI` plus `v{package version} · / commands · @ files · ! shell · & cloud`. After the first user turn the splash is dropped (composer + footer only). No mascot, no painted `> cortex` shell lines.
- Composer lock: empty is `> ` + white block at input col 0 + dim `Plan, search, build anything` after that cell (never a white rect after the placeholder). Blink-off (~530ms) hides the block so the placeholder starts at col 0. Typed copy is `#F5F5F5` with the block at the caret.
diff --git a/docs/configuration/config.md b/docs/configuration/config.md
index 104eb072..c85e84eb 100644
--- a/docs/configuration/config.md
+++ b/docs/configuration/config.md
@@ -150,9 +150,9 @@ max_bytes = 10000000
[tui]
animations = true
notifications = true
-# Stay inline in the host terminal (default / never). Set true only to take
-# over the alternate screen buffer. Equivalent CLI flag: --alternate-screen
-alternate_screen = false
+# Enter the alternate screen buffer (default / always). Set false to stay
+# inline in the host terminal. Equivalent CLI flag: --no-alternate-screen
+alternate_screen = true
[tui.theme]
name = "dark" # dark, light, ocean_dark, monokai
diff --git a/docs/guides/tui.md b/docs/guides/tui.md
index e913beb4..961ef887 100644
--- a/docs/guides/tui.md
+++ b/docs/guides/tui.md
@@ -16,26 +16,26 @@ cortex --profile work # load a profile from config.toml
The TUI needs a terminal on both stdin and stdout. If either is redirected it
refuses to start and points you at [`cortex run` or `cortex exec`](exec.md).
-The session runs **inline** in the host terminal (`alternate_screen` is
-never on by default). Cortex does not enter the alternate screen buffer
-on start, so your shell prompt and the typed command stay visible above
-the app. The welcome splash is two lines:
+The session enters the **alternate screen** on start (`alternate_screen`
+is always on by default) so Cortex takes the full viewport. The welcome
+splash is two lines:
`Welcome to **Cortex**, the coding agent CLI` then
`v{version} · / commands · @ files · ! shell · & cloud`. It does
not paint a fake shell prompt or working directory. After the first user
turn the splash is dropped; an empty session is composer and footer only.
-To opt in to a full-screen alternate buffer:
+To stay inline in the host terminal (shell prompt remains in scrollback
+above the app):
```bash
-cortex --alternate-screen
+cortex --no-alternate-screen
```
or in `~/.cortex/config.toml`:
```toml
[tui]
-alternate_screen = true
+alternate_screen = false
```
## The session view
diff --git a/docs/media/tui-lock/README.md b/docs/media/tui-lock/README.md
index 723e6506..ebb4f088 100644
--- a/docs/media/tui-lock/README.md
+++ b/docs/media/tui-lock/README.md
@@ -39,7 +39,9 @@ word-level colour on a changed line.
Splash copy in these frames is `Welcome to Cortex, the coding agent CLI`
plus `v{CARGO_PKG_VERSION} · / commands · …`. After the first user turn,
-`session_empty` is composer and footer only.
+`session_empty` is composer and footer only. Captures are full-viewport
+(matching the default alternate-screen launch); they do not paint a fake
+`> cortex` prompt or cwd line.
| File | Surface |
|------|---------|
diff --git a/docs/reference/cli.md b/docs/reference/cli.md
index 96840b20..5ae56c6f 100644
--- a/docs/reference/cli.md
+++ b/docs/reference/cli.md
@@ -32,7 +32,8 @@ A positional prompt seeds that session. Without a terminal, use
| `--add-dir
` | Extra writable directory; repeatable |
| `-i`, `--image ` | Attach an image to the initial prompt |
| `--search` | Enable web search |
-| `--alternate-screen` | Opt in to the alternate screen buffer. Default is inline (`never`): the host shell prompt stays visible above the app. Same as `[tui] alternate_screen = true`. |
+| `--alternate-screen` | Enter the alternate screen buffer. Default is always (`true`): Cortex takes the full viewport. Same as `[tui] alternate_screen = true`. |
+| `--no-alternate-screen` | Stay inline in the host terminal. Same as `[tui] alternate_screen = false`. |
| `--max-agent-threads ` | Concurrent agent threads |
| `--max-tool-threads ` | Concurrent tool executions |
| `--command-timeout ` | Shell command timeout |
diff --git a/src/cortex-cli/src/cli/args.rs b/src/cortex-cli/src/cli/args.rs
index ee9a7b72..219c81d2 100644
--- a/src/cortex-cli/src/cli/args.rs
+++ b/src/cortex-cli/src/cli/args.rs
@@ -208,16 +208,27 @@ pub struct InteractiveArgs {
#[arg(long = "search", default_value_t = false, help_heading = "Features")]
pub web_search: bool,
- /// Opt in to the alternate screen buffer. Default is **never** (inline):
- /// the host shell prompt stays visible above the app. Same as
- /// `[tui] alternate_screen = true`.
+ /// Enter the alternate screen buffer. Default is **always** (full
+ /// viewport). Same as `[tui] alternate_screen = true`. Use
+ /// `--no-alternate-screen` to stay inline.
#[arg(
long = "alternate-screen",
default_value_t = false,
+ conflicts_with = "no_alternate_screen",
help_heading = "Features"
)]
pub alternate_screen: bool,
+ /// Stay inline in the host terminal (never alternate screen). Same as
+ /// `[tui] alternate_screen = false`.
+ #[arg(
+ long = "no-alternate-screen",
+ default_value_t = false,
+ conflicts_with = "alternate_screen",
+ help_heading = "Features"
+ )]
+ pub no_alternate_screen: bool,
+
/// Maximum number of concurrent agent threads
#[arg(
long = "max-agent-threads",
@@ -974,6 +985,8 @@ mod tests {
assert!(args.add_dir.is_empty());
assert!(args.images.is_empty());
assert!(!args.web_search);
+ assert!(!args.alternate_screen);
+ assert!(!args.no_alternate_screen);
assert_eq!(args.log_level, LogLevel::Info);
assert!(!args.debug);
assert!(args.prompt.is_empty());
@@ -1194,15 +1207,39 @@ mod tests {
}
#[test]
- fn test_cli_alternate_screen_flag_defaults_off() {
+ fn test_cli_alternate_screen_flag_defaults_unset() {
let cli = Cli::try_parse_from(["cortex"]).expect("should parse");
assert!(
!cli.interactive.alternate_screen,
- "the flag is opt-in; default TUI is inline (never alt-screen)"
+ "the flag is unset unless passed; config default is always"
+ );
+ assert!(
+ !cli.interactive.no_alternate_screen,
+ "inline opt-out is unset unless --no-alternate-screen is passed"
);
let cli = Cli::try_parse_from(["cortex", "--alternate-screen"])
.expect("should parse --alternate-screen");
assert!(cli.interactive.alternate_screen);
+ assert!(!cli.interactive.no_alternate_screen);
+ let cli = Cli::try_parse_from(["cortex", "--no-alternate-screen"])
+ .expect("should parse --no-alternate-screen");
+ assert!(cli.interactive.no_alternate_screen);
+ assert!(!cli.interactive.alternate_screen);
+ }
+
+ #[test]
+ fn test_cli_alternate_screen_conflicts_with_no_alternate_screen() {
+ let result = Cli::try_parse_from(["cortex", "--alternate-screen", "--no-alternate-screen"]);
+ match result {
+ Err(err) => {
+ let rendered = err.to_string();
+ assert!(
+ rendered.contains("cannot be used with") || rendered.contains("conflict"),
+ "unexpected clap error: {rendered}"
+ );
+ }
+ Ok(_) => panic!("--alternate-screen and --no-alternate-screen must conflict"),
+ }
}
#[test]
diff --git a/src/cortex-cli/src/cli/handlers.rs b/src/cortex-cli/src/cli/handlers.rs
index 5860af9d..4ecd15ec 100644
--- a/src/cortex-cli/src/cli/handlers.rs
+++ b/src/cortex-cli/src/cli/handlers.rs
@@ -109,12 +109,20 @@ async fn run_tui(args: InteractiveArgs) -> Result<()> {
.as_ref()
.map(|m| resolve_model_alias(m).to_string()),
cwd: args.cwd.clone(),
- alternate_screen: args.alternate_screen.then_some(true),
+ alternate_screen: if args.no_alternate_screen {
+ Some(false)
+ } else if args.alternate_screen {
+ Some(true)
+ } else {
+ None
+ },
..Default::default()
})
.unwrap_or_else(|_| cortex_engine::Config::default());
- if args.alternate_screen {
+ if args.no_alternate_screen {
+ config.alternate_screen = false;
+ } else if args.alternate_screen {
config.alternate_screen = true;
}
diff --git a/src/cortex-engine/src/config/mod.rs b/src/cortex-engine/src/config/mod.rs
index 199f8502..ba5ebb9a 100644
--- a/src/cortex-engine/src/config/mod.rs
+++ b/src/cortex-engine/src/config/mod.rs
@@ -79,7 +79,7 @@ pub struct Config {
pub disable_paste_burst: bool,
/// Enable TUI animations.
pub animations: bool,
- /// Opt in to the alternate screen buffer. Default is inline (`never`).
+ /// Enter the alternate screen buffer. Default is always (`true`).
pub alternate_screen: bool,
/// Current agent profile.
pub current_agent: Option,
@@ -121,7 +121,7 @@ impl Default for Config {
check_for_update_on_startup: true,
disable_paste_burst: false,
animations: true,
- alternate_screen: false,
+ alternate_screen: true,
current_agent: None,
permission: PermissionConfig::default(),
small_model: None, // Auto-detected based on available providers
@@ -246,7 +246,7 @@ impl Config {
toml.tui
.as_ref()
.map(|t| t.alternate_screen)
- .unwrap_or(false)
+ .unwrap_or_else(|| TuiConfig::default().alternate_screen)
}),
current_agent: toml.current_agent,
permission: toml.permission,
@@ -320,7 +320,8 @@ pub struct ConfigOverrides {
pub additional_writable_roots: Vec,
/// Temperature override from CLI (0.0-2.0).
pub temperature: Option,
- /// When `Some(true)`, enter the alternate screen. Default (None/false) is inline.
+ /// When `Some`, force alternate screen on (`true`) or inline (`false`).
+ /// `None` keeps the config default (always).
pub alternate_screen: Option,
}
@@ -330,24 +331,24 @@ mod alternate_screen_tests {
use types::ConfigToml;
#[test]
- fn from_toml_keeps_alternate_screen_off_by_default() {
+ fn from_toml_keeps_alternate_screen_on_by_default() {
assert!(
- !Config::default().alternate_screen,
- "default must be inline (never alt-screen)"
+ Config::default().alternate_screen,
+ "default must enter the alternate screen (always)"
);
let toml: ConfigToml = toml::from_str("").unwrap();
let cfg = Config::from_toml(toml, ConfigOverrides::default(), PathBuf::from("/tmp"));
assert!(
- !cfg.alternate_screen,
- "default must be inline (never alt-screen)"
+ cfg.alternate_screen,
+ "default must enter the alternate screen (always)"
);
}
#[test]
- fn from_toml_honors_tui_alternate_screen_opt_in() {
- let toml: ConfigToml = toml::from_str("[tui]\nalternate_screen = true\n").unwrap();
+ fn from_toml_honors_tui_alternate_screen_opt_out() {
+ let toml: ConfigToml = toml::from_str("[tui]\nalternate_screen = false\n").unwrap();
let cfg = Config::from_toml(toml, ConfigOverrides::default(), PathBuf::from("/tmp"));
- assert!(cfg.alternate_screen);
+ assert!(!cfg.alternate_screen);
}
#[test]
@@ -363,4 +364,18 @@ mod alternate_screen_tests {
);
assert!(cfg.alternate_screen);
}
+
+ #[test]
+ fn cli_override_can_force_inline() {
+ let toml: ConfigToml = toml::from_str("[tui]\nalternate_screen = true\n").unwrap();
+ let cfg = Config::from_toml(
+ toml,
+ ConfigOverrides {
+ alternate_screen: Some(false),
+ ..Default::default()
+ },
+ PathBuf::from("/tmp"),
+ );
+ assert!(!cfg.alternate_screen);
+ }
}
diff --git a/src/cortex-engine/src/config/types.rs b/src/cortex-engine/src/config/types.rs
index 5b24b6d9..107a8455 100644
--- a/src/cortex-engine/src/config/types.rs
+++ b/src/cortex-engine/src/config/types.rs
@@ -327,8 +327,8 @@ pub enum ReasoningSummary {
pub struct TuiConfig {
#[serde(default = "default_animations")]
pub animations: bool,
- /// Default is **never** (inline). The host shell prompt stays visible
- /// above the app. Set `true` only to take over the alternate screen.
+ /// Default is **always** (full viewport). Set `false` to stay inline
+ /// so the host shell prompt remains visible above the app.
#[serde(default = "default_alternate_screen")]
pub alternate_screen: bool,
#[serde(default)]
@@ -341,7 +341,7 @@ impl Default for TuiConfig {
fn default() -> Self {
Self {
animations: true,
- alternate_screen: false,
+ alternate_screen: true,
notifications: NotificationsConfig::default(),
theme: ThemeConfig::default(),
}
@@ -353,7 +353,7 @@ fn default_animations() -> bool {
}
fn default_alternate_screen() -> bool {
- false
+ true
}
/// Theme configuration.
@@ -488,20 +488,20 @@ mod tui_alternate_screen_tests {
use super::*;
#[test]
- fn tui_alternate_screen_defaults_never() {
+ fn tui_alternate_screen_defaults_always() {
assert!(
- !TuiConfig::default().alternate_screen,
- "default must be inline (never alt-screen)"
+ TuiConfig::default().alternate_screen,
+ "default must enter the alternate screen (always)"
);
let parsed: ConfigToml = toml::from_str("").expect("empty config");
assert!(parsed.tui.is_none());
let parsed: ConfigToml = toml::from_str("[tui]\n").expect("empty tui table");
- assert!(!parsed.tui.expect("tui").alternate_screen);
+ assert!(parsed.tui.expect("tui").alternate_screen);
let parsed: ConfigToml =
- toml::from_str("[tui]\nalternate_screen = true\n").expect("opt-in");
- assert!(parsed.tui.expect("tui").alternate_screen);
+ toml::from_str("[tui]\nalternate_screen = false\n").expect("opt-out");
+ assert!(!parsed.tui.expect("tui").alternate_screen);
}
}
diff --git a/src/cortex-tui/src/lock_proof.rs b/src/cortex-tui/src/lock_proof.rs
index c9a6fa41..3f8ce16e 100644
--- a/src/cortex-tui/src/lock_proof.rs
+++ b/src/cortex-tui/src/lock_proof.rs
@@ -2,7 +2,8 @@
//!
//! Renders the real session, login, palette, and settings widgets through
//! [`cortex_tui_capture::MockTerminal`] and writes ANSI frames a rasteriser
-//! turns into PNGs.
+//! turns into PNGs. Frames are full-viewport (40×12 and 120×40), matching
+//! the default alternate-screen launch — no fake `> cortex` / cwd chrome.
use std::path::{Path, PathBuf};
@@ -2297,6 +2298,11 @@ mod tests {
);
}
assert!(!frame.plain.contains("▄█▀▀▀▀█▄"), "{}", frame.plain);
+ assert!(
+ !frame.plain.contains("> cortex"),
+ "splash must not paint a fake shell prompt at {size:?}:\n{}",
+ frame.plain
+ );
assert_no_junk(&frame.plain);
}
let wide = render_lock_scene("splash", 120, 40).expect("splash wide");
diff --git a/src/cortex-tui/src/runner/app_runner/runner.rs b/src/cortex-tui/src/runner/app_runner/runner.rs
index e9a2111e..49506186 100644
--- a/src/cortex-tui/src/runner/app_runner/runner.rs
+++ b/src/cortex-tui/src/runner/app_runner/runner.rs
@@ -79,9 +79,9 @@ impl AppRunner {
/// Create a new app runner with the given configuration.
///
- /// The TUI starts **inline** (never alternate screen) so the host shell
- /// prompt stays visible above the app. Set `tui.alternate_screen = true`
- /// to opt in to the alternate screen buffer.
+ /// The TUI starts on the **alternate screen** (always) so the session
+ /// takes the full viewport. Set `tui.alternate_screen = false` or pass
+ /// `--no-alternate-screen` to stay inline.
///
/// # Arguments
///
@@ -96,8 +96,6 @@ impl AppRunner {
pub fn new(config: Config) -> Self {
let terminal_options = if config.alternate_screen {
TerminalOptions::default()
- .alternate_screen(true)
- .clear_on_start(true)
} else {
TerminalOptions::inline()
};
@@ -1054,20 +1052,20 @@ mod tests {
fn test_app_runner_terminal_options() {
let config = Config::default();
- // Default: inline (never alternate screen)
+ // Default: alternate screen (always)
let runner = AppRunner::new(config.clone());
assert!(
- !runner.terminal_options.alternate_screen,
- "default must be inline (never alt-screen)"
+ runner.terminal_options.alternate_screen,
+ "default must enter the alternate screen (always)"
);
- assert!(!runner.terminal_options.clear_on_start);
-
- let mut fullscreen = config.clone();
- fullscreen.alternate_screen = true;
- let runner = AppRunner::new(fullscreen);
- assert!(runner.terminal_options.alternate_screen);
assert!(runner.terminal_options.clear_on_start);
+ let mut inline = config.clone();
+ inline.alternate_screen = false;
+ let runner = AppRunner::new(inline);
+ assert!(!runner.terminal_options.alternate_screen);
+ assert!(!runner.terminal_options.clear_on_start);
+
// Custom options
let custom_options = TerminalOptions::new()
.alternate_screen(false)
diff --git a/src/cortex-tui/src/runner/terminal.rs b/src/cortex-tui/src/runner/terminal.rs
index 5edb6626..6cd6a230 100644
--- a/src/cortex-tui/src/runner/terminal.rs
+++ b/src/cortex-tui/src/runner/terminal.rs
@@ -9,13 +9,13 @@
//! ```rust,ignore
//! use cortex_tui::runner::terminal::{CortexTerminal, TerminalOptions};
//!
-//! // Create with default options (inline, no alternate screen)
+//! // Create with default options (alternate screen, full viewport)
//! let mut terminal = CortexTerminal::new()?;
//!
//! // Or with custom options
//! let mut terminal = CortexTerminal::with_options(
//! TerminalOptions::new()
-//! .alternate_screen(true)
+//! .alternate_screen(false)
//! .mouse_capture(false)
//! .title("My App")
//! )?;
@@ -116,7 +116,7 @@ impl Drop for TerminalGuard {
/// Configuration options for terminal initialization.
///
/// This struct uses the builder pattern to allow flexible configuration
-/// of terminal features. Alternate screen is **off** by default (inline).
+/// of terminal features. Alternate screen is **on** by default (always).
///
/// # Example
///
@@ -146,13 +146,13 @@ pub struct TerminalOptions {
impl Default for TerminalOptions {
fn default() -> Self {
- // Interactive start is inline: never enter the alternate screen.
+ // Interactive start takes the full viewport (always alternate screen).
Self {
- alternate_screen: false,
+ alternate_screen: true,
mouse_capture: true,
bracketed_paste: true,
title: Some("Cortex".to_string()),
- clear_on_start: false,
+ clear_on_start: true,
}
}
}
@@ -160,12 +160,12 @@ impl Default for TerminalOptions {
impl TerminalOptions {
/// Create a new `TerminalOptions` with default settings.
///
- /// Default settings stay **inline** (never alternate screen):
- /// - Alternate screen: off (set `[tui] alternate_screen = true` to opt in)
+ /// Default settings enter the **alternate screen** (always):
+ /// - Alternate screen: on (set `[tui] alternate_screen = false` to stay inline)
/// - Mouse capture: enabled
/// - Bracketed paste: enabled
/// - Title: "Cortex"
- /// - Clear on start: off
+ /// - Clear on start: on
pub fn new() -> Self {
Self::default()
}
@@ -265,10 +265,10 @@ pub struct CortexTerminal {
}
impl CortexTerminal {
- /// Create a new terminal with default **inline** mode (no alternate screen).
+ /// Create a new terminal with default **alternate screen** mode.
///
/// This initializes the terminal with:
- /// - Inline buffer (host shell prompt stays visible above the app)
+ /// - Alternate screen buffer (full viewport)
/// - Mouse capture
/// - Bracketed paste mode
/// - Hidden cursor
@@ -987,13 +987,13 @@ mod tests {
fn test_terminal_options_default() {
let options = TerminalOptions::default();
assert!(
- !options.alternate_screen,
- "default must be inline (never alt-screen)"
+ options.alternate_screen,
+ "default must enter the alternate screen (always)"
);
assert!(options.mouse_capture);
assert!(options.bracketed_paste);
assert_eq!(options.title, Some("Cortex".to_string()));
- assert!(!options.clear_on_start);
+ assert!(options.clear_on_start);
}
#[test]