Skip to content

Commit fd84269

Browse files
committed
feat(tui): add update banner UI component in MinimalSessionView
1 parent 906bc04 commit fd84269

3 files changed

Lines changed: 80 additions & 3 deletions

File tree

src/cortex-tui/src/app/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ mod types;
1515
pub use approval::{ApprovalState, PendingToolResult};
1616
pub use autocomplete::{AutocompleteItem, AutocompleteState};
1717
pub use session::{ActiveModal, SessionSummary};
18-
pub use state::AppState;
18+
pub use state::{AppState, UpdateStatus};
1919
pub use streaming::StreamingState;
2020
pub use subagent::{
2121
SubagentDisplayStatus, SubagentTaskDisplay, SubagentTodoItem, SubagentTodoStatus,

src/cortex-tui/src/views/minimal_session/rendering.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,3 +960,65 @@ pub fn render_motd_compact(
960960

961961
Paragraph::new(lines).render(text_area, buf);
962962
}
963+
964+
/// Renders an update notification banner above the input box.
965+
/// Shows different states: Available, Downloading (with progress), ReadyToRestart
966+
pub fn render_update_banner(
967+
area: Rect,
968+
buf: &mut Buffer,
969+
colors: &AdaptiveColors,
970+
update_status: &crate::app::UpdateStatus,
971+
) {
972+
use crate::app::UpdateStatus;
973+
974+
if area.is_empty() || area.height < 1 {
975+
return;
976+
}
977+
978+
let (icon, text, style) = match update_status {
979+
UpdateStatus::Available { version } => {
980+
let icon = "↑";
981+
let text = format!(" A new version ({}) is available ", version);
982+
let style = Style::default()
983+
.fg(colors.accent)
984+
.add_modifier(Modifier::BOLD);
985+
(icon, text, style)
986+
}
987+
UpdateStatus::Downloading {
988+
version: _,
989+
progress,
990+
} => {
991+
let icon = "⟳";
992+
let text = format!(" Downloading update... {}% ", progress);
993+
let style = Style::default().fg(colors.warning);
994+
(icon, text, style)
995+
}
996+
UpdateStatus::ReadyToRestart { version: _ } => {
997+
let icon = "✓";
998+
let text = " You must restart to run the latest version ".to_string();
999+
let style = Style::default()
1000+
.fg(colors.success)
1001+
.add_modifier(Modifier::BOLD);
1002+
(icon, text, style)
1003+
}
1004+
_ => return, // Don't render for other states
1005+
};
1006+
1007+
// Calculate banner width
1008+
let banner_width = (icon.len() + text.len() + 2) as u16; // +2 for spacing
1009+
1010+
// Position at left side of the area with some padding
1011+
let x = area.x + 2;
1012+
let y = area.y;
1013+
1014+
// Ensure we don't overflow
1015+
if x + banner_width > area.right() {
1016+
return;
1017+
}
1018+
1019+
// Render icon
1020+
buf.set_string(x, y, icon, style);
1021+
1022+
// Render text
1023+
buf.set_string(x + icon.len() as u16 + 1, y, &text, style);
1024+
}

src/cortex-tui/src/views/minimal_session/view.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,8 @@ impl<'a> Widget for MinimalSessionView<'a> {
569569
self.app_state.autocomplete.visible && self.app_state.autocomplete.has_items();
570570
let autocomplete_height: u16 = if autocomplete_visible { 10 } else { 0 };
571571
let status_height: u16 = if is_task_running { 1 } else { 0 };
572+
let show_update_banner = self.app_state.should_show_update_banner();
573+
let update_banner_height: u16 = if show_update_banner { 1 } else { 0 };
572574
let input_height: u16 = 3;
573575
let hints_height: u16 = 1;
574576

@@ -584,7 +586,8 @@ impl<'a> Widget for MinimalSessionView<'a> {
584586
layout.gap(1);
585587

586588
// Calculate available height for scrollable content (before input/hints)
587-
let bottom_reserved = status_height + input_height + autocomplete_height + hints_height + 2; // +2 for gaps
589+
let bottom_reserved =
590+
status_height + update_banner_height + input_height + autocomplete_height + hints_height + 2; // +2 for gaps
588591
let available_height = area.height.saturating_sub(1 + bottom_reserved); // 1 for top margin
589592

590593
// Render scrollable content area (welcome cards + messages together)
@@ -608,7 +611,19 @@ impl<'a> Widget for MinimalSessionView<'a> {
608611
next_y += status_height;
609612
}
610613

611-
// 6. Input area - follows status (or content if no status)
614+
// 5.5 Update banner (if applicable) - above input
615+
if show_update_banner {
616+
let banner_area = Rect::new(area.x, next_y, area.width, update_banner_height);
617+
super::rendering::render_update_banner(
618+
banner_area,
619+
buf,
620+
&self.colors,
621+
&self.app_state.update_status,
622+
);
623+
next_y += update_banner_height;
624+
}
625+
626+
// 6. Input area - follows update banner (or status if no banner)
612627
let input_y = next_y;
613628
let input_area = Rect::new(area.x, input_y, area.width, input_height);
614629

0 commit comments

Comments
 (0)