#[path = "web_render/cdp.rs"]
mod cdp;
#[allow(unused_macros, unused_imports)]
mod common;
use std::collections::BTreeSet;
use std::net::Ipv4Addr;
use std::time::Duration;
use magi::queue::{Queue, Source, Task, TaskStatus};
use magi::run::{RunState, RunStatus};
use magi::talk::{self, Talks};
use serde_json::Value;
struct Case {
name: &'static str,
route: &'static str,
pane: &'static str,
rows: &'static str,
heading: &'static str,
link: fn(&str) -> String,
expect: usize,
}
const CASES: [Case; 3] = [
Case {
name: "queue",
route: "#/queue",
pane: "#view-queue",
rows: "#queue-sections li.card:not(.skeleton)",
heading: "queue-h",
link: |id| format!("a[href=\"#/queue/{id}\"]"),
expect: 5,
},
Case {
name: "runs",
route: "#/runs",
pane: ".runs-main",
rows: "#view-runs a.card.run-card",
heading: "runs-h",
link: |id| format!("a[href=\"#/runs/{id}\"]"),
expect: 1,
},
Case {
name: "chat",
route: "#/chat",
pane: "#view-talks",
rows: "#talks-list a.card",
heading: "talks-h",
link: |id| format!("a[href=\"#/chat/{id}\"]"),
expect: 3,
},
];
const VIEWPORTS: [(u32, u32, bool); 3] =
[(1280, 900, false), (1950, 1000, false), (390, 844, true)];
const KNOWN_FAILURES: &[(&str, &str, &str)] = &[];
const MEASURE: &str = r##"(() => {
const ROWS = __ROWS__, PANE = __PANE__, HEAD = __HEAD__;
const fails = [];
const shown = (e) => {
const r = e.getBoundingClientRect(), cs = getComputedStyle(e);
return r.width > 0 && r.height > 0 && cs.display !== "none" && cs.visibility !== "hidden";
};
const hit = (a, b) =>
a.left < b.right - 1 && b.left < a.right - 1 && a.top < b.bottom - 1 && b.top < a.bottom - 1;
const pane = document.querySelector(PANE);
if (!pane) fails.push(["pane-missing", PANE]);
const pr = pane ? pane.getBoundingClientRect() : null;
const pr0 = pr;
if (pane && pane.scrollWidth > pane.clientWidth + 1)
fails.push(["pane-overflow", `${[...pane.querySelectorAll("*")].filter((e) => e.getBoundingClientRect().right > pr0.right + 1).slice(0, 3).map((e) => e.tagName + "." + e.className + "#" + e.id).join(" | ")} ${PANE} scrollWidth ${pane.scrollWidth} > clientWidth ${pane.clientWidth}`]);
let rows = 0;
for (const row of document.querySelectorAll(ROWS)) {
if (!shown(row)) continue; // a collapsed section's rows have no layout
rows++;
const rr = row.getBoundingClientRect();
const link = row.querySelector("a[href]");
const label = (row.getAttribute("href") || (link && link.getAttribute("href")) || "row").toString();
if (pr && (rr.left < pr.left - 1 || rr.right > pr.right + 1))
fails.push(["row-overflow", `${label}: ${rr.left}..${rr.right} outside ${pr.left}..${pr.right}`]);
const title = row.querySelector(".card-title");
if (!title || !shown(title) || !(title.innerText || "").trim()) {
fails.push(["title-visible", `${label}: no visible title text`]);
continue;
}
const tr = title.getBoundingClientRect();
// Line 1 of a row is the title on the full row width, never a sliver.
if (tr.width < rr.width * 0.5)
fails.push(["title-narrow", `${label}: title ${Math.round(tr.width)}px of a ${Math.round(rr.width)}px row`]);
for (const other of row.querySelectorAll(".card-top > *, .card-meta > *")) {
if (other.contains(title) || title.contains(other) || !shown(other)) continue;
if (hit(tr, other.getBoundingClientRect()))
fails.push(["title-overlap", `${label}: title overlaps ${other.className || other.tagName}`]);
}
}
window.scrollTo(0, 0);
const h = document.getElementById(HEAD);
if (!h || !shown(h)) fails.push(["heading-covered", `${HEAD} is not visible`]);
else {
const r = h.getBoundingClientRect();
const top = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2);
if (!top || !h.contains(top))
fails.push(["heading-covered", `${HEAD} is covered by ${top ? (top.id || top.className || top.tagName) : "nothing"}`]);
}
return { rows, failures: fails };
})()"##;
fn console_problems(events: &[Value]) -> Vec<String> {
let mut out = Vec::new();
for e in events {
let p = &e["params"];
match e["method"].as_str().unwrap_or("") {
"Runtime.exceptionThrown" => out.push(format!(
"exception: {}",
p["exceptionDetails"]["exception"]["description"]
.as_str()
.or(p["exceptionDetails"]["text"].as_str())
.unwrap_or("?")
)),
"Runtime.consoleAPICalled" if p["type"] == "error" => {
out.push(format!("console.error: {}", p["args"]));
}
"Log.entryAdded" if p["entry"]["level"] == "error" => {
let entry = &p["entry"];
let text = entry["text"].as_str().unwrap_or("");
let url = entry["url"].as_str().unwrap_or("");
if url.contains("favicon") || text.contains("favicon") {
continue;
}
out.push(format!(
"log[{}]: {text} {url}",
entry["source"].as_str().unwrap_or("?")
));
}
_ => {}
}
}
out
}
fn known(page: &str, check: &str) -> Option<&'static str> {
KNOWN_FAILURES
.iter()
.find(|(p, c, _)| (*p == page || *p == "*") && *c == check)
.map(|(_, _, why)| *why)
}
async fn serve(
home: &std::path::Path,
queue: Queue,
talks: Talks,
runs_root: std::path::PathBuf,
repo: &std::path::Path,
) -> String {
let worktrees = home.join("wt").join("magi");
std::fs::create_dir_all(&worktrees).unwrap();
let ui = magi::web::Ui::new(
queue,
magi::ask::Questions::at(home.join("questions")),
talks,
runs_root,
home.to_path_buf(),
repo.to_path_buf(),
)
.with_worktrees_root(worktrees);
let listener = tokio::net::TcpListener::bind((Ipv4Addr::LOCALHOST, 0))
.await
.expect("bind loopback");
let base = format!("http://{}/", listener.local_addr().unwrap());
tokio::spawn(async move {
let _ = axum::serve(listener, ui.router()).await;
});
base
}
fn seed_tasks(queue: &Queue, repo: &std::path::Path, run_id: &str) -> Vec<String> {
let mut ids = Vec::new();
let long =
"A task whose title is long enough to wrap onto several lines on a narrow phone screen";
for (title, status) in [
("Running task", TaskStatus::Running),
("Queued task", TaskStatus::Queued),
("Held task", TaskStatus::Held),
("Failed task", TaskStatus::Failed),
(long, TaskStatus::Queued),
("Blocked task", TaskStatus::Blocked),
("Done task", TaskStatus::Done),
("Solo task from a chat", TaskStatus::Queued),
("Task that was tried twice", TaskStatus::Running),
("", TaskStatus::Queued),
] {
let source = if title.starts_with("Solo") {
Source::Agent {
run: "7dd7aaaa".to_owned(),
node: magi::queue::CHAT_NODE.to_owned(),
}
} else {
Source::Human
};
let mut t = Task::new(
title.to_owned(),
format!("Instruction for: {title}"),
repo.to_path_buf(),
source,
);
t.status = status;
t.solo = title.starts_with("Solo");
if title.contains("twice") {
t.attempts = 2;
}
match status {
TaskStatus::Running => t.runs = vec![run_id.to_owned()],
TaskStatus::Held => t.hold_reason = Some("held by hand".to_owned()),
TaskStatus::Failed => t.last_error = Some("the gate failed".to_owned()),
_ => {}
}
queue.put(&mut t).expect("seed task");
ids.push(t.id.clone());
}
ids
}
#[tokio::test]
async fn pages_render_with_visible_titles_and_no_console_errors() {
let Some(chrome) = cdp::find_chrome() else {
assert!(
std::env::var_os("CI").is_none(),
"CI is set but no Chrome/Chromium was found (set MAGI_CHROME)"
);
eprintln!("SKIP web_render: no Chrome/Chromium found (set MAGI_CHROME to run it)");
return;
};
let guard = common::home_lock().await;
let fx = common::fixture(guard, common::Judges::Unanimous, false);
let home = fx.tmp.path().join("magi-home");
let queue = Queue::at(home.join("queue"));
let talks = Talks::at(home.join("talks"));
let runs_root = home.join("runs");
let mut run_ids = Vec::new();
for (id, status, text) in [
(
"20260901-000001-aa01",
RunStatus::Reviewing,
"Add a retry budget to the fetch loop",
),
(
"20260901-000002-aa02",
RunStatus::Blocked,
"Rework the settings screen so that every field carries a long and \
rather descriptive instruction title that has to wrap on a phone",
),
] {
let mut run = RunState::new(
fx.repo.clone(),
"main".to_owned(),
"0000000".to_owned(),
text.to_owned(),
fx.config.clone(),
);
run.id = id.to_owned();
run.status = status;
run.save_under(&home).expect("seed run");
run_ids.push(id.to_owned());
}
let task_ids = seed_tasks(&queue, &fx.repo, &run_ids[0]);
let agent = fx.config.agents[0].id.clone();
let mut talk_ids = Vec::new();
for text in [
"How does the queue pick the next task?",
"A much longer opening question that keeps going so the conversation \
title has to be clipped or wrapped rather than pushing the row wide",
"Short one",
] {
let mut t =
talk::begin(&talks, &fx.config, fx.repo.clone(), Some(&agent)).expect("seed talk");
talk::record(&mut t, &talks, text, Vec::new()).expect("seed turn");
talk_ids.push(t.id.clone());
}
let base = serve(&home, queue, talks, runs_root, &fx.repo).await;
let mut browser = cdp::Browser::launch(&chrome)
.await
.unwrap_or_else(|e| panic!("could not start Chrome at {}: {e}", chrome.display()));
let mut failures: Vec<String> = Vec::new();
let mut hits: BTreeSet<(String, String)> = BTreeSet::new();
for case in &CASES {
let ids = match case.name {
"queue" => &task_ids,
"runs" => &run_ids,
_ => &talk_ids,
};
for &(w, h, mobile) in &VIEWPORTS {
let tag = format!("{} @{w}px", case.name);
let url = format!("{base}{}", case.route);
let page = browser
.open_page(&url, w, h, mobile)
.await
.unwrap_or_else(|e| panic!("{tag}: open: {e}"));
let all = ids
.iter()
.take(case.expect)
.map(|id| format!("!!document.querySelector('{}')", (case.link)(id)))
.collect::<Vec<_>>()
.join(" && ");
if let Err(e) = browser.wait_for(&page, &all, Duration::from_secs(30)).await {
let seen = browser
.eval(
&page,
"document.querySelector('main').innerText.slice(0, 600)",
)
.await
.unwrap_or_default();
let console = console_problems(&browser.take_events(&page.session));
panic!("{tag}: {e}\npage text: {seen}\nconsole: {console:?}");
}
browser
.eval(
&page,
"document.fonts.ready.then(() => new Promise(r => \
requestAnimationFrame(() => requestAnimationFrame(() => r(true)))))",
)
.await
.unwrap_or_else(|e| panic!("{tag}: settle: {e}"));
browser
.eval(
&page,
"document.querySelectorAll('details.list-section').forEach((d) => { d.open = true; })",
)
.await
.unwrap_or_else(|e| panic!("{tag}: expand: {e}"));
browser.settle(Duration::from_millis(300)).await.unwrap();
let script = MEASURE
.replace("__ROWS__", &Value::from(case.rows).to_string())
.replace("__PANE__", &Value::from(case.pane).to_string())
.replace("__HEAD__", &Value::from(case.heading).to_string());
let out = browser
.eval(&page, &script)
.await
.unwrap_or_else(|e| panic!("{tag}: measure: {e}"));
let rows = out["rows"].as_u64().unwrap_or(0);
if (rows as usize) < case.expect {
failures.push(format!(
"{tag}: {rows} visible row(s) rendered, expected {}",
case.expect
));
}
for f in out["failures"].as_array().into_iter().flatten() {
let check = f[0].as_str().unwrap_or("?");
let detail = f[1].as_str().unwrap_or("");
match known(case.name, check) {
Some(why) => {
eprintln!("KNOWN FAILURE [{tag}] {check} ({why}): {detail}");
hits.insert((case.name.to_owned(), check.to_owned()));
}
None => failures.push(format!("{tag}: {check}: {detail}")),
}
}
browser.settle(Duration::from_millis(200)).await.unwrap();
for p in console_problems(&browser.take_events(&page.session)) {
failures.push(format!("{tag}: {p}"));
}
browser.close_page(&page).await;
}
}
for (p, c, why) in KNOWN_FAILURES {
if !hits.contains(&((*p).to_owned(), (*c).to_owned())) {
eprintln!(
"NOTE: known failure ({p}, {c}) [{why}] did not fire; \
remove it from KNOWN_FAILURES in tests/web_render.rs"
);
}
}
assert!(
failures.is_empty(),
"web UI rendering regressions:\n {}",
failures.join("\n ")
);
}
#[tokio::test]
async fn runs_search_counts_extra_rows_as_shown_and_never_parses_hit_text() {
let Some(chrome) = cdp::find_chrome() else {
assert!(
std::env::var_os("CI").is_none(),
"CI is set but no Chrome/Chromium was found (set MAGI_CHROME)"
);
eprintln!("SKIP web_render: no Chrome/Chromium found (set MAGI_CHROME to run it)");
return;
};
let guard = common::home_lock().await;
let fx = common::fixture(guard, common::Judges::Unanimous, false);
let home = fx.tmp.path().join("magi-home");
let queue = Queue::at(home.join("queue"));
let talks = Talks::at(home.join("talks"));
const WINDOW: usize = 50;
let markup = "<img src=x id=pwn>";
for n in 1..=WINDOW + 1 {
let text = match n {
1 => format!("zebrafruit outside the window {markup}"),
n if n == WINDOW + 1 => "zebrafruit inside the window".to_owned(),
n => format!("ordinary run {n}"),
};
let mut run = RunState::new(
fx.repo.clone(),
"main".to_owned(),
"0000000".to_owned(),
text,
fx.config.clone(),
);
run.id = format!("20260901-{n:06}-bb{n:02}");
run.status = RunStatus::Reviewing;
run.save_under(&home).expect("seed run");
}
let base = serve(&home, queue, talks, home.join("runs"), &fx.repo).await;
let mut browser = cdp::Browser::launch(&chrome)
.await
.unwrap_or_else(|e| panic!("could not start Chrome at {}: {e}", chrome.display()));
let page = browser
.open_page(&format!("{base}#/runs"), 1280, 900, false)
.await
.expect("open");
let w = Duration::from_secs(30);
browser
.wait_for(
&page,
"(() => { const b = document.getElementById('runs-search'); \
return !!b && !b.hidden; })()",
w,
)
.await
.expect("search box");
browser
.eval(
&page,
"(() => { const i = document.getElementById('runs-search-input'); \
i.value = 'zebrafruit'; i.dispatchEvent(new Event('input', { bubbles: true })); \
return true; })()",
)
.await
.expect("type");
browser
.wait_for(
&page,
"(document.getElementById('runs-search-status').textContent || '').includes('match')",
w,
)
.await
.expect("search answered");
let out = browser
.eval(
&page,
"({ status: document.getElementById('runs-search-status').textContent, \
extra: document.querySelectorAll('#runs-search-extra li').length, \
pwn: document.querySelectorAll('#pwn').length, \
snippet: [...document.querySelectorAll('.card-snippet')].map((e) => e.textContent).join('\\n') })",
)
.await
.expect("read");
let status = out["status"].as_str().unwrap_or_default();
assert!(status.starts_with("2 runs match"), "status: {status}");
assert!(
!status.contains("hidden"),
"extra rows counted as hidden: {status}"
);
assert_eq!(
out["extra"], 1,
"the out-of-window hit is one extra row: {out}"
);
assert_eq!(out["pwn"], 0, "hit text was parsed as markup: {out}");
assert!(
out["snippet"].as_str().unwrap_or_default().contains(markup),
"snippet should show the markup literally: {out}"
);
browser.close_page(&page).await;
}
#[tokio::test]
async fn run_detail_tabs_landing_and_strip_hold_at_both_widths_and_themes() {
let Some(chrome) = cdp::find_chrome() else {
assert!(
std::env::var_os("CI").is_none(),
"CI is set but no Chrome/Chromium was found (set MAGI_CHROME)"
);
eprintln!("SKIP web_render: no Chrome/Chromium found (set MAGI_CHROME to run it)");
return;
};
let guard = common::home_lock().await;
let fx = common::fixture(guard, common::Judges::Unanimous, false);
let home = fx.tmp.path().join("magi-home");
let queue = Queue::at(home.join("queue"));
let talks = Talks::at(home.join("talks"));
let id = "20260901-000009-cc09";
let mut run = RunState::new(
fx.repo.clone(),
"main".to_owned(),
"0000000".to_owned(),
"Merged run with no fix round and no follow-up".to_owned(),
fx.config.clone(),
);
run.id = id.to_owned();
run.status = RunStatus::Merged;
run.pr = Some(magi::run::PrRecord {
url: "https://github.com/example/repo/pull/7".to_owned(),
number: 7,
state: "merged".to_owned(),
checks: "green".to_owned(),
round: 0,
rounds: 3,
red_at_merge: Vec::new(),
});
run.event("implement", "candidate A started");
run.event("implement", "candidate B started");
run.event("judge", "ranking in");
run.advice = Some(magi::advise::Advice {
records: (1..=3)
.map(|n| {
magi::advise::AdvisorRecord::failed(n, format!("agent-{n}"), "no proposal".into())
})
.collect(),
synthesis: Some("A blended brief.".to_owned()),
});
run.save_under(&home).expect("seed run");
let base = serve(&home, queue, talks, home.join("runs"), &fx.repo).await;
let mut browser = cdp::Browser::launch(&chrome)
.await
.unwrap_or_else(|e| panic!("could not start Chrome at {}: {e}", chrome.display()));
let w = Duration::from_secs(30);
for (width, height, mobile) in [(1280u32, 900u32, false), (390, 844, true)] {
for theme in ["light", "dark"] {
let tag = format!("run detail @{width}px {theme}");
let page = browser
.open_page(&format!("{base}#/runs/{id}"), width, height, mobile)
.await
.unwrap_or_else(|e| panic!("{tag}: open: {e}"));
browser
.wait_for(&page, "!!document.documentElement", w)
.await
.unwrap_or_else(|e| panic!("{tag}: document never ready: {e}"));
browser
.eval(
&page,
&format!("document.documentElement.dataset.theme = '{theme}'; true"),
)
.await
.unwrap();
browser
.wait_for(&page, "!!document.querySelector('#run-land .land-top')", w)
.await
.unwrap_or_else(|e| panic!("{tag}: land never rendered: {e}"));
let bare = browser
.eval(
&page,
"(() => { const out = []; const walk = document.createTreeWalker(\
document.getElementById('run-land'), NodeFilter.SHOW_TEXT); \
while (walk.nextNode()) { const t = walk.currentNode.textContent.trim(); \
if (t === 'null' || t === 'undefined') out.push(t); } return out; })()",
)
.await
.unwrap();
assert_eq!(bare, serde_json::json!([]), "{tag}: stringified child");
let strip = browser
.eval(
&page,
"document.getElementById('advise-strip').getBoundingClientRect().height",
)
.await
.unwrap();
let strip = strip.as_f64().unwrap_or(f64::MAX);
assert!(strip > 0.0 && strip < 120.0, "{tag}: strip height {strip}");
assert_eq!(
browser
.eval(
&page,
"document.querySelectorAll('#view-run svg.advise, #advise-converge').length"
)
.await
.unwrap(),
0
);
browser
.eval(
&page,
&format!("location.hash = '#/runs/{id}/report'; true"),
)
.await
.unwrap();
browser
.wait_for(
&page,
"(() => { const r = document.querySelector('#run-report-cards .rcard'); \
return !!r && r.offsetParent !== null; })()",
w,
)
.await
.unwrap_or_else(|e| panic!("{tag}: report tab: {e}"));
let cards = browser
.eval(
&page,
"(() => { const box = document.getElementById('run-report-cards'); \
const b = box.getBoundingClientRect(); \
const sums = [...box.querySelectorAll('.rcard-sum')]; \
return { raw_hidden: document.getElementById('run-report').hidden, \
n: sums.length, \
titles: sums.every((s) => s.querySelector('.rcard-title').textContent.trim() !== ''), \
inside: sums.every((s) => s.getBoundingClientRect().right <= b.right + 1) }; })()",
)
.await
.unwrap();
assert_eq!(cards["raw_hidden"], true, "{tag}: {cards}");
assert_eq!(cards["titles"], true, "{tag}: {cards}");
assert_eq!(cards["inside"], true, "{tag}: card spills out: {cards}");
assert!(cards["n"].as_u64().unwrap_or(0) >= 1, "{tag}: {cards}");
browser
.eval(
&page,
"document.getElementById('report-raw-toggle').click(); true",
)
.await
.unwrap();
assert_eq!(
browser
.eval(
&page,
"!document.getElementById('run-report').hidden && document.getElementById('run-report-cards').hidden"
)
.await
.unwrap(),
true,
"{tag}: the Raw toggle shows the text report"
);
browser
.eval(
&page,
"document.getElementById('report-raw-toggle').click(); true",
)
.await
.unwrap();
let problems = console_problems(&browser.take_events(&page.session));
assert!(problems.is_empty(), "{tag}: console: {problems:?}");
assert_eq!(
browser
.eval(
&page,
"document.getElementById('run-tabpanel-overview').hidden"
)
.await
.unwrap(),
true,
"{tag}"
);
browser
.eval(&page, "fetch('/api/runs').then(() => true)")
.await
.unwrap();
browser.settle(Duration::from_millis(400)).await.unwrap();
let after = browser
.eval(
&page,
"({ hash: location.hash, report: !document.getElementById('run-tabpanel-report').hidden, \
sel: document.getElementById('run-tab-report').getAttribute('aria-selected') })",
)
.await
.unwrap();
assert_eq!(after["hash"], format!("#/runs/{id}/report"), "{tag}");
assert_eq!(after["report"], true, "{tag}");
assert_eq!(after["sel"], "true", "{tag}");
browser
.eval(
&page,
&format!("location.hash = '#/runs/{id}/timeline'; true"),
)
.await
.unwrap();
browser
.wait_for(
&page,
"document.querySelectorAll('#run-events .tl-group').length === 2",
w,
)
.await
.unwrap_or_else(|e| panic!("{tag}: timeline groups: {e}"));
browser.close_page(&page).await;
}
}
let page = browser
.open_page(&format!("{base}#/runs"), 1280, 900, false)
.await
.expect("open");
browser
.wait_for(
&page,
"!!document.querySelector('.state-chip[data-key=all]')",
w,
)
.await
.expect("segmented control");
browser
.eval(
&page,
"document.querySelector('.state-chip[data-key=all]').click(); true",
)
.await
.unwrap();
browser
.wait_for(
&page,
"!!document.querySelector('#view-runs a.card.run-card')",
w,
)
.await
.expect("rows");
let nested = browser
.eval(
&page,
"(() => [...document.querySelectorAll('#view-runs *')].filter((e) => { \
const o = getComputedStyle(e).overflowY; \
return (o === 'auto' || o === 'scroll') && e.scrollHeight > e.clientHeight + 1; \
}).map((e) => e.className || e.tagName))()",
)
.await
.unwrap();
assert_eq!(
nested,
serde_json::json!([]),
"nested scroller in the list pane"
);
browser.close_page(&page).await;
}
fn seed_candidate(
label: char,
agent: &str,
failed: Option<&str>,
repo: &std::path::Path,
) -> magi::run::Candidate {
magi::run::Candidate {
index: (label as usize) - ('A' as usize),
label,
agent: agent.to_owned(),
branch: format!("magi/seed/{label}"),
worktree: repo.to_path_buf(),
summary: String::new(),
stat: String::new(),
files: 1,
commits: 1,
empty: false,
failed: failed.map(str::to_owned),
verified_noop: None,
duration_ms: 0,
folded: false,
}
}
#[tokio::test]
async fn verdict_panel_is_a_solo_strip_only_for_one_candidate() {
let Some(chrome) = cdp::find_chrome() else {
assert!(
std::env::var_os("CI").is_none(),
"CI is set but no Chrome/Chromium was found (set MAGI_CHROME)"
);
eprintln!("SKIP web_render: no Chrome/Chromium found (set MAGI_CHROME to run it)");
return;
};
let guard = common::home_lock().await;
let fx = common::fixture(guard, common::Judges::Unanimous, false);
let home = fx.tmp.path().join("magi-home");
let queue = Queue::at(home.join("queue"));
let talks = Talks::at(home.join("talks"));
let seed = |id: &str, cands: Vec<magi::run::Candidate>| {
let mut run = RunState::new(
fx.repo.clone(),
"main".to_owned(),
"0000000".to_owned(),
"verdict panel shape".to_owned(),
fx.config.clone(),
);
run.id = id.to_owned();
run.status = RunStatus::Merged;
run.candidates = cands;
run.save_under(&home).expect("seed run");
};
let solo_id = "20260901-000010-cc10";
let pair_id = "20260901-000011-cc11";
seed(solo_id, vec![seed_candidate('A', "alpha", None, &fx.repo)]);
seed(
pair_id,
vec![
seed_candidate('A', "alpha", None, &fx.repo),
seed_candidate('B', "beta", Some("quota"), &fx.repo),
],
);
let base = serve(&home, queue, talks, home.join("runs"), &fx.repo).await;
let mut browser = cdp::Browser::launch(&chrome)
.await
.unwrap_or_else(|e| panic!("could not start Chrome at {}: {e}", chrome.display()));
let w = Duration::from_secs(30);
for (width, height, mobile) in [(1280u32, 900u32, false), (390, 844, true)] {
let tag = format!("verdict solo @{width}px");
let page = browser
.open_page(&format!("{base}#/runs/{solo_id}"), width, height, mobile)
.await
.unwrap_or_else(|e| panic!("{tag}: open: {e}"));
browser
.wait_for(
&page,
"!!document.querySelector('#verdict-solo .advise-brief')",
w,
)
.await
.unwrap_or_else(|e| panic!("{tag}: solo strip never rendered: {e}"));
let got = browser
.eval(
&page,
"(() => { const s = document.getElementById('verdict-solo'); \
return { svgs: document.querySelectorAll('#converge svg').length, \
shown: !s.hidden, \
pill: s.querySelector('.advise-brief').textContent, \
chip: s.querySelector('.advise-chip').textContent, \
agent: getComputedStyle(s.querySelector('.advise-agent')).display, \
height: s.getBoundingClientRect().height, \
facts: document.getElementById('tally-facts').hidden }; })()",
)
.await
.unwrap();
assert_eq!(got["svgs"], 0, "{tag}: diagram drawn for a solo run");
assert_eq!(got["shown"], true, "{tag}: strip hidden");
assert_eq!(got["pill"], "solo", "{tag}: pill");
let chip = got["chip"].as_str().unwrap_or_default();
assert!(
chip.contains('A') && chip.contains("alpha"),
"{tag}: chip {chip}"
);
assert_ne!(got["agent"], "none", "{tag}: agent name hidden");
assert!(
got["height"].as_f64().unwrap_or(999.0) < 120.0,
"{tag}: strip tall"
);
assert_eq!(got["facts"], true, "{tag}: facts shown");
browser.close_page(&page).await;
let tag = format!("verdict pair @{width}px");
let page = browser
.open_page(&format!("{base}#/runs/{pair_id}"), width, height, mobile)
.await
.unwrap_or_else(|e| panic!("{tag}: open: {e}"));
browser
.wait_for(
&page,
"document.querySelectorAll('#converge svg').length > 0",
w,
)
.await
.unwrap_or_else(|e| panic!("{tag}: diagram never rendered: {e}"));
let hidden = browser
.eval(&page, "document.getElementById('verdict-solo').hidden")
.await
.unwrap();
assert_eq!(hidden, true, "{tag}: solo strip shown");
browser.close_page(&page).await;
}
}
#[tokio::test]
async fn queue_row_click_previews_task_in_split_pane() {
let Some(chrome) = cdp::find_chrome() else {
assert!(
std::env::var_os("CI").is_none(),
"CI is set but no Chrome/Chromium was found (set MAGI_CHROME)"
);
eprintln!("SKIP web_render: no Chrome/Chromium found (set MAGI_CHROME to run it)");
return;
};
let guard = common::home_lock().await;
let fx = common::fixture(guard, common::Judges::Unanimous, false);
let home = fx.tmp.path().join("magi-home");
let queue = Queue::at(home.join("queue"));
let talks = Talks::at(home.join("talks"));
let mut run = RunState::new(
fx.repo.clone(),
"main".to_owned(),
"0000000".to_owned(),
"A run".to_owned(),
fx.config.clone(),
);
run.id = "20260901-000001-aa01".to_owned();
run.status = RunStatus::Reviewing;
run.save_under(&home).expect("seed run");
let ids = seed_tasks(&queue, &fx.repo, &run.id);
let base = serve(&home, queue, talks, home.join("runs"), &fx.repo).await;
let mut browser = cdp::Browser::launch(&chrome)
.await
.unwrap_or_else(|e| panic!("could not start Chrome at {}: {e}", chrome.display()));
let w = Duration::from_secs(30);
let mut failures: Vec<String> = Vec::new();
for width in [1280u32, 1950] {
let tag = format!("queue click @{width}px");
let page = browser
.open_page(&format!("{base}#/queue"), width, 900, false)
.await
.expect("open");
let all = ids
.iter()
.map(|id| format!("!!document.querySelector('a[href=\"#/queue/{id}\"]')"))
.collect::<Vec<_>>()
.join(" && ");
browser.wait_for(&page, &all, w).await.expect("rows");
browser
.eval(
&page,
"document.querySelectorAll('details.list-section').forEach((d) => { d.open = true; })",
)
.await
.expect("expand");
browser
.wait_for(&page, "!!document.querySelector('main[data-split]')", w)
.await
.unwrap_or_else(|e| panic!("{tag}: not in the two-pane layout: {e}"));
browser.settle(Duration::from_millis(300)).await.unwrap();
for id in &ids {
let point = browser
.eval(
&page,
&format!(
"(() => {{ const row = document.querySelector('li.card[data-task-id=\"{id}\"]'); \
if (!row) return null; row.scrollIntoView({{ block: 'center' }}); \
const c = row.querySelector('.card-top .chip, .card-top > span'); \
if (!c) return null; const r = c.getBoundingClientRect(); \
const x = r.left + r.width / 2, y = r.top + r.height / 2; \
const t = document.elementFromPoint(x, y); \
return {{ x, y, inRow: !!t && row.contains(t), title: row.querySelector('.card-title').innerText }}; }})()"
),
)
.await
.unwrap_or_else(|e| panic!("{tag}: locate {id}: {e}"));
if point.is_null() || point["inRow"] != true {
failures.push(format!(
"{tag}: {id}: no clickable chip inside the row: {point}"
));
continue;
}
for kind in ["mousePressed", "mouseReleased"] {
browser
.call(
Some(&page.session),
"Input.dispatchMouseEvent",
serde_json::json!({
"type": kind, "x": point["x"], "y": point["y"],
"button": "left", "clickCount": 1,
}),
)
.await
.expect("mouse");
}
let want = format!("#/tasks/{id}");
let title = point["title"].as_str().unwrap_or_default();
let own = if title.is_empty() || title.starts_with("Instruction for") {
"true".to_owned()
} else {
format!(
"document.getElementById('task-h').textContent.includes({})",
Value::from(title)
)
};
let shown = format!(
"location.hash === {want:?} \
&& !document.getElementById('view-task').hidden \
&& document.getElementById('split-empty').hidden \
&& !document.getElementById('task-h').textContent.includes('Loading task') \
&& !document.getElementById('task-h').textContent.includes('could not be loaded') \
&& document.getElementById('task-h').textContent.trim() !== '' \
&& {own}"
);
if browser
.wait_for(&page, &shown, Duration::from_secs(5))
.await
.is_err()
{
let seen = browser
.eval(
&page,
"({ hash: location.hash, task: document.getElementById('view-task').hidden, \
empty: document.getElementById('split-empty').hidden, \
h: document.getElementById('task-h').textContent })",
)
.await
.unwrap_or_default();
failures.push(format!(
"{tag}: {id}: clicking the row did not open it: {seen}"
));
continue;
}
}
browser.close_page(&page).await;
}
assert!(
failures.is_empty(),
"queue row clicks:\n {}",
failures.join("\n ")
);
}
async fn client_harness(browser: &mut cdp::Browser, page: &cdp::Page) {
browser
.eval(
page,
r#"(async () => {
const source = await (await fetch('/app.js')).text();
window.deck = new Function(source.replace('queue: "/api/queue"', 'queue: "/api/queue?test_client=1"').replace(/\nboot\(\);\s*$/, `
return { state, loadQueue, loadRuns, loadTalks, applyRevisions_, storeReads, statsAgentTone, statsBarPlan, statsBarRows, statsScatterPlan, renderStatsReviewerScatter };
`))();
await Promise.all([deck.loadQueue(), deck.loadRuns(), deck.loadTalks()]);
})()"#,
)
.await
.expect("install client harness");
}
#[tokio::test]
async fn delta_client_merges_rows_coalesces_and_recovers() {
let Some(chrome) = cdp::find_chrome() else {
assert!(std::env::var_os("CI").is_none(), "Chrome required in CI");
eprintln!("SKIP delta client: no Chrome");
return;
};
let guard = common::home_lock().await;
let fx = common::fixture(guard, common::Judges::Unanimous, false);
let home = fx.tmp.path().join("magi-home");
let queue = Queue::at(home.join("queue"));
let ids = seed_tasks(&queue, &fx.repo, "run");
let base = serve(
&home,
queue.clone(),
Talks::at(home.join("talks")),
home.join("runs"),
&fx.repo,
)
.await;
let mut browser = cdp::Browser::launch(&chrome).await.expect("Chrome");
let page = browser
.open_page(&format!("{base}#/queue"), 1280, 900, false)
.await
.expect("page");
browser
.wait_for(
&page,
"!!document.querySelector('#queue-sections li.card')",
Duration::from_secs(30),
)
.await
.expect("loaded queue");
client_harness(&mut browser, &page).await;
let id = &ids[0];
let unchanged = &ids[1];
browser
.eval(
&page,
&format!(
r#"window.sameRow = deck.state.queue.find(t => t.id === {unchanged:?});
deck.state.rev.queue = 1; performance.clearResourceTimings();"#
),
)
.await
.unwrap();
let mut task = queue.get(id).expect("task");
task.title = "Updated through a delta".into();
queue.put(&mut task).unwrap();
let check = browser.eval(&page, &format!(r#"(async () => {{
await deck.loadQueue({{rev: 2, delta: {{base: 1, changed: [{id:?}], removed: []}}}});
return {{same: sameRow === deck.state.queue.find(t => t.id === {unchanged:?}),
title: deck.state.queue.find(t => t.id === {id:?}).title,
partial: performance.getEntriesByType('resource').some(r => r.name.includes('/api/queue?test_client=1&ids='))}};
}})()"#)).await.unwrap();
assert_eq!(check["same"], true);
assert_eq!(check["partial"], true);
assert_eq!(check["title"], "Updated through a delta");
let result = browser.eval(&page, r#"(async () => {
const native = window.fetch;
const requests = [];
let release;
window.fetch = async (url, options) => {
if (String(url).includes('/api/queue?test_client=1')) {
requests.push(String(url));
if (requests.length === 1) await new Promise(resolve => release = resolve);
}
return native(url, options);
};
const first = deck.loadQueue({rev: 3, delta: {base: 2, changed: [], removed: []}});
for (let rev = 4; rev <= 20; rev++) deck.loadQueue({rev, delta: {base: rev - 1, changed: [], removed: []}});
const before = requests.length;
release();
await first;
window.fetch = native;
const coalesced = {before, count: requests.length, fallback: requests[1] === '/api/queue?test_client=1', rev: deck.state.rev.queue};
window.fetch = async () => { throw new Error('test read failed'); };
await deck.loadQueue({rev: 21, delta: {base: 20, changed: [], removed: []}});
const failedRev = deck.state.rev.queue;
window.fetch = native;
performance.clearResourceTimings();
await deck.loadQueue({rev: 22, delta: {base: 21, changed: [], removed: []}});
return {coalesced, failedRev, recovered: deck.state.rev.queue === 22,
whole: performance.getEntriesByType('resource').some(r => r.name.endsWith('/api/queue?test_client=1'))};
})()"#).await.unwrap();
assert_eq!(
result["coalesced"],
serde_json::json!({"before": 1, "count": 2, "fallback": true, "rev": 20})
);
assert!(result["failedRev"].is_null());
assert_eq!(result["recovered"], true);
assert_eq!(result["whole"], true);
std::fs::remove_file(queue.path_of(id)).unwrap();
let removed = browser
.eval(
&page,
&format!(
r#"(async () => {{
await deck.loadQueue({{rev: 23, delta: {{base: 22, changed: [], removed: [{id:?}]}}}});
return !deck.state.queue.some(t => t.id === {id:?});
}})()"#
),
)
.await
.unwrap();
assert_eq!(removed, true);
browser
.eval(
&page,
r#"(async () => {
const native = window.fetch;
window.fetch = async () => { throw new Error('final event read failed'); };
await deck.loadQueue({rev: 24});
window.fetch = native;
performance.clearResourceTimings();
})()"#,
)
.await
.unwrap();
browser
.wait_for(
&page,
"deck.state.rev.queue === 24",
Duration::from_secs(10),
)
.await
.expect("failed final event retries without another notification");
let retry = browser.eval(&page, "performance.getEntriesByType('resource').some(r => r.name.endsWith('/api/queue?test_client=1'))").await.unwrap();
assert_eq!(retry, true);
browser.close_page(&page).await;
}
#[tokio::test]
#[ignore = "manual before/after measurement; requires MAGI_WEB_BENCH_HOME"]
async fn delta_list_benchmark() {
let source = std::path::PathBuf::from(
std::env::var_os("MAGI_WEB_BENCH_HOME").expect("benchmark source"),
);
let chrome = cdp::find_chrome().expect("benchmark requires Chrome");
let guard = common::home_lock().await;
let fx = common::fixture(guard, common::Judges::Unanimous, false);
let home = fx.tmp.path().join("magi-home");
for store in ["queue", "talks", "questions", "runs"] {
let target = home.join(store);
std::fs::create_dir_all(&target).unwrap();
for entry in std::fs::read_dir(source.join(store)).unwrap().flatten() {
let (from, to) = if store == "runs" {
let target = target.join(entry.file_name());
std::fs::create_dir_all(&target).unwrap();
(entry.path().join("run.json"), target.join("run.json"))
} else {
(entry.path(), target.join(entry.file_name()))
};
if from.extension().is_some_and(|ext| ext == "json") && from.is_file() {
std::fs::copy(from, to).unwrap();
}
}
}
let base = serve(
&home,
Queue::at(home.join("queue")),
Talks::at(home.join("talks")),
home.join("runs"),
&fx.repo,
)
.await;
let mut browser = cdp::Browser::launch(&chrome).await.expect("Chrome");
let page = browser
.open_page(&format!("{base}#/queue"), 1280, 900, false)
.await
.expect("page");
browser
.wait_for(
&page,
"!!document.querySelector('#queue-sections li.card')",
Duration::from_secs(30),
)
.await
.expect("queue loaded");
client_harness(&mut browser, &page).await;
for kind in ["runs", "queue+runs", "talks"] {
let script = r#"(async () => {
const source = await (await fetch('/app.js')).text();
window.bench = new Function(source.replace(/\nboot\(\);\s*$/, `
let renderMs = 0;
for (const [name, render] of [['queue', renderQueue], ['runs', renderRuns], ['talks', renderTalks]]) {
const timed = () => { const start = performance.now(); render(); renderMs += performance.now() - start; };
if (name === 'queue') renderQueue = timed;
if (name === 'runs') renderRuns = timed;
if (name === 'talks') renderTalks = timed;
}
return { state, loadQueue, loadRuns, loadTalks, reset: () => renderMs = 0, time: () => renderMs };
`))();
await Promise.all([bench.loadQueue(), bench.loadRuns(), bench.loadTalks()]);
const ids = Object.fromEntries(['queue', 'runs', 'talks'].map(k => [k,
(k === 'queue' ? bench.state.queue.find(t => t.status_str === 'running') || bench.state.queue[0] : bench.state[k][0]).id]));
const report = {};
for (const kind of ['runs', 'queue+runs', 'talks']) {
location.hash = kind === 'talks' ? '#/chat' : kind === 'runs' ? '#/runs' : '#/queue';
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
report[kind] = {};
for (const partial of [false, true]) {
const samples = [];
for (let n = 0; n < 25; n++) {
bench.reset(); performance.clearResourceTimings();
const change = name => ({rev: n + 1, delta: partial ? {base: bench.state.rev[name], changed: [ids[name]], removed: []} : null});
if (kind === 'runs') await bench.loadRuns(change('runs'));
if (kind === 'talks') await bench.loadTalks(change('talks'));
if (kind === 'queue+runs') await Promise.all([bench.loadQueue(change('queue')), bench.loadRuns({rev: n + 1})]);
// Resource timings are delivered after the response is consumed.
await new Promise(resolve => setTimeout(resolve, 0));
const bytes = performance.getEntriesByType('resource').filter(r => /\/api\/(queue|runs|talks)(\?|$)/.test(r.name)).reduce((sum, r) => sum + r.transferSize, 0);
samples.push({bytes, renderMs: bench.time()});
}
const percentile = (key, p) => samples.map(s => s[key]).sort((a,b) => a-b)[Math.ceil(samples.length*p)-1];
report[kind][partial ? 'after' : 'before'] = {bytesMedian: percentile('bytes', .5), bytesP95: percentile('bytes', .95), renderMedian: percentile('renderMs', .5), renderP95: percentile('renderMs', .95)};
}
}
report.counts = Object.fromEntries(['queue','runs','talks'].map(k => [k, bench.state[k].length]));
return report;
})()"#.replace("['runs', 'queue+runs', 'talks']", &format!("['{kind}']"));
let results = browser
.eval(&page, &script)
.await
.expect("benchmark scenario");
eprintln!("DELTA_BENCH {results}");
}
browser.close_page(&page).await;
}
#[tokio::test]
async fn stats_bars_are_stable_per_agent_and_honest_about_sample_size() {
let Some(chrome) = cdp::find_chrome() else {
assert!(std::env::var_os("CI").is_none(), "Chrome required in CI");
eprintln!("SKIP stats bars: no Chrome");
return;
};
let guard = common::home_lock().await;
let fx = common::fixture(guard, common::Judges::Unanimous, false);
let home = fx.tmp.path().join("magi-home");
let queue = Queue::at(home.join("queue"));
let base = serve(
&home,
queue.clone(),
Talks::at(home.join("talks")),
home.join("runs"),
&fx.repo,
)
.await;
let mut browser = cdp::Browser::launch(&chrome).await.expect("Chrome");
let page = browser
.open_page(&format!("{base}#/stats"), 390, 800, true)
.await
.expect("page");
client_harness(&mut browser, &page).await;
let out = browser
.eval(
&page,
r#"(() => {
const r = (agent, num, den) => ({ agent, fraction: `${num}/${den} won`,
rate: den ? { pct: 100 * num / den, denominator: den } : null });
const root = document.createElement('div');
document.body.append(root);
deck.statsBarRows(root, [r('a', 0, 2), r('sonnet', 56, 719), r('b', 1, 3), r('z', 0, 0), r('c', 9, 10)]);
const rows = [...root.querySelectorAll('.bar-row')];
const other = document.createElement('div');
deck.statsBarRows(other, [r('sonnet', 1, 1), r('x', 5, 9)]);
const tone = el => el.querySelector('.bar-fill').style.background;
const sonnet = rows.find(x => x.textContent.includes('sonnet'));
return {
order: rows.map(x => x.querySelector('.bar-row-name').firstChild.textContent),
low: rows.map(x => x.hasAttribute('data-low-n')),
tags: rows.map(x => !!x.querySelector('.bar-row-tag')),
tracks: rows.map(x => !!x.querySelector('.bar-track')),
n719: sonnet.textContent.includes('n=719'),
stable: tone(sonnet) === tone(other.querySelector('.bar-row')),
spill: root.scrollWidth <= root.clientWidth + 1,
n9: deck.statsBarPlan([r('q', 1, 9)])[0].tier,
n10: deck.statsBarPlan([r('q', 1, 10)])[0].tier,
};
})()"#,
)
.await
.unwrap();
assert_eq!(
out["order"],
serde_json::json!(["c", "sonnet", "b", "a", "z"])
);
assert_eq!(
out["low"],
serde_json::json!([false, false, true, true, false])
);
assert_eq!(
out["tags"],
serde_json::json!([false, false, true, true, false])
);
assert_eq!(
out["tracks"],
serde_json::json!([true, true, true, true, false])
);
assert_eq!(out["n719"], true);
assert_eq!(out["stable"], true);
assert_eq!(out["spill"], true);
assert_eq!(out["n9"], 1);
assert_eq!(out["n10"], 0);
browser.close_page(&page).await;
}
#[tokio::test]
async fn stats_scatter_plots_reviewers_on_a_log_axis() {
let Some(chrome) = cdp::find_chrome() else {
assert!(std::env::var_os("CI").is_none(), "Chrome required in CI");
eprintln!("SKIP stats scatter: no Chrome");
return;
};
let guard = common::home_lock().await;
let fx = common::fixture(guard, common::Judges::Unanimous, false);
let home = fx.tmp.path().join("magi-home");
let queue = Queue::at(home.join("queue"));
let base = serve(
&home,
queue.clone(),
Talks::at(home.join("talks")),
home.join("runs"),
&fx.repo,
)
.await;
let mut browser = cdp::Browser::launch(&chrome).await.expect("Chrome");
let page = browser
.open_page(&format!("{base}#/stats"), 390, 800, true)
.await
.expect("page");
client_harness(&mut browser, &page).await;
let out = browser
.eval(
&page,
r#"(() => {
const r = (agent, adopted, submitted) => ({ agent, adopted, submitted });
const root = document.getElementById('stats-reviewers-scatter');
const one = deck.statsScatterPlan([r('a', 1, 1)]);
deck.renderStatsReviewerScatter([r('a', 1, 1)]);
const hiddenOne = root.hidden;
const rows = [r('tiny', 3, 3), r('big', 74, 160), r('mid', 20, 40)];
const plan = deck.statsScatterPlan(rows);
const by = Object.fromEntries(plan.dots.map(d => [d.agent, d]));
deck.renderStatsReviewerScatter(rows);
const ys = plan.dots.map(d => d.labelY).sort((x, y) => x - y);
return {
one: one === null, hiddenOne,
shown: !root.hidden, dots: root.querySelectorAll('.scatter-dot').length,
logX: (by.big.x - by.mid.x) < (by.mid.x - by.tiny.x) * 2,
whisker: (by.tiny.yLo - by.tiny.yHi) > (by.big.yLo - by.big.yHi),
hollow: [by.tiny.low, by.big.low],
gaps: ys.every((y, i) => i === 0 || y - ys[i - 1] >= 10.99),
spill: root.scrollWidth <= root.clientWidth + 1,
};
})()"#,
)
.await
.unwrap();
assert_eq!(out["one"], true);
assert_eq!(out["hiddenOne"], true);
assert_eq!(out["shown"], true);
assert_eq!(out["dots"], 3);
assert_eq!(out["logX"], true);
assert_eq!(out["whisker"], true);
assert_eq!(out["hollow"], serde_json::json!([true, false]));
assert_eq!(out["gaps"], true);
assert_eq!(out["spill"], true);
browser.close_page(&page).await;
}