story-kit: merge 313_story_improve_htop_output_formatting_for_mobile_matrix_clients
This commit is contained in:
@@ -201,6 +201,10 @@ fn gather_process_stats(worktree_path: &str) -> Option<AgentProcessStats> {
|
||||
///
|
||||
/// `tick` is the number of updates sent so far (0 = initial).
|
||||
/// `total_duration_secs` is the configured auto-stop timeout.
|
||||
///
|
||||
/// Output uses a compact single-line format per agent so it renders
|
||||
/// without wrapping on narrow screens (~40 chars), such as mobile
|
||||
/// Matrix clients.
|
||||
pub fn build_htop_message(agents: &AgentPool, tick: u32, total_duration_secs: u64) -> String {
|
||||
let elapsed_secs = (tick as u64) * 5;
|
||||
let remaining_secs = total_duration_secs.saturating_sub(elapsed_secs);
|
||||
@@ -210,11 +214,11 @@ pub fn build_htop_message(agents: &AgentPool, tick: u32, total_duration_secs: u6
|
||||
let load = get_load_average();
|
||||
|
||||
let mut lines = vec![
|
||||
format!("**htop** — {load}"),
|
||||
format!(
|
||||
"*Updates every 5s · auto-stops in {}m{}s · send `htop stop` to stop*",
|
||||
"**htop** · auto-stops in {}m{}s",
|
||||
remaining_mins, remaining_secs_rem
|
||||
),
|
||||
load,
|
||||
String::new(),
|
||||
];
|
||||
|
||||
@@ -227,8 +231,6 @@ pub fn build_htop_message(agents: &AgentPool, tick: u32, total_duration_secs: u6
|
||||
if active.is_empty() {
|
||||
lines.push("*No agents currently running.*".to_string());
|
||||
} else {
|
||||
lines.push("| Agent | Story | CPU% | MEM% | Procs |".to_string());
|
||||
lines.push("|-------|-------|-----:|-----:|------:|".to_string());
|
||||
for agent in &active {
|
||||
let story_label = agent
|
||||
.story_id
|
||||
@@ -242,12 +244,8 @@ pub fn build_htop_message(agents: &AgentPool, tick: u32, total_duration_secs: u6
|
||||
.and_then(gather_process_stats)
|
||||
.unwrap_or_default();
|
||||
lines.push(format!(
|
||||
"| {} | {} | {:.1} | {:.1} | {} |",
|
||||
agent.agent_name,
|
||||
story_label,
|
||||
stats.cpu_pct,
|
||||
stats.mem_pct,
|
||||
stats.num_procs,
|
||||
"**{}** #{} cpu:{:.1}% mem:{:.1}%",
|
||||
agent.agent_name, story_label, stats.cpu_pct, stats.mem_pct,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -569,4 +567,55 @@ mod tests {
|
||||
"should show remaining time: {text}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_htop_message_load_on_own_line() {
|
||||
// Load average must be on its own line, not combined with the htop header.
|
||||
let pool = Arc::new(crate::agents::AgentPool::new_test(3000));
|
||||
let text = build_htop_message(&pool, 0, 300);
|
||||
let lines: Vec<&str> = text.lines().collect();
|
||||
let header_line = lines.first().expect("should have a header line");
|
||||
// Header line must NOT contain "load" — load is on the second line.
|
||||
assert!(
|
||||
!header_line.contains("load"),
|
||||
"load should be on its own line, not the header: {header_line}"
|
||||
);
|
||||
// Second line must contain "load".
|
||||
let load_line = lines.get(1).expect("should have a load line");
|
||||
assert!(
|
||||
load_line.contains("load"),
|
||||
"second line should contain load info: {load_line}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_htop_message_no_table_syntax() {
|
||||
// Must not use Markdown table format (pipes/separators) — those are too
|
||||
// wide for narrow mobile screens.
|
||||
let pool = Arc::new(crate::agents::AgentPool::new_test(3000));
|
||||
let text = build_htop_message(&pool, 0, 300);
|
||||
assert!(
|
||||
!text.contains("|----"),
|
||||
"output must not contain table separator rows: {text}"
|
||||
);
|
||||
assert!(
|
||||
!text.contains("| Agent"),
|
||||
"output must not contain table header row: {text}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_htop_message_header_fits_40_chars() {
|
||||
// The header line (htop + remaining time) must fit in ~40 rendered chars.
|
||||
let pool = Arc::new(crate::agents::AgentPool::new_test(3000));
|
||||
let text = build_htop_message(&pool, 0, 300);
|
||||
let header = text.lines().next().expect("should have a header line");
|
||||
// Strip markdown bold markers (**) for length calculation.
|
||||
let rendered = header.replace("**", "");
|
||||
assert!(
|
||||
rendered.len() <= 40,
|
||||
"header line too wide for mobile ({} chars): {rendered}",
|
||||
rendered.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user