Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
entry for it — membership (`codewhale login`) is the only door.
- Add the Tideline component family from the ratatui translation spec
(#5698's screens, riding the #5699 work-strip layout): hero startup
surface with quick actions and option strip, composer restyle with the
fluke cap, notifications inbox, merged footer band, pod ledger, receipt
surface with quick actions and option strip, notifications inbox, merged
footer band, pod ledger, receipt
stream, theme list with motion toggles, live preview, settings rail,
and the left rail — each a standalone render module pinned by 28 new
byte-exact golden buffers. Frame wiring follows the Tideline acceptance
Expand Down
4 changes: 2 additions & 2 deletions crates/tui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
entry for it — membership (`codewhale login`) is the only door.
- Add the Tideline component family from the ratatui translation spec
(#5698's screens, riding the #5699 work-strip layout): hero startup
surface with quick actions and option strip, composer restyle with the
fluke cap, notifications inbox, merged footer band, pod ledger, receipt
surface with quick actions and option strip, notifications inbox, merged
footer band, pod ledger, receipt
stream, theme list with motion toggles, live preview, settings rail,
and the left rail — each a standalone render module pinned by 28 new
byte-exact golden buffers. Frame wiring follows the Tideline acceptance
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,9 @@
"PhaseDone": "fet",
"PhaseFailed": "fallit",
"PhaseFinishing": "acabant",
"TidelineRuns": "EXECUCIONS",
"TidelineWhales": "BALENES",
"TidelineRunningCount": "{count} en execució",
"ChipModeAct": "work",
"ChipModePlan": "plan",
"ChipModeOperate": "operate",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,9 @@
"PhaseDone": "fertig",
"PhaseFailed": "fehlgeschlagen",
"PhaseFinishing": "schließt ab",
"TidelineRuns": "LÄUFE",
"TidelineWhales": "WALE",
"TidelineRunningCount": "{count} laufen",
"ChipModeAct": "work",
"ChipModePlan": "plan",
"ChipModeOperate": "operate",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,9 @@
"PhaseDone": "done",
"PhaseFailed": "failed",
"PhaseFinishing": "finishing",
"TidelineRuns": "RUNS",
"TidelineWhales": "WHALES",
"TidelineRunningCount": "{count} running",
"ChipModeAct": "work",
"ChipModePlan": "plan",
"ChipModeOperate": "operate",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/es-419.json
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,9 @@
"PhaseDone": "listo",
"PhaseFailed": "falló",
"PhaseFinishing": "finalizando",
"TidelineRuns": "EJECUCIONES",
"TidelineWhales": "BALLENAS",
"TidelineRunningCount": "{count} en ejecución",
"ChipModeAct": "work",
"ChipModePlan": "plan",
"ChipModeOperate": "operate",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,9 @@
"PhaseDone": "terminé",
"PhaseFailed": "échoué",
"PhaseFinishing": "finalisation",
"TidelineRuns": "EXÉCUTIONS",
"TidelineWhales": "BALEINES",
"TidelineRunningCount": "{count} en cours",
"ChipModeAct": "work",
"ChipModePlan": "plan",
"ChipModeOperate": "operate",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/hi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,9 @@
"PhaseDone": "पूर्ण",
"PhaseFailed": "विफल",
"PhaseFinishing": "समापन हो रहा",
"TidelineRuns": "रन",
"TidelineWhales": "व्हेल",
"TidelineRunningCount": "{count} चल रहे हैं",
"ChipModeAct": "work",
"ChipModePlan": "plan",
"ChipModeOperate": "operate",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,9 @@
"PhaseDone": "selesai",
"PhaseFailed": "gagal",
"PhaseFinishing": "menyelesaikan",
"TidelineRuns": "JALAN",
"TidelineWhales": "PAUS",
"TidelineRunningCount": "{count} berjalan",
"ChipModeAct": "work",
"ChipModePlan": "plan",
"ChipModeOperate": "operate",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,9 @@
"PhaseDone": "完了",
"PhaseFailed": "失敗",
"PhaseFinishing": "仕上げ中",
"TidelineRuns": "実行",
"TidelineWhales": "クジラ",
"TidelineRunningCount": "{count} 実行中",
"ChipModeAct": "work",
"ChipModePlan": "plan",
"ChipModeOperate": "operate",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,9 @@
"PhaseDone": "완료",
"PhaseFailed": "실패",
"PhaseFinishing": "마무리 중",
"TidelineRuns": "실행",
"TidelineWhales": "고래",
"TidelineRunningCount": "{count}개 실행 중",
"ChipModeAct": "실행",
"ChipModePlan": "계획",
"ChipModeOperate": "운영",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,9 @@
"PhaseDone": "concluído",
"PhaseFailed": "falhou",
"PhaseFinishing": "finalizando",
"TidelineRuns": "EXECUÇÕES",
"TidelineWhales": "BALEIAS",
"TidelineRunningCount": "{count} em execução",
"ChipModeAct": "work",
"ChipModePlan": "plan",
"ChipModeOperate": "operate",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,9 @@
"PhaseDone": "готово",
"PhaseFailed": "ошибка",
"PhaseFinishing": "завершение",
"TidelineRuns": "ЗАПУСКИ",
"TidelineWhales": "КИТЫ",
"TidelineRunningCount": "{count} выполняются",
"ChipModeAct": "work",
"ChipModePlan": "plan",
"ChipModeOperate": "operate",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,9 @@
"PhaseDone": "готово",
"PhaseFailed": "невдача",
"PhaseFinishing": "завершення",
"TidelineRuns": "ЗАПУСКИ",
"TidelineWhales": "КИТИ",
"TidelineRunningCount": "{count} виконуються",
"ChipModeAct": "work",
"ChipModePlan": "plan",
"ChipModeOperate": "operate",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,9 @@
"PhaseDone": "xong",
"PhaseFailed": "thất bại",
"PhaseFinishing": "đang hoàn tất",
"TidelineRuns": "LƯỢT CHẠY",
"TidelineWhales": "CÁ VOI",
"TidelineRunningCount": "{count} đang chạy",
"ChipModeAct": "work",
"ChipModePlan": "plan",
"ChipModeOperate": "operate",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,9 @@
"PhaseDone": "完成",
"PhaseFailed": "失败",
"PhaseFinishing": "收尾中",
"TidelineRuns": "运行",
"TidelineWhales": "鲸群",
"TidelineRunningCount": "{count} 个运行中",
"ChipModeAct": "work",
"ChipModePlan": "plan",
"ChipModeOperate": "operate",
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/locales/zh-Hant.json
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,9 @@
"PhaseDraft": "草稿",
"PhaseFailed": "失敗",
"PhaseFinishing": "收尾中",
"TidelineRuns": "執行",
"TidelineWhales": "鯨群",
"TidelineRunningCount": "{count} 個執行中",
"PhaseIdle": "閒置",
"PhaseReading": "讀取中",
"PhaseReasoning": "推理中",
Expand Down
7 changes: 7 additions & 0 deletions crates/tui/src/localization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1406,6 +1406,10 @@ pub enum MessageId {
PhaseDone,
PhaseFailed,
PhaseFinishing,
// Active-session Tideline rail group labels.
TidelineRuns,
TidelineWhales,
TidelineRunningCount,
// Underwater header chips: mode and permission words.
ChipModeAct,
ChipModePlan,
Expand Down Expand Up @@ -3344,6 +3348,9 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[
MessageId::PhaseDone,
MessageId::PhaseFailed,
MessageId::PhaseFinishing,
MessageId::TidelineRuns,
MessageId::TidelineWhales,
MessageId::TidelineRunningCount,
MessageId::ChipModeAct,
MessageId::ChipModePlan,
MessageId::ChipModeOperate,
Expand Down
32 changes: 17 additions & 15 deletions crates/tui/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,10 +421,6 @@ pub struct Settings {
pub composer_vim_mode: String,
/// Transcript spacing rhythm: compact, comfortable, spacious
pub transcript_spacing: String,
/// Show the pre-session launch menu. When false, Codewhale enters a new
/// session directly; resume remains available in-session.
#[serde(default)]
pub launch_screen: bool,
/// Default mode: "agent" (Act), "plan", or "operate". Legacy permission
/// shorthands are accepted for migration but never advertised as modes.
pub default_mode: String,
Expand Down Expand Up @@ -608,7 +604,6 @@ impl Default for Settings {
composer_multiline_mode: false,
composer_vim_mode: "normal".to_string(),
transcript_spacing: "comfortable".to_string(),
launch_screen: false,
default_mode: "agent".to_string(),
sidebar_width_percent: 28,
sidebar_focus: "auto".to_string(),
Expand Down Expand Up @@ -1482,9 +1477,6 @@ impl Settings {
}
self.transcript_spacing = normalized.to_string();
}
"launch_screen" | "launch" => {
self.launch_screen = parse_bool(value)?;
}
"status_indicator" | "indicator" => {
let normalized = normalize_status_indicator(value);
if !["cw", "whale", "dots", "off"].contains(&normalized) {
Expand Down Expand Up @@ -1698,7 +1690,6 @@ impl Settings {
self.workspace_follow_symlinks
));
lines.push(format!(" default_mode: {}", self.default_mode));
lines.push(format!(" launch_screen: {}", self.launch_screen));
lines.push(format!(" context_panel: {}", self.context_panel));
lines.push(format!(" cost_currency: {}", self.cost_currency));
lines.push(format!(" max_history: {}", self.max_input_history));
Expand Down Expand Up @@ -1876,10 +1867,6 @@ impl Settings {
"transcript_spacing",
"Transcript spacing: compact, comfortable, spacious",
),
(
"launch_screen",
"Show the pre-session launch menu on startup: on/off",
),
(
"status_indicator",
"Header status mark, shown before the route: cw, whale, dots, off",
Expand Down Expand Up @@ -3588,9 +3575,24 @@ mod tests {
);
assert!(!settings.low_motion);
assert_eq!(settings.transcript_spacing, "comfortable");
}

#[test]
fn retired_launch_screen_setting_is_accepted_and_dropped_on_save() {
let tmp = tempfile::tempdir().expect("tempdir");
let path = tmp.path().join("settings.toml");
std::fs::write(&path, "launch_screen = false\n").expect("legacy settings");

let settings = Settings::load_persisted_from_candidates(Some(path.clone()), None, None)
.expect("legacy setting must remain readable");
settings
.save_to_path(&path)
.expect("save normalized settings");

let saved = std::fs::read_to_string(&path).expect("read normalized settings");
assert!(
!settings.launch_screen,
"returning users enter a session directly"
!saved.contains("launch_screen"),
"the retired setting must not be written back: {saved}"
);
}

Expand Down
9 changes: 8 additions & 1 deletion crates/tui/src/test_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,18 @@ pub(crate) fn test_tui_options(workspace: impl AsRef<Path>) -> crate::tui::app::
/// `App::new` consults real persisted settings (provider/model maps,
/// auto-model, route limits, locale, currency), so an un-pinned fixture
/// computes against whatever the developer last configured. Every pin below
/// exists because some test was observed to depend on it.
/// exists because some test was observed to depend on it. This fixture models
/// a session after the user has chosen a Startup action; direct `App::new`
/// tests remain the clean-launch authority.
pub(crate) fn test_app_with_options(options: crate::tui::app::TuiOptions) -> crate::tui::app::App {
let config = crate::config::Config::default();
let mut app = crate::tui::app::App::new(options, &config);

// Shared behavior tests operate on the live session surface. Do not make
// the production startup conditional for them: clean launches are covered
// by direct `App::new` tests that retain the Tideline Startup Hero.
app.launch.visible = false;

// Deterministic presentation regardless of host locale.
app.cost_currency = crate::pricing::CostCurrency::Usd;
app.ui_locale = crate::localization::Locale::En;
Expand Down
8 changes: 6 additions & 2 deletions crates/tui/src/tui/app/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,12 @@ impl App {
false
};
settings.apply_env_overrides();
let launch_visible =
settings.launch_screen && resume_session_id.is_none() && initial_input.is_none();
// Tideline Startup is the fresh interactive landing surface. It must
// not be bypassed by a stale historical `launch_screen = false`, a
// provider/config notice, or a previous session record: only an
// intentional resume or explicit initial input enters the live session
// path directly.
let launch_visible = resume_session_id.is_none() && initial_input.is_none();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Run deferred setup gates after leaving Startup

Every clean interactive launch now sets launch.visible to true, but the event loop's only call to open_setup_checkpoint_if_due is guarded by !app.launch.visible; later, begin_launch_session merely clears the flag without invoking that gate. Consequently, a returning user with a due versioned Constitution checkpoint can choose New Session, Chat, or submit from the Startup composer and work for the entire process without seeing the checkpoint; --skip-onboarding also no longer records its deferral. Run the post-launch setup gate when Startup transitions into a session, or otherwise preserve the gate before exposing Startup.

Useful? React with 👍 / 👎.

let launch = LaunchState::new(launch_visible, &workspace);

// If settings.toml exists on disk but couldn't be parsed (we fell back
Expand Down
62 changes: 62 additions & 0 deletions crates/tui/src/tui/app/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ fn initial_input_prefill_waits_for_manual_submit() {

let app = App::new(options, &Config::default());

assert!(
!app.launch.visible,
"an intentional prefilled prompt must enter the live composer instead of the startup hero"
);
assert_eq!(app.input, "review this PR");
assert_eq!(app.cursor_position, "review this PR".chars().count());
assert!(!app.auto_submit_initial_input);
Expand All @@ -148,6 +152,10 @@ fn initial_input_submit_marks_startup_dispatch() {

let app = App::new(options, &Config::default());

assert!(
!app.launch.visible,
"an intentional submitted prompt must bypass the startup hero"
);
assert_eq!(app.input, "阅读项目 and wait for instructions");
assert_eq!(
app.cursor_position,
Expand All @@ -156,6 +164,60 @@ fn initial_input_submit_marks_startup_dispatch() {
assert!(app.auto_submit_initial_input);
}

#[test]
fn clean_launch_keeps_startup_hero_despite_a_startup_notice() {
let _env_lock = lock_test_env();
let tmp = tempfile::tempdir().expect("tempdir");
let config_path = tmp.path().join("config.toml");
let _config_env = EnvVarGuard::set("DEEPSEEK_CONFIG_PATH", &config_path);
std::fs::write(tmp.path().join("settings.toml"), "launch_screen = false\n")
.expect("legacy settings");
let mut options = test_options(false);
options.startup_notice =
Some("Provider route changed; inspect the route before sending".into());

let app = App::new(options, &Config::default());

assert!(
app.launch.visible,
"a fresh interactive launch must keep the Tideline startup hero visible; a notice is not an intentional resume or prompt"
);
}

#[test]
fn explicit_resume_bypasses_startup_hero() {
let _env_lock = lock_test_env();
let tmp = tempfile::tempdir().expect("tempdir");
let config_path = tmp.path().join("config.toml");
let _config_env = EnvVarGuard::set("DEEPSEEK_CONFIG_PATH", &config_path);
let mut options = test_options(false);
options.resume_session_id = Some("explicit-resume".into());

let app = App::new(options, &Config::default());

assert!(
!app.launch.visible,
"an explicit resume must preserve the existing session path"
);
}

#[test]
fn remote_control_initial_input_bypasses_startup_hero() {
let _env_lock = lock_test_env();
let tmp = tempfile::tempdir().expect("tempdir");
let config_path = tmp.path().join("config.toml");
let _config_env = EnvVarGuard::set("DEEPSEEK_CONFIG_PATH", &config_path);
let mut options = test_options(false);
options.initial_input = Some(InitialInput::RemoteControl);

let app = App::new(options, &Config::default());

assert!(
!app.launch.visible,
"an intentional remote-control launch must preserve its existing direct-session path"
);
}

#[test]
fn composer_arrows_scroll_default_is_true_without_mouse_capture() {
assert!(default_composer_arrows_scroll_for_platform(false, false));
Expand Down
Loading
Loading