Skip to content

Commit 09a6187

Browse files
committed
feat(tui): default unread on, gate via session.unread_indicator setting
Switch the unread feature from the `AOE_UNREAD` env testing-gate to a real config toggle, on by default: - Add `session.unread_indicator: bool` (default true) via the single-source settings schema, so it surfaces in the TUI and web settings automatically. - Replace the env-var `unread_enabled()` OnceLock with a config-backed AtomicBool (default true) refreshed by the TUI on startup and whenever config is re-applied, so a settings change takes effect without a restart. Keeps the cheap accessor the hot Attention-sort path relies on. - Update doc comments and context-menu tests for the now-default-on feature. 🤖 Generated with Claude Code
1 parent b0419e4 commit 09a6187

7 files changed

Lines changed: 55 additions & 26 deletions

File tree

src/session/config.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,20 @@ pub struct SessionConfig {
10181018
#[setting(label = "Confirm Before Quit", widget = "toggle", global_only)]
10191019
pub confirm_before_quit: bool,
10201020

1021+
/// Show an unread indicator on sessions. When on (default), a session
1022+
/// whose turn just finished is painted in the theme's unread color until
1023+
/// you view it (Tab into live-send or Enter to attach), and you can flag
1024+
/// a session unread for later with `v`; unread rows also sort just below
1025+
/// Waiting in the Attention sort. Turn this off to disable the indicator,
1026+
/// the auto-marking, and the `v` toggle entirely.
1027+
#[serde(default = "default_true")]
1028+
#[setting(
1029+
label = "Unread Session Indicator",
1030+
widget = "toggle",
1031+
category = "Interaction"
1032+
)]
1033+
pub unread_indicator: bool,
1034+
10211035
/// Keep an aoe-managed worktree session's directory leaf in sync with its
10221036
/// title. When enabled (default), renaming the session also moves its
10231037
/// worktree directory, and new sessions derive the directory leaf from the
@@ -1141,6 +1155,7 @@ impl Default for SessionConfig {
11411155
default_attach_mode: NewSessionAttachMode::default(),
11421156
click_action: ClickAction::default(),
11431157
confirm_before_quit: true,
1158+
unread_indicator: true,
11441159
tie_workdir_to_name: true,
11451160
}
11461161
}

src/session/instance.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ pub enum Status {
5757
/// - `Manual`: a deliberate "flag for later." Cleared ONLY by the manual
5858
/// toggle; it survives Tab/Enter.
5959
///
60-
/// Gated behind `crate::session::unread_enabled()` (env `AOE_UNREAD`) at every
61-
/// surface for the testing phase.
60+
/// Gated behind `crate::session::unread_enabled()` (the `session.unread_indicator`
61+
/// config toggle, on by default) at every surface.
6262
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6363
#[serde(rename_all = "lowercase")]
6464
pub enum UnreadKind {
@@ -440,9 +440,9 @@ pub struct Instance {
440440
/// `Manual` is a deliberate flag cleared only by the manual toggle (it
441441
/// survives Tab/Enter). Surfaced as a non-intrusive `theme.unread` row
442442
/// color and an Attention-sort promoter ranked just below Waiting. The
443-
/// whole feature is gated behind `unread_enabled()` (env `AOE_UNREAD`);
444-
/// when off, the field is never written and changes nothing. See
445-
/// [`UnreadKind`].
443+
/// whole feature is gated behind `unread_enabled()` (the
444+
/// `session.unread_indicator` config toggle, on by default); when off, the
445+
/// field is never written and changes nothing. See [`UnreadKind`].
446446
#[serde(default, skip_serializing_if = "Option::is_none")]
447447
pub unread: Option<UnreadKind>,
448448

src/session/mod.rs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,26 @@ pub use instance::{
5252
TMUX_SESSION_GONE_ERROR,
5353
};
5454

55-
/// Whether the unread-session feature is enabled. Gated behind the
56-
/// `AOE_UNREAD` env var for the testing phase (truthy `1` / `on` / `true`),
57-
/// read once at startup. When off, every unread code path is a no-op and
58-
/// behavior is identical to before the feature landed.
55+
use std::sync::atomic::{AtomicBool, Ordering};
56+
57+
/// Process-wide cache of the `session.unread_indicator` toggle (default on).
58+
/// The TUI refreshes it via [`set_unread_enabled`] on startup and whenever
59+
/// config is re-applied, so a runtime settings change takes effect without a
60+
/// restart. Defaults to `true` so the feature is on out of the box before the
61+
/// first config apply. Read on the hot Attention-sort path, hence a plain
62+
/// atomic load rather than threading the flag through every sort helper.
63+
static UNREAD_ENABLED: AtomicBool = AtomicBool::new(true);
64+
65+
/// Whether the unread-session indicator feature is enabled.
5966
pub fn unread_enabled() -> bool {
60-
use std::sync::OnceLock;
61-
static ON: OnceLock<bool> = OnceLock::new();
62-
*ON.get_or_init(|| {
63-
matches!(
64-
std::env::var("AOE_UNREAD").as_deref(),
65-
Ok("1") | Ok("on") | Ok("true")
66-
)
67-
})
67+
UNREAD_ENABLED.load(Ordering::Relaxed)
6868
}
69+
70+
/// Update the cached unread-indicator flag from resolved config.
71+
pub fn set_unread_enabled(on: bool) {
72+
UNREAD_ENABLED.store(on, Ordering::Relaxed);
73+
}
74+
6975
pub use profile_config::{
7076
load_profile_config, merge_configs, resolve_config, resolve_config_or_warn,
7177
save_profile_config, validate_check_interval, validate_env_format, validate_memory_limit,

src/tui/home/bindings.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ pub enum ActionId {
5757
ToggleFavorite,
5858
ToggleSnooze,
5959
/// Toggle the selected session's unread marker (read -> manual-unread;
60-
/// unread -> read). Gated behind the `AOE_UNREAD` feature flag; a no-op
61-
/// when disabled.
60+
/// unread -> read). Gated behind the `session.unread_indicator` config
61+
/// toggle (on by default); a no-op when disabled.
6262
ToggleUnread,
6363
ToggleContainer,
6464
TogglePreviewInfo,

src/tui/home/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,7 @@ impl HomeView {
12331233
let confirm_before_quit = resolved.session.confirm_before_quit;
12341234
let idle_decay_window =
12351235
crate::tui::styles::idle_decay_window(resolved.theme.idle_decay_minutes);
1236+
crate::session::set_unread_enabled(resolved.session.unread_indicator);
12361237
let user_config = load_config().ok().flatten();
12371238
let sort_order = user_config
12381239
.as_ref()
@@ -5453,6 +5454,7 @@ impl HomeView {
54535454
self.profile_default_attach_mode = config.session.default_attach_mode;
54545455
self.idle_decay_window =
54555456
crate::tui::styles::idle_decay_window(config.theme.idle_decay_minutes);
5457+
crate::session::set_unread_enabled(config.session.unread_indicator);
54565458
self.tool_configs = config.tools;
54575459
self.tool_hotkey_cache = input::build_tool_hotkey_cache(&self.tool_configs);
54585460
let hotkey_warnings = input::validate_tool_hotkeys(&self.tool_configs);

src/tui/home/tests.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11603,15 +11603,17 @@ mod right_click_context_menu {
1160311603
fn down_then_enter_in_menu_opens_delete_dialog() {
1160411604
let mut env = create_test_env_with_sessions(2);
1160511605
setup_inner(&mut env);
11606-
// Attention sort surfaces the full session menu (New Session / Rename /
11607-
// Archive / Snooze / Delete), so Delete is four Downs away.
11606+
// Attention sort surfaces the full session menu (New Session / Rename
11607+
// / Archive / Snooze / Mark unread / Delete), so Delete is five Downs
11608+
// away. (Unread defaults on, so the "Mark unread" row is present.)
1160811609
env.view.sort_order = SortOrder::Attention;
1160911610
env.view.flat_items = env.view.build_flat_items();
1161011611
env.view.handle_right_click(5, 1);
1161111612
env.view.handle_key(key(KeyCode::Down), None);
1161211613
env.view.handle_key(key(KeyCode::Down), None);
1161311614
env.view.handle_key(key(KeyCode::Down), None);
1161411615
env.view.handle_key(key(KeyCode::Down), None);
11616+
env.view.handle_key(key(KeyCode::Down), None);
1161511617
env.view.handle_key(key(KeyCode::Enter), None);
1161611618
assert!(env.view.context_menu.is_none());
1161711619
assert!(
@@ -11691,9 +11693,13 @@ mod right_click_context_menu {
1169111693
.iter()
1169211694
.map(|(_, l)| *l)
1169311695
.collect();
11694-
// Default sort here is Newest, where Snooze is gated out, so the
11695-
// archived-row menu is just New Session / Rename / Unarchive / Delete.
11696-
assert_eq!(labels, vec!["New Session", "Rename", "Unarchive", "Delete"]);
11696+
// Default sort here is Newest, where Snooze is gated out. The unread
11697+
// toggle is always-on (any sort) and defaults on, so the archived-row
11698+
// menu is New Session / Rename / Unarchive / Mark unread / Delete.
11699+
assert_eq!(
11700+
labels,
11701+
vec!["New Session", "Rename", "Unarchive", "Mark unread", "Delete"]
11702+
);
1169711703

1169811704
env.view.handle_key(key(KeyCode::Down), None); // New Session -> Rename
1169911705
env.view.handle_key(key(KeyCode::Down), None); // Rename -> Unarchive

src/tui/styles/themes.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ pub struct Theme {
9090
/// user hasn't viewed, or a manual "flag for later"). Applied to resting
9191
/// rows (Idle/Unknown) in place of the decaying idle color so unread work
9292
/// stands out without being as loud as Waiting/Error. Gated behind the
93-
/// `AOE_UNREAD` feature flag. Defaults to the theme's accent if a custom
94-
/// TOML omits it.
93+
/// `session.unread_indicator` config toggle (on by default). Defaults to
94+
/// the theme's accent if a custom TOML omits it.
9595
#[serde(with = "hex_color")]
9696
pub unread: Color,
9797
#[serde(with = "hex_color")]

0 commit comments

Comments
 (0)