chore: import upstream snapshot with attribution
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,395 @@
|
||||
//! Interactive, animated demo of the inline swarm gallery.
|
||||
//!
|
||||
//! This runs as a real full-screen terminal app so you can *see* the inline
|
||||
//! gallery the way it looks live in the jcode TUI, without touching any running
|
||||
//! agents. It simulates a handful of mock swarm workers that stream output,
|
||||
//! change status, and finish over time, rendering them through the exact same
|
||||
//! `render_swarm_gallery` layout the real TUI uses.
|
||||
//!
|
||||
//! Run with:
|
||||
//! cargo run --profile selfdev -p jcode-tui-render --example swarm_gallery_live
|
||||
//!
|
||||
//! Controls:
|
||||
//! q / Esc quit
|
||||
//! + / - more / fewer agents
|
||||
//! [ / ] shrink / grow the gallery band (the max_pct knob)
|
||||
//! space pause / resume the animation
|
||||
|
||||
use std::io::{self, Stdout};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use ratatui::crossterm::{
|
||||
event::{self, Event, KeyCode, KeyModifiers},
|
||||
execute,
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
||||
};
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::widgets::{Block, Borders, Clear, Paragraph, Wrap};
|
||||
|
||||
use jcode_tui_render::swarm_gallery::{GalleryMember, humanize_age, render_gallery};
|
||||
|
||||
/// A simulated worker, mirroring the fields the real adapter reads from a
|
||||
/// `SwarmMemberStatus` (name, role, status, streamed output tail).
|
||||
struct MockWorker {
|
||||
name: String,
|
||||
role: Option<&'static str>,
|
||||
status: String,
|
||||
/// Full streamed transcript so far; the tile shows the tail.
|
||||
transcript: Vec<String>,
|
||||
/// Scripted lines this worker will "stream" over time.
|
||||
script: Vec<String>,
|
||||
next_line: usize,
|
||||
/// Ticks until the next line is appended.
|
||||
cooldown: u16,
|
||||
started: Instant,
|
||||
}
|
||||
|
||||
impl MockWorker {
|
||||
fn new(name: &str, role: Option<&'static str>, script: Vec<&str>) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
role,
|
||||
status: "spawned".to_string(),
|
||||
transcript: Vec::new(),
|
||||
script: script.into_iter().map(|s| s.to_string()).collect(),
|
||||
next_line: 0,
|
||||
cooldown: 2,
|
||||
started: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(&mut self) {
|
||||
if self.next_line >= self.script.len() {
|
||||
if self.status != "completed" {
|
||||
self.status = "completed".to_string();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if self.cooldown > 0 {
|
||||
self.cooldown -= 1;
|
||||
// While waiting, alternate running/thinking to show the badge change.
|
||||
if self.transcript.is_empty() {
|
||||
self.status = "thinking".to_string();
|
||||
}
|
||||
return;
|
||||
}
|
||||
let line = self.script[self.next_line].clone();
|
||||
self.status = if line.starts_with("! ") {
|
||||
"blocked".to_string()
|
||||
} else {
|
||||
"running".to_string()
|
||||
};
|
||||
self.transcript
|
||||
.push(line.trim_start_matches("! ").to_string());
|
||||
self.next_line += 1;
|
||||
self.cooldown = 2 + (self.next_line as u16 % 3);
|
||||
}
|
||||
|
||||
fn age_secs(&self) -> u64 {
|
||||
self.started.elapsed().as_secs()
|
||||
}
|
||||
}
|
||||
|
||||
fn workers_to_members(workers: &[MockWorker]) -> Vec<GalleryMember> {
|
||||
workers
|
||||
.iter()
|
||||
.map(|w| {
|
||||
let mut body: Vec<String> = w.transcript.clone();
|
||||
body.push(format!("· {} ago", humanize_age(w.age_secs())));
|
||||
GalleryMember {
|
||||
label: w.name.clone(),
|
||||
icon: None,
|
||||
status: w.status.clone(),
|
||||
task: None,
|
||||
role: w.role.map(str::to_string),
|
||||
body,
|
||||
sort_key: w.name.clone(),
|
||||
todo: None,
|
||||
todo_items: Vec::new(),
|
||||
model: None,
|
||||
provider: None,
|
||||
auth_method: None,
|
||||
effort: None,
|
||||
elapsed_secs: Some(w.age_secs()),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn make_workers(n: usize) -> Vec<MockWorker> {
|
||||
let pool: Vec<(&str, Option<&'static str>, Vec<&str>)> = vec![
|
||||
(
|
||||
"researcher",
|
||||
Some("coordinator"),
|
||||
vec![
|
||||
"Searching the codebase for the auth flow...",
|
||||
"Found 12 candidate files.",
|
||||
"Reading crates/jcode-app-core/src/auth.rs",
|
||||
"The OAuth callback is handled in handle_login()",
|
||||
"Now cross-referencing the token refresh path.",
|
||||
"Refresh happens in refresh_session() (line 412).",
|
||||
"Summarizing findings for the implementer.",
|
||||
],
|
||||
),
|
||||
(
|
||||
"implementer",
|
||||
None,
|
||||
vec![
|
||||
"Editing crates/jcode-base/src/config.rs",
|
||||
"Added swarm_spawn_mode = inline",
|
||||
"Running cargo check...",
|
||||
"warning: unused import `Foo`",
|
||||
"Fixing the import.",
|
||||
"cargo check clean.",
|
||||
"Wiring the gallery into the draw path.",
|
||||
],
|
||||
),
|
||||
(
|
||||
"reviewer",
|
||||
None,
|
||||
vec![
|
||||
"Waiting for the implementer to finish.",
|
||||
"Reviewing the diff...",
|
||||
"Reviewed 4 files.",
|
||||
"! Blocked: needs a test for the new branch.",
|
||||
"Test added, re-reviewing.",
|
||||
"No blocking issues found.",
|
||||
"LGTM",
|
||||
],
|
||||
),
|
||||
(
|
||||
"tester",
|
||||
None,
|
||||
vec![
|
||||
"Building the selfdev profile...",
|
||||
"Compiling jcode-tui-render",
|
||||
"Running 5 tests",
|
||||
"test cells_are_width_bounded ... ok",
|
||||
"test many_agents_form_multiple_columns ... ok",
|
||||
"All tests passed.",
|
||||
],
|
||||
),
|
||||
(
|
||||
"doc-writer",
|
||||
None,
|
||||
vec![
|
||||
"Drafting the config docs.",
|
||||
"Documenting swarm_gallery_max_pct.",
|
||||
"Adding an example to default_file.rs.",
|
||||
"Proofreading.",
|
||||
"Docs ready.",
|
||||
],
|
||||
),
|
||||
(
|
||||
"packager",
|
||||
None,
|
||||
vec![
|
||||
"Preparing the release worktree.",
|
||||
"Bumping version.",
|
||||
"Staging changes.",
|
||||
"Commit drafted.",
|
||||
],
|
||||
),
|
||||
(
|
||||
"benchmarker",
|
||||
None,
|
||||
vec![
|
||||
"Warming up the bench harness.",
|
||||
"Running memory_recall_bench...",
|
||||
"p50 latency 18ms",
|
||||
"p99 latency 64ms",
|
||||
"Results recorded.",
|
||||
],
|
||||
),
|
||||
(
|
||||
"linter",
|
||||
None,
|
||||
vec![
|
||||
"Running clippy...",
|
||||
"warning: needless clone (3)",
|
||||
"Auto-fixing.",
|
||||
"clippy clean.",
|
||||
],
|
||||
),
|
||||
];
|
||||
pool.into_iter()
|
||||
.take(n.max(1))
|
||||
.map(|(name, role, script)| MockWorker::new(name, role, script))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn render_gallery_lines(
|
||||
workers: &[MockWorker],
|
||||
width: usize,
|
||||
max_height: usize,
|
||||
) -> Vec<Line<'static>> {
|
||||
// Delegate to the exact same shared renderer the live TUI uses; only the
|
||||
// member data (built from mock workers) differs.
|
||||
render_gallery(&workers_to_members(workers), width, max_height)
|
||||
}
|
||||
|
||||
fn draw(f: &mut Frame, workers: &[MockWorker], max_pct: usize, paused: bool) {
|
||||
let area = f.area();
|
||||
|
||||
// Reserve a top band like the real TUI does: a configurable share of the
|
||||
// chat column height, capped, with a >=5 row floor before it shows.
|
||||
let budget = ((area.height as usize * max_pct) / 100).clamp(0, 18);
|
||||
let lines = if budget >= 5 && area.width >= 24 {
|
||||
render_gallery_lines(workers, area.width as usize, budget)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let band_h = if lines.is_empty() {
|
||||
0u16
|
||||
} else {
|
||||
((lines.len() as u16) + 1).min(area.height / 2)
|
||||
};
|
||||
|
||||
let band = Rect {
|
||||
x: area.x,
|
||||
y: area.y,
|
||||
width: area.width,
|
||||
height: band_h,
|
||||
};
|
||||
let chat = Rect {
|
||||
x: area.x,
|
||||
y: area.y + band_h,
|
||||
width: area.width,
|
||||
height: area.height.saturating_sub(band_h),
|
||||
};
|
||||
|
||||
if band_h > 0 {
|
||||
f.render_widget(Clear, band);
|
||||
f.render_widget(Paragraph::new(lines), band);
|
||||
}
|
||||
|
||||
// Mock chat / instructions below the band.
|
||||
let help = vec![
|
||||
Line::from(vec![Span::styled(
|
||||
" inline swarm gallery — live demo (no real agents touched)",
|
||||
Style::default()
|
||||
.fg(Color::Rgb(200, 200, 210))
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)]),
|
||||
Line::from(""),
|
||||
Line::from(vec![Span::styled(
|
||||
" The band above is the same renderer the jcode TUI shows above your chat",
|
||||
Style::default().fg(Color::Rgb(150, 150, 160)),
|
||||
)]),
|
||||
Line::from(vec![Span::styled(
|
||||
" when you run a swarm with swarm_spawn_mode = \"inline\".",
|
||||
Style::default().fg(Color::Rgb(150, 150, 160)),
|
||||
)]),
|
||||
Line::from(""),
|
||||
Line::from(vec![
|
||||
Span::styled(
|
||||
" band size: ",
|
||||
Style::default().fg(Color::Rgb(150, 150, 160)),
|
||||
),
|
||||
Span::styled(
|
||||
format!("{max_pct}% "),
|
||||
Style::default().fg(Color::Rgb(255, 200, 100)),
|
||||
),
|
||||
Span::styled(
|
||||
"(agents.swarm_gallery_max_pct)",
|
||||
Style::default().fg(Color::Rgb(110, 110, 120)),
|
||||
),
|
||||
Span::styled(
|
||||
if paused { " [PAUSED]" } else { "" },
|
||||
Style::default().fg(Color::Rgb(255, 170, 80)),
|
||||
),
|
||||
]),
|
||||
Line::from(""),
|
||||
Line::from(vec![Span::styled(
|
||||
" keys: q/Esc quit +/- agents [ / ] band size space pause",
|
||||
Style::default().fg(Color::Rgb(120, 120, 130)),
|
||||
)]),
|
||||
];
|
||||
let block = Block::default()
|
||||
.borders(Borders::TOP)
|
||||
.border_style(Style::default().fg(Color::Rgb(70, 70, 80)))
|
||||
.title(Span::styled(
|
||||
" chat ",
|
||||
Style::default().fg(Color::Rgb(120, 120, 130)),
|
||||
));
|
||||
f.render_widget(
|
||||
Paragraph::new(help).block(block).wrap(Wrap { trim: false }),
|
||||
chat,
|
||||
);
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let mut stdout = io::stdout();
|
||||
enable_raw_mode()?;
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
let res = run(&mut terminal);
|
||||
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut() as &mut CrosstermBackend<Stdout>,
|
||||
LeaveAlternateScreen
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
res
|
||||
}
|
||||
|
||||
fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> io::Result<()> {
|
||||
let mut n_agents = 4usize;
|
||||
let mut workers = make_workers(n_agents);
|
||||
let mut max_pct = 40usize;
|
||||
let mut paused = false;
|
||||
let tick = Duration::from_millis(350);
|
||||
let mut last_tick = Instant::now();
|
||||
|
||||
loop {
|
||||
terminal.draw(|f| draw(f, &workers, max_pct, paused))?;
|
||||
|
||||
let timeout = tick.saturating_sub(last_tick.elapsed());
|
||||
if event::poll(timeout)?
|
||||
&& let Event::Key(key) = event::read()?
|
||||
{
|
||||
let ctrl_c =
|
||||
key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c');
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => break,
|
||||
_ if ctrl_c => break,
|
||||
KeyCode::Char('+') | KeyCode::Char('=') => {
|
||||
n_agents = (n_agents + 1).min(8);
|
||||
workers = make_workers(n_agents);
|
||||
}
|
||||
KeyCode::Char('-') | KeyCode::Char('_') => {
|
||||
n_agents = n_agents.saturating_sub(1).max(1);
|
||||
workers = make_workers(n_agents);
|
||||
}
|
||||
KeyCode::Char(']') => max_pct = (max_pct + 5).min(90),
|
||||
KeyCode::Char('[') => max_pct = max_pct.saturating_sub(5).max(5),
|
||||
KeyCode::Char(' ') => paused = !paused,
|
||||
KeyCode::Char('r') => {
|
||||
workers = make_workers(n_agents);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if last_tick.elapsed() >= tick {
|
||||
if !paused {
|
||||
for w in workers.iter_mut() {
|
||||
w.tick();
|
||||
}
|
||||
// When everything is done, loop the demo after a short beat.
|
||||
if workers.iter().all(|w| w.status == "completed")
|
||||
&& workers.iter().all(|w| w.age_secs() > 1)
|
||||
{
|
||||
// restart so the demo keeps streaming
|
||||
workers = make_workers(n_agents);
|
||||
}
|
||||
}
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
//! Visual preview of the swarm gallery layout against mock streams.
|
||||
//!
|
||||
//! Run with: `cargo run --profile selfdev -p jcode-tui-render --example swarm_gallery_preview`
|
||||
|
||||
use jcode_tui_render::swarm_gallery::{
|
||||
GalleryMember, SwarmStripHint, render_swarm_compact, render_swarm_dock, render_swarm_panel,
|
||||
render_swarm_strip, render_swarm_strip_vertical,
|
||||
};
|
||||
use jcode_tui_render::swarm_tiles::{SwarmGalleryConfig, SwarmTile, render_swarm_gallery};
|
||||
use ratatui::prelude::*;
|
||||
|
||||
fn accent(status: &str) -> Color {
|
||||
match status {
|
||||
"running" => Color::Rgb(255, 200, 100),
|
||||
"thinking" => Color::Rgb(140, 180, 255),
|
||||
"done" => Color::Rgb(100, 200, 100),
|
||||
"blocked" => Color::Rgb(255, 170, 80),
|
||||
"failed" => Color::Rgb(255, 100, 100),
|
||||
_ => Color::Rgb(140, 140, 150),
|
||||
}
|
||||
}
|
||||
|
||||
fn mk(name: &str, role: Option<&str>, status: &str, body: &[&str]) -> SwarmTile {
|
||||
let mut t = SwarmTile::new(name, status, accent(status))
|
||||
.with_body(body.iter().map(|s| s.to_string()).collect());
|
||||
if let Some(r) = role {
|
||||
t = t.with_role_glyph(r);
|
||||
}
|
||||
t
|
||||
}
|
||||
|
||||
fn print_lines(label: &str, lines: &[Line<'static>]) {
|
||||
println!("\n=== {label} ===");
|
||||
for line in lines {
|
||||
let text: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
|
||||
println!("{text}");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let header = Line::from(Span::styled(
|
||||
"🐝 swarm · 3 agents running",
|
||||
Style::default().fg(Color::Rgb(255, 200, 100)),
|
||||
));
|
||||
|
||||
let three = vec![
|
||||
mk(
|
||||
"researcher",
|
||||
Some("★"),
|
||||
"thinking",
|
||||
&[
|
||||
"Searching the codebase for the auth flow...",
|
||||
"Found 12 candidate files.",
|
||||
"Reading crates/jcode-app-core/src/auth.rs",
|
||||
"The OAuth callback is handled in handle_login()",
|
||||
"Now cross-referencing the token refresh path.",
|
||||
],
|
||||
),
|
||||
mk(
|
||||
"implementer",
|
||||
None,
|
||||
"running",
|
||||
&[
|
||||
"Editing crates/jcode-base/src/config.rs",
|
||||
"Added swarm_spawn_mode = inline",
|
||||
"Running cargo check...",
|
||||
"warning: unused import `Foo`",
|
||||
"Fixing the import.",
|
||||
],
|
||||
),
|
||||
mk(
|
||||
"reviewer",
|
||||
None,
|
||||
"done",
|
||||
&["Reviewed 4 files.", "No blocking issues found.", "LGTM ✓"],
|
||||
),
|
||||
];
|
||||
|
||||
let cfg = SwarmGalleryConfig::default();
|
||||
print_lines(
|
||||
"3 agents @ width 100",
|
||||
&render_swarm_gallery(&three, 100, &cfg, Some(header.clone())),
|
||||
);
|
||||
print_lines(
|
||||
"3 agents @ width 60",
|
||||
&render_swarm_gallery(&three, 60, &cfg, Some(header.clone())),
|
||||
);
|
||||
|
||||
let six: Vec<SwarmTile> = (0..6)
|
||||
.map(|i| {
|
||||
let status = ["running", "thinking", "done", "blocked"][i % 4];
|
||||
mk(
|
||||
&format!("agent-{i}"),
|
||||
None,
|
||||
status,
|
||||
&[
|
||||
&format!("step {i}.1 doing work"),
|
||||
&format!("step {i}.2 still going"),
|
||||
&format!("step {i}.3 almost there"),
|
||||
],
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
print_lines(
|
||||
"6 agents @ width 120",
|
||||
&render_swarm_gallery(&six, 120, &cfg, Some(header.clone())),
|
||||
);
|
||||
|
||||
let many: Vec<SwarmTile> = (0..12)
|
||||
.map(|i| {
|
||||
mk(
|
||||
&format!("worker-{i:02}"),
|
||||
None,
|
||||
"running",
|
||||
&["...", "working"],
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let tight = SwarmGalleryConfig {
|
||||
max_height: 12,
|
||||
..Default::default()
|
||||
};
|
||||
print_lines(
|
||||
"12 agents @ width 120, height 12",
|
||||
&render_swarm_gallery(&many, 120, &tight, Some(header)),
|
||||
);
|
||||
|
||||
// ---- New list+detail panel ----
|
||||
let gm = |name: &str, role: Option<&str>, status: &str, body: &[&str]| GalleryMember {
|
||||
label: name.to_string(),
|
||||
icon: None,
|
||||
status: status.to_string(),
|
||||
task: None,
|
||||
role: role.map(str::to_string),
|
||||
body: body.iter().map(|s| s.to_string()).collect(),
|
||||
sort_key: name.to_string(),
|
||||
todo: None,
|
||||
todo_items: Vec::new(),
|
||||
model: None,
|
||||
provider: None,
|
||||
auth_method: None,
|
||||
effort: None,
|
||||
elapsed_secs: None,
|
||||
};
|
||||
let panel_members = vec![
|
||||
gm(
|
||||
"researcher",
|
||||
Some("coordinator"),
|
||||
"thinking",
|
||||
&["Cross-referencing the token refresh path.", "· 2s ago"],
|
||||
),
|
||||
gm(
|
||||
"implementer",
|
||||
None,
|
||||
"running",
|
||||
&[
|
||||
"Running cargo check...",
|
||||
"warning: unused import `Foo`",
|
||||
"· 5s ago",
|
||||
],
|
||||
),
|
||||
gm("reviewer", None, "done", &["LGTM ✓", "· 1m ago"]),
|
||||
gm(
|
||||
"doc-writer",
|
||||
None,
|
||||
"blocked",
|
||||
&["waiting on reviewer", "· 12s ago"],
|
||||
),
|
||||
];
|
||||
print_lines(
|
||||
"PANEL: 4 agents, selected #1 (implementer), focused @ width 70 h 14",
|
||||
&render_swarm_panel(&panel_members, 1, true, 70, 14),
|
||||
);
|
||||
print_lines(
|
||||
"PANEL: 4 agents, selected #0, unfocused @ width 70 h 14",
|
||||
&render_swarm_panel(&panel_members, 0, false, 70, 14),
|
||||
);
|
||||
print_lines(
|
||||
"PANEL: narrow @ width 44 h 12",
|
||||
&render_swarm_panel(&panel_members, 2, true, 44, 12),
|
||||
);
|
||||
|
||||
// ---- New compact strip (above status line) ----
|
||||
let hints = vec![
|
||||
SwarmStripHint {
|
||||
key: "alt+↑/↓".into(),
|
||||
label: "select".into(),
|
||||
},
|
||||
SwarmStripHint {
|
||||
key: "alt+o".into(),
|
||||
label: "open".into(),
|
||||
},
|
||||
SwarmStripHint {
|
||||
key: "alt+shift+p".into(),
|
||||
label: "prompt".into(),
|
||||
},
|
||||
SwarmStripHint {
|
||||
key: "esc".into(),
|
||||
label: "exit".into(),
|
||||
},
|
||||
];
|
||||
print_lines(
|
||||
"STRIP: unfocused @ width 90",
|
||||
&render_swarm_strip(
|
||||
&panel_members,
|
||||
1,
|
||||
false,
|
||||
&hints,
|
||||
Some("alt+n controls"),
|
||||
0,
|
||||
90,
|
||||
12,
|
||||
),
|
||||
);
|
||||
print_lines(
|
||||
"STRIP: focused, selected #1 @ width 90",
|
||||
&render_swarm_strip(&panel_members, 1, true, &hints, None, 3, 90, 12),
|
||||
);
|
||||
print_lines(
|
||||
"STRIP: focused narrow @ width 54",
|
||||
&render_swarm_strip(&panel_members, 0, true, &hints, None, 5, 54, 12),
|
||||
);
|
||||
|
||||
// ---- Vertical strip (default layout: one agent per row) ----
|
||||
let mut vert_members = panel_members.clone();
|
||||
for (m, icon) in vert_members.iter_mut().zip(["🦊", "🐝", "🐅", "🦉"]) {
|
||||
m.icon = Some(icon.to_string());
|
||||
m.task = Some(match m.label.as_str() {
|
||||
"researcher" => "wire the auth flow".to_string(),
|
||||
"implementer" => "audit the webhook path".to_string(),
|
||||
"reviewer" => "support/contact page".to_string(),
|
||||
_ => "misc background task".to_string(),
|
||||
});
|
||||
}
|
||||
vert_members[1].todo = Some((3, 9));
|
||||
print_lines(
|
||||
"VERTICAL: unfocused @ width 90",
|
||||
&render_swarm_strip_vertical(
|
||||
&vert_members,
|
||||
1,
|
||||
false,
|
||||
&hints,
|
||||
Some("alt+n controls"),
|
||||
0,
|
||||
90,
|
||||
4,
|
||||
12,
|
||||
),
|
||||
);
|
||||
print_lines(
|
||||
"VERTICAL: focused (accordion), selected #1 @ width 90",
|
||||
&render_swarm_strip_vertical(&vert_members, 1, true, &hints, None, 3, 90, 4, 12),
|
||||
);
|
||||
print_lines(
|
||||
"VERTICAL: 7 agents overflow @ width 80",
|
||||
&render_swarm_strip_vertical(
|
||||
&{
|
||||
let mut many = Vec::new();
|
||||
for i in 0..7 {
|
||||
let mut m = gm(&format!("agent-{i}"), None, "running", &[]);
|
||||
m.icon = Some("🐜".to_string());
|
||||
many.push(m);
|
||||
}
|
||||
many
|
||||
},
|
||||
0,
|
||||
false,
|
||||
&hints,
|
||||
Some("alt+n controls"),
|
||||
0,
|
||||
80,
|
||||
4,
|
||||
12,
|
||||
),
|
||||
);
|
||||
|
||||
// ---- Dock (vertical agent list for the info-widget margins) ----
|
||||
print_lines(
|
||||
"DOCK: 4 agents, selected #0, unfocused @ width 34 h 12",
|
||||
&render_swarm_dock(&panel_members, 0, false, Some((3, 7)), 0, 34, 12),
|
||||
);
|
||||
print_lines(
|
||||
"DOCK: 4 agents, selected #1, focused @ width 34 h 14",
|
||||
&render_swarm_dock(&panel_members, 1, true, Some((3, 7)), 2, 34, 14),
|
||||
);
|
||||
print_lines(
|
||||
"DOCK: narrow @ width 24 h 8",
|
||||
&render_swarm_dock(&panel_members, 2, false, None, 0, 24, 8),
|
||||
);
|
||||
|
||||
// ---- Compact summary (two lines: tally + plan bar) ----
|
||||
print_lines(
|
||||
"COMPACT: 4 agents, nodes 5/12 (3 running) @ width 34",
|
||||
&render_swarm_compact(&panel_members, Some((5, 3, 12)), 34, 2),
|
||||
);
|
||||
print_lines(
|
||||
"COMPACT: no plan @ width 34",
|
||||
&render_swarm_compact(&panel_members, None, 34, 2),
|
||||
);
|
||||
print_lines(
|
||||
"COMPACT: narrow @ width 20",
|
||||
&render_swarm_compact(&panel_members, Some((5, 3, 12)), 20, 2),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user