use ratatui::text::Span;
use crate::model::snapshot::{ProviderState, Snapshot};
use crate::view::fitted::{columns, Fitted, GAP};
use crate::view::palette;
use crate::view::phrase;
use crate::view::row::WARNING;
use crate::view::{Notice, Said};
pub(super) fn notices(snapshot: &Snapshot, standing: &[Notice]) -> Vec<Notice> {
let collected = match snapshot.agents.state {
ProviderState::Answering | ProviderState::Absent => None,
ProviderState::NotAnswering => Some(Notice::AgentsUnknown),
};
let unanswered = snapshot
.agents
.unanswered()
.map(|session| Notice::SessionUnanswered(session.to_string()));
let guessed =
(!snapshot.projects_named_without_git.is_empty()).then_some(Notice::ProjectNamedWithoutGit);
collected
.into_iter()
.chain(unanswered)
.chain(guessed)
.chain(standing.iter().cloned())
.collect()
}
pub(super) fn status_bar(
notices: &[Notice],
said: Option<&Said>,
prompt: Option<&str>,
keys: &str,
width: usize,
) -> Fitted {
if let Some(typed) = prompt {
return Fitted::new(
vec![Span::raw(phrase::prompt(typed))],
Vec::new(),
Vec::new(),
);
}
let keys = Span::raw(keys.to_string());
let answer: Vec<Span<'static>> = said
.map(|said| Span::raw(phrase::said(said)))
.into_iter()
.collect();
if notices.is_empty() {
return Fitted::new(vec![keys], Vec::new(), answer).state_or_nothing();
}
Fitted::new(
vec![Span::styled(warnings(notices, width), palette::ATTENTION)],
answer,
vec![keys],
)
.title_or_nothing()
.state_or_nothing()
}
fn warnings(notices: &[Notice], width: usize) -> String {
let mut words = notices.iter().map(phrase::notice).collect::<Vec<_>>();
for (at, notice) in notices.iter().enumerate().rev() {
if columns(&[Span::raw(marked(&words))]) <= width {
break;
}
words[at] = phrase::brief_notice(notice);
}
marked(&words)
}
fn marked(words: &[String]) -> String {
words
.iter()
.map(|said| format!("{WARNING} {said}"))
.collect::<Vec<_>>()
.join(&" ".repeat(GAP))
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use crate::config::Scope;
use crate::model::snapshot::{a_provider, Filter, ProviderState as State, A_PROVIDER};
use crate::view::draw::tests::*;
fn behind_a_frame(agents: crate::model::snapshot::AgentProvider) -> Snapshot {
Snapshot {
agents,
..nothing_to_qualify()
}
}
fn nothing_to_qualify() -> Snapshot {
Snapshot::awaiting(
Vec::new(),
Vec::new(),
A_PROVIDER,
Scope::Everything,
Filter::LiveAgents,
"2026-08-30T12:00:00Z".parse().expect("the instant parses"),
)
}
#[test]
fn a_project_named_without_git_is_said_at_the_foot_from_the_snapshot() {
let guessed = Snapshot {
projects_named_without_git: vec!["orbital".to_string()],
..nothing_to_qualify()
};
assert_eq!(
notices(&guessed, &[]),
vec![Notice::ProjectNamedWithoutGit],
"the foot did not read the guess off the snapshot"
);
assert_eq!(
notices(¬hing_to_qualify(), &[]),
Vec::new(),
"a run that guessed no name was warned about one"
);
}
#[test]
fn several_guessed_names_are_one_notice() {
let guessed = Snapshot {
projects_named_without_git: vec!["orbital".to_string(), "ferry".to_string()],
..nothing_to_qualify()
};
assert_eq!(notices(&guessed, &[]), vec![Notice::ProjectNamedWithoutGit]);
}
#[test]
fn a_guessed_name_outranks_what_this_process_settled() {
let guessed = Snapshot {
projects_named_without_git: vec!["orbital".to_string()],
..behind_a_frame(a_provider(State::NotAnswering))
};
assert_eq!(
notices(&guessed, &[Notice::NoInboundChannel]),
vec![
Notice::AgentsUnknown,
Notice::ProjectNamedWithoutGit,
Notice::NoInboundChannel
]
);
}
#[test]
fn the_foot_of_the_screen_shows_the_keys_it_is_handed() {
let drawn = Painted::of(status_bar(&[], None, None, A_KEY_ROW, 60), 60, 1).rows();
assert!(drawn[0].starts_with(A_KEY_ROW), "{drawn:?}");
}
#[test]
fn a_herdr_that_could_not_be_reached_is_said_where_nothing_can_hide_it() {
let drawn = Painted::of(
status_bar(&[Notice::AgentsUnknown], None, None, A_KEY_ROW, 90),
90,
1,
)
.rows();
says(
&drawn[0],
"no herdr session · which agents are alive is unknown",
);
}
#[test]
fn a_bdi_nothing_can_reach_says_so_for_the_life_of_the_session() {
let drawn = Painted::of(
status_bar(&[Notice::NoInboundChannel], None, None, A_KEY_ROW, 90),
90,
1,
)
.rows();
says(
&drawn[0],
"nothing can tell bdi a project changed · every project is polled instead",
);
}
#[test]
fn a_foot_with_room_says_every_notice_it_is_given() {
let drawn = Painted::of(
status_bar(
&[Notice::AgentsUnknown, Notice::NoInboundChannel],
None,
None,
A_KEY_ROW,
200,
),
200,
1,
)
.rows();
for words in [
"no herdr session · which agents are alive is unknown",
"nothing can tell bdi a project changed · every project is polled instead",
] {
says(&drawn[0], words);
}
}
#[test]
fn a_narrow_foot_gives_up_the_last_notices_words_first() {
let drawn = Painted::of(
status_bar(
&[Notice::AgentsUnknown, Notice::NoInboundChannel],
None,
None,
A_KEY_ROW,
80,
),
80,
1,
)
.rows();
says(
&drawn[0],
"no herdr session · which agents are alive is unknown",
);
says(&drawn[0], "polled, not reported");
}
#[test]
fn a_copied_id_is_said_after_the_keys_where_nothing_is_wrong() {
let drawn = Painted::of(
status_bar(
&[],
Some(&Said::Copied("grv-1".to_string())),
None,
A_KEY_ROW,
80,
),
80,
1,
)
.rows();
assert!(drawn[0].starts_with(A_KEY_ROW), "{drawn:?}");
assert!(drawn[0].trim_end().ends_with("copied grv-1"), "{drawn:?}");
}
#[test]
fn a_copied_id_is_said_between_a_notice_and_the_keys() {
let drawn = Painted::of(
status_bar(
&[Notice::AgentsUnknown],
Some(&Said::Copied("grv-1".to_string())),
None,
A_KEY_ROW,
120,
),
120,
1,
)
.rows();
assert_eq!(
drawn[0].trim_end(),
format!(
"⚠ no herdr session · which agents are alive is unknown copied grv-1{}{A_KEY_ROW}",
" ".repeat(120 - 54 - 2 - 12 - A_KEY_ROW.chars().count())
)
);
}
#[test]
fn a_copied_id_the_row_has_no_room_for_is_dropped_whole_and_the_keys_stay() {
let drawn = Painted::of(
status_bar(
&[],
Some(&Said::Copied("grv-1".to_string())),
None,
A_KEY_ROW,
50,
),
50,
1,
)
.rows();
assert_eq!(drawn[0].trim_end(), A_KEY_ROW);
}
#[test]
fn the_foot_says_nothing_about_how_fresh_the_rows_above_it_are() {
let forest = opened(&snapshot(
vec![grove(1)],
Vec::new(),
ProviderState::Answering,
));
let foot = frame_of(&forest, 74, 4).rows().remove(3);
assert_eq!(foot.trim_end(), A_KEY_ROW);
}
#[test]
fn the_narrowest_screen_still_says_the_view_is_polled() {
let drawn = Painted::of(
status_bar(
&[Notice::AgentsUnknown, Notice::NoInboundChannel],
None,
None,
A_KEY_ROW,
40,
),
40,
1,
)
.rows();
assert_eq!(drawn[0], "⚠ agents unknown ⚠ polled, not reported");
}
#[test]
fn a_lone_notice_too_wide_for_the_row_is_said_briefly() {
let drawn = Painted::of(
status_bar(&[Notice::NoInboundChannel], None, None, A_KEY_ROW, 60),
60,
1,
)
.rows();
assert!(drawn[0].starts_with("⚠ polled, not reported"), "{drawn:?}");
}
#[test]
fn a_notice_said_briefly_is_still_painted_as_a_warning() {
let painted = Painted::of(
status_bar(&[Notice::NoInboundChannel], None, None, A_KEY_ROW, 60),
60,
1,
)
.row(0);
assert!(
painted
.iter()
.any(|run| run.said.contains("polled, not reported")
&& run.style.fg == palette::ATTENTION.fg),
"{painted:?}"
);
}
#[test]
fn a_socket_another_bdi_holds_says_that_rather_than_only_what_it_cost() {
let drawn = Painted::of(
status_bar(
&[Notice::AnotherBdiHadTheInboundChannel],
None,
None,
A_KEY_ROW,
100,
),
100,
1,
)
.rows();
says(
&drawn[0],
"another bdi held the inbound channel · every project is polled instead",
);
}
#[test]
fn the_narrowest_screen_still_says_another_bdi_took_the_channel() {
let drawn = Painted::of(
status_bar(
&[
Notice::AgentsUnknown,
Notice::AnotherBdiHadTheInboundChannel,
],
None,
None,
A_KEY_ROW,
40,
),
40,
1,
)
.rows();
assert_eq!(
drawn[0].trim_end(),
"⚠ agents unknown ⚠ another bdi had it"
);
}
#[test]
fn a_socket_another_bdi_holds_is_painted_as_a_warning() {
let painted = Painted::of(
status_bar(
&[Notice::AnotherBdiHadTheInboundChannel],
None,
None,
A_KEY_ROW,
100,
),
100,
1,
)
.row(0);
assert!(
painted.iter().any(
|run| run.said.contains("another bdi") && run.style.fg == palette::ATTENTION.fg
),
"{painted:?}"
);
}
#[test]
fn the_snapshots_notice_outranks_the_sessions() {
assert_eq!(
notices(
&behind_a_frame(a_provider(ProviderState::NotAnswering)),
&[Notice::NoInboundChannel]
),
vec![Notice::AgentsUnknown, Notice::NoInboundChannel]
);
}
#[test]
fn a_provider_that_was_never_installed_is_not_warned_about() {
assert_eq!(
notices(
&behind_a_frame(a_provider(ProviderState::Absent)),
&[Notice::NoInboundChannel]
),
vec![Notice::NoInboundChannel]
);
assert_eq!(
notices(&behind_a_frame(a_provider(ProviderState::Absent)), &[]),
Vec::new()
);
}
#[test]
fn a_session_notice_stands_alone_where_the_snapshot_is_well() {
assert_eq!(
notices(
&behind_a_frame(a_provider(ProviderState::Answering)),
&[Notice::NoInboundChannel]
),
vec![Notice::NoInboundChannel]
);
}
#[test]
fn a_session_with_nothing_wrong_leaves_the_foot_to_the_keys() {
assert_eq!(
notices(&behind_a_frame(a_provider(ProviderState::Answering)), &[]),
Vec::new()
);
}
#[test]
fn a_narrow_foot_gives_up_the_keys_before_the_missing_herdr() {
let drawn = Painted::of(
status_bar(&[Notice::AgentsUnknown], None, None, A_KEY_ROW, 60),
60,
1,
)
.rows();
assert!(drawn[0].contains("no herdr session"), "{drawn:?}");
assert_eq!(drawn[0].chars().count(), 60);
}
#[test]
fn a_foot_too_narrow_for_the_whole_key_row_draws_none_of_it() {
let drawn = Painted::of(
status_bar(&[Notice::AgentsUnknown], None, None, A_KEY_ROW, 60),
60,
1,
)
.rows();
assert_eq!(
drawn[0].trim_end(),
"⚠ no herdr session · which agents are alive is unknown"
);
}
#[test]
fn the_narrowest_screen_draws_no_part_of_the_key_row_beside_a_notice() {
let drawn = Painted::of(
status_bar(&[Notice::AgentsUnknown], None, None, A_KEY_ROW, 40),
40,
1,
)
.rows();
assert_eq!(drawn[0].trim_end(), "⚠ agents unknown");
}
#[test]
fn the_key_row_is_drawn_whole_at_the_first_width_that_holds_it() {
let notice = "⚠ no herdr session · which agents are alive is unknown";
let fits = notice.chars().count() + GAP + A_KEY_ROW.chars().count();
let row = |width: usize| {
Painted::of(
status_bar(&[Notice::AgentsUnknown], None, None, A_KEY_ROW, width),
width as u16,
1,
)
.rows()
.remove(0)
};
assert_eq!(row(fits), format!("{notice} {A_KEY_ROW}"));
assert_eq!(row(fits - 1).trim_end(), notice);
}
}