use super::super::*;
impl App {
pub(crate) fn task_lines(&self) -> Vec<String> {
let running = self
.running_task
.as_ref()
.filter(|_| self.state != State::Idle);
if self.queue.is_empty() {
return Vec::new();
}
let width = self.width as usize;
let cap = width.saturating_sub(8);
let mut lines = vec![pad_to(
&Style::new()
.fg(TN_GRAY)
.render(&format!(" ─ tasks · ✓ {} done ────────", self.completed)),
width,
)];
if let Some(t) = running {
lines.push(pad_to(
&Style::new()
.fg(TN_YELLOW)
.render(&format!(" ⏳ {}", truncate(t, cap))),
width,
));
}
let mut q: Vec<&Queued> = self.queue.iter().collect();
q.sort_by_key(|x| (x.prio, x.seq));
for item in q.iter().take(6) {
lines.push(pad_to(
&Style::new()
.fg(TN_GRAY)
.render(&format!(" ▱ {}", truncate(&item.text, cap))),
width,
));
}
lines
}
pub(crate) fn viewport_rows(&self) -> usize {
let n = (self.task_lines().len() + self.plan_lines().len() + self.subagent_lines().len())
as u16;
self.height.saturating_sub(6 + self.input_height() + n) as usize
}
pub(crate) fn relayout(&mut self) {
self.viewport
.resize(self.width.saturating_sub(1), self.viewport_rows() as u16);
}
pub(crate) fn set_plan(&mut self, tasks: &[a3s_code_core::planning::Task]) {
self.plan = tasks
.iter()
.map(|t| {
let (g, c) = task_status_style(t.status);
(t.id.clone(), t.content.clone(), g, c)
})
.collect();
self.relayout();
}
pub(crate) fn set_task_status(&mut self, id: &str, glyph: char, color: Color) {
if let Some(t) = self.plan.iter_mut().find(|t| t.0 == id) {
t.2 = glyph;
t.3 = color;
}
}
pub(crate) fn plan_lines(&self) -> Vec<String> {
if self.plan.is_empty() {
return Vec::new();
}
let width = self.width as usize;
let cap = width.saturating_sub(10);
let mut lines = Vec::new();
for (i, (_, text, glyph, color)) in self.plan.iter().take(8).enumerate() {
let (boxc, bcolor, done, inprog) = match glyph {
'✔' => ('✔', TN_GRAY, true, false),
'▶' => ('◼', TN_ORANGE, false, true),
'✗' => ('✗', TN_RED, false, false),
_ => ('◻', TN_GRAY, false, false),
};
let text_style = if done {
Style::new().fg(*color).strikethrough()
} else if inprog {
Style::new().fg(TN_ORANGE)
} else {
Style::new().fg(*color)
};
let conn = if i == 0 { "⎿ " } else { " " };
lines.push(pad_to(
&format!(
" {conn}{} {}",
Style::new().fg(bcolor).render(&boxc.to_string()),
text_style.render(&truncate(text, cap)),
),
width,
));
}
lines
}
pub(crate) fn subagent_lines(&self) -> Vec<String> {
if self.subagents.is_empty() {
return Vec::new();
}
let width = self.width as usize;
let now = Instant::now();
let total = self.subagents.len();
let done = self.subagents.iter().filter(|s| s.done).count();
let tokens = self.subagents.iter().map(|s| s.tokens).sum::<u64>();
let started = self
.subagents
.iter()
.map(|s| s.started)
.min()
.unwrap_or(now);
let ended = if done == total {
self.subagents
.iter()
.filter_map(|s| s.ended)
.max()
.unwrap_or(now)
} else {
now
};
let elapsed = ended.saturating_duration_since(started);
let status = if done == total {
format!("{done}/{total} agents done")
} else {
format!(
"{} running · {done}/{total} done",
total.saturating_sub(done)
)
};
let right = if tokens > 0 {
format!(
"{status} · {} · ↓ {} tokens",
fmt_elapsed(elapsed),
fmt_tokens(tokens)
)
} else {
format!("{status} · {}", fmt_elapsed(elapsed))
};
let task = self
.running_task
.as_deref()
.unwrap_or("parallel agents")
.trim();
let slug = workflow_slug(task);
let rlen = a3s_tui::style::visible_len(&right);
let maxleft = width.saturating_sub(rlen + 3).max(8);
let left = truncate(&format!(" ◯ {slug} {task}"), maxleft);
let pad = width.saturating_sub(a3s_tui::style::visible_len(&left) + rlen + 1);
let mut out = vec![format!(
"{}{}{}",
Style::new().fg(ACCENT).bold().render(&left),
" ".repeat(pad),
Style::new().fg(TN_GRAY).render(&right),
)];
for s in self.subagents.iter().filter(|s| !s.done).take(4) {
let el = fmt_elapsed(s.started.elapsed());
let right = if s.tokens > 0 {
format!("{el} · ↓ {} tokens", fmt_tokens(s.tokens))
} else {
el
};
let rlen = a3s_tui::style::visible_len(&right);
let maxleft = width.saturating_sub(rlen + 3).max(8);
let left = truncate(&format!(" ◯ {} {}", s.agent, s.description), maxleft);
let pad = width.saturating_sub(a3s_tui::style::visible_len(&left) + rlen + 1);
out.push(format!(
"{}{}{}",
Style::new().fg(TN_PURPLE).render(&left),
" ".repeat(pad),
Style::new().fg(TN_GRAY).render(&right),
));
}
out
}
}
fn workflow_slug(text: &str) -> String {
let mut slug = String::new();
let mut last_dash = false;
for ch in text.chars().flat_map(|ch| ch.to_lowercase()) {
if ch.is_ascii_alphanumeric() {
slug.push(ch);
last_dash = false;
} else if !last_dash && !slug.is_empty() {
slug.push('-');
last_dash = true;
}
if slug.len() >= 24 {
break;
}
}
while slug.ends_with('-') {
slug.pop();
}
if slug.is_empty() {
"parallel-agents".to_string()
} else {
slug
}
}