use ratatui::{
style::{Color, Modifier, Style},
text::{Line, Span},
};
use crate::client::{CommandResult, HealthReport, ManagedSessionView};
fn error_style() -> Style {
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)
}
fn heading_style() -> Style {
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD)
}
pub fn render_result(result: &CommandResult) -> Vec<Line<'static>> {
match result {
CommandResult::Error(msg) => {
vec![Line::from(Span::styled(format!("✗ {msg}"), error_style()))]
}
CommandResult::Help(text) => text.lines().map(|l| Line::from(l.to_string())).collect(),
CommandResult::ManagedSessions(views) => render_managed_sessions(views),
CommandResult::ManagedSession(view) => render_managed_session(view),
CommandResult::ManagedSpawned {
id,
name,
state,
runtime,
attach_cmd,
} => vec![
heading_line(format!("spawned {name} [{state}]")),
detail_line(format!("id {id} · runtime {runtime}")),
detail_line(format!("attach: {attach_cmd}")),
],
CommandResult::ManagedSent { id, tmux_name } => {
vec![ok_line(format!("sent → {tmux_name} ({})", short(id)))]
}
CommandResult::ManagedAnswered {
id,
answer,
tmux_name,
} => vec![ok_line(format!(
"answered {tmux_name} ({}) → {answer}",
short(id)
))],
CommandResult::ManagedActivity {
id,
state,
summary,
runtime_active,
pending_decision,
..
} => render_activity(id, state, summary, *runtime_active, pending_decision),
CommandResult::ManagedAttachCmd { id, attach_cmd } => vec![
heading_line(format!("attach {}", short(id))),
detail_line(attach_cmd.clone()),
],
CommandResult::ManagedLifecycle {
id,
name,
state,
action,
..
} => vec![ok_line(format!(
"{action} {name} [{state}] ({})",
short(id)
))],
CommandResult::Health(report) => render_health(report),
other => vec![Line::from(format!("{other:?}"))],
}
}
fn render_health(report: &HealthReport) -> Vec<Line<'static>> {
if !report.reachable {
return vec![Line::from(Span::styled(
format!("✗ daemon unreachable ({})", report.url),
error_style(),
))];
}
let catalog = if report.catalog_unknown {
"catalog: never synced"
} else if report.catalog_stale {
"catalog: updates available"
} else {
"catalog: up to date"
};
vec![
heading_line(format!("daemon {} ({})", report.status, report.url)),
detail_line(catalog.to_string()),
detail_line(format!(
"fleet: {} session(s), {} awaiting a decision",
report.managed_total, report.managed_pending_decisions
)),
]
}
fn render_managed_sessions(views: &[ManagedSessionView]) -> Vec<Line<'static>> {
let mut lines = vec![heading_line(format!("managed sessions ({})", views.len()))];
if views.is_empty() {
lines.push(detail_line("(none)".to_string()));
return lines;
}
for v in views {
let mut text = format!("• {} {} [{}]", short(&v.id), v.name, v.state);
if let Some(decision) = &v.pending_decision {
text.push_str(&format!(" · decision: {decision}"));
}
lines.push(Line::from(text));
}
lines
}
fn render_managed_session(v: &ManagedSessionView) -> Vec<Line<'static>> {
let mut lines = vec![heading_line(format!(
"{} {} [{}]",
short(&v.id),
v.name,
v.state
))];
if let Some(branch) = &v.branch {
lines.push(detail_line(format!("branch {branch}")));
}
if let Some(ws) = &v.workspace_path {
lines.push(detail_line(format!("workspace {ws}")));
}
if let Some(decision) = &v.pending_decision {
lines.push(detail_line(format!("pending: {decision}")));
}
lines
}
fn render_activity(
id: &str,
state: &str,
summary: &str,
runtime_active: bool,
pending_decision: &Option<String>,
) -> Vec<Line<'static>> {
let runtime = if runtime_active { "alive" } else { "down" };
let mut lines = vec![
heading_line(format!(
"activity {} [{state}] runtime {runtime}",
short(id)
)),
detail_line(summary.to_string()),
];
if let Some(decision) = pending_decision {
lines.push(detail_line(format!("pending: {decision}")));
}
lines
}
fn short(id: &str) -> String {
id.chars().take(8).collect()
}
fn heading_line(text: String) -> Line<'static> {
Line::from(Span::styled(text, heading_style()))
}
fn detail_line(text: String) -> Line<'static> {
Line::from(Span::styled(
format!(" {text}"),
Style::default().fg(Color::DarkGray),
))
}
fn ok_line(text: String) -> Line<'static> {
Line::from(Span::styled(
format!("✓ {text}"),
Style::default().fg(Color::Green),
))
}