mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-11 01:51:34 -10:00
refactor(tui): remove SidebarFocus and the classic sidebar's last state
Spec step 4 (part 2) of rail-unification-task-20260802 (checklist section 2A). The type and every field it hung off of are gone; nothing in the tree can mutate or gate on a panel system that no longer exists. - app.rs: SidebarFocus the enum, its from_setting/as_setting impls, App.sidebar_focus, App.sidebar_width_percent, set_sidebar_focus, and the whole classic interaction block (sidebar_resizing/_hovered, resize anchors, last_sidebar_area/_host_width/_handle_area, sidebar_resize_total_width), plus ViewportState.last_sidebar_area. - app/init.rs: the settings reads and field initializers for all of the above. - subagent_routing: agents_sidebar_surface_visible now means what it says in the rail world (panel == Agents && last_area); agents_panel_has_content (auto-mode remnant) deleted. - sidebar.rs: the five explicit_tasks_focus gates re-point at work_surface.panel == RailPanel::Tasks; orphaned ratatui/theme imports dropped. - mouse_ui.rs: approval wheel ownership and the context-menu on_sidebar gate re-point at work_surface.last_area (rail rows keep their right-click Run/Copy menu via the shared hover sections); sidebar_click_action was already section-driven and is untouched; the scroll-leak consume block for the never-Some legacy rect is deleted. - settings.rs: sidebar_width_percent and sidebar_focus become load-only migration shims (#[serde(skip_serializing)] — read by migrate_sidebar_settings_to_rail, never written back); update_sidebar_width deleted. - ui.rs: the per-frame reset of the deleted fields is gone (the live sidebar_hover_tooltip clear stays). Tests: sidebar.rs setups (~30) and ui/app tests re-point at work_surface.panel/placement/last_area; entering_operate_preserves_ user_rail_panel replaces the sidebar_focus variant; the approval-wheel test drops the legacy rect (rail ownership covered by the work_surface arm); the two loading-hover tests keep their assertions against the generic tooltip-clear path; context-menu tests drive the rail's work_surface.last_area; sidebar_focus from_setting coverage lives on in the settings migration tests. Gates: cargo fmt --check -p codewhale-tui clean; sidebar: 129 passed; mouse: 70; hotbar: 92; footer: 104; settings: 121; commands: 760; work_surface: 64 — all 0 failed.
This commit is contained in:
1 parent
0f50ad834d
commit
70759e1c14
11 files changed
+89
-254
No files matched your search
@@ -387,9 +387,14 @@ pub struct Settings {
|
||||
/// Default mode: "agent" (Act), "plan", or "operate". Legacy permission
|
||||
/// shorthands are accepted for migration but never advertised as modes.
|
||||
pub default_mode: String,
|
||||
/// Sidebar width as percentage of terminal width
|
||||
/// Legacy sidebar width as percentage of terminal width. Load-only
|
||||
/// migration shim (0.9.4 rail unification): read by
|
||||
/// `migrate_sidebar_settings_to_rail`, never written back.
|
||||
#[serde(skip_serializing)]
|
||||
pub sidebar_width_percent: u16,
|
||||
/// Sidebar focus mode: pinned, auto, tasks, agents, context, hidden
|
||||
/// Legacy sidebar focus mode: pinned, auto, tasks, agents, context,
|
||||
/// hidden. Load-only migration shim, never written back.
|
||||
#[serde(skip_serializing)]
|
||||
pub sidebar_focus: String,
|
||||
/// Enable the session-context panel (#504). Shows working set, tokens,
|
||||
/// cost, MCP/LSP status, cycle count, and memory info.
|
||||
@@ -1128,12 +1133,6 @@ impl Settings {
|
||||
atomically_replace_settings_file(path, body.as_bytes())
|
||||
}
|
||||
|
||||
/// Update and persist sidebar width percentage (10-50) — used by the
|
||||
/// drag-to-resize handle in the TUI.
|
||||
pub fn update_sidebar_width(&mut self, percent: u16) {
|
||||
self.sidebar_width_percent = percent.clamp(10, 50);
|
||||
}
|
||||
|
||||
/// Set a single setting by key
|
||||
pub fn set(&mut self, key: &str, value: &str) -> Result<()> {
|
||||
match key {
|
||||
|
||||
@@ -292,21 +292,6 @@ pub struct TurnCacheRecord {
|
||||
pub recorded_at: Instant,
|
||||
}
|
||||
|
||||
/// Sidebar content focus mode.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SidebarFocus {
|
||||
Auto,
|
||||
Pinned,
|
||||
Tasks,
|
||||
Agents,
|
||||
Context,
|
||||
/// Dedicated Sessions browser panel (#2934). Renders the persistent
|
||||
/// sessions rail as the sole sidebar content, regardless of whether the
|
||||
/// `sessions_rail` setting is enabled. Focuses session navigation.
|
||||
Sessions,
|
||||
Hidden,
|
||||
}
|
||||
|
||||
/// Browsing context captured when the `/model` picker is dismissed (#4109).
|
||||
/// Plain data so `App` does not depend on the picker's internal view enum.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -482,36 +467,6 @@ impl Default for LspRepairState {
|
||||
}
|
||||
}
|
||||
|
||||
impl SidebarFocus {
|
||||
#[must_use]
|
||||
pub fn from_setting(value: &str) -> Self {
|
||||
match value.trim().to_ascii_lowercase().as_str() {
|
||||
"pinned" | "visible" | "show" | "on" | "work" | "plan" | "todos" => Self::Pinned,
|
||||
// Persist/compat key remains "tasks"; user-facing panel is Activity (#4147/#4135).
|
||||
"tasks" | "activity" | "live" | "running" => Self::Tasks,
|
||||
"agents" | "subagents" | "sub-agents" => Self::Agents,
|
||||
"context" => Self::Context,
|
||||
"sessions" | "sessions_rail" | "session_history" => Self::Sessions,
|
||||
"hidden" | "hide" | "closed" | "off" | "none" => Self::Hidden,
|
||||
_ => Self::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(dead_code)]
|
||||
pub fn as_setting(self) -> &'static str {
|
||||
match self {
|
||||
Self::Auto => "auto",
|
||||
Self::Pinned => "pinned",
|
||||
Self::Tasks => "tasks",
|
||||
Self::Agents => "agents",
|
||||
Self::Context => "context",
|
||||
Self::Sessions => "sessions",
|
||||
Self::Hidden => "hidden",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pre-session launch menu state for the underwater shell.
|
||||
///
|
||||
/// This is deliberately separate from onboarding and from the post-launch
|
||||
@@ -685,10 +640,6 @@ pub struct ViewportState {
|
||||
/// Painted band occupied by the active inline approval. Stored so wheel
|
||||
/// routing can prefer the visible card over side surfaces underneath it.
|
||||
pub last_approval_area: Option<Rect>,
|
||||
/// Outer rect of the right-hand sidebar (when visible), stored at render
|
||||
/// time so mouse hit-testing can keep scroll events over the sidebar from
|
||||
/// leaking into the transcript viewport.
|
||||
pub last_sidebar_area: Option<Rect>,
|
||||
/// WorkflowPanel rect above the composer (#4121), for mouse toggle/cancel.
|
||||
pub last_workflow_panel_area: Option<Rect>,
|
||||
pub last_workflow_cancel_area: Option<Rect>,
|
||||
@@ -721,7 +672,6 @@ impl Default for ViewportState {
|
||||
last_transcript_area: None,
|
||||
last_composer_area: None,
|
||||
last_approval_area: None,
|
||||
last_sidebar_area: None,
|
||||
last_workflow_panel_area: None,
|
||||
last_workflow_cancel_area: None,
|
||||
last_transcript_top: 0,
|
||||
@@ -1391,8 +1341,6 @@ pub struct App {
|
||||
/// Toggled by `/voice-control`.
|
||||
pub voice_control_enabled: bool,
|
||||
pub transcript_spacing: TranscriptSpacing,
|
||||
pub sidebar_width_percent: u16,
|
||||
pub sidebar_focus: SidebarFocus,
|
||||
/// Sidebar hover state for mouse tooltip support.
|
||||
pub sidebar_hover: SidebarHoverState,
|
||||
/// Current hover tooltip text, if any.
|
||||
@@ -1408,23 +1356,6 @@ pub struct App {
|
||||
pub provider_picker_memory: Option<ProviderPickerMemory>,
|
||||
/// Last known mouse position for tooltip placement.
|
||||
pub last_mouse_pos: Option<(u16, u16)>,
|
||||
/// Whether the user is currently dragging the sidebar resize handle.
|
||||
pub sidebar_resizing: bool,
|
||||
/// Whether the pointer is over the classic sidebar resize handle.
|
||||
pub sidebar_resize_hovered: bool,
|
||||
/// Mouse column at the start of a sidebar-resize drag.
|
||||
pub sidebar_resize_anchor_x: u16,
|
||||
/// Sidebar width in columns at the start of a sidebar-resize drag.
|
||||
pub sidebar_resize_anchor_width: u16,
|
||||
/// Last sidebar area rendered (for mouse hit-testing the resize handle).
|
||||
pub last_sidebar_area: Option<Rect>,
|
||||
/// Last total chat/sidebar width considered for sidebar rendering.
|
||||
pub last_sidebar_host_width: Option<u16>,
|
||||
/// Handle rect painted on the left edge of the sidebar (1 col).
|
||||
pub last_sidebar_handle_area: Option<Rect>,
|
||||
/// Total horizontal space (chat + sidebar) used to compute the percentage
|
||||
/// during sidebar resize drag.
|
||||
pub sidebar_resize_total_width: u16,
|
||||
/// Whether the session-context panel is enabled (#504).
|
||||
pub context_panel: bool,
|
||||
/// Whether the persistent Sessions rail is enabled (#2934). Opt-in.
|
||||
@@ -4097,13 +4028,6 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_sidebar_focus(&mut self, focus: SidebarFocus) {
|
||||
if self.sidebar_focus != focus {
|
||||
self.sidebar_focus = focus;
|
||||
}
|
||||
self.needs_redraw = true;
|
||||
}
|
||||
|
||||
pub fn close_slash_menu(&mut self) {
|
||||
self.slash_menu_hidden = true;
|
||||
self.needs_redraw = true;
|
||||
|
||||
@@ -241,8 +241,6 @@ impl App {
|
||||
.trim()
|
||||
.eq_ignore_ascii_case("vim");
|
||||
let transcript_spacing = TranscriptSpacing::from_setting(&settings.transcript_spacing);
|
||||
let sidebar_width_percent = settings.sidebar_width_percent;
|
||||
let sidebar_focus = SidebarFocus::from_setting(&settings.sidebar_focus);
|
||||
let max_input_history = settings.max_input_history;
|
||||
let use_paste_burst_detection = settings.paste_burst_detection;
|
||||
// Resolve the named theme from settings; unknown values were already
|
||||
@@ -714,22 +712,12 @@ impl App {
|
||||
voice_send_enabled: false,
|
||||
voice_control_enabled: false,
|
||||
transcript_spacing,
|
||||
sidebar_width_percent,
|
||||
sidebar_focus,
|
||||
sidebar_hover: SidebarHoverState::default(),
|
||||
sidebar_hover_tooltip: None,
|
||||
cached_work_summary: None,
|
||||
model_picker_memory: None,
|
||||
provider_picker_memory: None,
|
||||
last_mouse_pos: None,
|
||||
sidebar_resizing: false,
|
||||
sidebar_resize_hovered: false,
|
||||
sidebar_resize_anchor_x: 0,
|
||||
sidebar_resize_anchor_width: 0,
|
||||
last_sidebar_area: None,
|
||||
last_sidebar_host_width: None,
|
||||
last_sidebar_handle_area: None,
|
||||
sidebar_resize_total_width: 0,
|
||||
context_panel: settings.context_panel,
|
||||
sessions_rail: settings.sessions_rail,
|
||||
sessions_rail_cache: None,
|
||||
|
||||
@@ -1682,35 +1682,6 @@ fn cny_cache_savings_falls_back_to_usd_for_usd_only_models() {
|
||||
assert!((savings - 0.79).abs() < 1e-9, "got {savings}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sidebar_focus_accepts_pinned_and_maps_legacy_trackers_to_pinned() {
|
||||
assert_eq!(SidebarFocus::from_setting("auto"), SidebarFocus::Auto);
|
||||
assert_eq!(SidebarFocus::from_setting("pinned"), SidebarFocus::Pinned);
|
||||
assert_eq!(SidebarFocus::from_setting("work"), SidebarFocus::Pinned);
|
||||
assert_eq!(SidebarFocus::from_setting("plan"), SidebarFocus::Pinned);
|
||||
assert_eq!(SidebarFocus::from_setting("todos"), SidebarFocus::Pinned);
|
||||
assert_eq!(SidebarFocus::from_setting("tasks"), SidebarFocus::Tasks);
|
||||
assert_eq!(SidebarFocus::from_setting("activity"), SidebarFocus::Tasks);
|
||||
assert_eq!(SidebarFocus::from_setting("live"), SidebarFocus::Tasks);
|
||||
assert_eq!(SidebarFocus::from_setting("running"), SidebarFocus::Tasks);
|
||||
assert_eq!(SidebarFocus::from_setting("agents"), SidebarFocus::Agents);
|
||||
assert_eq!(SidebarFocus::from_setting("context"), SidebarFocus::Context);
|
||||
assert_eq!(SidebarFocus::from_setting("sessions"), SidebarFocus::Sessions);
|
||||
assert_eq!(
|
||||
SidebarFocus::from_setting("sessions_rail"),
|
||||
SidebarFocus::Sessions
|
||||
);
|
||||
assert_eq!(
|
||||
SidebarFocus::from_setting("session_history"),
|
||||
SidebarFocus::Sessions
|
||||
);
|
||||
assert_eq!(SidebarFocus::from_setting("hidden"), SidebarFocus::Hidden);
|
||||
assert_eq!(SidebarFocus::from_setting("off"), SidebarFocus::Hidden);
|
||||
assert_eq!(SidebarFocus::Pinned.as_setting(), "pinned");
|
||||
assert_eq!(SidebarFocus::Hidden.as_setting(), "hidden");
|
||||
assert_eq!(SidebarFocus::Sessions.as_setting(), "sessions");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_command_classifier_treats_absolute_path_as_message() {
|
||||
assert!(looks_like_slash_command_input("/"));
|
||||
@@ -2873,12 +2844,15 @@ fn clear_todos_is_atomic_and_invalidates_cached_work_summary() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entering_operate_preserves_user_sidebar_focus() {
|
||||
fn entering_operate_preserves_user_rail_panel() {
|
||||
let mut app = App::new(test_options(false), &Config::default());
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Agents;
|
||||
|
||||
assert!(app.set_mode(AppMode::Operate));
|
||||
assert_eq!(app.sidebar_focus, SidebarFocus::Tasks);
|
||||
assert_eq!(
|
||||
app.work_surface.panel,
|
||||
crate::tui::work_surface::RailPanel::Agents
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -383,10 +383,11 @@ mod tests {
|
||||
let options = TuiOptions {
|
||||
..crate::test_support::test_tui_options(PathBuf::from("."))
|
||||
};
|
||||
// Pin sidebar so dogfood machines with Agents-visible settings.toml
|
||||
// do not hide the footer agents chip this test asserts.
|
||||
// Pin the rail panel so dogfood machines whose settings.toml migrates
|
||||
// a legacy Agents sidebar focus do not hide the footer agents chip
|
||||
// this test asserts.
|
||||
let mut app = App::new(options, &Config::default());
|
||||
app.sidebar_focus = crate::tui::app::SidebarFocus::Hidden;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
app
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::commands::{self, CommandInfo, CommandResult};
|
||||
use crate::config::{ApiProvider, Config};
|
||||
use crate::localization::{Locale, MessageId, tr};
|
||||
use crate::provider_lake::all_catalog_models_for_provider;
|
||||
use crate::tui::app::{App, AppAction, AppMode, SidebarFocus};
|
||||
use crate::tui::app::{App, AppAction, AppMode};
|
||||
use crate::tui::command_palette::{
|
||||
CommandPaletteView, build_entries as build_command_palette_entries,
|
||||
};
|
||||
|
||||
@@ -44,7 +44,6 @@ pub(crate) fn should_drop_loading_mouse_motion(app: &App, mouse: MouseEvent) ->
|
||||
// these events wedges the resize state mid-drag (#3063).
|
||||
!app.viewport.transcript_selection.dragging
|
||||
&& !app.viewport.transcript_scrollbar_dragging
|
||||
&& !app.sidebar_resizing
|
||||
&& !app.work_surface.is_resizing()
|
||||
}
|
||||
_ => false,
|
||||
@@ -298,8 +297,7 @@ pub(crate) fn handle_mouse_event(app: &mut App, mouse: MouseEvent) -> Vec<ViewEv
|
||||
// Other modals still own their wheel input exclusively (#4371).
|
||||
if app.view_stack.top_kind() == Some(ModalKind::Approval) {
|
||||
let over_approval = mouse_hits_rect(mouse, app.viewport.last_approval_area);
|
||||
let over_side_surface = mouse_hits_rect(mouse, app.viewport.last_sidebar_area)
|
||||
|| mouse_hits_rect(mouse, app.work_surface.last_area);
|
||||
let over_side_surface = mouse_hits_rect(mouse, app.work_surface.last_area);
|
||||
match mouse.kind {
|
||||
MouseEventKind::ScrollUp => {
|
||||
if over_approval || !over_side_surface {
|
||||
@@ -385,22 +383,6 @@ pub(crate) fn handle_mouse_event(app: &mut App, mouse: MouseEvent) -> Vec<ViewEv
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// Scroll events while the cursor is over the right-hand sidebar must not
|
||||
// drive the transcript scroll. The sidebar is a fixed dashboard with no
|
||||
// scroll state of its own, so consume the wheel event instead of leaking
|
||||
// it into the transcript viewport behind it.
|
||||
if matches!(
|
||||
mouse.kind,
|
||||
MouseEventKind::ScrollUp | MouseEventKind::ScrollDown
|
||||
) && app.viewport.last_sidebar_area.is_some_and(|area| {
|
||||
mouse.column >= area.x
|
||||
&& mouse.column < area.x.saturating_add(area.width)
|
||||
&& mouse.row >= area.y
|
||||
&& mouse.row < area.y.saturating_add(area.height)
|
||||
}) {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
match mouse.kind {
|
||||
MouseEventKind::Moved => {
|
||||
// Update last mouse position for tooltip rendering + hover layer.
|
||||
@@ -1107,7 +1089,7 @@ pub(crate) fn open_context_menu(app: &mut App, mouse: MouseEvent) {
|
||||
pub(crate) fn build_context_menu_entries(app: &App, mouse: MouseEvent) -> Vec<ContextMenuEntry> {
|
||||
let mut entries = Vec::new();
|
||||
let mut git_path = None;
|
||||
let on_sidebar = mouse_hits_rect(mouse, app.viewport.last_sidebar_area);
|
||||
let on_sidebar = mouse_hits_rect(mouse, app.work_surface.last_area);
|
||||
|
||||
if on_sidebar {
|
||||
if let Some(command) = sidebar_click_action(app, mouse)
|
||||
@@ -1680,7 +1662,7 @@ mod tests {
|
||||
#[test]
|
||||
fn context_menu_keeps_paste_first_outside_sidebar() {
|
||||
let mut app = create_test_app();
|
||||
app.viewport.last_sidebar_area = Some(Rect::new(60, 4, 20, 6));
|
||||
app.work_surface.last_area = Some(Rect::new(60, 4, 20, 6));
|
||||
|
||||
let entries = build_context_menu_entries(&app, right_click(10, 4));
|
||||
|
||||
@@ -1693,7 +1675,7 @@ mod tests {
|
||||
#[test]
|
||||
fn sidebar_context_menu_omits_paste_without_row_action() {
|
||||
let mut app = create_test_app();
|
||||
app.viewport.last_sidebar_area = Some(Rect::new(60, 4, 20, 6));
|
||||
app.work_surface.last_area = Some(Rect::new(60, 4, 20, 6));
|
||||
app.sidebar_hover.sections.push(SidebarHoverSection {
|
||||
content_area: Rect::new(60, 4, 20, 6),
|
||||
lines: vec!["header".to_string()],
|
||||
@@ -1713,7 +1695,7 @@ mod tests {
|
||||
#[test]
|
||||
fn sidebar_context_menu_runs_clickable_row_action() {
|
||||
let mut app = create_test_app();
|
||||
app.viewport.last_sidebar_area = Some(Rect::new(60, 4, 20, 6));
|
||||
app.work_surface.last_area = Some(Rect::new(60, 4, 20, 6));
|
||||
app.sidebar_hover.sections.push(SidebarHoverSection {
|
||||
content_area: Rect::new(60, 4, 20, 6),
|
||||
lines: vec!["job row".to_string()],
|
||||
@@ -1793,7 +1775,7 @@ mod tests {
|
||||
#[test]
|
||||
fn sidebar_click_routes_inline_stop_zone_before_row_action() {
|
||||
let mut app = create_test_app();
|
||||
app.viewport.last_sidebar_area = Some(Rect::new(60, 4, 20, 4));
|
||||
app.work_surface.last_area = Some(Rect::new(60, 4, 20, 4));
|
||||
app.sidebar_hover.sections.push(SidebarHoverSection {
|
||||
content_area: Rect::new(60, 4, 20, 4),
|
||||
lines: vec!["job row [x]".to_string()],
|
||||
@@ -1819,7 +1801,7 @@ mod tests {
|
||||
#[test]
|
||||
fn sidebar_click_routes_agent_inline_stop_zone_before_peek_action() {
|
||||
let mut app = create_test_app();
|
||||
app.viewport.last_sidebar_area = Some(Rect::new(60, 4, 24, 4));
|
||||
app.work_surface.last_area = Some(Rect::new(60, 4, 24, 4));
|
||||
app.sidebar_hover.sections.push(SidebarHoverSection {
|
||||
content_area: Rect::new(60, 4, 24, 4),
|
||||
lines: vec!["[~] worker Agent 1 [x]".to_string()],
|
||||
@@ -1854,7 +1836,7 @@ mod tests {
|
||||
#[test]
|
||||
fn sidebar_context_menu_offers_copy_of_hovered_row() {
|
||||
let mut app = create_test_app();
|
||||
app.viewport.last_sidebar_area = Some(Rect::new(60, 4, 20, 6));
|
||||
app.work_surface.last_area = Some(Rect::new(60, 4, 20, 6));
|
||||
app.sidebar_hover.sections.push(SidebarHoverSection {
|
||||
content_area: Rect::new(60, 4, 20, 6),
|
||||
lines: vec!["agent row".to_string()],
|
||||
|
||||
@@ -13,28 +13,22 @@ use crate::localization::Locale;
|
||||
use crate::tui::app::HuntVerdict;
|
||||
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::Rect,
|
||||
prelude::Widget,
|
||||
style::Style,
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Paragraph, Wrap},
|
||||
};
|
||||
|
||||
use crate::deepseek_theme::Theme;
|
||||
use crate::palette;
|
||||
use crate::tools::subagent::{AgentWorkerStatus, SubAgentStatus, localized_whale_display_names};
|
||||
use crate::tools::todo::TodoStatus;
|
||||
|
||||
use super::app::{
|
||||
AgentCurrentActivity, AgentCurrentActivityStatus, App, SidebarFocus, SidebarHoverRow,
|
||||
SidebarHoverSection, SidebarRowAction, TaskPanelEntry, TaskPanelEntryKind,
|
||||
AgentCurrentActivity, AgentCurrentActivityStatus, App, SidebarHoverRow, SidebarRowAction,
|
||||
TaskPanelEntry, TaskPanelEntryKind,
|
||||
};
|
||||
use super::history::{GenericToolCell, HistoryCell, ToolCell, ToolStatus, summarize_tool_output};
|
||||
use super::motion::MotionPolicy;
|
||||
use super::spinner::{LIVE_MARKER_DELAY_MS, braille_spinner_frame_for_elapsed_ms};
|
||||
use super::subagent_routing::active_fanout_counts;
|
||||
use super::ui::FILE_TREE_MIN_HOST_WIDTH;
|
||||
use super::ui_text::{concise_shell_command_label, truncate_line_to_width};
|
||||
|
||||
/// Tolerance for floating-point cost comparison in the sidebar breakdown.
|
||||
@@ -811,7 +805,7 @@ struct TaskPanelRowSets {
|
||||
}
|
||||
|
||||
fn task_panel_row_sets(app: &App) -> TaskPanelRowSets {
|
||||
let explicit_tasks_focus = app.sidebar_focus == SidebarFocus::Tasks;
|
||||
let explicit_tasks_focus = app.work_surface.panel == crate::tui::work_surface::RailPanel::Tasks;
|
||||
let active = active_tool_rows(app);
|
||||
// Auto/Pinned mode deliberately skips live-tool dedup (passes an empty
|
||||
// slice), matching the previous per-consumer call sites.
|
||||
@@ -845,7 +839,7 @@ fn task_panel_rows(
|
||||
let theme = &app.ui_theme;
|
||||
let mut lines: Vec<Line<'static>> = Vec::with_capacity(max_rows.max(4));
|
||||
let mut actions: Vec<Option<SidebarRowAction>> = Vec::with_capacity(max_rows.max(4));
|
||||
let explicit_tasks_focus = app.sidebar_focus == SidebarFocus::Tasks;
|
||||
let explicit_tasks_focus = app.work_surface.panel == crate::tui::work_surface::RailPanel::Tasks;
|
||||
|
||||
if explicit_tasks_focus && app.runtime_turn_id.is_some() {
|
||||
let status = app
|
||||
@@ -986,7 +980,7 @@ fn task_panel_rows(
|
||||
// Yank hint: surface the keyboard path for copying the focused task/turn ID.
|
||||
if lines.len() + 1 < max_rows
|
||||
&& app.runtime_turn_id.is_some()
|
||||
&& app.sidebar_focus == SidebarFocus::Tasks
|
||||
&& app.work_surface.panel == crate::tui::work_surface::RailPanel::Tasks
|
||||
{
|
||||
lines.push(Line::from(Span::styled(
|
||||
"y → copy turn id · Y → copy full status",
|
||||
@@ -1016,7 +1010,7 @@ fn task_panel_rows(
|
||||
|
||||
fn task_panel_hover_texts(app: &App, row_sets: &TaskPanelRowSets, max_rows: usize) -> Vec<String> {
|
||||
let mut texts = Vec::with_capacity(max_rows.max(4));
|
||||
let explicit_tasks_focus = app.sidebar_focus == SidebarFocus::Tasks;
|
||||
let explicit_tasks_focus = app.work_surface.panel == crate::tui::work_surface::RailPanel::Tasks;
|
||||
|
||||
if explicit_tasks_focus && let Some(turn_id) = app.runtime_turn_id.as_ref() {
|
||||
let status = app.runtime_turn_status.as_deref().unwrap_or("unknown");
|
||||
@@ -1094,7 +1088,7 @@ fn task_panel_hover_texts(app: &App, row_sets: &TaskPanelRowSets, max_rows: usiz
|
||||
|
||||
if texts.len() + 1 < max_rows
|
||||
&& app.runtime_turn_id.is_some()
|
||||
&& app.sidebar_focus == SidebarFocus::Tasks
|
||||
&& app.work_surface.panel == crate::tui::work_surface::RailPanel::Tasks
|
||||
{
|
||||
texts.push("y -> copy turn id · Y -> copy full status".to_string());
|
||||
}
|
||||
@@ -2854,16 +2848,15 @@ fn agent_stop_action_for_click(action: &SidebarRowAction) -> Option<SidebarRowAc
|
||||
mod tests {
|
||||
use super::{
|
||||
ACTIVE_TOOL_COMPLETED_ROW_TTL, ACTIVE_TOOL_STALE_RUNNING_ROW_TTL, HotbarSlotState,
|
||||
SidebarAgentRow, SidebarFocus, SidebarHoverRow, SidebarHoverSection,
|
||||
SidebarSubagentSummary, SidebarToolRow, SidebarWorkChecklistItem, SidebarWorkSummary,
|
||||
ToolRowOrder, agent_row_hover_text, background_task_spinner_prefix,
|
||||
cached_agent_activity_is_live, context_panel_cost_line, editorial_tool_rows,
|
||||
hotbar_panel_enabled, hotbar_panel_hover_texts, hotbar_panel_lines, hotbar_panel_slots,
|
||||
normalize_activity_text, sidebar_agent_rows, sidebar_hover_rows, sidebar_work_summary,
|
||||
sort_sidebar_agent_rows_as_tree, subagent_output_handle, subagent_panel_hover_texts,
|
||||
subagent_panel_lines, subagent_panel_rows, task_panel_hover_texts, task_panel_lines,
|
||||
task_panel_row_sets, task_panel_rows, work_panel_empty_hint, work_panel_hover_texts,
|
||||
work_panel_lines,
|
||||
SidebarAgentRow, SidebarHoverRow, SidebarSubagentSummary, SidebarToolRow,
|
||||
SidebarWorkChecklistItem, SidebarWorkSummary, ToolRowOrder, agent_row_hover_text,
|
||||
background_task_spinner_prefix, cached_agent_activity_is_live, context_panel_cost_line,
|
||||
editorial_tool_rows, hotbar_panel_enabled, hotbar_panel_hover_texts, hotbar_panel_lines,
|
||||
hotbar_panel_slots, normalize_activity_text, sidebar_agent_rows, sidebar_hover_rows,
|
||||
sidebar_work_summary, sort_sidebar_agent_rows_as_tree, subagent_output_handle,
|
||||
subagent_panel_hover_texts, subagent_panel_lines, subagent_panel_rows,
|
||||
task_panel_hover_texts, task_panel_lines, task_panel_row_sets, task_panel_rows,
|
||||
work_panel_empty_hint, work_panel_hover_texts, work_panel_lines,
|
||||
};
|
||||
use crate::config::Config;
|
||||
use crate::localization::Locale;
|
||||
@@ -2873,8 +2866,8 @@ mod tests {
|
||||
use crate::tui::active_cell::ActiveCell;
|
||||
use crate::tui::app::{
|
||||
AgentCurrentActivity, AgentCurrentActivityStatus, AgentProgressMeta, App, AppMode,
|
||||
HuntVerdict, SidebarHoverState, SidebarRowAction, TaskPanelEntry, TaskPanelEntryKind,
|
||||
TuiOptions,
|
||||
HuntVerdict, SidebarHoverSection, SidebarHoverState, SidebarRowAction, TaskPanelEntry,
|
||||
TaskPanelEntryKind, TuiOptions,
|
||||
};
|
||||
use crate::tui::history::{
|
||||
ExecCell, ExecSource, GenericToolCell, HistoryCell, ToolCell, ToolStatus,
|
||||
@@ -3161,7 +3154,7 @@ mod tests {
|
||||
// `hotbar_panel_slots_resolve_configured_bindings_and_active_state`.
|
||||
let mut app = create_test_app();
|
||||
app.mode = AppMode::Agent;
|
||||
app.sidebar_focus = SidebarFocus::Pinned;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Pinned;
|
||||
|
||||
assert!(
|
||||
!hotbar_panel_enabled(&app, &Config::default()),
|
||||
@@ -3190,7 +3183,7 @@ mod tests {
|
||||
fn hotbar_panel_slots_resolve_configured_bindings_and_active_state() {
|
||||
let mut app = create_test_app();
|
||||
app.mode = AppMode::Agent;
|
||||
app.sidebar_focus = SidebarFocus::Pinned;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Pinned;
|
||||
let config = Config {
|
||||
hotbar: Some(
|
||||
codewhale_config::default_hotbar_bindings()
|
||||
@@ -3285,7 +3278,7 @@ mod tests {
|
||||
fn hotbar_panel_lines_keep_two_fixed_rows_and_hover_status() {
|
||||
let mut app = create_test_app();
|
||||
app.mode = AppMode::Agent;
|
||||
app.sidebar_focus = SidebarFocus::Pinned;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Pinned;
|
||||
let config = Config {
|
||||
hotbar: Some(
|
||||
codewhale_config::default_hotbar_bindings()
|
||||
@@ -3799,7 +3792,7 @@ mod tests {
|
||||
assert!(!joined.contains("Strategy") && !joined.contains("route "));
|
||||
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
let activity = lines_to_text(&task_panel_lines(&app, 48, 4));
|
||||
assert!(
|
||||
activity
|
||||
@@ -3816,7 +3809,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_renders_active_tool_rows_before_background_empty_state() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
let mut active = ActiveCell::new();
|
||||
active.push_tool(
|
||||
"tool-1",
|
||||
@@ -3856,7 +3849,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_renders_recent_completed_tool_rows() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
app.history
|
||||
.push(HistoryCell::Tool(ToolCell::Generic(GenericToolCell {
|
||||
name: "read_file".to_string(),
|
||||
@@ -3884,7 +3877,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_expires_completed_active_tool_rows() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
let mut active = ActiveCell::new();
|
||||
active.push_tool(
|
||||
"tool-1",
|
||||
@@ -3924,7 +3917,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_lingers_fresh_completed_active_tool_rows() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
let mut active = ActiveCell::new();
|
||||
active.push_tool(
|
||||
"tool-1",
|
||||
@@ -3983,7 +3976,7 @@ mod tests {
|
||||
files_touched: 0,
|
||||
});
|
||||
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
let focused = task_panel_row_sets(&app);
|
||||
assert!(
|
||||
focused.background.is_empty(),
|
||||
@@ -3991,7 +3984,7 @@ mod tests {
|
||||
focused.background
|
||||
);
|
||||
|
||||
app.sidebar_focus = SidebarFocus::Auto;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Pinned;
|
||||
let auto = task_panel_row_sets(&app);
|
||||
assert_eq!(
|
||||
auto.background.len(),
|
||||
@@ -4003,7 +3996,7 @@ mod tests {
|
||||
#[test]
|
||||
fn task_panel_rows_and_hover_share_one_snapshot() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
app.runtime_turn_id = Some("turn_abcdef123456".to_string());
|
||||
app.runtime_turn_status = Some("in_progress".to_string());
|
||||
app.turn_counter = 3;
|
||||
@@ -4051,7 +4044,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_collapses_stale_running_tool_rows() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
let mut active = ActiveCell::new();
|
||||
for (idx, command) in ["long one", "long two"].into_iter().enumerate() {
|
||||
active.push_tool(
|
||||
@@ -4090,7 +4083,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_does_not_double_count_running_shell_job_as_live_and_background() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
let mut active = ActiveCell::new();
|
||||
active.push_tool(
|
||||
"shell-1",
|
||||
@@ -4358,10 +4351,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tasks_panel_auto_mode_shows_only_live_background_jobs() {
|
||||
fn tasks_panel_non_tasks_mode_shows_only_live_background_jobs() {
|
||||
let mut app = create_test_app();
|
||||
app.low_motion = false;
|
||||
app.sidebar_focus = SidebarFocus::Auto;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Pinned;
|
||||
app.runtime_turn_id = Some("turn_abcdef123456".to_string());
|
||||
app.runtime_turn_status = Some("in_progress".to_string());
|
||||
let mut active = ActiveCell::new();
|
||||
@@ -4435,7 +4428,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_keeps_model_reasoning_in_transcript_only() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
app.runtime_turn_id = Some("turn_reasoning".to_string());
|
||||
app.runtime_turn_status = Some("in_progress".to_string());
|
||||
app.history.push(HistoryCell::Thinking {
|
||||
@@ -4459,7 +4452,7 @@ mod tests {
|
||||
#[test]
|
||||
fn task_panel_actions_make_single_background_job_clickable() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
app.task_panel.push(TaskPanelEntry {
|
||||
id: "shell_only".to_string(),
|
||||
status: "running".to_string(),
|
||||
@@ -4502,7 +4495,7 @@ mod tests {
|
||||
#[test]
|
||||
fn stale_background_job_row_shows_no_output_warning_and_cancel_hint() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
app.task_panel.push(TaskPanelEntry {
|
||||
id: "shell_stale".to_string(),
|
||||
status: "running".to_string(),
|
||||
@@ -4548,7 +4541,7 @@ mod tests {
|
||||
#[test]
|
||||
fn task_panel_actions_route_each_job_to_its_own_id() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
app.task_panel.push(TaskPanelEntry {
|
||||
id: "shell_aaa".to_string(),
|
||||
status: "running".to_string(),
|
||||
@@ -4632,7 +4625,7 @@ mod tests {
|
||||
#[test]
|
||||
fn task_panel_finished_job_detail_row_shows_instead_of_cancels() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
app.task_panel.push(TaskPanelEntry {
|
||||
id: "shell_done".to_string(),
|
||||
status: "completed".to_string(),
|
||||
@@ -4669,7 +4662,7 @@ mod tests {
|
||||
#[test]
|
||||
fn task_panel_actions_align_with_lines_when_live_tools_present() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
app.runtime_turn_id = Some("0196f0a3-aaaa-bbbb-cccc-ddddeeee0000".to_string());
|
||||
let mut active = ActiveCell::new();
|
||||
active.push_tool(
|
||||
@@ -5133,7 +5126,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_collapses_repeated_low_value_recent_tools_after_failures() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
for path in ["src/a.rs", "src/b.rs", "src/c.rs"] {
|
||||
app.history
|
||||
.push(HistoryCell::Tool(ToolCell::Generic(GenericToolCell {
|
||||
@@ -5200,7 +5193,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_collapses_repeated_pending_ci_polls() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
for _ in 0..3 {
|
||||
app.history.push(HistoryCell::Tool(ToolCell::Exec(ExecCell {
|
||||
command: "cd /tmp/repo && sleep 15 && gh pr checks 1616 --repo Hmbown/CodeWhale"
|
||||
@@ -5248,7 +5241,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_failed_shell_rows_point_to_activity_details() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
app.history.push(HistoryCell::Tool(ToolCell::Exec(ExecCell {
|
||||
command: "cargo test -p codewhale-tui".to_string(),
|
||||
status: ToolStatus::Failed,
|
||||
@@ -5282,7 +5275,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_keeps_duration_and_status_on_recent_shell_rows() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
app.history.push(HistoryCell::Tool(ToolCell::Exec(ExecCell {
|
||||
command: "cargo check".to_string(),
|
||||
status: ToolStatus::Success,
|
||||
@@ -5314,7 +5307,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_uses_plain_names_for_shell_background_helpers() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
let mut active = ActiveCell::new();
|
||||
active.push_tool(
|
||||
"shell-wait",
|
||||
@@ -5346,7 +5339,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_collapses_repeated_shell_waits_for_same_job() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
let mut active = ActiveCell::new();
|
||||
for id in ["shell-wait-1", "shell-wait-2"] {
|
||||
active.push_tool(
|
||||
@@ -5383,7 +5376,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_collapses_repeated_shell_waits_without_task_marker() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
let mut active = ActiveCell::new();
|
||||
for (id, summary) in [
|
||||
("shell-wait-1", "Background task running (no new output)."),
|
||||
@@ -6418,7 +6411,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_shows_stable_turn_label_not_uuid() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
app.runtime_turn_id = Some("0196f0a3-1111-2222-3333-444455556666".to_string());
|
||||
app.runtime_turn_status = Some("in_progress".to_string());
|
||||
app.turn_counter = 3;
|
||||
@@ -6443,7 +6436,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tasks_panel_turn_label_falls_back_before_first_counted_turn() {
|
||||
let mut app = create_test_app();
|
||||
app.sidebar_focus = SidebarFocus::Tasks;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
app.runtime_turn_id = Some("0196f0a3-1111-2222-3333-444455556666".to_string());
|
||||
app.runtime_turn_status = Some("in_progress".to_string());
|
||||
app.turn_counter = 0;
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::tools::subagent::{
|
||||
};
|
||||
use crate::tui::app::{
|
||||
AgentCurrentActivity, AgentCurrentActivityStatus, AgentProgressMeta, AgentRecentAction, App,
|
||||
AppMode, MAX_AGENT_RECENT_ACTIONS, SidebarFocus, TaskPanelEntry, TaskPanelEntryKind,
|
||||
AppMode, MAX_AGENT_RECENT_ACTIONS, TaskPanelEntry, TaskPanelEntryKind,
|
||||
bound_agent_activity_text,
|
||||
};
|
||||
use crate::tui::history::{HistoryCell, SubAgentCell, summarize_tool_output};
|
||||
@@ -23,13 +23,6 @@ use crate::tui::workspace_context;
|
||||
const SUBAGENT_TERMINAL_CARD_TTL: Duration = Duration::from_secs(5 * 60);
|
||||
const SUBAGENT_TERMINAL_CARD_MAX_RETAINED: usize = 24;
|
||||
|
||||
fn agents_panel_has_content(app: &App) -> bool {
|
||||
!app.subagent_cache.is_empty()
|
||||
|| !app.agent_progress.is_empty()
|
||||
|| active_fanout_counts(app).is_some()
|
||||
|| foreground_rlm_running(app)
|
||||
}
|
||||
|
||||
fn foreground_rlm_running(app: &App) -> bool {
|
||||
use crate::tui::history::{HistoryCell, ToolCell, ToolStatus};
|
||||
app.active_cell.as_ref().is_some_and(|active| {
|
||||
@@ -46,14 +39,11 @@ fn foreground_rlm_running(app: &App) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
/// True when the Agents sidebar panel is on-screen and already owns fanout summary.
|
||||
/// True when the rail's Agents panel is on-screen and already owns fanout
|
||||
/// summary, so the footer chip would be redundant.
|
||||
pub(super) fn agents_sidebar_surface_visible(app: &App) -> bool {
|
||||
match app.sidebar_focus {
|
||||
SidebarFocus::Hidden => false,
|
||||
SidebarFocus::Agents => true,
|
||||
SidebarFocus::Auto => agents_panel_has_content(app),
|
||||
_ => false,
|
||||
}
|
||||
app.work_surface.panel == crate::tui::work_surface::RailPanel::Agents
|
||||
&& app.work_surface.last_area.is_some()
|
||||
}
|
||||
|
||||
pub(super) fn running_agent_count(app: &App) -> usize {
|
||||
|
||||
@@ -138,10 +138,10 @@ use super::key_actions;
|
||||
use super::app::{
|
||||
ActiveTurnMetadata, AgentCurrentActivity, AgentCurrentActivityStatus, App, AppAction, AppMode,
|
||||
ComposerSubmitAction, ComposerSubmitChord, EffectiveReasoningEffort, HuntVerdict,
|
||||
OnboardingState, PendingProviderSwitch, QueuedMessage, ReasoningEffort, SidebarFocus,
|
||||
StatusToast, StatusToastLevel, SubmitDisposition, TaskPanelEntry, TaskPanelEntryKind,
|
||||
ToolEvidence, TuiOptions, bound_agent_activity_text, is_stop_word,
|
||||
looks_like_slash_command_input, shell_command_from_bang_input,
|
||||
OnboardingState, PendingProviderSwitch, QueuedMessage, ReasoningEffort, StatusToast,
|
||||
StatusToastLevel, SubmitDisposition, TaskPanelEntry, TaskPanelEntryKind, ToolEvidence,
|
||||
TuiOptions, bound_agent_activity_text, is_stop_word, looks_like_slash_command_input,
|
||||
shell_command_from_bang_input,
|
||||
};
|
||||
use super::approval::{
|
||||
ApprovalMode, ApprovalRequest, ApprovalView, ElevationRequest, ElevationView, ReviewDecision,
|
||||
@@ -13830,15 +13830,6 @@ fn render(f: &mut Frame, app: &mut App, _config: &Config) {
|
||||
app.file_tree_visible = false;
|
||||
work_chat_area
|
||||
};
|
||||
app.last_sidebar_host_width = Some(chat_area.width);
|
||||
// The legacy classic-shell sidebar no longer exists: no sidebar area
|
||||
// is ever produced, so clear the interaction state its former mouse
|
||||
// handlers read.
|
||||
app.viewport.last_sidebar_area = None;
|
||||
app.last_sidebar_area = None;
|
||||
app.last_sidebar_handle_area = None;
|
||||
app.sidebar_resizing = false;
|
||||
app.sidebar_resize_hovered = false;
|
||||
app.sidebar_hover_tooltip = None;
|
||||
|
||||
let chat_widget = ChatWidget::new(app, chat_area).with_ocean_viewport(size);
|
||||
|
||||
@@ -812,7 +812,7 @@ fn approval_mouse_wheel_reviews_transcript_without_closing_card() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approval_wheel_preserves_sidebar_and_work_surface_ownership() {
|
||||
fn approval_wheel_preserves_work_surface_ownership() {
|
||||
let mut app = create_test_app();
|
||||
app.view_stack.push(ApprovalView::new(ApprovalRequest::new(
|
||||
"approval-scroll",
|
||||
@@ -821,11 +821,10 @@ fn approval_wheel_preserves_sidebar_and_work_surface_ownership() {
|
||||
&serde_json::json!({"command": "git status"}),
|
||||
"approval-scroll-key",
|
||||
)));
|
||||
app.viewport.last_sidebar_area = Some(Rect::new(60, 0, 20, 20));
|
||||
app.work_surface.last_area = Some(Rect::new(0, 0, 30, 20));
|
||||
app.viewport.last_approval_area = Some(Rect::new(0, 12, 80, 8));
|
||||
|
||||
for (column, row) in [(65, 4), (10, 4)] {
|
||||
for (column, row) in [(10, 4), (20, 4)] {
|
||||
let events = handle_mouse_event(
|
||||
&mut app,
|
||||
MouseEvent {
|
||||
@@ -1980,12 +1979,7 @@ fn loading_mouse_filter_keeps_hover_and_active_drags() {
|
||||
app.viewport.transcript_scrollbar_dragging = true;
|
||||
assert!(!should_drop_loading_mouse_motion(&app, drag));
|
||||
|
||||
// Sidebar drag-to-resize must also survive the loading filter (#3063).
|
||||
app.viewport.transcript_scrollbar_dragging = false;
|
||||
app.sidebar_resizing = true;
|
||||
assert!(!should_drop_loading_mouse_motion(&app, drag));
|
||||
|
||||
app.sidebar_resizing = false;
|
||||
app.work_surface.last_area = Some(Rect::new(0, 0, 80, 3));
|
||||
let started = crate::tui::work_surface::handle_mouse(
|
||||
&mut app,
|
||||
@@ -2007,7 +2001,6 @@ fn loading_mouse_filter_keeps_hover_and_active_drags() {
|
||||
fn loading_mouse_filter_allows_sidebar_hover_to_clear() {
|
||||
let mut app = create_test_app();
|
||||
app.is_loading = true;
|
||||
app.viewport.last_sidebar_area = Some(Rect::new(60, 4, 20, 6));
|
||||
app.sidebar_hover_tooltip = Some("Stale sidebar tooltip".to_string());
|
||||
let moved = MouseEvent {
|
||||
kind: MouseEventKind::Moved,
|
||||
@@ -2027,7 +2020,6 @@ fn loading_mouse_filter_allows_sidebar_hover_to_clear() {
|
||||
fn loading_mouse_filter_allows_sidebar_exit_to_clear_highlight() {
|
||||
let mut app = create_test_app();
|
||||
app.is_loading = true;
|
||||
app.viewport.last_sidebar_area = Some(Rect::new(60, 4, 20, 6));
|
||||
app.last_mouse_pos = Some((60, 5));
|
||||
|
||||
let exit_left = MouseEvent {
|
||||
@@ -8785,10 +8777,10 @@ fn hotbar_alt_digit_fires_from_composer_and_sidebar_states() {
|
||||
app.input = " ".to_string();
|
||||
assert_eq!(hotbar_slot_from_key(&app, &alt_four), Some(4));
|
||||
|
||||
app.sidebar_focus = SidebarFocus::Hidden;
|
||||
app.work_surface.placement = crate::tui::work_surface::WorkSurfacePlacement::Off;
|
||||
assert_eq!(hotbar_slot_from_key(&app, &alt_four), Some(4));
|
||||
|
||||
app.sidebar_focus = SidebarFocus::Agents;
|
||||
app.work_surface.panel = crate::tui::work_surface::RailPanel::Agents;
|
||||
assert_eq!(hotbar_slot_from_key(&app, &alt_four), Some(4));
|
||||
}
|
||||
|
||||
@@ -9147,7 +9139,8 @@ fn ctrl_x_jobs_prefill_only_catches_running_shell_jobs_in_tasks_sidebar() {
|
||||
#[test]
|
||||
fn ctrl_x_jobs_prefill_falls_through_outside_tasks_sidebar_shell_jobs() {
|
||||
let mut non_shell = create_test_app();
|
||||
non_shell.sidebar_focus = SidebarFocus::Tasks;
|
||||
non_shell.work_surface.panel = crate::tui::work_surface::RailPanel::Tasks;
|
||||
non_shell.work_surface.last_area = Some(ratatui::layout::Rect::new(0, 0, 100, 3));
|
||||
non_shell.input = "draft".to_string();
|
||||
non_shell.cursor_position = non_shell.input.len();
|
||||
non_shell.task_panel.push(TaskPanelEntry {
|
||||
|
||||
Reference in new issue
Block a user