diff --git a/crates/shepherd-hud/src/app.rs b/crates/shepherd-hud/src/app.rs index 13e5a16..13906c8 100644 --- a/crates/shepherd-hud/src/app.rs +++ b/crates/shepherd-hud/src/app.rs @@ -405,6 +405,8 @@ fn build_hud_content(state: SharedState) -> gtk4::Box { entry_name, warning_issued_at, time_remaining_at_warning, + message, + severity, .. } => { app_label_clone.set_text(entry_name); @@ -412,10 +414,28 @@ fn build_hud_content(state: SharedState) -> gtk4::Box { let elapsed = warning_issued_at.elapsed().as_secs(); let remaining = time_remaining_at_warning.saturating_sub(elapsed); time_display_clone.set_remaining(Some(remaining)); - warning_label_clone.set_text(&format!( - "Only {} seconds remaining!", - remaining - )); + // Use configuration-defined message if present, otherwise show time-based message + let warning_text = message.clone().unwrap_or_else(|| { + format!("Only {} seconds remaining!", remaining) + }); + warning_label_clone.set_text(&warning_text); + + // Apply severity-based CSS classes + warning_box_clone.remove_css_class("warning-info"); + warning_box_clone.remove_css_class("warning-warn"); + warning_box_clone.remove_css_class("warning-critical"); + match severity { + shepherd_api::WarningSeverity::Info => { + warning_box_clone.add_css_class("warning-info"); + } + shepherd_api::WarningSeverity::Warn => { + warning_box_clone.add_css_class("warning-warn"); + } + shepherd_api::WarningSeverity::Critical => { + warning_box_clone.add_css_class("warning-critical"); + } + } + warning_box_clone.set_visible(true); } SessionState::Ending { reason, .. } => { @@ -517,6 +537,31 @@ fn load_css() { padding: 4px 12px; } + .warning-banner.warning-info { + background-color: rgba(136, 192, 208, 0.2); + } + + .warning-banner.warning-info .warning-text { + color: var(--color-info); + } + + .warning-banner.warning-warn { + background-color: rgba(235, 203, 139, 0.2); + } + + .warning-banner.warning-warn .warning-text { + color: var(--color-warning); + } + + .warning-banner.warning-critical { + background-color: rgba(255, 107, 107, 0.2); + animation: blink 1s infinite; + } + + .warning-banner.warning-critical .warning-text { + color: var(--color-critical); + } + .warning-text { color: var(--color-warning); font-weight: bold; diff --git a/crates/shepherd-hud/src/state.rs b/crates/shepherd-hud/src/state.rs index 1d16a08..a36b91b 100644 --- a/crates/shepherd-hud/src/state.rs +++ b/crates/shepherd-hud/src/state.rs @@ -3,7 +3,7 @@ //! The HUD subscribes to events from shepherdd and tracks session state. use chrono::Local; -use shepherd_api::{Event, EventPayload, SessionEndReason, VolumeInfo, VolumeRestrictions}; +use shepherd_api::{Event, EventPayload, SessionEndReason, VolumeInfo, VolumeRestrictions, WarningSeverity}; use shepherd_util::{EntryId, SessionId}; use std::sync::Arc; use tokio::sync::watch; @@ -31,6 +31,10 @@ pub enum SessionState { entry_name: String, warning_issued_at: std::time::Instant, time_remaining_at_warning: u64, + /// Optional custom message from configuration + message: Option, + /// Severity level of the warning + severity: WarningSeverity, }, /// Session is ending @@ -210,9 +214,12 @@ impl SharedState { EventPayload::WarningIssued { session_id, time_remaining, + message, + severity, .. } => { self.session_tx.send_modify(|state| { + // Handle transition from Active state if let SessionState::Active { session_id: sid, entry_id, @@ -227,6 +234,28 @@ impl SharedState { entry_name: entry_name.clone(), warning_issued_at: std::time::Instant::now(), time_remaining_at_warning: time_remaining.as_secs(), + message: message.clone(), + severity: *severity, + }; + } + } + // Handle update when already in Warning state (subsequent warnings) + else if let SessionState::Warning { + session_id: sid, + entry_id, + entry_name, + .. + } = state + { + if sid == session_id { + *state = SessionState::Warning { + session_id: session_id.clone(), + entry_id: entry_id.clone(), + entry_name: entry_name.clone(), + warning_issued_at: std::time::Instant::now(), + time_remaining_at_warning: time_remaining.as_secs(), + message: message.clone(), + severity: *severity, }; } }