use ratatui::text::Span;
use crate::model::snapshot::{ProviderState, Snapshot};
use crate::view::fitted::{columns, Fitted, GAP};
use crate::view::forest::Spine;
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: &[String],
spine: Spine,
width: usize,
) -> Fitted {
if let Some(typed) = prompt {
return Fitted::new(
vec![Span::raw(phrase::prompt(typed))],
Vec::new(),
Vec::new(),
);
}
let answer: Vec<Span<'static>> = said
.map(phrase::said)
.or_else(|| phrase::spine(spine).map(str::to_string))
.map(Span::raw)
.into_iter()
.collect();
if notices.is_empty() {
return Fitted::new(vec![Span::raw(fullest(keys, width))], Vec::new(), answer)
.state_or_nothing();
}
let warning = warnings(notices, width);
let room = width.saturating_sub(columns(&[Span::raw(warning.clone())]) + GAP);
Fitted::new(
vec![Span::styled(warning, palette::ATTENTION)],
answer,
vec![Span::raw(fullest(keys, room))],
)
.title_or_nothing()
.state_or_nothing()
}
fn fullest(forms: &[String], room: usize) -> String {
forms
.iter()
.find(|form| columns(&[Span::raw((*form).clone())]) <= room)
.or_else(|| forms.last())
.cloned()
.unwrap_or_default()
}
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::*;
use crate::view::Action;
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!["dunwich".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!["dunwich".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!["dunwich".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(), Spine::EveryCopy, 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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
80,
),
80,
1,
)
.rows();
says(
&drawn[0],
"no herdr session · which agents are alive is unknown",
);
says(&drawn[0], "polled, not reported");
}
fn a_row_that_can_say_less() -> Vec<String> {
[
"Esc back ? keys Tab related y id",
"Esc back ? keys Tab related",
"Esc back ? keys",
"Esc back",
]
.iter()
.map(|form| (*form).to_string())
.collect()
}
#[test]
fn a_row_that_can_say_less_says_less_rather_than_going() {
let drawn = Painted::of(
status_bar(
&[Notice::AgentsUnknown],
None,
None,
&a_row_that_can_say_less(),
Spine::EveryCopy,
80,
),
80,
1,
)
.rows();
assert!(
drawn[0].trim_end().ends_with("Esc back ? keys"),
"{drawn:?}"
);
}
#[test]
fn a_row_too_narrow_for_even_its_shortest_form_draws_none_of_it() {
let drawn = Painted::of(
status_bar(
&[Notice::AgentsUnknown],
None,
None,
&a_row_that_can_say_less(),
Spine::EveryCopy,
60,
),
60,
1,
)
.rows();
assert_eq!(
drawn[0].trim_end(),
"\u{26a0} no herdr session \u{b7} which agents are alive is unknown"
);
}
#[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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
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_rule_the_forest_starts_under_is_not_named() {
let drawn = Painted::of(
status_bar(&[], None, None, &a_key_row(), Spine::EveryCopy, 100),
100,
1,
)
.rows();
assert_eq!(drawn[0].trim_end(), A_KEY_ROW);
}
#[test]
fn a_rule_the_reader_put_in_force_is_said_at_the_foot() {
let drawn = Painted::of(
status_bar(&[], None, None, &a_key_row(), Spine::Deepest, 100),
100,
1,
)
.rows();
assert!(drawn[0].starts_with(A_KEY_ROW), "{drawn:?}");
says(&drawn[0], "opening the deepest copy of each bead");
}
#[test]
fn the_foot_names_the_rule_the_forest_was_drawn_under() {
let mut forest = opened(&snapshot(
vec![grove(1)],
Vec::new(),
ProviderState::Answering,
));
forest.apply(Action::CycleSpineForest);
let foot = frame_of(&forest, 100, 4).rows().remove(3);
says(
&foot,
phrase::spine(forest.spine()).expect("a rule a reader can reach is named"),
);
}
#[test]
fn each_rule_the_reader_can_reach_is_named_in_its_own_words() {
let mut said: Vec<&str> = Vec::new();
let reachable = Spine::EVERY
.iter()
.copied()
.filter(|rule| *rule != Spine::default());
for rule in reachable {
let words = phrase::spine(rule).unwrap_or_else(|| panic!("{rule:?} is named"));
let drawn =
Painted::of(status_bar(&[], None, None, &a_key_row(), rule, 100), 100, 1).rows();
says(&drawn[0], words);
said.push(words);
}
let every = said.len();
said.sort_unstable();
said.dedup();
assert_eq!(said.len(), every, "two rules share an account: {said:?}");
}
#[test]
fn what_a_keystroke_came_to_takes_the_rules_place_while_it_is_up() {
let drawn = Painted::of(
status_bar(
&[],
Some(&Said::Copied("dun-1".to_string())),
None,
&a_key_row(),
Spine::Deepest,
100,
),
100,
1,
)
.rows();
assert!(drawn[0].trim_end().ends_with("copied dun-1"), "{drawn:?}");
assert!(
!drawn[0].contains("deepest"),
"the rule and the answer were both said: {drawn:?}"
);
}
#[test]
fn a_rule_beside_a_notice_is_said_between_it_and_the_keys() {
let painted = Painted::of(
status_bar(
&[Notice::AgentsUnknown],
None,
None,
&a_key_row(),
Spine::Deepest,
150,
),
150,
1,
);
let drawn = painted.rows();
assert_eq!(
drawn[0].trim_end(),
format!(
"⚠ no herdr session · which agents are alive is unknown \
opening the deepest copy of each bead{}{A_KEY_ROW}",
" ".repeat(150 - 54 - 2 - 37 - A_KEY_ROW.chars().count())
)
);
assert!(
painted
.row(0)
.iter()
.any(|run| run.said.contains("deepest") && run.style.fg != palette::ATTENTION.fg),
"a rule the reader asked for was painted as a warning: {:?}",
painted.row(0)
);
}
#[test]
fn a_rule_the_row_has_no_room_for_is_dropped_whole_and_the_keys_stay() {
let row = |width: usize| {
Painted::of(
status_bar(&[], None, None, &a_key_row(), Spine::Deepest, width),
width as u16,
1,
)
.rows()
.remove(0)
};
let rule = "opening the deepest copy of each bead";
let fits = A_KEY_ROW.chars().count() + GAP + rule.chars().count();
assert_eq!(row(fits), format!("{A_KEY_ROW} {rule}"));
assert_eq!(row(fits - 1).trim_end(), A_KEY_ROW);
}
#[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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
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(),
Spine::EveryCopy,
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);
}
}