Skip to content

Commit 0403b30

Browse files
cursoragentechobt
andcommitted
fix(tui): default alternate_screen to never
Keep the welcome splash inline in the host terminal. The shell prompt stays above the app unless the user opts in via config or --alternate-screen. Co-authored-by: Mathis <echobt@users.noreply.github.com>
1 parent 3b444b4 commit 0403b30

11 files changed

Lines changed: 87 additions & 80 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
## Unreleased
1111

1212
### Changed
13-
### Changed
14-
- The TUI enters the **alternate screen** on interactive start so the host shell prompt is hidden. Opt out with `[tui] alternate_screen = false`.
13+
- 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`.
1514
- 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.
1615
- 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.
1716

docs/configuration/config.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,9 @@ max_bytes = 10000000
150150
[tui]
151151
animations = true
152152
notifications = true
153-
# Enter the alternate screen (default). Set false to stay inline in the host
154-
# terminal. Equivalent CLI flag: --alternate-screen
155-
alternate_screen = true
153+
# Stay inline in the host terminal (default / never). Set true only to take
154+
# over the alternate screen buffer. Equivalent CLI flag: --alternate-screen
155+
alternate_screen = false
156156

157157
[tui.theme]
158158
name = "dark" # dark, light, ocean_dark, monokai

docs/guides/tui.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,27 @@ cortex --profile work # load a profile from config.toml
1616
The TUI needs a terminal on both stdin and stdout. If either is redirected it
1717
refuses to start and points you at [`cortex run` or `cortex exec`](exec.md).
1818

19-
The session starts on the **alternate screen**, so the host shell prompt
20-
and the typed `cortex` command are hidden. The welcome splash is two lines:
19+
The session runs **inline** in the host terminal (`alternate_screen` is
20+
never on by default). Cortex does not enter the alternate screen buffer
21+
on start, so your shell prompt and the typed command stay visible above
22+
the app. The welcome splash is two lines:
2123
`Welcome to **Cortex**, the coding agent CLI` then
2224
`v{version} · / commands · @ files · ! shell · & cloud`. It does
2325
not paint a fake shell prompt or working directory. After the first user
2426
turn the splash is dropped; an empty session is composer and footer only.
2527

26-
To stay inline in the host terminal instead:
28+
To opt in to a full-screen alternate buffer:
2729

2830
```bash
29-
# config.toml
30-
[tui]
31-
alternate_screen = false
31+
cortex --alternate-screen
3232
```
3333

34-
`--alternate-screen` forces the alternate buffer on (already the default).
34+
or in `~/.cortex/config.toml`:
35+
36+
```toml
37+
[tui]
38+
alternate_screen = true
39+
```
3540

3641
## The session view
3742

docs/reference/cli.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ A positional prompt seeds that session. Without a terminal, use
3232
| `--add-dir <DIR>` | Extra writable directory; repeatable |
3333
| `-i`, `--image <PATH>` | Attach an image to the initial prompt |
3434
| `--search` | Enable web search |
35-
| `--alternate-screen` | Force the alternate screen buffer (already the default). Set `[tui] alternate_screen = false` to stay inline. |
35+
| `--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`. |
3636
| `--max-agent-threads <N>` | Concurrent agent threads |
3737
| `--max-tool-threads <N>` | Concurrent tool executions |
3838
| `--command-timeout <SECONDS>` | Shell command timeout |

src/cortex-cli/src/cli/args.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,9 @@ pub struct InteractiveArgs {
208208
#[arg(long = "search", default_value_t = false, help_heading = "Features")]
209209
pub web_search: bool,
210210

211-
/// Force the alternate screen buffer. Default is already on; use
212-
/// `[tui] alternate_screen = false` to stay inline in the host terminal.
211+
/// Opt in to the alternate screen buffer. Default is **never** (inline):
212+
/// the host shell prompt stays visible above the app. Same as
213+
/// `[tui] alternate_screen = true`.
213214
#[arg(
214215
long = "alternate-screen",
215216
default_value_t = false,
@@ -1197,7 +1198,7 @@ mod tests {
11971198
let cli = Cli::try_parse_from(["cortex"]).expect("should parse");
11981199
assert!(
11991200
!cli.interactive.alternate_screen,
1200-
"the flag is force-on; config already enables alt-screen"
1201+
"the flag is opt-in; default TUI is inline (never alt-screen)"
12011202
);
12021203
let cli = Cli::try_parse_from(["cortex", "--alternate-screen"])
12031204
.expect("should parse --alternate-screen");

src/cortex-engine/src/config/mod.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ pub struct Config {
7979
pub disable_paste_burst: bool,
8080
/// Enable TUI animations.
8181
pub animations: bool,
82-
/// Opt in to the alternate screen buffer. Default is inline (false).
82+
/// Opt in to the alternate screen buffer. Default is inline (`never`).
8383
pub alternate_screen: bool,
8484
/// Current agent profile.
8585
pub current_agent: Option<String>,
@@ -121,7 +121,7 @@ impl Default for Config {
121121
check_for_update_on_startup: true,
122122
disable_paste_burst: false,
123123
animations: true,
124-
alternate_screen: true,
124+
alternate_screen: false,
125125
current_agent: None,
126126
permission: PermissionConfig::default(),
127127
small_model: None, // Auto-detected based on available providers
@@ -246,7 +246,7 @@ impl Config {
246246
toml.tui
247247
.as_ref()
248248
.map(|t| t.alternate_screen)
249-
.unwrap_or(true)
249+
.unwrap_or(false)
250250
}),
251251
current_agent: toml.current_agent,
252252
permission: toml.permission,
@@ -330,17 +330,24 @@ mod alternate_screen_tests {
330330
use types::ConfigToml;
331331

332332
#[test]
333-
fn from_toml_enables_alternate_screen_by_default() {
333+
fn from_toml_keeps_alternate_screen_off_by_default() {
334+
assert!(
335+
!Config::default().alternate_screen,
336+
"default must be inline (never alt-screen)"
337+
);
334338
let toml: ConfigToml = toml::from_str("").unwrap();
335339
let cfg = Config::from_toml(toml, ConfigOverrides::default(), PathBuf::from("/tmp"));
336-
assert!(cfg.alternate_screen);
340+
assert!(
341+
!cfg.alternate_screen,
342+
"default must be inline (never alt-screen)"
343+
);
337344
}
338345

339346
#[test]
340-
fn from_toml_honors_tui_alternate_screen_opt_out() {
341-
let toml: ConfigToml = toml::from_str("[tui]\nalternate_screen = false\n").unwrap();
347+
fn from_toml_honors_tui_alternate_screen_opt_in() {
348+
let toml: ConfigToml = toml::from_str("[tui]\nalternate_screen = true\n").unwrap();
342349
let cfg = Config::from_toml(toml, ConfigOverrides::default(), PathBuf::from("/tmp"));
343-
assert!(!cfg.alternate_screen);
350+
assert!(cfg.alternate_screen);
344351
}
345352

346353
#[test]

src/cortex-engine/src/config/types.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,8 @@ pub enum ReasoningSummary {
327327
pub struct TuiConfig {
328328
#[serde(default = "default_animations")]
329329
pub animations: bool,
330-
/// Interactive start always uses the alternate screen so the host
331-
/// shell prompt is hidden. Set `false` only to stay inline.
330+
/// Default is **never** (inline). The host shell prompt stays visible
331+
/// above the app. Set `true` only to take over the alternate screen.
332332
#[serde(default = "default_alternate_screen")]
333333
pub alternate_screen: bool,
334334
#[serde(default)]
@@ -341,7 +341,7 @@ impl Default for TuiConfig {
341341
fn default() -> Self {
342342
Self {
343343
animations: true,
344-
alternate_screen: true,
344+
alternate_screen: false,
345345
notifications: NotificationsConfig::default(),
346346
theme: ThemeConfig::default(),
347347
}
@@ -353,7 +353,7 @@ fn default_animations() -> bool {
353353
}
354354

355355
fn default_alternate_screen() -> bool {
356-
true
356+
false
357357
}
358358

359359
/// Theme configuration.
@@ -488,17 +488,20 @@ mod tui_alternate_screen_tests {
488488
use super::*;
489489

490490
#[test]
491-
fn tui_alternate_screen_defaults_on() {
492-
assert!(TuiConfig::default().alternate_screen);
491+
fn tui_alternate_screen_defaults_never() {
492+
assert!(
493+
!TuiConfig::default().alternate_screen,
494+
"default must be inline (never alt-screen)"
495+
);
493496

494497
let parsed: ConfigToml = toml::from_str("").expect("empty config");
495498
assert!(parsed.tui.is_none());
496499

497500
let parsed: ConfigToml = toml::from_str("[tui]\n").expect("empty tui table");
498-
assert!(parsed.tui.expect("tui").alternate_screen);
501+
assert!(!parsed.tui.expect("tui").alternate_screen);
499502

500503
let parsed: ConfigToml =
501-
toml::from_str("[tui]\nalternate_screen = false\n").expect("opt-out");
502-
assert!(!parsed.tui.expect("tui").alternate_screen);
504+
toml::from_str("[tui]\nalternate_screen = true\n").expect("opt-in");
505+
assert!(parsed.tui.expect("tui").alternate_screen);
503506
}
504507
}

src/cortex-tui/src/runner/app_runner/runner.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,9 @@ impl AppRunner {
7979

8080
/// Create a new app runner with the given configuration.
8181
///
82-
/// The TUI starts on the **alternate screen** so the host shell prompt
83-
/// is hidden. Set `tui.alternate_screen = false` to stay inline.
82+
/// The TUI starts **inline** (never alternate screen) so the host shell
83+
/// prompt stays visible above the app. Set `tui.alternate_screen = true`
84+
/// to opt in to the alternate screen buffer.
8485
///
8586
/// # Arguments
8687
///
@@ -1053,17 +1054,20 @@ mod tests {
10531054
fn test_app_runner_terminal_options() {
10541055
let config = Config::default();
10551056

1056-
// Default: alternate screen
1057+
// Default: inline (never alternate screen)
10571058
let runner = AppRunner::new(config.clone());
1059+
assert!(
1060+
!runner.terminal_options.alternate_screen,
1061+
"default must be inline (never alt-screen)"
1062+
);
1063+
assert!(!runner.terminal_options.clear_on_start);
1064+
1065+
let mut fullscreen = config.clone();
1066+
fullscreen.alternate_screen = true;
1067+
let runner = AppRunner::new(fullscreen);
10581068
assert!(runner.terminal_options.alternate_screen);
10591069
assert!(runner.terminal_options.clear_on_start);
10601070

1061-
let mut inline = config.clone();
1062-
inline.alternate_screen = false;
1063-
let runner = AppRunner::new(inline);
1064-
assert!(!runner.terminal_options.alternate_screen);
1065-
assert!(!runner.terminal_options.clear_on_start);
1066-
10671071
// Custom options
10681072
let custom_options = TerminalOptions::new()
10691073
.alternate_screen(false)

src/cortex-tui/src/runner/login_screen.rs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Login Screen
22
//!
3-
//! Enters the alternate screen so the host shell prompt is hidden while
4-
//! the picker is open.
3+
//! Inline TUI (no alternate screen) so the host shell prompt stays in
4+
//! scrollback above the picker.
55
66
use std::io::stdout;
77
use std::path::{Path, PathBuf};
@@ -191,26 +191,19 @@ impl LoginScreen {
191191
pub async fn run(&mut self) -> Result<LoginResult> {
192192
crossterm::terminal::enable_raw_mode()?;
193193
let mut stdout = stdout();
194-
crossterm::execute!(
195-
stdout,
196-
crossterm::terminal::EnterAlternateScreen,
197-
crossterm::event::EnableMouseCapture,
198-
crossterm::terminal::Clear(crossterm::terminal::ClearType::All),
199-
)?;
194+
crossterm::execute!(stdout, crossterm::event::EnableMouseCapture)?;
200195

201196
let backend = CrosstermBackend::new(stdout);
202197
let mut terminal = Terminal::new(backend)?;
203-
terminal.hide_cursor()?;
204198

205199
let result = self.run_loop(&mut terminal).await;
206200

207-
let _ = crossterm::execute!(
201+
crossterm::terminal::disable_raw_mode()?;
202+
crossterm::execute!(
208203
terminal.backend_mut(),
209204
crossterm::event::DisableMouseCapture,
210-
crossterm::terminal::LeaveAlternateScreen,
211-
);
212-
let _ = crossterm::terminal::disable_raw_mode();
213-
let _ = terminal.show_cursor();
205+
)?;
206+
terminal.show_cursor()?;
214207

215208
result
216209
}

src/cortex-tui/src/runner/terminal.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,7 @@ impl Drop for TerminalGuard {
116116
/// Configuration options for terminal initialization.
117117
///
118118
/// This struct uses the builder pattern to allow flexible configuration
119-
/// of terminal features. All features are enabled by default for the
120-
/// best user experience.
119+
/// of terminal features. Alternate screen is **off** by default (inline).
121120
///
122121
/// # Example
123122
///
@@ -147,26 +146,26 @@ pub struct TerminalOptions {
147146

148147
impl Default for TerminalOptions {
149148
fn default() -> Self {
150-
// Interactive start uses the alternate screen by default.
149+
// Interactive start is inline: never enter the alternate screen.
151150
Self {
152-
alternate_screen: true,
151+
alternate_screen: false,
153152
mouse_capture: true,
154153
bracketed_paste: true,
155154
title: Some("Cortex".to_string()),
156-
clear_on_start: true,
155+
clear_on_start: false,
157156
}
158157
}
159158
}
160159

161160
impl TerminalOptions {
162161
/// Create a new `TerminalOptions` with default settings.
163162
///
164-
/// Default settings enter the alternate screen so the host shell is hidden:
165-
/// - Alternate screen: on (set `tui.alternate_screen = false` to stay inline)
163+
/// Default settings stay **inline** (never alternate screen):
164+
/// - Alternate screen: off (set `[tui] alternate_screen = true` to opt in)
166165
/// - Mouse capture: enabled
167166
/// - Bracketed paste: enabled
168167
/// - Title: "Cortex"
169-
/// - Clear on start: on
168+
/// - Clear on start: off
170169
pub fn new() -> Self {
171170
Self::default()
172171
}
@@ -266,10 +265,10 @@ pub struct CortexTerminal {
266265
}
267266

268267
impl CortexTerminal {
269-
/// Create a new terminal with default full-screen mode.
268+
/// Create a new terminal with default **inline** mode (no alternate screen).
270269
///
271270
/// This initializes the terminal with:
272-
/// - Alternate screen buffer
271+
/// - Inline buffer (host shell prompt stays visible above the app)
273272
/// - Mouse capture
274273
/// - Bracketed paste mode
275274
/// - Hidden cursor
@@ -987,11 +986,14 @@ mod tests {
987986
#[test]
988987
fn test_terminal_options_default() {
989988
let options = TerminalOptions::default();
990-
assert!(options.alternate_screen);
989+
assert!(
990+
!options.alternate_screen,
991+
"default must be inline (never alt-screen)"
992+
);
991993
assert!(options.mouse_capture);
992994
assert!(options.bracketed_paste);
993995
assert_eq!(options.title, Some("Cortex".to_string()));
994-
assert!(options.clear_on_start);
996+
assert!(!options.clear_on_start);
995997
}
996998

997999
#[test]

0 commit comments

Comments
 (0)