Add Daybreak state to the status line and terminal title (#49861)

## What changed

Add a selectable `daybreak` item to the status line and terminal title, including setup previews and terminal-title validation. Display `Daybreak on` when enabled for the thread and `Daybreak off` otherwise, including during side conversations.

## Testing

Add coverage for on/off rendering and side conversations, update setup preview snapshots, and verify that terminal-title diagnostics accept `daybreak`.

GitOrigin-RevId: b0b7a6f0ee0a107ba4ee2d56113a5b9b6ebd1d41
This commit is contained in:
Eric Traut
2026-10-01 04:25:23 +00:00
committed by copyberry
parent cd4a9cbd25
commit 8ea2428c38
10 changed files with 68 additions and 11 deletions
+6 -6
View File
@@ -142,6 +142,7 @@ fn terminal_title_item_id(item: &str) -> Option<&'static str> {
"total-output-tokens" => Some("total-output-tokens"),
"thread-id" | "session-id" => Some("thread-id"),
"fast-mode" => Some("fast-mode"),
"daybreak" => Some("daybreak"),
"model" | "model-name" => Some("model"),
"model-with-reasoning" => Some("model-with-reasoning"),
"reasoning" => Some("reasoning"),
@@ -309,11 +310,12 @@ mod tests {
}
#[test]
fn terminal_title_accepts_thread_usage_items() {
fn terminal_title_accepts_configured_items() {
let check = terminal_title_check_from_inputs(TerminalTitleInputs {
configured_items: Some(vec![
"thread-credits".to_string(),
"estimated-thread-cost".to_string(),
"daybreak".to_string(),
]),
cwd: PathBuf::from("/workspace/project"),
project_root: None,
@@ -321,11 +323,9 @@ mod tests {
assert_eq!(check.status, CheckStatus::Ok);
assert_eq!(check.summary, "terminal title configured");
assert!(
check.details.contains(
&"terminal title items: thread-credits, estimated-thread-cost".to_string()
)
);
assert!(check.details.contains(
&"terminal title items: thread-credits, estimated-thread-cost, daybreak".to_string()
));
assert!(check.issues.is_empty());
}
@@ -141,6 +141,9 @@ pub(crate) enum StatusLineItem {
/// Whether Fast mode is currently active.
FastMode,
/// Whether Daybreak is enabled for this thread.
Daybreak,
/// Whether raw scrollback mode is currently active.
RawOutput,
@@ -204,6 +207,7 @@ impl StatusLineItem {
}
StatusLineItem::SessionId => "Current thread identifier (omitted until thread starts)",
StatusLineItem::FastMode => "Whether Fast mode is currently active",
StatusLineItem::Daybreak => "Whether Daybreak is enabled for this thread",
StatusLineItem::RawOutput => "Whether raw scrollback mode is active",
StatusLineItem::ThreadName => "Current thread name (omitted when unnamed)",
StatusLineItem::ThreadTitle => {
@@ -245,6 +249,7 @@ impl StatusLineItem {
StatusLineItem::EstimatedThreadCost => StatusSurfacePreviewItem::EstimatedThreadCost,
StatusLineItem::SessionId => StatusSurfacePreviewItem::SessionId,
StatusLineItem::FastMode => StatusSurfacePreviewItem::FastMode,
StatusLineItem::Daybreak => StatusSurfacePreviewItem::Daybreak,
StatusLineItem::RawOutput => StatusSurfacePreviewItem::RawOutput,
StatusLineItem::ThreadName => StatusSurfacePreviewItem::ThreadName,
StatusLineItem::ThreadTitle => StatusSurfacePreviewItem::ThreadTitle,
@@ -54,7 +54,9 @@ impl StatusLineAccent {
StatusLineItem::CodexVersion | StatusLineItem::Hostname | StatusLineItem::SessionId => {
Self::Metadata
}
StatusLineItem::FastMode | StatusLineItem::RawOutput => Self::Mode,
StatusLineItem::FastMode | StatusLineItem::Daybreak | StatusLineItem::RawOutput => {
Self::Mode
}
StatusLineItem::Permissions => Self::Mode,
StatusLineItem::ApprovalMode => Self::Mode,
StatusLineItem::ThreadName
@@ -34,6 +34,7 @@ pub(crate) enum StatusSurfacePreviewItem {
EstimatedThreadCost,
SessionId,
FastMode,
Daybreak,
RawOutput,
WorkspaceHeadline,
Model,
@@ -71,6 +72,7 @@ impl StatusSurfacePreviewItem {
StatusSurfacePreviewItem::EstimatedThreadCost => "~$1.82",
StatusSurfacePreviewItem::SessionId => "550e8400-e29b-41d4",
StatusSurfacePreviewItem::FastMode => "Fast on",
StatusSurfacePreviewItem::Daybreak => "Daybreak off",
StatusSurfacePreviewItem::RawOutput => "raw output",
StatusSurfacePreviewItem::WorkspaceHeadline => "Workspace headline",
StatusSurfacePreviewItem::Model => "gpt-5.2-codex",
@@ -108,6 +110,7 @@ impl StatusSurfacePreviewItem {
Self::EstimatedThreadCost,
Self::SessionId,
Self::FastMode,
Self::Daybreak,
Self::RawOutput,
Self::WorkspaceHeadline,
Self::Model,
@@ -83,6 +83,8 @@ pub(crate) enum TerminalTitleItem {
SessionId,
/// Whether Fast mode is currently active.
FastMode,
/// Whether Daybreak is enabled for this thread.
Daybreak,
/// Current model name.
#[strum(to_string = "model", serialize = "model-name")]
Model,
@@ -135,6 +137,7 @@ impl TerminalTitleItem {
"Current thread identifier (omitted until thread starts)"
}
TerminalTitleItem::FastMode => "Whether Fast mode is currently active",
TerminalTitleItem::Daybreak => "Whether Daybreak is enabled for this thread",
TerminalTitleItem::Model => "Current model name",
TerminalTitleItem::ModelWithReasoning => "Current model name with reasoning level",
TerminalTitleItem::Reasoning => "Current reasoning level",
@@ -170,6 +173,7 @@ impl TerminalTitleItem {
}
TerminalTitleItem::SessionId => Some(StatusSurfacePreviewItem::SessionId),
TerminalTitleItem::FastMode => Some(StatusSurfacePreviewItem::FastMode),
TerminalTitleItem::Daybreak => Some(StatusSurfacePreviewItem::Daybreak),
TerminalTitleItem::Model => Some(StatusSurfacePreviewItem::Model),
TerminalTitleItem::ModelWithReasoning => {
Some(StatusSurfacePreviewItem::ModelWithReasoning)
@@ -12,9 +12,9 @@ expression: status_line_popup_snapshot(&mut chat)
[x] project-name Project name (omitted when unavailable)
[x] git-branch Current Git branch (omitted when unavailable)
[x] thread-title Current thread title, or thread identifier when unnamed
[x] daybreak Whether Daybreak is enabled for this thread
[ ] model Current model name
[ ] model-with-reasoning Current model name with reasoning level
[ ] reasoning Current reasoning level
↓
my-project · feature/mixed-preview · Mixed preview thread
my-project · feature/mixed-preview · Mixed preview thread · Daybreak on
space toggle · ←/→ reorder · enter save · esc cancel
@@ -10,11 +10,11 @@ expression: terminal_title_popup_snapshot(&mut chat)
› [x] project-name Project name (falls back to current directory name)
[x] thread-title Current thread title, or thread identifier when unnamed
[x] task-progress Latest task progress from update_plan (omitted until available)
[x] daybreak Whether Daybreak is enabled for this thread
[ ] app-name Codex app name
[ ] current-dir Current working directory
[ ] activity Spinner while working, action-required message while blocked
[ ] run-state Compact session run-state text (Ready, Working, Thinking)
[ ] thread-name Current thread name (omitted when unnamed)
↓
project | Mixed preview thread | Tasks 0/0
project | Mixed preview thread | Tasks 0/0 | Daybreak on
space toggle · ←/→ reorder · enter save · esc cancel
@@ -815,6 +815,13 @@ impl ChatWidget {
"Fast off".to_string()
}
}),
StatusLineItem::Daybreak => Some(
if self.daybreak_enabled && !self.side_conversation_active() {
"Daybreak on".to_string()
} else {
"Daybreak off".to_string()
},
),
StatusLineItem::RawOutput => self.raw_output_mode().then(|| "raw output".to_string()),
StatusLineItem::ThreadName => {
self.thread_name.as_deref().and_then(normalize_thread_name)
@@ -868,6 +875,7 @@ impl ChatWidget {
StatusSurfacePreviewItem::EstimatedThreadCost => StatusLineItem::EstimatedThreadCost,
StatusSurfacePreviewItem::SessionId => StatusLineItem::SessionId,
StatusSurfacePreviewItem::FastMode => StatusLineItem::FastMode,
StatusSurfacePreviewItem::Daybreak => StatusLineItem::Daybreak,
StatusSurfacePreviewItem::RawOutput => StatusLineItem::RawOutput,
StatusSurfacePreviewItem::WorkspaceHeadline => StatusLineItem::WorkspaceHeadline,
StatusSurfacePreviewItem::Model => StatusLineItem::ModelName,
@@ -947,6 +955,9 @@ impl ChatWidget {
TerminalTitleItem::FastMode => self
.status_line_value_for_item(StatusLineItem::FastMode)
.map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)),
TerminalTitleItem::Daybreak => {
self.status_line_value_for_item(StatusLineItem::Daybreak)
}
TerminalTitleItem::Model => Some(Self::truncate_terminal_title_part(
self.model_display_name().to_string(),
/*max_chars*/ 32,
@@ -448,10 +448,12 @@ async fn status_line_setup_popup_mixed_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.status_line_branch = Some("feature/mixed-preview".to_string());
chat.thread_name = Some("Mixed preview thread".to_string());
chat.set_daybreak_enabled(/*enabled*/ true);
chat.local_settings.tui.status_line = Some(vec![
"project-name".to_string(),
"git-branch".to_string(),
"thread-title".to_string(),
"daybreak".to_string(),
]);
assert_chatwidget_snapshot!(
@@ -499,10 +501,12 @@ async fn terminal_title_setup_popup_hardcoded_only_snapshot() {
async fn terminal_title_setup_popup_mixed_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.thread_name = Some("Mixed preview thread".to_string());
chat.set_daybreak_enabled(/*enabled*/ true);
chat.local_settings.tui.terminal_title = Some(vec![
"project-name".to_string(),
"thread-title".to_string(),
"task-progress".to_string(),
"daybreak".to_string(),
]);
assert_chatwidget_snapshot!(
@@ -4,6 +4,34 @@ use super::*;
use crate::bottom_pane::goal_status_indicator_line;
use pretty_assertions::assert_eq;
#[tokio::test]
async fn daybreak_status_surfaces_follow_the_thread_preference() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.local_settings.tui.terminal_title = Some(vec!["daybreak".into()]);
let mut values = Vec::new();
for enabled in [false, true] {
chat.daybreak_enabled = enabled;
chat.refresh_terminal_title();
values.push(format!(
"{} | {}",
chat.status_line_value(StatusLineItem::Daybreak).unwrap(),
chat.last_terminal_title.as_deref().unwrap()
));
}
chat.set_side_conversation_active(/*active*/ true);
chat.refresh_terminal_title();
values.push(format!(
"{} | {}",
chat.status_line_value(StatusLineItem::Daybreak).unwrap(),
chat.last_terminal_title.as_deref().unwrap()
));
insta::assert_snapshot!(values.join("\n"), @r"
Daybreak off | Daybreak off
Daybreak on | Daybreak on
Daybreak off | Daybreak off
");
}
#[tokio::test]
async fn goal_clock_refresh_redraws_only_when_elapsed_label_changes() {
let (frame_requester, mut draw_rx) = FrameRequester::test_channel();