2026-11-06 22:20:06 +00:00
from __future__ import annotations
2026-11-07 19:59:47 +00:00
import array
2026-11-07 15:03:14 +00:00
import base64
2026-11-07 19:59:47 +00:00
import contextlib
import http.server
2026-11-06 22:20:06 +00:00
import json
2026-11-07 19:59:47 +00:00
import math
2026-11-06 22:20:06 +00:00
import os
import shutil
2026-11-08 13:37:02 +00:00
import ssl
2026-11-06 22:20:06 +00:00
import subprocess
import sys
import tempfile
2026-11-07 15:03:14 +00:00
import threading
2026-11-06 22:20:06 +00:00
import time
import unittest
2026-11-07 15:03:14 +00:00
from collections.abc import Callable
2026-11-06 22:20:06 +00:00
from pathlib import Path
2026-11-07 19:59:47 +00:00
from unittest import mock
2026-11-08 13:37:02 +00:00
from urllib.parse import urlsplit
2026-11-06 22:20:06 +00:00
HERE = Path ( __file__ ) . resolve () . parent
sys . path . insert ( 0 , str ( HERE . parent ))
from hub_case import HubCase
2026-11-07 19:59:47 +00:00
from hub_engines import wav_bytes
2026-11-09 06:53:55 +00:00
from hub_support import ASK , HOST_STATE , OPTIONS , RUNNER , iso , verifier
2026-11-07 19:59:47 +00:00
from kvb_orchestrator import skill_scripts
2026-11-07 15:03:14 +00:00
from test_hub_cleanup import DONE_TIP , FAKE_PKGMGR , host
2026-11-07 17:46:39 +00:00
from test_hub_offices import COUNCIL_SCRIPT
from test_hub_offices import host as office_host
2026-11-06 22:20:06 +00:00
2026-11-07 15:03:14 +00:00
OUT = Path ( os . environ . get ( "E2E_OUT" , "/tmp/orchestrator-phase4-shots" ))
2026-11-06 22:20:06 +00:00
WIDTHS = (( 1920 , 1080 ), ( 1366 , 768 ), ( 390 , 844 ))
2026-11-07 15:03:14 +00:00
VIEWS = ( "orbit" , "chord" , "swimlane" , "tree" , "3d" )
2026-11-09 22:22:46 +00:00
TASK_VIEWS = ( "list" , "board" , "rasci" , "gantt" , "burn" , "3d" )
2026-11-07 23:43:26 +00:00
SCREENS = [ "stream" , "stream/inbox" , "stream/rules" , "stream/radio" , * ( f " { tab } / { view } " for tab in ( "agents" , "artifacts" ) for view in VIEWS ), "agents/hierarchy" , * ( f "tasks/ { view } " for view in TASK_VIEWS )]
2026-11-08 17:26:51 +00:00
SUBS = [ "Inbox" , "Firewall" , "Radio" ]
2026-11-07 23:43:26 +00:00
TABS = [ "Stream" , "Agents" , "Artifacts" , "Tasks" ]
2026-11-07 15:03:14 +00:00
HELPERS = """
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const until = async (check, ms) => {
const end = Date.now() + ms;
while (Date.now() < end) {
const found = check();
if (found) {
return found;
}
await sleep(200);
}
return null;
};
const all = (selector, root) => [...(root || document).querySelectorAll(selector)];
2026-11-06 22:20:06 +00:00
const text = (node) => (node ? node.textContent.trim() : null);
2026-11-07 15:03:14 +00:00
const name = (node) => (node ? node.getAttribute("aria-label") || text(node) : null);
2026-11-09 22:22:46 +00:00
const toolbarRows = () => {
const bar = document.querySelector('#main [role="toolbar"][aria-label="Tasks"]');
const boxes = bar ? [...bar.children].filter((node) => node.getClientRects().length).map((node) => node.getBoundingClientRect()).sort((a, b) => a.top - b.top) : [];
let [rows, bottom] = [0, -Infinity];
for (const box of boxes) {
if (box.top >= bottom - 1) {
rows += 1;
bottom = box.bottom;
} else {
bottom = Math.max(bottom, box.bottom);
}
}
return rows;
};
const taskLayout = () => all("#main .graph.tasks > *").map((node) => (node.matches('[role="toolbar"]') ? "toolbar" : node.matches('[aria-label="Task filters"]') ? "chips" : "body"));
2026-11-07 15:03:14 +00:00
"""
PROBE = """(async () => {@HELPERS@
2026-11-08 17:26:51 +00:00
await until(() => !document.querySelector("#main .space-note") || !text(document.querySelector("#main .space-note")).startsWith("Loading"), 8000);
2026-11-06 22:20:06 +00:00
const badge = (button) => {
const found = button.querySelector(".badge");
return found && !found.hidden ? text(found) : null;
};
2026-11-07 15:03:14 +00:00
const icons = all("svg.bi").filter((svg) => !svg.closest(".sprite"));
2026-11-06 22:20:06 +00:00
const banner = document.getElementById("mayday");
return {
hash: location.hash,
title: document.title,
h1: text(document.querySelector("h1")),
header: all("header.top button").map((button) => button.id),
2026-11-07 15:03:14 +00:00
tabs: all("#tabs > .tab-group > button").map(name),
subs: all("#tabs .subtabs button").map(name),
2026-11-06 22:20:06 +00:00
badges: Object.fromEntries(all("#tabs button").map((button) => [button.id, badge(button)])),
banner: banner.hidden ? null : text(banner),
rail: Boolean(document.getElementById("rail")),
2026-11-08 17:26:51 +00:00
views: all('#main [aria-label="View"] button').map(name),
layouts: all('#main [aria-label="Layout"] button').map(name),
chips: all('#main [aria-label="Categories"] button').map(name),
2026-11-07 15:03:14 +00:00
menus: all("#main details.menu > summary").map(name),
2026-11-06 22:20:06 +00:00
time: text(document.querySelector("#time > summary")),
replay: Boolean(document.querySelector("#time .replay input[type=range]")),
custom: Boolean(document.querySelector('#main details.menu input[type="datetime-local"]')),
2026-11-07 15:03:14 +00:00
toolbar: all('#main [role="toolbar"] > *').map((node) => name(node.tagName === "DETAILS" ? node.querySelector("summary") : node.querySelector("button") || node)),
2026-11-09 22:22:46 +00:00
spot: (() => {
const box = document.getElementById("radio-spot");
return box ? {hidden: box.hidden, channels: [...box.querySelector("form.composer select").options].map((option) => option.value)} : null;
})(),
composers: all("#main form.composer").map((form) => (form.closest("#radio-spot") ? "spot" : "other")),
2026-11-06 22:20:06 +00:00
inbox: all("#main .inbox-list > li").length,
2026-11-07 15:03:14 +00:00
icons: icons.length,
unlabelled: icons.map((svg) => svg.parentElement).filter((owner) => !text(owner) && !(owner.getAttribute("title") && owner.getAttribute("aria-label"))).map((owner) => owner.outerHTML.slice(0, 160)),
broken: all("svg.bi use").filter((use) => !document.getElementById(use.getAttribute("href").slice(1))).map((use) => use.getAttribute("href")),
states: all("#main span.state").map(name),
2026-11-08 17:26:51 +00:00
deletes: all('#main button[aria-label="Delete"], #main button[aria-label="Delete all merged"]').map((button) => [name(button), button.disabled, button.title]),
2026-11-07 15:03:14 +00:00
spawn: all("#main .spawn-tree li").map((item) => [item.dataset.kind, item.dataset.running || null]),
marks: all("#main svg.chart .node").length,
note: text(document.querySelector("#main .space-note")),
canvas: Boolean(document.querySelector("#main .graph3d canvas")),
2026-11-07 17:46:39 +00:00
org: all("#main .org-chart .org-row[data-office]").map((row) => [row.dataset.office, text(row.querySelector(".org-holder"))]),
toggles: all("#main .org-toggle").map((button) => [name(button), button.getAttribute("aria-expanded")]),
order: all("#main .agent-order li").map((item) => item.dataset.name),
2026-11-08 17:26:51 +00:00
taskFilters: all('#main [aria-label="Task filters"] button').map((button) => [button.dataset.filter, button.getAttribute("aria-pressed"), text(button.querySelector(".count"))]),
2026-11-07 23:43:26 +00:00
taskRows: all("#main .task-row").map((row) => [row.dataset.task, row.classList.contains("open-work")]),
columns: all("#main .task-column").map((column) => [column.dataset.column, all(".task-card", column).map((card) => card.dataset.task)]),
matrix: [all("#main table.matrix thead th").length, all("#main table.matrix tbody tr").length, all("#main table.matrix td .letter").map(text).join("")],
flows: all("#main .flow").map((flow) => flow.dataset.flow),
flowEdges: all("#main svg.chart path.flow").map((path) => path.dataset.side),
openWork: all("#main .open-work").length,
2026-11-08 21:18:55 +00:00
panel: all("#drawer, .drawer, #main aside, #main .panel, #main .graph-body").length,
nodeChips: all("#main .space-list, #main .chip[data-node]").length,
2026-11-06 22:20:06 +00:00
wide: document.documentElement.scrollWidth > window.innerWidth + 1,
2026-11-08 13:37:02 +00:00
tall: document.documentElement.scrollHeight > window.innerHeight,
2026-11-06 22:20:06 +00:00
};
2026-11-07 15:03:14 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-07 17:46:39 +00:00
ORG_OPEN = """(async () => {@HELPERS@
const row = await until(() => document.querySelector('#main .org-row[data-office="identity/oidc"]'), 8000);
if (!row) {
return {error: text(document.getElementById("main"))};
}
2026-11-08 17:26:51 +00:00
const toggle = all("#main .org-toggle").find((button) => name(button) === "Collapse: identity");
2026-11-07 17:46:39 +00:00
toggle.focus();
return {focused: document.activeElement.dataset.focus, offices: all("#main .org-row[data-office]").map((entry) => entry.dataset.office)};
})()""" . replace ( "@HELPERS@" , HELPERS )
ORG_FOLDED = """(async () => {@HELPERS@
const toggle = document.activeElement;
const folded = {focused: toggle.dataset.focus, expanded: toggle.getAttribute("aria-expanded"), label: name(toggle), hidden: document.getElementById(toggle.getAttribute("aria-controls")).hidden, seen: all("#main .org-row[data-office]").filter((entry) => entry.offsetParent !== null).map((entry) => entry.dataset.office)};
toggle.click();
await sleep(200);
const before = all("#main .agent-order li").map((item) => item.dataset.name);
all("#main .agent-order li")[1].querySelector('[data-focus^="up:"]').focus();
return {...folded, again: toggle.getAttribute("aria-expanded"), before};
})()""" . replace ( "@HELPERS@" , HELPERS )
ORDER_MOVED = """(async () => {@HELPERS@
const after = all("#main .agent-order li").map((item) => item.dataset.name);
const focused = document.activeElement.dataset.focus || null;
2026-11-08 17:26:51 +00:00
const save = all("#main .agent-order button").find((button) => text(button) === "Save order");
2026-11-07 17:46:39 +00:00
const disabled = save.disabled;
save.click();
2026-11-08 17:26:51 +00:00
const toast = await until(() => all("#toasts .toast").find((box) => text(box).startsWith("Priority")), 8000);
2026-11-07 17:46:39 +00:00
return {after, focused, disabled, toast: text(toast)};
})()""" . replace ( "@HELPERS@" , HELPERS )
OPEN_TAKEOVER = """(async () => {@HELPERS@
document.querySelector('#main [data-focus="takeover:identity/oidc"]').click();
await sleep(400);
2026-11-08 17:26:51 +00:00
return {title: text(document.querySelector(".sheet-head .panel-title")), options: all(".sheet-box select option").map((option) => option.value), work: document.querySelector('.sheet-box input[aria-label="Work"]').value, focused: document.activeElement.tagName};
2026-11-07 17:46:39 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS )
CONFIRM_TAKEOVER = """(async () => {@HELPERS@
2026-11-08 17:26:51 +00:00
all(".sheet-box button").find((button) => name(button) === "Hand over").click();
2026-11-07 17:46:39 +00:00
const done = await until(() => document.querySelector(".sheet-box [data-result]"), 30000);
return {result: done ? done.dataset.result : null, text: text(done), toasts: all("#toasts .toast").map(text)};
})()""" . replace ( "@HELPERS@" , HELPERS )
AFTER_TAKEOVER = """(async () => {@HELPERS@
document.querySelector(".sheet-head button").click();
const row = await until(() => {
const found = document.querySelector('#main .org-row[data-office="identity/oidc"] .org-holder');
return found && !text(found).endsWith("Stage") ? found : null;
}, 15000);
return {holder: text(row)};
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-07 23:43:26 +00:00
INBOX_CHIP = """(async () => {@HELPERS@
2026-11-08 17:26:51 +00:00
const chip = (id) => document.querySelector(`#main [aria-label="Task filters"] button[data-filter="$ {id} "]`);
2026-11-07 23:43:26 +00:00
chip("operator").click();
await until(() => location.hash === "#stream/inbox" && document.querySelector("#main .inbox-list"), 4000);
const inbox = {hash: location.hash, items: all("#main .inbox-list > li").length, counted: Number(text(chip("operator").querySelector(".count"))), pressed: chip("operator").getAttribute("aria-pressed"), current: all("#tabs button[aria-current=page]").map(name)};
2026-11-08 17:26:51 +00:00
chip("grant").click();
2026-11-07 23:43:26 +00:00
await until(() => location.hash.startsWith("#tasks/"), 4000);
await sleep(300);
return {inbox, back: {hash: location.hash, rows: all("#main .task-row").map((row) => [row.dataset.task, text(row.querySelector(".pill"))])}};
})()""" . replace ( "@HELPERS@" , HELPERS )
PIN = """(async () => {@HELPERS@
const row = await until(() => all("#main .task-row").find((item) => item.dataset.task === "work/docs"), 8000);
if (!row) {
return {error: text(document.getElementById("main"))};
}
2026-11-08 17:26:51 +00:00
row.querySelector('button[aria-label="Priority"]').click();
2026-11-07 23:43:26 +00:00
await sleep(400);
2026-11-08 17:26:51 +00:00
const field = document.querySelector('.sheet-box input[aria-label="Priority"]');
2026-11-07 23:43:26 +00:00
field.value = "2";
field.dispatchEvent(new Event("input", {bubbles: true}));
const preview = all(".sheet-box .task-edit .command code").map(text);
2026-11-08 17:26:51 +00:00
all(".sheet-box button").find((button) => name(button) === "Pin").click();
2026-11-07 23:43:26 +00:00
const done = await until(() => document.querySelector(".sheet-box [data-jobs]"), 20000);
return {title: text(document.querySelector(".sheet-head .panel-title")), preview, jobs: done ? done.dataset.jobs : null, toasts: all("#toasts .toast").map(text)};
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-07 15:03:14 +00:00
ACTIONS = """(async () => {@HELPERS@
2026-11-06 22:20:06 +00:00
const press = async (key) => {
document.dispatchEvent(new KeyboardEvent("keydown", {key, bubbles: true}));
await sleep(300);
return location.hash;
};
2026-11-07 23:43:26 +00:00
const found = {keys: [await press("2"), await press("3"), await press("4"), await press("1")]};
2026-11-06 22:20:06 +00:00
document.getElementById("settings-open").click();
await sleep(300);
2026-11-07 15:03:14 +00:00
found.ambience = all(".sheet-box form > section:first-child button").map((button) => button.id);
2026-11-09 22:22:46 +00:00
found.sections = all(".sheet-box form.settings > section > h3").map(text);
found.radioFields = all('.sheet-box [aria-label="Squelch"], .sheet-box [aria-label="Classes"], .sheet-box [aria-label="Speakers"], .sheet-box .sound-row').length;
2026-11-07 15:03:14 +00:00
found.settingsUnlabelled = all(".sheet-box svg.bi").map((svg) => svg.parentElement).filter((owner) => !text(owner) && !(owner.getAttribute("title") && owner.getAttribute("aria-label"))).length;
2026-11-06 22:20:06 +00:00
document.querySelector(".sheet-head button").click();
document.getElementById("palette-open").click();
await sleep(300);
2026-11-07 15:03:14 +00:00
found.palette = all(".palette-list li > span:first-child").map(text);
2026-11-06 22:20:06 +00:00
document.dispatchEvent(new KeyboardEvent("keydown", {key: "Escape", bubbles: true}));
await sleep(200);
2026-11-08 17:26:51 +00:00
document.querySelector('#main [aria-label="Categories"] button:nth-child(2)').click();
2026-11-06 22:20:06 +00:00
await sleep(900);
2026-11-08 17:26:51 +00:00
document.querySelector('#main button.linkish[title^="Open "]').click();
2026-11-07 15:03:14 +00:00
await sleep(500);
2026-11-08 21:18:55 +00:00
found.popup = all("#popup input[type=radio]").map((input) => [input.value, input.checked]);
found.popupUnlabelled = all("#popup svg.bi").map((svg) => svg.parentElement).filter((owner) => !text(owner) && !(owner.getAttribute("title") && owner.getAttribute("aria-label"))).length;
found.centred = [document.getElementById("popup").open, document.querySelector("#popup .leader line").getAttribute("display")];
2026-11-07 15:03:14 +00:00
return found;
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-08 21:18:55 +00:00
POPUPS = """
const dialog = document.getElementById("popup");
const box = dialog.querySelector(".popup-box");
const line = dialog.querySelector(".leader line");
const click = (target) => target.dispatchEvent(new MouseEvent("click", {bubbles: true, cancelable: true}));
const tint = document.createElement("i");
tint.style.color = "var(--alarm)";
document.body.append(tint);
const red = getComputedStyle(tint).color;
tint.remove();
const tall = () => document.documentElement.scrollHeight > innerHeight;
const look = (node) => ({picked: node.classList.contains("picked"), size: Math.round(node.querySelector("circle").getBoundingClientRect().width), red: getComputedStyle(node.querySelector("circle")).fill === red});
const opened = () => {
const rect = box.getBoundingClientRect();
const ends = line.getAttribute("display") === "none" ? null : ["x1", "y1", "x2", "y2"].map((key) => Number(line.getAttribute(key)));
return {
open: dialog.open, fade: [dialog.classList.contains("fade"), getComputedStyle(dialog).animationName], title: text(box.querySelector(".panel-title")), opacity: Number(getComputedStyle(box).opacity), line: ends,
onBox: Boolean(ends) && (Math.abs(ends[2] - rect.left) < 1 || Math.abs(ends[2] - rect.right) < 1 || Math.abs(ends[3] - rect.top) < 1 || Math.abs(ends[3] - rect.bottom) < 1),
inside: rect.left >= 0 && rect.top >= 0 && rect.right <= innerWidth && rect.bottom <= innerHeight,
tall: tall(), wide: document.documentElement.scrollWidth > innerWidth + 1, panel: all("#drawer, .drawer, #main aside, #main .panel, #main .graph-body").length,
};
};
const away = async (pick) => {
const outside = document.elementFromPoint(2, innerHeight - 2);
click(outside);
await sleep(250);
return {...look(pick()), open: dialog.open, outside: outside === dialog};
};
"""
POPUP_ORBIT = """(async () => {@HELPERS@@POPUPS@
const pick = () => document.querySelector('#main svg .node[data-pick="s2"]');
if (!(await until(pick, 8000))) {
return {error: text(document.getElementById("main"))};
}
const before = look(pick());
const flat = !tall();
click(pick());
await sleep(400);
2026-11-08 22:06:31 +00:00
const art = box.querySelector(".popup-head .avatar.big");
const face = art ? {key: art.dataset.face, svg: Boolean(art.querySelector("svg.face-art .mouth")), text: art.textContent} : null;
const shown = {...opened(), ...look(pick()), sections: all("h3", box).map((head) => head.firstChild.textContent), composer: Boolean(box.querySelector("form textarea")), offices: text(box.querySelector(".popup-body p")), face};
2026-11-08 21:18:55 +00:00
const gone = await away(pick);
click(pick());
await sleep(300);
const again = dialog.open;
(document.activeElement || document.body).dispatchEvent(new KeyboardEvent("keydown", {key: "Escape", bubbles: true}));
await sleep(250);
const escaped = {...look(pick()), open: dialog.open, again};
click(pick());
await sleep(500);
return {before, flat, shown, gone, escaped, last: dialog.open};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@POPUPS@" , POPUPS )
POPUP_ARTIFACT = """(async () => {@HELPERS@@POPUPS@
const first = await until(() => document.querySelector('#main svg .node[data-pick^="branch:"]'), 8000);
if (!first) {
return {error: text(document.getElementById("main"))};
}
const id = first.dataset.pick;
const pick = () => all("#main svg .node[data-pick]").find((node) => node.dataset.pick === id);
const before = look(pick());
const flat = !tall();
click(pick());
await sleep(400);
const shown = {...opened(), ...look(pick()), label: first.getAttribute("aria-label").split(",")[0]};
const gone = await away(pick);
click(pick());
await sleep(400);
return {before, flat, shown, gone, last: dialog.open};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@POPUPS@" , POPUPS )
2026-11-07 15:03:14 +00:00
PICK_3D = """(async () => {@HELPERS@
2026-11-08 21:18:55 +00:00
const field = await until(() => document.querySelector('#main input[aria-label="Find a node"]'), 8000);
const ready = await until(() => document.querySelector("#main .graph3d canvas") && !text(document.querySelector("#main .space-note")).startsWith("Loading"), 8000);
if (!field || !ready) {
2026-11-07 15:03:14 +00:00
return {error: text(document.querySelector("#main .graph"))};
}
2026-11-08 21:18:55 +00:00
const type = async (value) => {
field.value = value;
field.dispatchEvent(new Event("input", {bubbles: true}));
await sleep(150);
return all("#main .hits .chip").map((chip) => [chip.dataset.hit, text(chip)]);
};
const tree = await (await fetch("/api/tree", {cache: "no-store"})).json();
const found = {empty: await type(""), twins: tree.repos.flatMap((repo) => repo.branches).filter((branch) => branch.name === "main").length, twin: await type("main"), hits: await type("@QUERY@")};
found.flat = document.documentElement.scrollHeight <= innerHeight;
const chip = all("#main .hits .chip").find((node) => node.dataset.hit.startsWith("@PREFIX@"));
2026-11-07 15:03:14 +00:00
if (!chip) {
2026-11-08 21:18:55 +00:00
return {error: found};
2026-11-07 15:03:14 +00:00
}
chip.click();
await sleep(600);
2026-11-08 21:18:55 +00:00
const dialog = document.getElementById("popup");
const line = dialog.querySelector(".leader line");
const host = () => document.querySelector("#main .graph3d");
found.opened = {
open: dialog.open, title: text(dialog.querySelector(".popup-box .panel-title")), label: text(chip), hit: chip.dataset.hit, picked: host().dataset.picked || null,
line: line.getAttribute("display") !== "none", lists: all("#main .space-list, #main .chip[data-node]").length, flat: document.documentElement.scrollHeight <= innerHeight,
};
dialog.dispatchEvent(new MouseEvent("click", {bubbles: true}));
await sleep(300);
found.closed = {open: dialog.open, picked: host().dataset.picked || null};
chip.click();
await sleep(600);
return found;
2026-11-07 15:03:14 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS )
CALL = """(async () => {@HELPERS@
2026-11-08 17:26:51 +00:00
document.querySelector('#main [aria-label="Categories"] button[aria-label="Radio"]').click();
const someone = await until(() => document.querySelector('#main button.linkish[title^="Open "]'), 8000);
2026-11-06 22:20:06 +00:00
someone.click();
await sleep(500);
2026-11-08 21:18:55 +00:00
document.querySelector('#popup input[type=radio][value="call"]').click();
const field = document.querySelector("#popup textarea");
2026-11-07 15:03:14 +00:00
field.value = "E2E call ping";
field.dispatchEvent(new Event("input", {bubbles: true}));
2026-11-08 21:18:55 +00:00
document.querySelector('#popup button[type="submit"]').click();
2026-11-08 17:26:51 +00:00
const entry = await until(() => all("#main .entry").find((row) => row.textContent.includes("Read") && row.textContent.includes("E2E call ping")), 20000);
2026-11-07 15:03:14 +00:00
return {to: text(someone), receipt: entry ? text(entry) : null, sent: all("#main .entry").filter((row) => row.textContent.includes("E2E call ping")).length};
})()""" . replace ( "@HELPERS@" , HELPERS )
OPEN_DELETE = """(async () => {@HELPERS@
const card = await until(() => all("#main details.repo").find((details) => details.textContent.includes("github.com/me/demo")), 8000);
if (!card) {
return {error: text(document.getElementById("main"))};
}
card.open = true;
await sleep(300);
2026-11-08 17:26:51 +00:00
const every = document.querySelector('#main button[aria-label="Delete all merged"]');
const buttons = all('button[aria-label="Delete"]', card);
2026-11-07 15:03:14 +00:00
const states = all("span.state", card).map(name);
buttons.find((button) => !button.disabled).click();
await sleep(500);
2026-11-08 17:26:51 +00:00
return {states, every: [every.disabled, every.title], buttons: buttons.map((button) => [button.disabled, button.title]), title: text(document.querySelector(".sheet-head .panel-title")), dry: all('.sheet-box [aria-label="Dry run"] pre').map(text)};
2026-11-07 15:03:14 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS )
CONFIRM = """(async () => {@HELPERS@
2026-11-08 17:26:51 +00:00
all(".sheet-box button").find((button) => name(button) === "Delete").click();
2026-11-07 15:03:14 +00:00
const done = await until(() => document.querySelector(".sheet-box [data-result]"), 30000);
const job = document.querySelector(".sheet-box [data-cleanup]");
return {job: job ? job.dataset.cleanup : null, result: done ? done.dataset.result : null, text: text(done), toasts: all("#toasts .toast").map(text)};
})()""" . replace ( "@HELPERS@" , HELPERS )
AFTER = """(async () => {@HELPERS@
document.querySelector(".sheet-head button").click();
2026-11-08 17:26:51 +00:00
const past = await until(() => all("#main details.repo").find((details) => details.textContent.includes("Recently deleted")), 15000);
2026-11-07 15:03:14 +00:00
return {past: past ? [past.querySelector("[data-result]").dataset.result, name(past.querySelector("span.state"))] : null, branches: all("#main details.repo").filter((details) => details !== past).flatMap((details) => all(".branch-name strong", details)).map(text)};
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-07 19:59:47 +00:00
OVERLAP = """
const shown = (node) => node.getClientRects().length > 0 && node.getBoundingClientRect().width > 0;
const words = (label) => [...label.childNodes].filter((node) => node.nodeType === 3).map((node) => node.textContent).join("").trim();
const called = (node) => node.getAttribute("aria-label") || (node.closest("label") ? words(node.closest("label")) : "") || text(node) || node.type;
const clashes = (root) => {
const boxes = all("input, select, textarea, button", root).filter(shown).map((node) => ({node, box: node.getBoundingClientRect()}));
const found = [];
boxes.forEach((a, index) => boxes.slice(index + 1).forEach((b) => {
const x = Math.min(a.box.right, b.box.right) - Math.max(a.box.left, b.box.left);
const y = Math.min(a.box.bottom, b.box.bottom) - Math.max(a.box.top, b.box.top);
if (x > 0.5 && y > 0.5) {
found.push(`${called(a.node)} × ${called(b.node)}`);
}
}));
const outside = boxes.filter(( {box} ) => box.right > root.getBoundingClientRect().right + 0.5).map(( {node} ) => called(node));
return {found, outside};
};
"""
RULES_OVERLAP = """(async () => {@HELPERS@@CLASHES@
const open = document.querySelector(".sheet-head button");
if (open) {
open.click();
}
const rules = await until(() => document.querySelector("#main .rule-form"), 8000);
const kind = rules.querySelector("select");
const found = {} ;
for (const value of ["decision", "review", "ask"]) {
kind.value = value;
kind.dispatchEvent(new Event("change", {bubbles: true}));
await sleep(150);
found[value] = {...clashes(rules), labels: all("label", rules).filter(shown).map(words)};
}
return found;
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@CLASHES@" , OVERLAP )
SETTINGS_OVERLAP = """(async () => {@HELPERS@@CLASHES@
document.getElementById("settings-open").click();
const settings = await until(() => document.querySelector(".sheet-box form.settings"), 8000);
2026-11-08 17:26:51 +00:00
all("button", settings).find((button) => text(button) === "+ Station").click();
2026-11-07 19:59:47 +00:00
const theme = all("select", settings).find((select) => [...select.options].some((option) => option.value === "custom"));
theme.value = "custom";
theme.dispatchEvent(new Event("change", {bubbles: true}));
await sleep(250);
2026-11-08 17:26:51 +00:00
const place = all("section", settings).find((section) => text(section.querySelector("h3")) === "Location");
2026-11-07 19:59:47 +00:00
place.scrollIntoView({block: "start"});
return clashes(settings);
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@CLASHES@" , OVERLAP )
MEDIA_LOG = """() => {
const keep = (entry) => {
try {
const list = JSON.parse(sessionStorage.getItem("e2e-media") || "[]");
list.push({...entry, page: Math.round(performance.timeOrigin)});
sessionStorage.setItem("e2e-media", JSON.stringify(list));
} catch (error) {
return;
}
};
const play = HTMLMediaElement.prototype.play;
HTMLMediaElement.prototype.play = function () {
window.e2eAudio = this;
const src = this.getAttribute("src") || "";
const result = play.call(this);
result.then(() => keep({play: "ok", src}), (error) => keep({play: error.name, src}));
return result;
};
document.addEventListener("securitypolicyviolation", (event) => keep({csp: event.effectiveDirective, blocked: event.blockedURI}));
}"""
MEDIA = """
const log = () => JSON.parse(sessionStorage.getItem("e2e-media") || "[]");
const sample = () => (window.e2eAudio ? {paused: window.e2eAudio.paused, time: window.e2eAudio.currentTime, error: window.e2eAudio.error ? window.e2eAudio.error.code : null, src: window.e2eAudio.getAttribute("src")} : null);
const advancing = async () => {
const first = sample();
await sleep(1500);
const second = sample();
return {first, second, playing: Boolean(first && second && !second.paused && second.time - first.time > 0.5)};
};
"""
ADD_STATION = """(async () => {@HELPERS@
const clock = (date) => `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
document.getElementById("settings-open").click();
const form = await until(() => document.querySelector(".sheet-box form.settings"), 8000);
2026-11-08 17:26:51 +00:00
all("button", form).find((button) => text(button) === "+ Station").click();
2026-11-07 19:59:47 +00:00
await sleep(200);
const row = all(".stream-row", form).pop();
const [label, address, start, end] = all("input.field", row);
const set = (input, value) => {
input.value = value;
input.dispatchEvent(new Event("input", {bubbles: true}));
};
set(label, "@NAME@");
set(address, "@STATION@");
set(start, clock(new Date(Date.now() - 3600000)));
set(end, clock(new Date(Date.now() + 3600000)));
all('.days input[type="checkbox"]', row).forEach((box) => {
box.checked = true;
});
all("button", form).find((button) => button.type === "submit").click();
2026-11-08 17:26:51 +00:00
await until(() => all("#toasts .toast").map(text).find((toast) => toast.startsWith("New station")), 8000);
2026-11-07 19:59:47 +00:00
return {toasts: all("#toasts .toast").map(text)};
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-08 17:26:51 +00:00
STATION_NAME = "E2E tone with a very long station name that fits on no single line"
2026-11-08 13:37:02 +00:00
DLF_PATH = "/dlf/01/high/aac/stream.aac?aggregator=web"
DLF_NODE = "/ard/dlf/01/aac/192/stream.aac?aggregator=web"
2026-11-07 19:59:47 +00:00
RADIO_STATE = """(async () => {@HELPERS@@MEDIA@
await until(() => log().some((entry) => entry.play), 8000);
return {log: log(), media: await advancing(), toasts: all("#toasts .toast").map(text)};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@MEDIA@" , MEDIA )
RADIO_VIEW = """(async () => {@HELPERS@@MEDIA@
location.hash = "#stream/radio";
const row = await until(() => document.querySelector('#main [data-station="0"]'), 8000);
if (!row) {
const initial = JSON.parse(document.getElementById("state").textContent);
return {error: "no station row", log: log(), wrapped: !String(HTMLMediaElement.prototype.play).includes("[native code]"), streams: initial.settings.streams, media: initial.media, now: new Date().toString()};
}
const label = row.querySelector(".station-name");
const style = getComputedStyle(label);
return {
state: row.dataset.state,
note: text(row.querySelector(".station-note")),
title: label.title,
line: [style.whiteSpace, style.textOverflow, label.scrollWidth > label.clientWidth, label.getBoundingClientRect().height < 2 * parseFloat(style.lineHeight)],
media: await advancing(),
log: log(),
toasts: all("#toasts .toast").map(text),
};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@MEDIA@" , MEDIA )
RADIO_CLICK = """(async () => {@HELPERS@@MEDIA@
const row = document.querySelector('#main [data-station="0"]');
if (!row) {
return {error: "no station row", log: log(), media: await advancing()};
}
const play = row.querySelector('button[data-action="play"]');
const clicked = Boolean(sample() && sample().paused && play);
if (clicked) {
play.click();
}
await until(() => sample() && !sample().paused, 6000);
const media = await advancing();
const now = document.querySelector('#main [data-station="0"]');
return {clicked, state: now.dataset.state, note: text(now.querySelector(".station-note")), media, log: log()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@MEDIA@" , MEDIA )
SPEAKING = """
const caption = () => {
const node = document.querySelector("#main .radio-caption");
return node && !node.hidden ? node : null;
};
const ring = (chip) => (chip ? getComputedStyle(chip.querySelector(".speaker-face"), "::after").animationName : null);
2026-11-08 22:06:31 +00:00
const mouth = (art) => (art ? [art.classList.contains("speaking"), getComputedStyle(art.querySelector(".mouth")).animationName] : null);
const faces = () => ({
speaking: mouth(document.querySelector('#main .speakers .avatar[data-face="s2"]')),
silent: mouth(document.querySelector('#main .speakers .avatar[data-face="s1"]')),
keys: [...new Set(all("#main .avatar.speaking").map((art) => art.dataset.face))],
});
2026-11-07 19:59:47 +00:00
const cue = async (word) => {
await until(() => document.querySelector("#main .speakers .speaker"), 8000);
await fetch("/api/tts", {method: "POST", headers: {"X-Kvb-Orchestrator": "1", "Content-Type": "application/json"}, body: JSON.stringify({text: word, voice: ""}), cache: "no-store"});
const found = await until(caption, 25000);
const chip = document.querySelector("#main .speaker.speaking");
2026-11-08 22:06:31 +00:00
return found ? {who: text(found.querySelector("strong")), line: text(found.querySelector(".caption-line")), chip: chip ? chip.dataset.key : null, ring: ring(chip), faces: faces()} : {error: "no caption", toasts: all("#toasts .toast").map(text)};
2026-11-07 19:59:47 +00:00
};
"""
SPEAK = """(async () => {@HELPERS@@SPEAKING@
return cue("@CUE@");
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@SPEAKING@" , SPEAKING )
SPEAK_GRAPH = """(async () => {@HELPERS@@SPEAKING@
location.hash = "#agents/orbit";
const node = await until(() => document.querySelector("#main svg .node.speaking"), 6000);
2026-11-08 22:06:31 +00:00
const silent = document.querySelector('#main svg .node[data-pick="s1"] .mouth');
return {
node: node ? node.getAttribute("aria-label") : null, ring: node ? getComputedStyle(node.querySelector("circle")).animationName : null,
mouth: node ? getComputedStyle(node.querySelector(".mouth")).animationName : null, silent: silent ? getComputedStyle(silent).animationName : null,
};
2026-11-07 19:59:47 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@SPEAKING@" , SPEAKING )
SPEAK_END = """(async () => {@HELPERS@@SPEAKING@
const ended = Boolean(await until(() => !document.querySelector("#main svg .node") || !document.querySelector("#main svg .node.speaking"), 20000));
location.hash = "#stream/radio";
await until(() => document.querySelector("#main .radio-caption"), 6000);
await sleep(300);
2026-11-08 22:06:31 +00:00
return {ended, caption: Boolean(caption()), chips: all("#main .speaker.speaking").length, faces: all("#main .avatar.speaking").length};
2026-11-07 19:59:47 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@SPEAKING@" , SPEAKING )
2026-11-09 22:22:46 +00:00
SPOT_OPEN = """
const spotToggle = await until(() => all('#main [role="toolbar"] button').find((node) => name(node) === "Radio"), 8000);
if (spotToggle && spotToggle.getAttribute("aria-expanded") !== "true") {
spotToggle.click();
await sleep(300);
}
const spot = document.getElementById("radio-spot");
"""
COMPOSE = """(async () => {@HELPERS@@SPOT_OPEN@
const form = spot ? spot.querySelector("form.composer") : null;
2026-11-07 19:59:47 +00:00
if (!form) {
return {error: "no composer"};
}
const [channel, recipient] = all("select", form);
await until(() => [...recipient.options].some((option) => option.value === "@TO@"), 8000);
channel.value = "call";
channel.dispatchEvent(new Event("change", {bubbles: true}));
recipient.value = "@TO@";
const record = all("button", form).find((button) => button.hasAttribute("aria-pressed"));
record.click();
await sleep(1500);
const recording = record.getAttribute("aria-pressed");
record.click();
const field = form.querySelector("textarea");
await until(() => field.value && form.querySelector('.voice-note input[type="checkbox"]'), 60000);
const replay = form.querySelector(".voice-note audio");
const attach = form.querySelector('.voice-note input[type="checkbox"]');
return {
channels: all("option", channel).map((option) => option.value),
recipients: all("option", recipient).map((option) => option.value),
recording,
text: field.value,
replay: replay ? replay.getAttribute("src") : null,
attach: attach ? [text(attach.closest("label")), attach.checked] : null,
status: text(form.querySelector(".composer-status")),
toasts: all("#toasts .toast").map(text),
clashes: (() => {@CLASHES@
return clashes(form);
})(),
};
2026-11-09 22:22:46 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@SPOT_OPEN@" , SPOT_OPEN ) . replace ( "@CLASHES@" , OVERLAP )
2026-11-07 19:59:47 +00:00
COMPOSE_SEND = """(async () => {@HELPERS@
const form = document.querySelector("#main form.composer");
const field = form.querySelector("textarea");
2026-11-08 17:26:51 +00:00
field.value = `$ {field.value} please`;
2026-11-07 19:59:47 +00:00
field.dispatchEvent(new Event("input", {bubbles: true}));
form.querySelector('.voice-note input[type="checkbox"]').checked = @ATTACH@;
const before = all("#toasts .toast").length;
form.querySelector('button[type="submit"]').click();
2026-11-08 17:26:51 +00:00
const sent = await until(() => all("#toasts .toast").slice(before).map(text).find((toast) => toast.startsWith("Call to")), 20000);
2026-11-07 19:59:47 +00:00
return {sent, left: field.value, voice: Boolean(form.querySelector(".voice-note audio"))};
})()""" . replace ( "@HELPERS@" , HELPERS )
CLEAR_MAYDAY = """(async () => {@HELPERS@
2026-11-09 22:22:46 +00:00
const questions = [];
let accept = false;
window.confirm = (question) => {
questions.push(question);
return accept;
};
const posted = [];
const send = window.fetch.bind(window);
window.fetch = (path, init) => {
if (String(path).startsWith("/api/board/")) {
posted.push(String(path));
}
return send(path, init);
};
await until(() => document.querySelector('#main [role="toolbar"]'), 8000);
const toolbar = all('#main [role="toolbar"] button, #main [role="toolbar"] summary').map(name);@SPOT_OPEN@
const button = await until(() => spot && all(".spot-head button", spot).find((node) => name(node) === "Cancel mayday" && !node.disabled), 8000);
2026-11-07 19:59:47 +00:00
if (!button) {
2026-11-09 22:22:46 +00:00
return {error: "no enabled Cancel mayday button in the radio spot", toolbar};
2026-11-07 19:59:47 +00:00
}
2026-11-09 22:22:46 +00:00
const shown = new Set(all("#toasts .toast"));
const fresh = () => all("#toasts .toast").filter((node) => !shown.has(node)).map(text);
2026-11-07 19:59:47 +00:00
button.click();
2026-11-09 22:22:46 +00:00
await sleep(800);
const declined = {question: questions.slice(), posted: posted.slice(), toasts: fresh()};
accept = true;
button.click();
const done = await until(() => fresh().find((toast) => toast.startsWith("Cancel mayday")), 15000);
return {toolbar, title: button.title, declined, accepted: {question: questions.slice(declined.question.length), posted: posted.slice(declined.posted.length)}, done};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@SPOT_OPEN@" , SPOT_OPEN )
2026-11-08 23:18:18 +00:00
NIGHT_MENU = """(async () => {@HELPERS@
const questions = [];
window.confirm = (question) => {
questions.push(question);
return true;
};
let asked = 0;
const get = navigator.credentials.get.bind(navigator.credentials);
navigator.credentials.get = (options) => {
asked += 1;
return @SIGNS@ ? get(options) : Promise.reject(new DOMException("The operator canceled.", "NotAllowedError"));
};
const posted = [];
const send = window.fetch.bind(window);
window.fetch = (path, init) => {
if (String(path).startsWith("/api/board/")) {
const body = JSON.parse(init.body);
posted.push([String(path), Object.keys(body), body.action || null]);
}
return send(path, init);
};
const menu = await until(() => all('#main [role="toolbar"] details.menu').find((node) => name(node.querySelector("summary")) === "Day/Night"), 8000);
if (!menu) {
return {error: text(document.getElementById("main"))};
}
menu.open = true;
const buttons = all(".actions button", menu);
const runs = [];
for (const button of buttons) {
const label = name(button);
const [before, prompts, sent] = [asked, questions.length, posted.length];
const shown = new Set(all("#toasts .toast"));
button.click();
const toast = await until(() => all("#toasts .toast").filter((box) => !shown.has(box)).map(text).find((entry) => entry.startsWith(`$ {label} :`)), 15000);
runs.push({label, question: questions.slice(prompts), asked: asked - before, posted: posted.slice(sent), toast});
}
return {labels: buttons.map(name), runs};
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-09 22:22:46 +00:00
NIGHT_LOOK = """(async () => {@HELPERS@
const menu = await until(() => all('#main [role="toolbar"] details.menu').find((node) => name(node.querySelector("summary")) === "Day/Night"), 8000);
if (!menu) {
return {error: text(document.getElementById("main"))};
}
menu.open = true;
await sleep(200);
const box = menu.querySelector(".menu-box");
return {
children: [...box.children].map((node) => [node.tagName.toLowerCase(), node.className]),
badge: all(".pill", box).map(text),
buttons: all("button", box).map((button) => [name(button), button.title]),
paragraphs: all("p", box).length,
wide: document.documentElement.scrollWidth > window.innerWidth + 1,
};
})()""" . replace ( "@HELPERS@" , HELPERS )
FITS = "tall: document.documentElement.scrollHeight > window.innerHeight, wide: document.documentElement.scrollWidth > window.innerWidth + 1"
SPOT_SEND = """(async () => {@HELPERS@
const questions = [];
window.confirm = (question) => {
questions.push(question);
return true;
};
const toggle = await until(() => all('#main [role="toolbar"] button').find((node) => name(node) === "Radio"), 8000);
if (!toggle) {
return {error: text(document.getElementById("main"))};
}
const bar = document.querySelector('#main [role="toolbar"]');
const spot = document.getElementById("radio-spot");
const closed = [spot.hidden, toggle.getAttribute("aria-expanded")];
toggle.click();
await sleep(400);
const form = spot.querySelector("form.composer");
const [channel, recipient] = all("select", form);
const field = form.querySelector("textarea");
const found = {
closed,
open: [spot.hidden, toggle.getAttribute("aria-expanded"), document.activeElement === field],
inline: [getComputedStyle(spot).position, spot.getBoundingClientRect().top >= bar.getBoundingClientRect().bottom, spot.previousElementSibling === bar],
toolbar: all('#main [role="toolbar"] > *').map((node) => name(node.tagName === "DETAILS" ? node.querySelector("summary") : node)),
channels: all("option", channel).map((option) => option.value),
record: all("button", form).filter((button) => button.hasAttribute("aria-pressed")).map(text),
heads: all(".spot-head button", spot).map((button) => [name(button), button.title]),
@FITS@,
};
await until(() => [...recipient.options].some((option) => option.value === "Stage"), 8000);
found.runs = [];
for (const cls of found.channels) {
channel.value = cls;
channel.dispatchEvent(new Event("change", {bubbles: true}));
const direct = !recipient.closest("label").hidden;
if (direct) {
recipient.value = "Stage";
}
field.value = `@TEXT@ $ {cls} `;
field.dispatchEvent(new Event("input", {bubbles: true}));
const shown = new Set(all("#toasts .toast"));
const prompts = questions.length;
form.querySelector('button[type="submit"]').click();
const toast = await until(() => all("#toasts .toast").filter((node) => !shown.has(node)).map(text).find((entry) => entry.includes(" to ")), 20000);
found.runs.push({cls, direct, toast, question: questions.slice(prompts), left: field.value});
}
document.getElementById("toasts").replaceChildren();
return found;
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@FITS@" , FITS )
RADIO_JUMP = """(async () => {@HELPERS@
const jump = await until(() => all("#main .radio-view button").find((button) => name(button) === "Send …"), 8000);
if (!jump) {
return {error: text(document.getElementById("main"))};
}
const composers = all("#main form.composer").length;
jump.click();
const spot = await until(() => location.hash.startsWith("#agents/") && document.querySelector("#radio-spot:not([hidden])"), 4000);
await sleep(200);
return {composers, hash: location.hash, open: Boolean(spot), focused: Boolean(spot) && document.activeElement === spot.querySelector("textarea")};
})()""" . replace ( "@HELPERS@" , HELPERS )
GEAR_SAVE = """(async () => {@HELPERS@@CLASHES@
const spot = document.getElementById("radio-spot");
const gear = all(".spot-head button", spot).find((button) => name(button) === "Radio settings");
gear.click();
const form = await until(() => spot.querySelector("form.radio-settings"), 4000);
if (!form) {
return {error: "no radio settings behind the gear"};
}
const classes = () => all('[aria-label="Classes"] input', form);
const found = {
expanded: gear.getAttribute("aria-expanded"),
sections: all("section > h3", form).map(text),
tones: all(".sound-row strong", form).map(text),
classes: classes().map((input) => [input.parentElement.textContent.trim(), input.checked, input.disabled]),
clashes: clashes(form),
@FITS@,
};
const squelch = form.querySelector('input[aria-label="Squelch"]');
squelch.value = "@SQUELCH@";
squelch.dispatchEvent(new Event("input", {bubbles: true}));
form.querySelector('input[aria-label="All radio on"]').checked = @TONE@;
classes().find((input) => input.parentElement.textContent.trim() === "Post").checked = @POST@;
const before = new Set(all("#toasts .toast"));
all("button", form).find((button) => button.type === "submit").click();
found.toast = await until(() => all("#toasts .toast").filter((node) => !before.has(node)).map(text).find((entry) => entry.includes("ettings")), 15000);
await sleep(300);
found.after = [gear.getAttribute("aria-expanded"), Boolean(spot.querySelector("form.radio-settings"))];
document.getElementById("settings-open").click();
const sheet = await until(() => document.querySelector(".sheet-box form.settings"), 4000);
found.general = {sections: all("section > h3", sheet).map(text), radio: all('[aria-label="Squelch"], [aria-label="Classes"], [aria-label="Speakers"], .sound-row', sheet).length};
document.querySelector(".sheet-head button").click();
return found;
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@CLASHES@" , OVERLAP ) . replace ( "@FITS@" , FITS )
GEAR_READ = """(async () => {@HELPERS@@SPOT_OPEN@
const gear = spot ? all(".spot-head button", spot).find((button) => name(button) === "Radio settings") : null;
if (!gear) {
return {error: text(document.getElementById("main"))};
}
gear.click();
const form = await until(() => spot.querySelector("form.radio-settings"), 4000);
return {
squelch: Number(form.querySelector('input[aria-label="Squelch"]').value),
tone: form.querySelector('input[aria-label="All radio on"]').checked,
post: all('[aria-label="Classes"] input', form).find((input) => input.parentElement.textContent.trim() === "Post").checked,
@FITS@,
};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@SPOT_OPEN@" , SPOT_OPEN ) . replace ( "@FITS@" , FITS )
GRAPH_HOOK = """() => {
let made = null;
Object.defineProperty(window, "ForceGraph3D", {
configurable: true,
get: () => made && ((options) => {
const graph = made(options);
window.e2eGraphs = [...(window.e2eGraphs || []), graph];
return graph;
}),
set: (value) => {
made = value;
},
});
}"""
CAMERA = """(async () => {@HELPERS@
const ready = await until(() => window.e2eGraphs && document.querySelector("#main .graph3d canvas") && !text(document.querySelector("#main .space-note")).startsWith("Loading"), 15000);
if (!ready) {
return {error: text(document.querySelector("#main .graph"))};
}
const graph = window.e2eGraphs[window.e2eGraphs.length - 1];
const position = () => {
const at = graph.cameraPosition();
return [at.x, at.y, at.z];
};
await sleep(5500);
let [fed, stops] = [0, 0];
const stopped = graph.onEngineStop();
graph.onEngineStop(() => {
stops += 1;
stopped();
});
const feed = graph.graphData;
graph.graphData = function (...args) {
fed += args.length;
return feed.apply(graph, args);
};
const built = position();
const canvas = document.querySelector("#main .graph3d canvas");
const rect = canvas.getBoundingClientRect();
for (let turn = 0; turn < 4; turn += 1) {
canvas.dispatchEvent(new WheelEvent("wheel", {deltaY: -120, clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2, bubbles: true, cancelable: true}));
await sleep(60);
}
await sleep(900);
const moved = position();
const refreshes = [];
for (let round = 0; round < 2; round += 1) {
const [feeds, ends] = [fed, stops];
document.dispatchEvent(new Event("visibilitychange"));
const rebuilt = Boolean(await until(() => fed > feeds, 8000));
const settled = Boolean(await until(() => stops > ends, 10000));
await sleep(900);
refreshes.push({rebuilt, settled, camera: position()});
}
all("#main button").find((button) => name(button) === "Reset view").click();
await sleep(1500);
return {hooked: typeof stopped === "function", built, moved, refreshes, reset: position()};
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-07 19:59:47 +00:00
def tone ( seconds : int , rate : int ) -> bytes :
frames = array . array ( "h" , ( int ( 6000 * math . sin ( 2 * math . pi * 440 * n / rate )) for n in range ( seconds * rate )))
return wav_bytes ( 1 , 2 , rate , frames . tobytes ())
class Station ( http . server . BaseHTTPRequestHandler ):
def do_GET ( self ) -> None :
2026-11-08 13:37:02 +00:00
if self . server . moved :
self . send_response ( 302 )
self . send_header ( "Location" , self . server . moved )
self . send_header ( "Content-Length" , "0" )
self . end_headers ()
return
2026-11-07 19:59:47 +00:00
body = self . server . body
self . send_response ( 200 )
self . send_header ( "Content-Type" , "audio/wav" )
self . send_header ( "Content-Length" , str ( len ( body )))
self . end_headers ()
with contextlib . suppress ( OSError ):
self . wfile . write ( body )
def log_message ( self , * args : object ) -> None :
return
2026-11-07 15:03:14 +00:00
2026-11-07 18:14:21 +00:00
LABELS = """
const unlabelled = (root) => all("svg.bi", root).filter((svg) => !svg.closest(".sprite")).map((svg) => svg.parentElement).filter((owner) => !text(owner) && !(owner.getAttribute("title") && owner.getAttribute("aria-label"))).map((owner) => owner.outerHTML.slice(0, 160));
const wide = () => document.documentElement.scrollWidth > window.innerWidth + 1;
const rowOf = (words) => all("#main .inbox-list > li").find((li) => li.textContent.includes(words));
const press = (root, label) => all("button", root).find((button) => name(button) === label).click();
const out = () => document.querySelector("#main .console .console-out");
const atEnd = () => out().scrollTop + out().clientHeight >= out().scrollHeight - 12;
const line = () => text(document.querySelector("#main .console .console-status"));
"""
RUN_START = """(async () => {@HELPERS@@LABELS@
2026-11-08 17:26:51 +00:00
const row = await until(() => rowOf("Row"), 8000);
2026-11-07 18:14:21 +00:00
if (!row) {
return {error: text(document.getElementById("main"))};
}
2026-11-08 17:26:51 +00:00
press(row, "Run");
const toast = await until(() => all("#toasts .toast.wait").find((box) => box.textContent.includes("Run")), 8000);
2026-11-07 18:14:21 +00:00
const queued = await until(() => out() && line(), 15000);
2026-11-08 17:26:51 +00:00
await until(() => out().textContent.includes("Row 60"), 20000);
2026-11-07 18:14:21 +00:00
return {toast: text(toast), queued, running: line(), overflows: out().scrollHeight > out().clientHeight, followed: atEnd()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
RUN_HELD = """(async () => {@HELPERS@@LABELS@
out().scrollTop = 0;
await sleep(300);
2026-11-08 17:26:51 +00:00
await until(() => out().textContent.includes("Row 80"), 20000);
2026-11-07 18:14:21 +00:00
await sleep(300);
return {held: out().scrollTop, running: line()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
RUN_DONE = """(async () => {@HELPERS@@LABELS@
out().scrollTop = out().scrollHeight;
await until(() => document.querySelector("#main .console").dataset.status === "done", 20000);
await sleep(500);
const lines = out().textContent.trim().split(" \\ n");
return {done: line(), last: lines[lines.length - 1], lines: lines.length, followed: atEnd(), actions: all(".item-actions button", out().closest("li")).map(name)};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
LAYOUT = """(async () => {@HELPERS@@LABELS@
2026-11-08 17:26:51 +00:00
await until(() => document.querySelector("#main .console") && rowOf("Rule request"), 8000);
const rule = rowOf("Rule request");
2026-11-07 18:14:21 +00:00
return {
kinds: all("#main .inbox-list > li").map((li) => text(li.querySelector(".pill"))),
splits: all("#main .deny-split").map((group) => all("button", group).map(name)),
console: line(),
followed: atEnd(),
fits: out().getBoundingClientRect().right <= window.innerWidth,
dry: text(rule.querySelector(".dry-run-count")),
fields: all(".rule-request dt", rule).map(text),
2026-11-08 17:26:51 +00:00
accept: all("button", rule).filter((button) => name(button) === "Accept").length,
2026-11-07 18:14:21 +00:00
unlabelled: unlabelled(document),
wide: wide(),
};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
DENY_SHEET = """(async () => {@HELPERS@@LABELS@
const row = await until(() => rowOf("git push"), 8000);
2026-11-08 17:26:51 +00:00
press(row, "Deny via …");
2026-11-07 18:14:21 +00:00
const box = await until(() => document.querySelector(".sheet-box"), 4000);
return {
title: text(box.querySelector(".panel-title")),
channels: all(".deny-channels input[type=radio]", box).map((input) => [input.value, input.checked]),
hints: all(".deny-channel small", box).map(text),
reason: Boolean(box.querySelector("textarea")),
unlabelled: unlabelled(box),
wide: wide(),
};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
DENY = """(async () => {@HELPERS@@LABELS@
const row = await until(() => rowOf("Bash: make deploy"), 8000);
2026-11-08 17:26:51 +00:00
press(row, "Deny via …");
2026-11-07 18:14:21 +00:00
const box = await until(() => document.querySelector(".sheet-box"), 4000);
box.querySelector('input[value="call"]').click();
2026-11-08 17:26:51 +00:00
box.querySelector("textarea").value = "make the gate green first";
press(box.querySelector(".actions"), "Deny");
const undo = await until(() => all("#toasts .toast.wait").find((toast) => toast.textContent.includes("Denial")), 8000);
2026-11-07 18:14:21 +00:00
return {undo: text(undo), sheet: !document.getElementById("sheet").hidden};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
DENIED = """(async () => {@HELPERS@@LABELS@
return {gone: Boolean(await until(() => !rowOf("Bash: make deploy"), 20000))};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
ACCEPT = """(async () => {@HELPERS@@LABELS@
2026-11-08 17:26:51 +00:00
press(rowOf("Rule request"), "Accept");
const toast = await until(() => all("#toasts .toast").find((box) => box.textContent.includes("Created: Rule")), 15000);
await until(() => !rowOf("Rule request"), 8000);
return {toast: text(toast), left: Boolean(rowOf("Rule request"))};
2026-11-07 18:14:21 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
2026-11-09 06:53:55 +00:00
WATCH = """
const questions = [];
window.confirm = (question) => {
questions.push(question);
return true;
};
let asked = 0;
const get = navigator.credentials.get.bind(navigator.credentials);
navigator.credentials.get = (options) => {
asked += 1;
return @SIGNS@ ? get(options) : Promise.reject(new DOMException("The operator canceled.", "NotAllowedError"));
};
const posted = [];
const send = window.fetch.bind(window);
window.fetch = (path, init) => {
if (["/api/choices/sign", "/api/choices", "/api/sign", "/api/inbox/approve"].includes(String(path))) {
const body = JSON.parse(init.body);
posted.push([String(path), Object.keys(body).sort(), body.class || null, body.to || null]);
}
return send(path, init);
};
2026-11-09 22:22:46 +00:00
const bar = () => document.getElementById("choice-request");
2026-11-09 06:53:55 +00:00
const fresh = async (shown, prefix) => until(() => all("#toasts .toast").filter((box) => !shown.has(box)).map(text).find((entry) => entry.startsWith(prefix)), 20000);
const fits = () => ({tall: document.documentElement.scrollHeight > window.innerHeight, wide: document.documentElement.scrollWidth > window.innerWidth + 1});
"""
CHOICE_VIEW = """(async () => {@HELPERS@@LABELS@
2026-11-09 22:22:46 +00:00
const found = await until(() => document.getElementById("choice-request") && all("#main .inbox-list > li .options").length === @CARDS@ && document.querySelector('#choice-request input[type="checkbox"]'), 8000);
2026-11-09 06:53:55 +00:00
if (!found) {
return {error: text(document.getElementById("main"))};
}
2026-11-09 22:22:46 +00:00
const box = document.getElementById("choice-request");
2026-11-09 06:53:55 +00:00
const chip = document.querySelector('#main [aria-label="Task filters"] button[data-filter="operator"]');
2026-11-09 22:22:46 +00:00
const closed = all("button[data-channel]", box).filter((button) => button.checkVisibility()).length;
2026-11-09 06:53:55 +00:00
return {
kinds: [...new Set(all("#main .inbox-list > li").map((li) => text(li.querySelector(".pill"))))].sort(),
filter: [text(chip).replace(/[0-9]+$/, ""), chip.getAttribute("aria-pressed")],
channels: all("button[data-channel]", box).map((button) => [button.dataset.channel, name(button), button.disabled]),
fields: all("textarea, input:not([type=checkbox])", box).length,
2026-11-09 22:22:46 +00:00
agents: all('input[type="checkbox"]', box).map((input) => input.value),
multi: [name(box.querySelector("summary")), box.open, closed, text(box.querySelector(".menu-box h3 .count")), all('input[type="checkbox"]', box).filter((input) => input.checked).length],
bar: [box.parentElement.getAttribute("aria-label"), toolbarRows(), taskLayout()],
2026-11-09 06:53:55 +00:00
cards: all("#main .inbox-list > li").filter((li) => li.querySelector(".options")).map((li) => [text(li.querySelector(".pill")), all(".option strong", li).map(text)]),
choices: all("#main [data-column], #main .group-title, #main h2, #main h3").filter((node) => node.dataset.column === "choices" || text(node) === "Choices").length,
unlabelled: unlabelled(document),
tall: document.documentElement.scrollHeight > window.innerHeight,
wide: wide(),
};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
CHOICE_SEND = """(async () => {@HELPERS@@WATCH@
2026-11-09 22:22:46 +00:00
const menu = bar();
menu.querySelector("summary").click();
2026-11-09 06:53:55 +00:00
await sleep(200);
const boxes = all('input[type="checkbox"]', menu).slice(0, 2);
boxes.forEach((box) => box.click());
2026-11-09 22:22:46 +00:00
const counted = text(menu.querySelector(".menu-box h3 .count"));
const opened = [menu.open, all("button[data-channel]", menu).filter((button) => button.checkVisibility()).length];
2026-11-09 06:53:55 +00:00
const picked = boxes.map((box) => box.value).sort((a, b) => a.localeCompare(b));
const runs = [];
for (const button of all("button[data-channel]", bar())) {
const [before, prompts, sent] = [asked, questions.length, posted.length];
const shown = new Set(all("#toasts .toast"));
const disabled = button.disabled;
button.click();
const toast = await fresh(shown, "Choice request");
runs.push({channel: button.dataset.channel, disabled, asked: asked - before, question: questions.slice(prompts), posted: posted.slice(sent), toast});
}
2026-11-09 22:22:46 +00:00
return {picked, counted, opened, runs, fields: all("textarea, input:not([type=checkbox])", bar()).length, ...fits()};
2026-11-09 06:53:55 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@WATCH@" , WATCH )
CHOICE_ANSWER = """(async () => {@HELPERS@@WATCH@
2026-11-09 22:22:46 +00:00
bar().querySelector("summary").click();
await sleep(200);
const closed = [bar().open, all("button[data-channel]", bar()).filter((button) => button.checkVisibility()).length];
2026-11-09 06:53:55 +00:00
const card = await until(() => all("#main .inbox-list > li").find((li) => li.textContent.includes("@QUESTION@")), 8000);
if (!card) {
return {error: text(document.getElementById("main"))};
}
const option = all(".option", card).find((button) => text(button.querySelector("strong")) === "Wait");
const title = option.title;
const shown = new Set(all("#toasts .toast"));
option.click();
const toast = await fresh(shown, "Choice: Wait");
const gone = @SIGNS@ ? Boolean(await until(() => !all("#main .inbox-list > li").some((li) => li.textContent.includes("@QUESTION@")), 20000)) : null;
2026-11-09 22:22:46 +00:00
return {title, asked, posted, toast, gone, closed, ...fits()};
2026-11-09 06:53:55 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@WATCH@" , WATCH )
TASKS_FIT = """(async () => {@HELPERS@
2026-11-09 22:22:46 +00:00
await until(() => document.getElementById("choice-request") && document.querySelector("#main .tasks-body > *"), 8000);
2026-11-09 06:53:55 +00:00
document.getElementById("toasts").replaceChildren();
await sleep(300);
return {
hash: location.hash,
2026-11-09 22:22:46 +00:00
bar: all("#choice-request button[data-channel]").map((button) => button.dataset.channel),
rows: toolbarRows(),
layout: taskLayout(),
2026-11-09 06:53:55 +00:00
columns: all("#main .task-column").map((column) => column.dataset.column),
choices: all("#main [data-column], #main .group-title, #main h2, #main h3").filter((node) => node.dataset.column === "choices" || text(node) === "Choices").length,
tall: document.documentElement.scrollHeight > window.innerHeight,
wide: document.documentElement.scrollWidth > window.innerWidth + 1,
};
})()""" . replace ( "@HELPERS@" , HELPERS )
PLAN_HELPERS = """
2026-11-09 22:22:46 +00:00
const fits = () => ({tall: document.documentElement.scrollHeight > window.innerHeight, wide: document.documentElement.scrollWidth > window.innerWidth + 1, rows: toolbarRows(), layout: taskLayout()});
2026-11-09 06:53:55 +00:00
const modes = () => all('#main [aria-label="Estimation"] button').map((button) => [button.dataset.estimation, button.getAttribute("aria-pressed")]);
const requestShown = () => {
const button = document.getElementById("request-estimates");
return button ? !button.hidden && button.getClientRects().length > 0 : null;
};
const choose = async (select, value) => {
select.value = value;
select.dispatchEvent(new Event("change", {bubbles: true}));
await sleep(300);
};
const fresh = async (shown, prefix) => until(() => all("#toasts .toast").filter((box) => !shown.has(box)).map(text).find((entry) => entry.startsWith(prefix)), 20000);
"""
GANTT = """(async () => {@HELPERS@@PLAN_HELPERS@
const svg = await until(() => document.querySelector("#main svg.gantt"), 8000);
if (!svg) {
return {error: text(document.getElementById("main"))};
}
const forecast = document.querySelector('#main select[aria-label="Forecast"]');
const scroll = document.querySelector("#main .gantt-scroll");
const marks = () => all("#main svg.gantt [data-forecast]").map((mark) => [mark.dataset.task, mark.dataset.forecast === "true", Number(mark.dataset.start), Number(mark.dataset.end), mark.tagName.toLowerCase()]);
const found = {
hash: location.hash,
forecast: [forecast.value, all("option", forecast).map((option) => [option.value, text(option)])],
modes: modes(),
request: requestShown(),
bars: marks(),
edges: all("#main svg.gantt path.gantt-edge").map((path) => [path.dataset.kind, path.dataset.from, path.dataset.to, path.getAttribute("marker-end")]),
hatched: all("#main svg.gantt .gantt-bar.forecast").map((bar) => getComputedStyle(bar).fill.includes("gantt-hatch")),
inner: Boolean(scroll && scroll.closest(".tasks-body")),
...fits(),
};
await choose(forecast, "real");
found.real = marks().filter(([, hatched]) => hatched).length;
await choose(forecast, "points");
found.pointsOnly = marks().filter(([, hatched]) => hatched).map(([task, , start, end, tag]) => [task, end - start, tag]);
await choose(forecast, "always");
return found;
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS )
BURN = """(async () => {@HELPERS@@PLAN_HELPERS@
const svg = await until(() => document.querySelector("#main svg.burn"), 8000);
if (!svg) {
return {error: text(document.getElementById("main"))};
}
const scope = document.querySelector('#main select[aria-label="Scope"]');
const series = () => all("#main svg.burn path.burn-line").map((path) => [path.dataset.series, Number(path.dataset.last)]);
const found = {hash: location.hash, modes: modes(), request: requestShown(), scope: [scope.value, all("option", scope).map((option) => option.value)], series: series(), ...fits()};
await choose(scope, "agent:Docs");
found.agent = series();
await choose(scope, "all");
return found;
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS )
POINTS_ON = """(async () => {@HELPERS@@PLAN_HELPERS@
const before = {modes: modes(), request: requestShown()};
all('#main [aria-label="Estimation"] button').find((button) => button.dataset.estimation === "points").click();
const shown = await until(() => requestShown(), 8000);
return {before, modes: modes(), request: Boolean(shown), ...fits()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS )
REQUEST = """(async () => {@HELPERS@@PLAN_HELPERS@
const svg = await until(() => document.querySelector("#main svg.burn") && requestShown(), 8000);
if (!svg) {
return {error: text(document.getElementById("main")), modes: modes()};
}
const series = all("#main svg.burn path.burn-line").map((path) => [path.dataset.series, Number(path.dataset.last)]);
const shown = new Set(all("#toasts .toast"));
document.getElementById("request-estimates").click();
const toast = await fresh(shown, "Request estimates");
return {modes: modes(), series, toast, ...fits()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS )
BULK = """(async () => {@HELPERS@@PLAN_HELPERS@
const list = await until(() => all("#main .task-row").length && document.getElementById("bulk-value"), 8000);
if (!list) {
return {error: text(document.getElementById("main"))};
}
let asked = 0;
const get = navigator.credentials.get.bind(navigator.credentials);
navigator.credentials.get = (options) => {
asked += 1;
return @SIGNS@ ? get(options) : Promise.reject(new DOMException("The operator canceled.", "NotAllowedError"));
};
const posted = [];
const send = window.fetch.bind(window);
window.fetch = (path, init) => {
if (["/api/sign", "/api/tasks"].includes(String(path))) {
const body = JSON.parse(init.body);
posted.push([String(path), Object.keys(body).sort(), body.items.map((item) => [item.id, item.argv.join(" ")])]);
}
return send(path, init);
};
const rows = () => all("#main .task-row").map((row) => row.dataset.task);
const sort = document.querySelector('#main select[aria-label="Sort"]');
const direction = document.querySelector("#main button[data-direction]");
const found = {sorts: all("option", sort).map((option) => option.value), prio: rows()};
await choose(sort, "status");
found.status = rows();
direction.click();
await sleep(300);
found.statusDown = [rows(), name(direction)];
direction.click();
await choose(sort, "prio");
found.picks = all("#main .task-row input.task-pick").map((box) => box.closest(".task-row").dataset.task);
for (const task of ["work/next", "work/child"]) {
all("#main .task-row").find((row) => row.dataset.task === task).querySelector("input.task-pick").click();
}
const button = document.getElementById("bulk-value");
found.armed = [button.disabled, text(button.querySelector(".count"))];
await choose(document.querySelector('#main select[aria-label="Business value of the selection"]'), "@VALUE@");
const shown = new Set(all("#toasts .toast"));
button.click();
found.toast = await fresh(shown, "Business value @VALUE@ for 2 tasks");
found.asked = asked;
found.posted = posted;
found.after = [button.disabled, all("#main .task-row input.task-pick:checked").length];
Object.assign(found, fits());
all('#main [aria-label="Estimation"] button').find((entry) => entry.dataset.estimation === "none").click();
found.reset = Boolean(await until(() => requestShown() === false, 8000));
return found;
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS )
2026-11-09 22:22:46 +00:00
ETA_LIST = """(async () => {@HELPERS@@PLAN_HELPERS@
const list = await until(() => all("#main .task-row .task-eta").length >= 8, 8000);
if (!list) {
return {error: text(document.getElementById("main"))};
}
const number = (value) => (value === "" ? null : Number(value));
const read = () => Object.fromEntries(all("#main .task-row").map((row) => {
const eta = row.querySelector(".task-eta");
return [row.dataset.task, [number(eta.dataset.start), number(eta.dataset.eta), eta.dataset.forecast === "true", text(eta), eta.title]];
}));
const toggle = async (mode) => {
const before = JSON.stringify(read());
all('#main [aria-label="Estimation"] button').find((button) => button.dataset.estimation === mode).click();
await until(() => JSON.stringify(read()) !== before, 8000);
await sleep(300);
return read();
};
const found = {none: read(), modes: modes(), ...fits()};
found.points = await toggle("points");
found.back = await toggle("none");
return found;
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS )
TREND_READ = """
const trendOf = () => {
const select = document.querySelector('#main select[aria-label="Trend"]');
const days = document.querySelector('#main input[aria-label="Velocity days"]');
const note = document.querySelector("#main .burn-forecast");
const marker = document.querySelector("#main svg.burn .forecast-line");
return {
value: select.value, options: all("option", select).map((option) => [option.value, text(option)]), days: [Number(days.value), days.getClientRects().length > 0],
note: note ? [note.dataset.forecast === "" ? null : Number(note.dataset.forecast), text(note)] : null, marker: marker ? Number(marker.dataset.forecast) : null,
lines: all("#main svg.burn path.burn-trend").map((path) => [path.dataset.series, Number(path.dataset.from), Number(path.dataset.to)]),
points: all('#main svg.burn circle.burn-point[data-series="remaining"]').map((circle) => [Number(circle.dataset.t), Number(circle.dataset.value)]),
};
};
const chart = async () => until(() => document.querySelector("#main svg.burn") && document.querySelector('#main select[aria-label="Trend"]'), 8000);
"""
TREND_SET = """(async () => {@HELPERS@@PLAN_HELPERS@@TREND_READ@
if (!(await chart())) {
return {error: text(document.getElementById("main"))};
}
const before = trendOf();
const shown = new Set(all("#toasts .toast"));
await choose(document.querySelector('#main select[aria-label="Trend"]'), "@MODE@");
const toasts = [await fresh(shown, "Trend: ")];
if ("@MODE@" === "velocity") {
const days = document.querySelector('#main input[aria-label="Velocity days"]');
const again = new Set(all("#toasts .toast"));
days.value = "@DAYS@";
days.dispatchEvent(new Event("change", {bubbles: true}));
toasts.push(await fresh(again, "Trend: velocity over @DAYS@ days"));
}
await sleep(400);
return {before, after: trendOf(), toasts, ...fits()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS ) . replace ( "@TREND_READ@" , TREND_READ )
TREND_RELOADED = """(async () => {@HELPERS@@PLAN_HELPERS@@TREND_READ@
if (!(await chart())) {
return {error: text(document.getElementById("main"))};
}
await sleep(300);
return {...trendOf(), ...fits()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS ) . replace ( "@TREND_READ@" , TREND_READ )
STEP_TIPS = """(async () => {@HELPERS@@PLAN_HELPERS@@TREND_READ@
if (!(await chart())) {
return {error: text(document.getElementById("main"))};
}
const steps = all('#main svg.burn circle.burn-point[data-series="remaining"]').map((circle) => ({
t: Number(circle.dataset.t), started: circle.dataset.started, ended: circle.dataset.ended, joined: circle.dataset.joined, tip: text(circle.querySelector("title")),
series: all(`#main svg.burn circle.burn-point[data-step="$ {circle.dataset.step} "]`).map((mark) => mark.dataset.series),
}));
const shown = new Set(all("#toasts .toast"));
await choose(document.querySelector('#main select[aria-label="Trend"]'), "off");
const reset = await fresh(shown, "Trend: off");
await sleep(300);
return {steps, reset, note: Boolean(document.querySelector("#main .burn-forecast")), lines: all("#main svg.burn path.burn-trend").length, ...fits()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS ) . replace ( "@TREND_READ@" , TREND_READ )
TASKS_3D = """(async () => {@HELPERS@@PLAN_HELPERS@
const note = () => text(document.querySelector("#main .space-note")) || "";
const ready = await until(() => window.e2eGraphs && document.querySelector("#main .graph3d canvas") && !note().startsWith("Loading"), 15000);
if (!ready) {
return {error: text(document.querySelector("#main .graph"))};
}
const graph = () => window.e2eGraphs[window.e2eGraphs.length - 1];
const id = (end) => (typeof end === "object" ? end.id : end);
const look = () => {
const data = graph().graphData();
const color = graph().linkColor();
return {
note: note(), layouts: all('#main [aria-label="Layout"] button').map((button) => [button.dataset.layout, button.getAttribute("aria-pressed")]),
nodes: data.nodes.map((node) => [node.id, Number.isFinite(node.fz) ? node.fz : null, Number.isFinite(node.ts) ? node.ts : null]),
links: data.links.map((link) => [id(link.source), id(link.target), link.kind, color(link)]),
label: (() => {
const shown = graph().nodeLabel()(data.nodes.find((node) => node.id === "work/t5"));
return shown instanceof Element ? shown.textContent : String(shown);
})(),
};
};
const layout = async (mode, word) => {
all('#main [aria-label="Layout"] button').find((button) => button.dataset.layout === mode).click();
await until(() => note().endsWith(word), 8000);
await sleep(400);
return look();
};
const css = getComputedStyle(document.documentElement);
const palette = Object.fromEntries(["violet", "info", "warn"].map((role) => [role, css.getPropertyValue(`--$ {role} `).trim()]));
const force = look();
const time = await layout("time", "Time");
const field = document.querySelector('#main input[aria-label="Find a node"]');
field.value = "work/t5";
field.dispatchEvent(new Event("input", {bubbles: true}));
await sleep(200);
const chip = all("#main .hits .chip").find((node) => node.dataset.hit === "work/t5");
if (!chip) {
return {error: "no search hit", force, time};
}
chip.click();
await sleep(600);
const dialog = document.getElementById("popup");
const eta = dialog.querySelector(".task-eta");
const popup = {
open: dialog.open, title: text(dialog.querySelector(".popup-box .panel-title")), id: text(dialog.querySelector(".popup-head .task-id")), picked: document.querySelector("#main .graph3d").dataset.picked || null,
eta: eta ? eta.dataset.eta : null, open_button: name(dialog.querySelector(".popup-body .actions button")), line: dialog.querySelector(".leader line").getAttribute("display") !== "none",
};
(document.activeElement || document.body).dispatchEvent(new KeyboardEvent("keydown", {key: "Escape", bubbles: true}));
await sleep(300);
popup.closed = [dialog.open, document.querySelector("#main .graph3d").dataset.picked || null];
const back = await layout("none", "Force");
await sleep(1200);
return {palette, force, time, popup, back: back.note, ...fits()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS )
2026-11-09 06:53:55 +00:00
PLAN_VALUES = { 1920 : "13" , 1366 : "21" , 390 : "34" }
2026-11-09 22:22:46 +00:00
TREND_DAYS = { 1920 : 7 , 1366 : 3 , 390 : 14 }
2026-11-09 06:53:55 +00:00
STATUS_ORDER = [ "queued" , "awaiting" , "claimed" , "running" , "blocked" , "stale" , "done" , "stopped" ]
CHOICE_REQUEST = "send me your open decisions as choices"
CHOICE_CHANNELS = [ "post" , "call" , "newsletter" , "broadcast" , "pan-pan" , "security" , "mayday" ]
2026-11-09 22:22:46 +00:00
NIGHT_HINTS = [
( "Drain" , "Drain: the agents finish their task and take nothing new; your passkey signs it" ),
( "Suspend" , "Suspend: the agents park their work, write the handover and idle; your passkey signs it" ),
( "Resume" , "Resume: all agents continue from their handover; your passkey signs it" ),
]
2026-11-09 06:53:55 +00:00
SLOW = "seq -f 'Row %g ' 1 60; sleep 4; seq -f 'Row %g ' 61 80; sleep 4; echo finished"
2026-11-07 18:14:21 +00:00
2026-11-07 15:03:14 +00:00
def labelled ( probe : dict ) -> dict :
return { key : probe [ key ] for key in ( "unlabelled" , "broken" )}
2026-11-06 22:20:06 +00:00
@unittest.skipUnless ( shutil . which ( os . environ . get ( "CHROMIUM" , "chromium" )), "no Chromium" )
class TestOrchestratorE2E ( HubCase ):
def setUp ( self ):
super () . setUp ()
OUT . mkdir ( parents = True , exist_ok = True )
now = time . time ()
2026-11-07 23:43:26 +00:00
self . root = self . write_tree ()
feature = f " { self . root } /worktrees/github.com/me/demo/feature"
2026-11-06 22:20:06 +00:00
self . claim ( "s1" , "Skills session" )
self . write_council ({
"work/s2/claim.json" : { "office" : "work/s2" , "session" : "s2" , "runtime" : "claude" , "name" : "Stage" , "since" : iso ( now - 7200 ), "beat" : now - 2000 , "work" : "x" },
2026-11-07 17:46:39 +00:00
"chair/claim.json" : { "office" : "chair" , "session" : "s1" , "runtime" : "claude" , "name" : "Skills session" , "since" : iso ( now - 7200 ), "beat" : now , "work" : "allocate" },
"identity/oidc/claim.json" : { "office" : "identity/oidc" , "session" : "s2" , "runtime" : "claude" , "name" : "Stage" , "since" : iso ( now - 7200 ), "beat" : now - 2000 , "work" : "oidc line" },
2026-11-07 23:43:26 +00:00
"rasci.json" : {
"work/stage" : { "title" : "Stage line" , "status" : "running" , "worker" : "Stage (this session)" , "roles" : { "Stage (this session)" : "RA" }},
"work/feature" : {
2026-11-08 17:26:51 +00:00
"title" : "Show the merge flow in the hub" , "status" : "running" , "worker" : "Skills session" , "objective" : "The operator sees where a branch flows" , "workdir" : feature , "prio" : 1 ,
"roles" : { "Skills session" : "RA" , "Operator" : "C" , "Docs" : "I" }, "key_results" : [{ "text" : "Flow in the tree" , "met" : True }, { "text" : "Flow in Tasks" , "met" : False }],
2026-11-07 23:43:26 +00:00
"estimates" : { "Skills" : { "value" : 8 , "complexity" : 3 , "at" : iso ( now - 600 )}},
},
2026-11-08 17:26:51 +00:00
"work/docs" : { "title" : "Update the README" , "status" : "blocked" , "worker" : "Docs" , "branch" : "main" , "roles" : { "Docs" : "R" , "Maintainer" : "A" , "Skills" : "S" }, "estimates" : { "Docs" : { "value" : 3 , "complexity" : 2 }}},
"work/old" : { "title" : "Old line" , "status" : "done" , "worker" : "Stage" , "end" : iso ( now - 86400 ), "roles" : { "Stage" : "RA" }},
2026-11-07 23:43:26 +00:00
"operator/push" : { "title" : "git push" , "workdir" : "/repo" , "status" : "done" , "roles" : { "Operator" : "R" }},
},
"queue.json" : { "seq" : 3 , "entries" : [{ "id" : "gate-3" , "kind" : "gate" , "task" : "work/feature" , "workdir" : feature , "session" : "s1" , "name" : "Skills" , "status" : "waiting" , "enqueued" : now - 120 , "cmd" : "make test" }]},
2026-11-06 22:20:06 +00:00
"radio-silence.json" : { "since" : now - 60 , "until" : now + 3600 , "by" : "Docs" , "text" : "disk full" },
"news.jsonl" : self . jsonl ([
{ "ts" : now - 600 , "from" : "Skills" , "to" : [ "Stage" ], "text" : "rebase please" , "class" : "post" , "v" : 2 },
{ "ts" : now - 500 , "from" : "Stage" , "to" : [ "Skills" ], "text" : "gate red" , "class" : "call" , "v" : 2 },
{ "ts" : now - 400 , "from" : "Maintainer" , "to" : [], "text" : "main is green" , "class" : "newsletter" , "v" : 2 },
{ "ts" : now - 300 , "from" : "Maintainer" , "to" : [], "text" : "rotate keys" , "class" : "security" , "v" : 2 },
{ "ts" : now - 60 , "from" : "Docs" , "to" : [], "text" : "MAYDAY disk full" , "class" : "mayday" , "v" : 2 },
]),
})
2026-11-07 15:03:14 +00:00
self . subagents ( now )
2026-11-06 22:20:06 +00:00
self . request ( "feat: orchestrator" , "" )
2026-11-09 06:53:55 +00:00
self . create_ask ({ ** ASK , "timeout" : 3600 })
2026-11-06 22:20:06 +00:00
self . base = f "http:// { self . host } /"
2026-11-07 15:03:14 +00:00
def subagents ( self , now : float ) -> None :
def line ( ts : float ) -> dict :
return { "type" : "user" , "sessionId" : "s1" , "timestamp" : iso ( ts ), "message" : { "role" : "user" , "content" : "x" }}
folder = self . projects / "-repo" / "s1" / "subagents"
folder . mkdir ( parents = True )
( self . projects / "-repo" / "s1.jsonl" ) . write_text ( self . jsonl ([ line ( now - 3600 ), line ( now - 30 )]), encoding = "utf-8" )
for ident , finished in (( "abc" , True ), ( "def" , False )):
( folder / f "agent- { ident } .meta.json" ) . write_text ( json . dumps ({ "agentType" : "Explore" , "description" : f "look { ident } " , "spawnDepth" : 1 , "finished" : finished }), encoding = "utf-8" )
( folder / f "agent- { ident } .jsonl" ) . write_text ( self . jsonl ([ line ( now - 1800 ), line ( now - 60 )]), encoding = "utf-8" )
2026-11-07 19:59:47 +00:00
def drive ( self , steps : list [ dict ], authenticator : dict | None , driver : str = "cdp.mjs" , binary : str = os . environ . get ( "CHROMIUM" , "chromium" ), init : str | None = None ) -> dict :
2026-11-06 22:20:06 +00:00
with tempfile . NamedTemporaryFile ( "w" , suffix = ".json" , delete = False ) as handle :
2026-11-07 19:59:47 +00:00
json . dump ({ "steps" : steps , "authenticator" : authenticator , "init" : init }, handle )
2026-11-06 22:20:06 +00:00
self . addCleanup ( os . unlink , handle . name )
2026-11-07 15:03:14 +00:00
done = subprocess . run ([ "node" , str ( HERE / driver ), binary , handle . name ], capture_output = True , text = True , timeout = 900 , check = False )
2026-11-06 22:20:06 +00:00
self . assertEqual ( done . returncode , 0 , done . stderr [ - 2000 :])
return json . loads ( done . stdout )
2026-11-07 15:03:14 +00:00
def beside ( self , work : Callable [[], None ]) -> None :
stop = threading . Event ()
def loop () -> None :
while not stop . wait ( 0.2 ):
work ()
thread = threading . Thread ( target = loop , daemon = True )
thread . start ()
self . addCleanup ( thread . join , 10 )
self . addCleanup ( stop . set )
def shot ( self , name : str ) -> str :
return str ( OUT / f "chromium- { name } .png" )
2026-11-07 19:59:47 +00:00
def authenticator ( self ) -> dict :
signer = self . pin ()
private = "" . join ( line for line in signer . pem . read_text ( encoding = "utf-8" ) . splitlines () if not line . startswith ( "-----" ))
return { "credentialId" : base64 . b64encode ( signer . raw_id ) . decode (), "privateKey" : private , "rpId" : "localhost" }
def speaks ( self , cues : tuple [ str , ... ]) -> None :
hub = self . httpd . hub
2026-11-08 10:23:25 +00:00
hub . settings . put ({ ** hub . settings . get (), "speech" : { "on" : True , "classes" : [ "mayday" , "pan-pan" , "security" ], "muted" : []}}, hub . settings . get ())
2026-11-07 19:59:47 +00:00
spoken = tone ( 6 , 8000 )
def synthesize ( body : bytes ) -> bytes :
word = json . loads ( body )[ "text" ]
if word in cues :
2026-11-08 17:26:51 +00:00
row = { "ts" : time . time (), "from" : "Stage" , "to" : [], "text" : f "Disk almost full ( { word } ) \n second line" , "class" : "security" , "v" : 2 }
2026-11-07 19:59:47 +00:00
with ( self . council / "news.jsonl" ) . open ( "a" , encoding = "utf-8" ) as handle :
handle . write ( self . jsonl ([ row ]))
return spoken
patch = mock . patch . object ( hub . speech , "synthesize" , synthesize )
patch . start ()
self . addCleanup ( patch . stop )
2026-11-08 13:37:02 +00:00
def station ( self , tls : ssl . SSLContext | None = None , moved : str | None = None ) -> str :
"""Args:
tls: serves https with this context instead of plain http.
moved: answers every request with a 302 to this URL instead of
the tone.
"""
2026-11-07 19:59:47 +00:00
httpd = http . server . ThreadingHTTPServer (( "127.0.0.1" , 0 ), Station )
2026-11-08 13:37:02 +00:00
httpd . body , httpd . moved = tone ( 30 , 22050 ), moved
if tls :
httpd . socket = tls . wrap_socket ( httpd . socket , server_side = True )
2026-11-07 19:59:47 +00:00
thread = threading . Thread ( target = httpd . serve_forever , args = ( 0.05 ,), daemon = True )
thread . start ()
self . addCleanup ( httpd . server_close )
self . addCleanup ( thread . join , 10 )
self . addCleanup ( httpd . shutdown )
2026-11-08 13:37:02 +00:00
return f " { 'https' if tls else 'http' } ://127.0.0.1: { httpd . server_address [ 1 ] }{ DLF_PATH if moved else '/tone.wav' } "
def redirected ( self ) -> tuple [ str , list [ str ]]:
"""Returns:
A station that replays the recorded Deutschlandfunk chain, a 302
from st01.sslstream.dlf.de to an f111.rndfnk.com node, as a 302 to
a second https origin, and that origin as its released via.
"""
folder = Path ( tempfile . mkdtemp ())
self . addCleanup ( shutil . rmtree , folder )
key , cert = folder / "key.pem" , folder / "cert.pem"
subprocess . run ([
"openssl" , "req" , "-x509" , "-newkey" , "ec" , "-pkeyopt" , "ec_paramgen_curve:P-256" , "-nodes" , "-subj" , "/CN=127.0.0.1" ,
"-addext" , "subjectAltName=IP:127.0.0.1" , "-days" , "1" , "-keyout" , str ( key ), "-out" , str ( cert ),
], capture_output = True , timeout = 60 , check = True )
tls = ssl . SSLContext ( ssl . PROTOCOL_TLS_SERVER )
tls . load_cert_chain ( cert , key )
node = urlsplit ( self . station ( tls ))
origin = f "https:// { node . netloc } "
return self . station ( tls , f " { origin }{ DLF_NODE } " ), [ origin ]
2026-11-07 19:59:47 +00:00
def check_overlaps ( self , browser : str , driver : str , binary : str ) -> None :
steps = []
for width , height in WIDTHS :
steps . append ({ "width" : width , "height" : height , "url" : f " { self . base } #stream/rules" , "wait" : 900 , "probe" : RULES_OVERLAP , "shot" : str ( OUT / f " { browser } - { width } -rules-form.png" )})
steps . append ({ "width" : width , "height" : height , "url" : None , "wait" : 0 , "probe" : SETTINGS_OVERLAP , "shot" : str ( OUT / f " { browser } - { width } -settings-form.png" )})
data = self . drive ( steps , None , driver , binary )
( OUT / f " { browser } -overlap.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
2026-11-08 17:26:51 +00:00
hidden = { "decision" : ( "Tool (glob)" , "Directory (glob)" ), "review" : ( "Tool (glob)" ,), "ask" : ()}
2026-11-07 19:59:47 +00:00
for index , ( width , _ ) in enumerate ( WIDTHS ):
rules , settings = data [ "results" ][ 2 * index : 2 * index + 2 ]
with self . subTest ( browser = browser , width = width ):
self . assertNotIn ( "error" , rules )
self . assertNotIn ( "error" , settings )
for kind , found in rules . items ():
self . assertEqual (( found [ "found" ], found [ "outside" ]), ([], []), kind )
self . assertEqual ([ label for label in hidden [ kind ] if label in found [ "labels" ]], [], kind )
2026-11-08 17:26:51 +00:00
self . assertIn ( "Directory (glob)" , rules [ "review" ][ "labels" ])
2026-11-07 19:59:47 +00:00
self . assertEqual ( settings , { "found" : [], "outside" : []})
2026-11-08 13:37:02 +00:00
def check_radio ( self , browser : str , driver : str , binary : str , redirected : bool = False ) -> dict :
2026-11-07 19:59:47 +00:00
"""Args:
driver: cdp.mjs saves the station through the settings form and
signs its new origin with a virtual passkey; bidi.mjs has no
virtual authenticator, so the station is saved before the page
loads.
2026-11-08 13:37:02 +00:00
redirected: plays the recorded Deutschlandfunk redirect chain,
saved before the page loads with its resolved via.
2026-11-07 19:59:47 +00:00
"""
2026-11-08 13:37:02 +00:00
station , via = self . redirected () if redirected else ( self . station (), [])
browser = f " { browser } -dlf" if redirected else browser
signs = driver == "cdp.mjs" and not redirected
2026-11-07 19:59:47 +00:00
if signs :
first = { "width" : 1366 , "height" : 768 , "url" : f "http://localhost: { self . port } /#stream" , "wait" : 900 , "probe" : ADD_STATION . replace ( "@STATION@" , station ) . replace ( "@NAME@" , STATION_NAME ), "shot" : None , "gesture" : True }
else :
hub = self . httpd . hub
2026-11-08 13:37:02 +00:00
hub . settings . put ({ ** hub . settings . get (), "streams" : [{ "name" : STATION_NAME , "url" : station , "from" : "00:00" , "to" : "24:00" , "days" : list ( range ( 7 )), "on" : True , "via" : via }]}, hub . settings . get ())
2026-11-07 19:59:47 +00:00
first = { "width" : 1366 , "height" : 768 , "url" : f " { self . base } #stream" , "wait" : 900 , "probe" : None , "shot" : None }
steps = [
first ,
{ "width" : 390 , "height" : 844 , "url" : None , "wait" : 3000 , "probe" : RADIO_VIEW , "shot" : str ( OUT / f " { browser } -390-radio-station.png" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : RADIO_CLICK , "shot" : str ( OUT / f " { browser } -1366-radio-playing.png" ), "gesture" : True },
]
data = self . drive ( steps , self . authenticator () if signs else None , driver , binary , MEDIA_LOG )
( OUT / f " { browser } -radio.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
saved , view , click = data [ "results" ]
if signs :
2026-11-08 17:26:51 +00:00
self . assertTrue ( any ( toast . startswith ( "New station" ) for toast in saved [ "toasts" ]), saved )
2026-11-07 19:59:47 +00:00
self . assertNotIn ( "error" , view )
self . assertEqual ( view [ "title" ], STATION_NAME )
self . assertEqual ( view [ "line" ], [ "nowrap" , "ellipsis" , True , True ])
blocked = [ entry for entry in view [ "log" ] if entry . get ( "play" ) == "NotAllowedError" ]
if not view [ "media" ][ "playing" ]:
self . assertTrue ( blocked , view )
self . assertEqual ( view [ "state" ], "blocked" , view )
2026-11-08 17:26:51 +00:00
self . assertIn ( "Click" , view [ "note" ])
self . assertTrue ( any ( "blocked" in toast for toast in view [ "toasts" ]), view [ "toasts" ])
2026-11-07 19:59:47 +00:00
self . assertTrue ( click [ "clicked" ], click )
self . assertNotIn ( "error" , click )
self . assertTrue ( click [ "media" ][ "playing" ], click )
self . assertEqual ( click [ "state" ], "playing" , click )
self . assertEqual ([ entry for entry in click [ "log" ] if entry . get ( "csp" ) == "media-src" ], [])
return data
2026-11-08 21:18:55 +00:00
def popped ( self , result : dict , title : str ) -> None :
before , shown , gone = result [ "before" ], result [ "shown" ], result [ "gone" ]
self . assertEqual (( before [ "picked" ], before [ "red" ]), ( False , False ))
self . assertEqual (( shown [ "open" ], shown [ "picked" ], shown [ "red" ], shown [ "fade" ], shown [ "title" ]), ( True , True , True , [ True , "fade" ], title ), shown )
self . assertGreater ( shown [ "size" ], before [ "size" ] * 1.4 )
self . assertLess ( shown [ "opacity" ], 1 )
self . assertIsNotNone ( shown [ "line" ], shown )
self . assertTrue ( shown [ "onBox" ] and shown [ "inside" ], shown )
self . assertEqual (( not shown [ "tall" ], shown [ "wide" ], shown [ "panel" ]), ( result [ "flat" ], False , 0 ))
self . assertEqual ( gone , { "picked" : False , "size" : before [ "size" ], "red" : False , "open" : False , "outside" : True })
self . assertTrue ( result [ "last" ])
def check_popups ( self , browser : str , driver : str , binary : str ) -> None :
steps = []
for width , height in WIDTHS :
steps . append ({ "width" : width , "height" : height , "url" : f " { self . base } #agents/orbit" , "wait" : 1200 , "probe" : POPUP_ORBIT , "shot" : str ( OUT / f " { browser } - { width } -agents-orbit-popup.png" )})
steps . append ({ "width" : width , "height" : height , "url" : f " { self . base } #artifacts/orbit" , "wait" : 1200 , "probe" : POPUP_ARTIFACT , "shot" : str ( OUT / f " { browser } - { width } -artifacts-orbit-popup.png" )})
for tab , query , prefix in (( "artifacts" , "feature" , "branch:" ), ( "agents" , "sta" , "s2" )):
steps . append ({ "width" : 1366 , "height" : 768 , "url" : f " { self . base } # { tab } /3d" , "wait" : 900 , "probe" : PICK_3D . replace ( "@QUERY@" , query ) . replace ( "@PREFIX@" , prefix ), "shot" : str ( OUT / f " { browser } -1366- { tab } -3d-picked.png" )})
data = self . drive ( steps , None , driver , binary )
( OUT / f " { browser } -popups.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ([ error for error in data [ "errors" ] if "(style-src-elem)" not in error and "/favicon.ico" not in error ], [])
results = data [ "results" ]
for index , ( width , _ ) in enumerate ( WIDTHS ):
agent , artifact = results [ 2 * index : 2 * index + 2 ]
with self . subTest ( browser = browser , width = width ):
self . assertNotIn ( "error" , agent )
self . assertNotIn ( "error" , artifact )
self . assertTrue ( agent [ "flat" ], "#agents/orbit scrolls" )
self . popped ( agent , "Stage" )
2026-11-08 22:06:31 +00:00
self . assertEqual ( agent [ "shown" ][ "face" ], { "key" : "s2" , "svg" : True , "text" : "" })
2026-11-08 21:18:55 +00:00
self . assertEqual ( agent [ "shown" ][ "sections" ], [ "Tasks" , "Claims" , "Subagents" , "History" ])
self . assertTrue ( agent [ "shown" ][ "composer" ])
self . assertRegex ( agent [ "shown" ][ "offices" ], r "^Offices: .*identity/oidc" )
escaped = agent [ "escaped" ]
self . assertEqual (( escaped [ "again" ], escaped [ "open" ], escaped [ "picked" ], escaped [ "red" ], escaped [ "size" ]), ( True , False , False , False , agent [ "before" ][ "size" ]))
self . popped ( artifact , artifact [ "shown" ][ "label" ])
for found , tab in zip ( results [ - 2 :], ( "artifacts" , "agents" ), strict = True ):
with self . subTest ( browser = browser , tab = tab ):
self . assertNotIn ( "error" , found )
self . assertEqual ( found [ "empty" ], [])
labels = [ label for _ , label in found [ "hits" ]]
self . assertEqual ( len ( labels ), len ( set ( labels )), labels )
self . assertLessEqual ( len ( labels ), 8 )
opened = found [ "opened" ]
self . assertEqual (( opened [ "open" ], opened [ "title" ], opened [ "picked" ], opened [ "line" ], opened [ "lists" ], opened [ "flat" ]), ( True , opened [ "label" ], opened [ "hit" ], True , 0 , found [ "flat" ]), opened )
self . assertEqual ( found [ "closed" ], { "open" : False , "picked" : None })
artifacts , agents = results [ - 2 :]
self . assertEqual (( artifacts [ "twins" ], [ label for _ , label in artifacts [ "twin" ]]), ( 2 , [ "main" ]))
self . assertTrue ( artifacts [ "opened" ][ "hit" ] . startswith ( "branch:" ), artifacts [ "opened" ])
self . assertEqual (( agents [ "hits" ], agents [ "opened" ][ "title" ]), ([[ "s2" , "Stage" ]], "Stage" ))
def test_chromium_opens_agent_and_node_popups_and_finds_nodes_at_three_widths ( self ):
self . check_popups ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ))
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_opens_agent_and_node_popups_and_finds_nodes_at_three_widths ( self ):
self . check_popups ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ))
2026-11-07 19:59:47 +00:00
def test_chromium_form_controls_never_overlap ( self ):
self . check_overlaps ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ))
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_form_controls_never_overlap ( self ):
self . check_overlaps ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ))
def test_chromium_plays_a_saved_station ( self ):
self . check_radio ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ))
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_plays_a_saved_station ( self ):
self . check_radio ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ))
2026-11-08 13:37:02 +00:00
def test_chromium_plays_deutschlandfunk_through_its_redirect ( self ):
self . check_radio ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ), redirected = True )
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_plays_deutschlandfunk_through_its_redirect ( self ):
self . check_radio ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ), redirected = True )
2026-11-06 22:20:06 +00:00
def test_chromium_walks_every_screen_at_three_widths ( self ):
2026-11-07 15:03:14 +00:00
steps = [{ "width" : width , "height" : height , "url" : f " { self . base } # { screen } " , "wait" : 900 , "probe" : PROBE , "shot" : self . shot ( f " { width } - { screen . replace ( '/' , '-' ) } " )} for width , height in WIDTHS for screen in SCREENS ]
2026-11-06 22:20:06 +00:00
steps += [{ "width" : 1366 , "height" : 768 , "url" : f " { self . base } # { old } " , "wait" : 600 , "probe" : PROBE , "shot" : None } for old in ( "graph" , "graph/chord" , "tree" )]
steps . append ({ "width" : 1366 , "height" : 768 , "url" : f " { self . base } #stream" , "wait" : 900 , "probe" : ACTIONS , "shot" : None })
2026-11-07 15:03:14 +00:00
steps . append ({ "width" : 1366 , "height" : 768 , "url" : f " { self . base } #artifacts/3d" , "wait" : 900 , "probe" : PROBE , "shot" : self . shot ( "1366-artifacts-3d-reduced-motion" ), "media" : [{ "name" : "prefers-reduced-motion" , "value" : "reduce" }]})
2026-11-08 17:26:51 +00:00
steps . append ({ "width" : 1366 , "height" : 768 , "url" : f " { self . base } #tasks/list" , "wait" : 900 , "probe" : INBOX_CHIP , "shot" : self . shot ( "1366-tasks-grant" )})
2026-11-07 15:03:14 +00:00
data = self . drive ( steps , None )
( OUT / "chromium-walk.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
2026-11-06 22:20:06 +00:00
self . assertEqual ( data [ "errors" ], [])
probes = data [ "results" ][: len ( WIDTHS ) * len ( SCREENS )]
for probe , step in zip ( probes , steps , strict = False ):
with self . subTest ( url = step [ "url" ], width = step [ "width" ]):
self . assertNotIn ( "error" , probe )
self . assertEqual (( probe [ "title" ], probe [ "h1" ]), ( "(2) Agent Orchestrator" , "Agent Orchestrator" ))
self . assertEqual ( probe [ "header" ], [ "palette-open" , "settings-open" ])
2026-11-07 23:43:26 +00:00
self . assertEqual (( probe [ "tabs" ], probe [ "subs" ]), ( TABS , SUBS ))
self . assertEqual ( probe [ "badges" ], { "tab-stream" : None , "tab-inbox" : "2" , "tab-rules" : None , "tab-radio" : None , "tab-agents" : "1" , "tab-artifacts" : None , "tab-tasks" : None })
2026-11-07 15:03:14 +00:00
self . assertEqual ( probe [ "banner" ], "MAYDAY · Docs: disk full" )
2026-11-06 22:20:06 +00:00
self . assertFalse ( probe [ "rail" ])
self . assertEqual (( probe [ "time" ], probe [ "replay" ]), ( "24 h ▾" , True ))
2026-11-07 15:03:14 +00:00
self . assertGreater ( probe [ "icons" ], 8 )
self . assertEqual ( labelled ( probe ), { "unlabelled" : [], "broken" : []})
2026-11-06 22:20:06 +00:00
self . assertFalse ( probe [ "wide" ], "the page scrolls sideways" )
2026-11-08 21:18:55 +00:00
self . assertEqual (( probe [ "panel" ], probe [ "nodeChips" ]), ( 0 , 0 ))
2026-11-09 22:22:46 +00:00
self . assertEqual ( probe [ "composers" ], [ "spot" ] if probe [ "hash" ] . startswith ( "#agents/" ) else [])
2026-11-08 13:37:02 +00:00
self . assertEqual ([ probe [ "tall" ] for probe in probes if probe [ "hash" ] == "#agents/orbit" ], [ False ] * len ( WIDTHS ))
2026-11-06 22:20:06 +00:00
by_hash = { probe [ "hash" ]: probe for probe in probes }
stream = by_hash [ "#stream" ]
2026-11-08 17:26:51 +00:00
self . assertEqual ([ probe [ "chips" ] for probe in probes if probe [ "hash" ] == "#stream" ], [[ "For you" , "Radio" , "Work" , "Transcripts" ]] * len ( WIDTHS ))
2026-11-07 15:03:14 +00:00
self . assertEqual (( stream [ "menus" ], stream [ "custom" ]), ([ "Filter" ], True ))
2026-11-06 22:20:06 +00:00
self . assertEqual ( by_hash [ "#stream/inbox" ][ "inbox" ], 2 )
2026-11-08 17:26:51 +00:00
for tab , own in (( "agents" , [ "Hierarchy" ]), ( "artifacts" , [])):
2026-11-06 22:20:06 +00:00
with self . subTest ( tab = tab ):
2026-11-07 17:46:39 +00:00
self . assertEqual ( by_hash [ f "# { tab } /orbit" ][ "views" ], [ "Orbit" , "Chord" , "Swimlane" , "Tree" , "3D" , * own ])
2026-11-08 17:26:51 +00:00
self . assertEqual ( by_hash [ f "# { tab } /3d" ][ "layouts" ], [ "Force" , "Time" , "Spawn depth" ])
2026-11-07 15:03:14 +00:00
self . assertTrue ( by_hash [ f "# { tab } /3d" ][ "canvas" ], by_hash [ f "# { tab } /3d" ][ "note" ])
2026-11-08 17:26:51 +00:00
self . assertRegex ( by_hash [ f "# { tab } /3d" ][ "note" ], r "^\d+ nodes · \d+ edges · Force$" )
2026-11-06 22:20:06 +00:00
agents = by_hash [ "#agents/orbit" ]
2026-11-09 22:22:46 +00:00
self . assertEqual ( agents [ "toolbar" ], [ "Radio" , "Priority" , "Day/Night" ])
self . assertEqual ( agents [ "spot" ], { "hidden" : True , "channels" : CHOICE_CHANNELS })
2026-11-07 17:46:39 +00:00
hierarchy = by_hash [ "#agents/hierarchy" ]
self . assertEqual ([ office for office , _ in hierarchy [ "org" ]], [ "chair" , "identity/oidc" , "work/s1" , "work/s2" ])
2026-11-08 17:26:51 +00:00
self . assertEqual ( hierarchy [ "toggles" ], [[ "Collapse: Operator" , "true" ], [ "Collapse: Chair" , "true" ], [ "Collapse: identity" , "true" ], [ "Collapse: Presidents" , "true" ]])
2026-11-07 17:46:39 +00:00
self . assertEqual ( hierarchy [ "toolbar" ], agents [ "toolbar" ])
self . assertGreaterEqual ( len ( hierarchy [ "order" ]), 2 )
2026-11-07 15:03:14 +00:00
self . assertEqual ( by_hash [ "#agents/tree" ][ "spawn" ][ 0 ], [ "operator" , None ])
self . assertIn ([ "twig" , "true" ], by_hash [ "#agents/tree" ][ "spawn" ])
self . assertIn ([ "twig" , "false" ], by_hash [ "#agents/tree" ][ "spawn" ])
artifacts = by_hash [ "#artifacts/tree" ]
2026-11-08 17:26:51 +00:00
self . assertEqual ( artifacts [ "deletes" ], [[ "Delete all merged" , True , "Delete all merged blocked: Secret Service locked" ], [ "Delete" , True , "Delete blocked: Secret Service locked" ]])
for state in ( "merged by #12 into main" , "upstream gone" , "protected" , "1 commits unpushed" , "clean" , "not committed: 0 staged, 0 changed, 1 new" , "gc skips: ignored files beyond known caches: notes.txt" ):
2026-11-07 15:03:14 +00:00
with self . subTest ( state = state ):
self . assertIn ( state , artifacts [ "states" ])
for view in ( "orbit" , "chord" ):
with self . subTest ( view = view ):
self . assertGreaterEqual ( by_hash [ f "#artifacts/ { view } " ][ "marks" ], 4 )
2026-11-07 23:43:26 +00:00
self . assertEqual (( artifacts [ "flows" ], artifacts [ "openWork" ]), ([ "file" , "invalid" ], 5 ))
2026-11-08 17:26:51 +00:00
for state in ( "open work: not committed, task open" , "open work: unpushed, task open" , "open work: not merged" , "3 worktrees with open work" ):
2026-11-07 23:43:26 +00:00
with self . subTest ( state = state ):
self . assertIn ( state , artifacts [ "states" ])
self . assertEqual ( by_hash [ "#artifacts/chord" ][ "flowEdges" ], [ "pull" , "push" ])
order = [ "work/feature" , "work/docs" , "work/stage" , "queue/gate-3" , "work/old" , "operator/push" ]
listed = by_hash [ "#tasks/list" ]
2026-11-09 22:22:46 +00:00
self . assertEqual ( listed [ "views" ], [ "List" , "Board" , "RASCI" , "Gantt" , "Burndown and burnup" , "3D" ])
self . assertEqual ( by_hash [ "#tasks/3d" ][ "layouts" ], [ "Force" , "Time" ])
self . assertTrue ( by_hash [ "#tasks/3d" ][ "canvas" ], by_hash [ "#tasks/3d" ][ "note" ])
self . assertRegex ( by_hash [ "#tasks/3d" ][ "note" ], r "^6 nodes · \d+ edges · Force$" )
2026-11-08 17:26:51 +00:00
self . assertEqual ( listed [ "taskFilters" ], [[ "all" , "true" , "6" ], [ "open" , "false" , "4" ], [ "work" , "false" , "4" ], [ "operator" , "false" , "2" ], [ "grant" , "false" , "1" ], [ "ended" , "false" , "2" ]])
2026-11-07 23:43:26 +00:00
self . assertEqual ( listed [ "taskRows" ], [[ task , task not in ( "work/old" , "operator/push" )] for task in order ])
self . assertEqual ( listed [ "flows" ], [ "file" , "file" ])
self . assertEqual ( by_hash [ "#tasks/board" ][ "columns" ], [[ "queued" , [ "queue/gate-3" ]], [ "running" , [ "work/feature" , "work/stage" ]], [ "blocked" , [ "work/docs" ]], [ "done" , [ "work/old" , "operator/push" ]], [ "stopped" , []]])
self . assertEqual ( by_hash [ "#tasks/rasci" ][ "matrix" ], [ 6 , 6 , "CIRARASRARAR" ])
self . assertEqual ([ probe [ "taskFilters" ][ 3 ][ 1 ] for probe in probes if probe [ "hash" ] == "#stream/inbox" ], [ "true" ] * len ( WIDTHS ))
2026-11-07 15:03:14 +00:00
extra = data [ "results" ][ len ( probes ) :]
self . assertEqual ([ probe [ "hash" ] for probe in extra [: 3 ]], [ "#agents/orbit" , "#agents/chord" , "#artifacts/tree" ])
actions = extra [ 3 ]
2026-11-06 22:20:06 +00:00
self . assertNotIn ( "error" , actions )
2026-11-09 22:22:46 +00:00
self . assertEqual ( actions [ "keys" ], [ "#agents/chord" , "#artifacts/tree" , "#tasks/3d" , "#stream" ])
2026-11-06 22:20:06 +00:00
self . assertEqual ( actions [ "ambience" ], [ "sky-look" , "music-toggle" , "leitstand" , "notify" ])
2026-11-08 21:18:55 +00:00
self . assertEqual (( actions [ "settingsUnlabelled" ], actions [ "popupUnlabelled" ]), ( 0 , 0 ))
2026-11-09 22:22:46 +00:00
self . assertEqual (( actions [ "sections" ], actions [ "radioFields" ]), ([ "Ambience" , "Location" , "Appearance" , "Music" , "Notifications" , "Internet radio" , "Passkey" ], 0 ))
for tool in ( "Radio …" , "Cancel mayday" , "Priority …" , "Drain" , "Suspend" , "Resume" , "Firewall …" , "Settings …" , "Look at the sky" , "Toggle control room" , "Enable notifications" ):
2026-11-06 22:20:06 +00:00
with self . subTest ( tool = tool ):
self . assertTrue ( any ( tool in entry for entry in actions [ "palette" ]), actions [ "palette" ])
2026-11-08 21:18:55 +00:00
self . assertEqual ( actions [ "popup" ], [[ "post" , True ], [ "call" , False ]])
self . assertEqual ( actions [ "centred" ], [ True , "none" ])
calm , chips = extra [ 4 :]
2026-11-08 17:26:51 +00:00
self . assertRegex ( calm [ "note" ], r "Force · without motion \(prefers-reduced-motion\)$" )
2026-11-07 23:43:26 +00:00
self . assertEqual ({ key : value for key , value in chips [ "inbox" ] . items () if key != "counted" }, { "hash" : "#stream/inbox" , "items" : chips [ "inbox" ][ "counted" ], "pressed" : "true" , "current" : [ "Inbox" ]})
self . assertGreaterEqual ( chips [ "inbox" ][ "items" ], 1 )
2026-11-08 17:26:51 +00:00
self . assertEqual ( chips [ "back" ], { "hash" : "#tasks/list" , "rows" : [[ "queue/gate-3" , "Queue job" ]]})
2026-11-07 15:03:14 +00:00
def test_chromium_sends_a_call_from_the_agent_panel_and_shows_its_receipt ( self ):
handled = []
def answer () -> None :
for script , args in self . scripts . calls [ len ( handled ) :]:
handled . append ( script )
if script . endswith ( "radio.py" ) and args [: 2 ] == [ "send" , "call" ]:
to , text , now = args [ args . index ( "--to" ) + 1 ], args [ - 1 ], time . time ()
rows = [{ "ts" : now , "from" : "Operator" , "to" : [ to ], "text" : text , "class" : "call" , "v" : 2 }, { "ts" : now + 0.5 , "from" : to , "to" : [ "Operator" ], "text" : text , "class" : "receipt" , "v" : 2 , "call" : now }]
with ( self . council / "news.jsonl" ) . open ( "a" , encoding = "utf-8" ) as handle :
handle . write ( self . jsonl ( rows ))
self . beside ( answer )
2026-11-07 19:59:47 +00:00
local = f "http://localhost: { self . port } /"
data = self . drive ([{ "width" : 1366 , "height" : 768 , "url" : f " { local } #stream" , "wait" : 900 , "probe" : CALL , "shot" : self . shot ( "1366-call-receipt" )}], self . authenticator ())
2026-11-07 15:03:14 +00:00
self . assertEqual ( data [ "errors" ], [])
result = data [ "results" ][ 0 ]
self . assertNotIn ( "error" , result )
self . assertIsNotNone ( result [ "receipt" ], result )
2026-11-08 17:26:51 +00:00
self . assertIn ( "Read" , result [ "receipt" ])
2026-11-07 15:03:14 +00:00
2026-11-07 19:59:47 +00:00
def test_chromium_shows_who_speaks_while_a_message_is_read ( self ):
self . speaks (( "e2e-cue-1" , "e2e-cue-2" ))
steps = [
{ "width" : 1366 , "height" : 768 , "url" : f " { self . base } #stream/radio" , "wait" : 900 , "probe" : SPEAK . replace ( "@CUE@" , "e2e-cue-1" ), "shot" : self . shot ( "1366-radio-speaking" ), "gesture" : True },
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : SPEAK_GRAPH , "shot" : self . shot ( "1366-agents-speaking" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : SPEAK_END , "shot" : None },
{ "width" : 390 , "height" : 844 , "url" : None , "wait" : 0 , "probe" : SPEAK . replace ( "@CUE@" , "e2e-cue-2" ), "shot" : self . shot ( "390-radio-speaking-reduced-motion" ), "gesture" : True , "media" : [{ "name" : "prefers-reduced-motion" , "value" : "reduce" }]},
]
data = self . drive ( steps , None )
( OUT / "chromium-speaking.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
speaking , graph , ended , calm = data [ "results" ]
2026-11-08 22:06:31 +00:00
self . talks ( speaking , graph )
self . assertEqual ( ended , { "ended" : True , "caption" : False , "chips" : 0 , "faces" : 0 })
2026-11-08 17:26:51 +00:00
self . assertEqual (( calm [ "who" ], calm [ "line" ], calm [ "ring" ]), ( "Stage speaking" , "Disk almost full (e2e-cue-2)" , "none" ), calm )
2026-11-08 22:06:31 +00:00
self . assertEqual ( calm [ "faces" ][ "speaking" ], [ True , "none" ], calm )
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_shows_who_speaks_and_moves_only_the_speakers_mouth ( self ):
self . speaks (( "e2e-cue-1" ,))
steps = [
{ "width" : 1366 , "height" : 768 , "url" : f " { self . base } #stream/radio" , "wait" : 900 , "probe" : SPEAK . replace ( "@CUE@" , "e2e-cue-1" ), "shot" : str ( OUT / "firefox-1366-radio-speaking.png" ), "gesture" : True },
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : SPEAK_GRAPH , "shot" : str ( OUT / "firefox-1366-agents-speaking.png" )},
]
data = self . drive ( steps , None , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ))
( OUT / "firefox-speaking.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ([ error for error in data [ "errors" ] if "/favicon.ico" not in error ], [])
self . talks ( * data [ "results" ])
def talks ( self , speaking : dict , graph : dict ) -> None :
"""Args:
speaking: the SPEAK probe while Stage (s2) is read aloud.
graph: the SPEAK_GRAPH probe on #agents/orbit right after it.
"""
self . assertEqual (( speaking [ "who" ], speaking [ "line" ], speaking [ "chip" ], speaking [ "ring" ]), ( "Stage speaking" , "Disk almost full (e2e-cue-1)" , "s2" , "speaking" ), speaking )
self . assertEqual ( speaking [ "faces" ], { "speaking" : [ True , "talk" ], "silent" : [ False , "none" ], "keys" : [ "s2" ]}, speaking )
self . assertEqual (( graph [ "node" ] or "" ) . split ( "," )[ 0 ], "Stage" , graph )
self . assertEqual (( graph [ "ring" ], graph [ "mouth" ], graph [ "silent" ]), ( "speaking-node" , "talk" , "none" ), graph )
2026-11-07 19:59:47 +00:00
def test_chromium_records_transcribes_and_sends_a_signed_call_with_voice ( self ):
local = f "http://localhost: { self . port } /"
steps = [
2026-11-09 22:22:46 +00:00
{ "width" : 1366 , "height" : 768 , "url" : f " { local } #agents/orbit" , "wait" : 1200 , "probe" : COMPOSE . replace ( "@TO@" , "Stage" ), "shot" : self . shot ( "1366-radio-spot-composer" ), "gesture" : True },
2026-11-07 19:59:47 +00:00
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : COMPOSE_SEND . replace ( "@ATTACH@" , "false" ), "shot" : None },
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : COMPOSE . replace ( "@TO@" , "Stage" ), "shot" : None , "gesture" : True },
2026-11-09 22:22:46 +00:00
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : COMPOSE_SEND . replace ( "@ATTACH@" , "true" ), "shot" : self . shot ( "1366-radio-spot-composer-sent" )},
2026-11-07 19:59:47 +00:00
]
data = self . drive ( steps , self . authenticator ())
( OUT / "chromium-composer.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
composed , plain , again , sent = data [ "results" ]
self . assertNotIn ( "error" , composed )
self . assertEqual ( composed [ "channels" ], [ "post" , "call" , "newsletter" , "broadcast" , "pan-pan" , "security" , "mayday" ])
self . assertIn ( "Stage" , composed [ "recipients" ])
self . assertEqual ( composed [ "recording" ], "true" )
self . assertEqual ( composed [ "text" ], "hello from the mic second line" , composed )
self . assertRegex ( composed [ "replay" ] or "" , r "^/api/voice/[0-9a-f] {16} \.wav$" )
2026-11-08 17:26:51 +00:00
self . assertEqual ( composed [ "attach" ], [ "Attach recording" , False ])
2026-11-07 19:59:47 +00:00
self . assertEqual ( composed [ "clashes" ], { "found" : [], "outside" : []})
2026-11-08 17:26:51 +00:00
self . assertEqual ( again [ "attach" ], [ "Attach recording" , False ])
2026-11-07 19:59:47 +00:00
note = again [ "replay" ] . removeprefix ( "/api/voice/" ) . removesuffix ( ".wav" )
for result in ( plain , sent ):
self . assertEqual (( result [ "left" ], result [ "voice" ]), ( "" , False ), result )
2026-11-08 17:26:51 +00:00
self . assertTrue ( result [ "sent" ] . startswith ( "Call to Stage: sent" ), result )
2026-11-07 19:59:47 +00:00
calls = [ args for script , args in self . scripts . calls if script == skill_scripts . RADIO ]
self . assertEqual ( len ( calls ), 2 , self . scripts . calls )
self . assertEqual ([ call [: 4 ] for call in calls ], [[ "send" , "call" , "--to" , "Stage" ]] * 2 )
2026-11-08 17:26:51 +00:00
self . assertTrue ( calls [ 0 ][ - 1 ] . endswith ( "] hello from the mic second line please" ), calls [ 0 ])
self . assertTrue ( calls [ 1 ][ - 1 ] . endswith ( f "] hello from the mic second line please voice: { HOST_STATE } /voice/ { note } .wav" ), calls [ 1 ])
2026-11-07 19:59:47 +00:00
status , _ , body = self . get ( f "/api/voice/ { note } .wav" )
self . assertEqual (( status , body [: 4 ]), ( 200 , b "RIFF" ))
2026-11-09 22:22:46 +00:00
def test_chromium_clears_the_mayday_from_the_radio_spot_only_after_confirmation_and_passkey_at_three_widths ( self ):
2026-11-07 19:59:47 +00:00
local = f "http://localhost: { self . port } /"
2026-11-09 22:22:46 +00:00
steps = [{ "width" : width , "height" : height , "url" : f " { local } ? { width } #agents/orbit" , "wait" : 1200 , "probe" : CLEAR_MAYDAY , "shot" : self . shot ( f " { width } -radio-spot-mayday-cleared" )} for width , height in WIDTHS ]
data = self . drive ( steps , self . authenticator ())
( OUT / "chromium-mayday.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
2026-11-07 19:59:47 +00:00
self . assertEqual ( data [ "errors" ], [])
2026-11-09 22:22:46 +00:00
question = "Lift the radio silence of the active mayday?"
for result , ( width , _ ) in zip ( data [ "results" ], WIDTHS , strict = True ):
with self . subTest ( width = width ):
self . assertNotIn ( "error" , result )
self . assertEqual ( result [ "toolbar" ], [ "Radio" , "Priority" , "Day/Night" , "Drain" , "Suspend" , "Resume" ])
self . assertEqual ( result [ "title" ], "Cancel mayday: lift the radio silence so the agents write again; your passkey signs it" )
self . assertEqual ( result [ "declined" ], { "question" : [ question ], "posted" : [], "toasts" : []})
self . assertEqual ( result [ "accepted" ], { "question" : [ question ], "posted" : [ "/api/board/sign" , "/api/board/clear-mayday" ]})
self . assertTrue (( result [ "done" ] or "" ) . startswith ( "Cancel mayday: done" ), result )
self . assertEqual ( self . scripts . calls , [( skill_scripts . RADIO , [ "clear" , "--force" , "--session" , "Operator" ])] * len ( WIDTHS ))
def test_chromium_sends_every_class_from_the_radio_spot_and_keeps_its_gear_settings_through_a_reload_at_three_widths ( self ):
local = f "http://localhost: { self . port } /"
values = { 1920 : ( "0.35" , "false" , "true" ), 1366 : ( "0.45" , "true" , "false" ), 390 : ( "0.55" , "false" , "true" )}
steps = [{ "width" : 1366 , "height" : 768 , "url" : f " { local } #stream/radio" , "wait" : 1200 , "probe" : RADIO_JUMP , "shot" : self . shot ( "1366-radio-tab-jump-to-spot" )}]
for width , height in WIDTHS :
squelch , tone , post = values [ width ]
gear = GEAR_SAVE . replace ( "@SQUELCH@" , squelch ) . replace ( "@TONE@" , tone ) . replace ( "@POST@" , post )
steps += [
{ "width" : width , "height" : height , "url" : f " { local } ? { width } #agents/orbit" , "wait" : 1200 , "probe" : SPOT_SEND . replace ( "@TEXT@" , f "spot { width } " ), "shot" : self . shot ( f " { width } -radio-spot-sent" )},
{ "width" : width , "height" : height , "url" : None , "wait" : 0 , "probe" : gear , "shot" : None },
{ "width" : width , "height" : height , "url" : f " { local } ? { width } -reloaded#agents/orbit" , "wait" : 1200 , "probe" : GEAR_READ , "shot" : self . shot ( f " { width } -radio-spot-gear" )},
]
data = self . drive ( steps , self . authenticator ())
( OUT / "chromium-radio-spot.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
labels = dict ( zip ( CHOICE_CHANNELS , [ "Post" , "Call" , "Newsletter" , "Broadcast" , "Pan-Pan" , "Sécurité" , "Mayday" ], strict = True ))
mayday = "Send a mayday to all? All agents pause writing steps until you cancel it."
jump , * walked = data [ "results" ]
self . assertEqual ( jump , { "composers" : 0 , "hash" : "#agents/orbit" , "open" : True , "focused" : True })
for index , ( width , _ ) in enumerate ( WIDTHS ):
sent , saved , read = walked [ 3 * index : 3 * ( index + 1 )]
squelch , tone , post = values [ width ]
with self . subTest ( width = width ):
for probe in ( sent , saved , read ):
self . assertNotIn ( "error" , probe )
self . assertEqual (( sent [ "closed" ], sent [ "open" ]), ([ True , "false" ], [ False , "true" , True ]))
self . assertEqual ( sent [ "inline" ], [ "static" , True , True ])
self . assertEqual (( sent [ "toolbar" ], sent [ "channels" ], sent [ "record" ]), ([ "Radio" , "Priority" , "Day/Night" ], CHOICE_CHANNELS , [ "Record" ]))
self . assertEqual ( sent [ "heads" ], [[ "Radio settings" , "Radio settings: tones, squelch and read aloud" ], [ "Cancel mayday" , "Cancel mayday: lift the radio silence so the agents write again; your passkey signs it" ]])
self . assertEqual (( sent [ "tall" ], sent [ "wide" ]), ( False , False ))
self . assertEqual ([ run [ "cls" ] for run in sent [ "runs" ]], CHOICE_CHANNELS )
for run in sent [ "runs" ]:
direct = run [ "cls" ] in ( "post" , "call" )
self . assertEqual (( run [ "direct" ], run [ "question" ], run [ "left" ]), ( direct , [ mayday ] if run [ "cls" ] == "mayday" else [], "" ), run )
self . assertTrue (( run [ "toast" ] or "" ) . startswith ( f " { labels [ run [ 'cls' ]] } to { 'Stage' if direct else 'all' } : sent" ), run )
self . assertEqual (( saved [ "expanded" ], saved [ "sections" ], saved [ "clashes" ]), ( "true" , [ "Tones" , "Read radio aloud" ], { "found" : [], "outside" : []}))
self . assertEqual ( saved [ "tones" ], [ "MAYDAY / PAN-PAN" , "Reviews, asks, decisions" , "Post and calls to you" , "All radio" ])
self . assertEqual ([ entry [ 0 ] for entry in saved [ "classes" ]], [ "Post" , "Call" , "Newsletter" , "Broadcast" , "MAYDAY" , "PAN-PAN" , "Sécurité" ])
self . assertEqual ([ entry [ 1 :] for entry in saved [ "classes" ] if entry [ 0 ] == "MAYDAY" ], [[ True , True ]])
self . assertEqual (( saved [ "toast" ], saved [ "after" ]), ( "Radio settings saved" , [ "false" , False ]))
self . assertEqual ( saved [ "general" ], { "sections" : [ "Ambience" , "Location" , "Appearance" , "Music" , "Notifications" , "Internet radio" , "Passkey" ], "radio" : 0 })
self . assertEqual (( saved [ "tall" ], saved [ "wide" ]), ( False , False ))
self . assertEqual ( read , { "squelch" : float ( squelch ), "tone" : tone == "true" , "post" : post == "true" , "tall" : False , "wide" : False })
heads = { "post" : [ "post" , "--to" , "Stage" ], "call" : [ "send" , "call" , "--to" , "Stage" ], "newsletter" : [ "post" ]}
radio = [ args for script , args in self . scripts . calls if script == skill_scripts . RADIO ]
sends = [( width , cls ) for width , _ in WIDTHS for cls in CHOICE_CHANNELS ]
self . assertEqual ([ args [: - 1 ] for args in radio ], [[ * heads . get ( cls , [ "send" , cls ]), "--session" , "Operator" ] for _ , cls in sends ])
for args , ( width , cls ) in zip ( radio , sends , strict = True ):
with self . subTest ( width = width , cls = cls ):
self . assertTrue ( args [ - 1 ] . endswith ( f "] spot { width } { cls } " ), args )
stored = self . httpd . hub . settings . get ()
self . assertEqual (( stored [ "squelch" ], stored [ "sounds" ][ "radio" ][ "on" ], "post" in stored [ "speech" ][ "classes" ]), ( 0.55 , False , True ))
def test_chromium_keeps_the_3d_camera_through_two_state_refreshes_at_three_widths ( self ):
steps = [{ "width" : width , "height" : height , "url" : f " { self . base } ? { width } #agents/3d" , "wait" : 900 , "probe" : CAMERA , "shot" : self . shot ( f " { width } -agents-3d-steady-camera" )} for width , height in WIDTHS ]
data = self . drive ( steps , None , init = GRAPH_HOOK )
( OUT / "chromium-camera.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
for result , ( width , _ ) in zip ( data [ "results" ], WIDTHS , strict = True ):
with self . subTest ( width = width ):
self . assertNotIn ( "error" , result )
self . assertTrue ( result [ "hooked" ], result )
self . assertGreater ( math . dist ( result [ "built" ], result [ "moved" ]), 1 , result )
for refresh in result [ "refreshes" ]:
self . assertEqual (( refresh [ "rebuilt" ], refresh [ "settled" ]), ( True , True ), result )
self . assertLess ( math . dist ( refresh [ "camera" ], result [ "moved" ]), 0.01 , result )
self . assertGreater ( math . dist ( result [ "reset" ], result [ "moved" ]), 1 , result )
2026-11-07 19:59:47 +00:00
2026-11-08 23:18:18 +00:00
def check_night_menu ( self , browser : str , driver : str , binary : str , signs : bool ) -> None :
"""Args:
signs: cdp.mjs signs every prompt with its virtual passkey; bidi.mjs
has no virtual authenticator, so the probe answers each prompt
the way a canceled passkey dialog does.
"""
authenticator = self . authenticator ()
probe = NIGHT_MENU . replace ( "@SIGNS@" , "true" if signs else "false" )
2026-11-09 22:22:46 +00:00
looks = [{ "width" : width , "height" : height , "url" : f "http://localhost: { self . port } /? { width } #agents/orbit" , "wait" : 1200 , "probe" : NIGHT_LOOK , "shot" : str ( OUT / f " { browser } - { width } -night-menu-tooltips.png" )} for width , height in WIDTHS ]
2026-11-08 23:18:18 +00:00
step = { "width" : 1366 , "height" : 768 , "url" : f "http://localhost: { self . port } /#agents/orbit" , "wait" : 1200 , "probe" : probe , "shot" : str ( OUT / f " { browser } -1366-night-menu.png" )}
2026-11-09 22:22:46 +00:00
data = self . drive ([ * looks , step ], authenticator if signs else None , driver , binary )
2026-11-08 23:18:18 +00:00
( OUT / f " { browser } -night-menu.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ([ error for error in data [ "errors" ] if "(style-src-elem)" not in error and "/favicon.ico" not in error ], [])
2026-11-09 22:22:46 +00:00
* seen , found = data [ "results" ]
for look , ( width , _ ) in zip ( seen , WIDTHS , strict = True ):
with self . subTest ( browser = browser , width = width ):
self . assertNotIn ( "error" , look )
self . assertEqual ( look [ "children" ], [[ "span" , "" ], [ "div" , "actions" ]])
self . assertEqual (( look [ "badge" ], look [ "paragraphs" ], look [ "wide" ]), ([ "Day" ], 0 , False ))
self . assertEqual ( look [ "buttons" ], [ list ( entry ) for entry in NIGHT_HINTS ])
2026-11-08 23:18:18 +00:00
self . assertNotIn ( "error" , found )
self . assertEqual ( found [ "labels" ], [ "Drain" , "Suspend" , "Resume" ])
for run , action in zip ( found [ "runs" ], ( "drain" , "suspend" , "resume" ), strict = True ):
with self . subTest ( browser = browser , action = action ):
self . assertEqual ( run [ "asked" ], 1 , run )
self . assertEqual ( len ( run [ "question" ]), 1 , run )
self . assertIn ( f "kvb-agent- { action } " , run [ "question" ][ 0 ])
signed = [[ "/api/board/sign" , [ "action" ], action ]]
if signs :
self . assertEqual ( run [ "posted" ], [ * signed , [ f "/api/board/ { action } " , [ "proof" ], None ]])
self . assertTrue (( run [ "toast" ] or "" ) . startswith ( f " { run [ 'label' ] } : done" ), run )
else :
self . assertEqual ( run [ "posted" ], signed )
self . assertTrue (( run [ "toast" ] or "" ) . startswith ( f " { run [ 'label' ] } : not signed" ), run )
nights = [ args for script , args in self . scripts . calls if script == skill_scripts . NIGHT ]
self . assertEqual ( nights , [[ action , "--session" , "Operator" ] for action in ( "drain" , "suspend" , "resume" )] if signs else [])
if not signs :
self . assertEqual ( self . scripts . calls , [])
def test_chromium_runs_drain_suspend_and_resume_only_after_the_passkey_signs ( self ):
self . check_night_menu ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ), signs = True )
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_asks_the_passkey_for_drain_suspend_and_resume_and_runs_nothing_unsigned ( self ):
self . check_night_menu ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ), signs = False )
2026-11-07 15:03:14 +00:00
def test_chromium_deletes_a_merged_branch_after_dry_run_and_signature ( self ):
tree = json . loads ( self . tree_file . read_text ( encoding = "utf-8" ))
tree [ "delete_ready" ], tree [ "delete_reason" ] = True , ""
self . tree_file . write_text ( json . dumps ( tree ), encoding = "utf-8" )
demo = f " { self . root } /repos/github.com/me/demo"
Path ( demo ) . mkdir ( parents = True )
fake = self . outside / "bin" / "pkgmgr"
fake . parent . mkdir ()
fake . write_text ( FAKE_PKGMGR . format ( python = sys . executable ), encoding = "utf-8" )
fake . chmod ( 0o755 )
worktree = f " { self . root } /worktrees/github.com/me/demo/feature-done"
( fake . parent / "plan.json" ) . write_text ( json . dumps ([{ "branch" : "feature/done" , "worktree" : worktree , "local_sha" : DONE_TIP , "head" : DONE_TIP , "state" : "merged-clean" , "skip" : None }]), encoding = "utf-8" )
following = json . loads ( json . dumps ( tree ))
following [ "repos" ][ 0 ][ "items" ] = [ item for item in following [ "repos" ][ 0 ][ "items" ] if item [ "branch" ] != "feature/done" ]
( fake . parent / "next-tree.json" ) . write_text ( json . dumps ( following ), encoding = "utf-8" )
2026-11-07 19:59:47 +00:00
authenticator = self . authenticator ()
2026-11-07 15:03:14 +00:00
trust = verifier . Trust ( approvers = self . approvers , replay = self . replay / "cleanup.json" , origin = self . origin )
runner = host . Cleanup ( trust , self . tree_file , fake )
jobs , results = self . cleanup / "jobs" , self . cleanup / "results"
self . beside ( lambda : any ( jobs . iterdir ()) and host . cycle ( jobs , results , runner ))
local = f "http://localhost: { self . port } /"
steps = [
{ "width" : 1366 , "height" : 768 , "url" : f " { local } #artifacts/tree" , "wait" : 1200 , "probe" : OPEN_DELETE , "shot" : self . shot ( "1366-delete-dry-run" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : CONFIRM , "shot" : self . shot ( "1366-delete-result" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : AFTER , "shot" : self . shot ( "1366-delete-tree-after" )},
]
data = self . drive ( steps , authenticator )
( OUT / "chromium-delete.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
opened , confirmed , after = data [ "results" ]
self . assertNotIn ( "error" , opened )
2026-11-08 17:26:51 +00:00
self . assertEqual ( opened [ "every" ], [ False , "Delete all merged: 1 branches after dry run and signature" ])
self . assertEqual ( opened [ "buttons" ], [[ False , "Delete: remove feature/done locally, after dry run and signature" ]])
self . assertEqual ( opened [ "title" ], "Delete feature/done" )
2026-11-07 15:03:14 +00:00
self . assertEqual ( len ( opened [ "dry" ]), 1 )
2026-11-08 17:26:51 +00:00
self . assertTrue ( opened [ "dry" ][ 0 ] . startswith ( "feature/done @ 5b1e6fd3a7c0: merged-clean, merged by #12" ), opened [ "dry" ])
self . assertIn ( "stays: remote-branch (local only)" , opened [ "dry" ][ 0 ])
self . assertEqual (( confirmed [ "result" ], confirmed [ "text" ]), ( "done" , "feature/donedeleted" ), confirmed )
self . assertEqual ( confirmed [ "toasts" ], [ "Delete: donefeature/done: deleted" ])
self . assertEqual ( after [ "past" ], [ "done" , "deleted" ])
2026-11-07 15:03:14 +00:00
self . assertNotIn ( "feature/done" , after [ "branches" ])
written = [ json . loads ( path . read_text ( encoding = "utf-8" )) for path in results . glob ( "*.json" )]
self . assertEqual ([[( item [ "branch" ], item [ "status" ]) for item in result [ "items" ]] for result in written ], [[( "feature/done" , "done" )]])
calls = [ json . loads ( line )[ "argv" ] for line in ( fake . parent / "calls.jsonl" ) . read_text ( encoding = "utf-8" ) . splitlines ()]
self . assertEqual ( calls , [
[ "branch" , "cleanup" , "--json" , "--local" , "--branch" , "feature/done" ],
[ "branch" , "cleanup" , "--apply" , "--json" , "--local" , "--branch" , "feature/done" ],
[ "branch" , "tree" , "--format" , "json" , "--output" , str ( self . tree_file )],
])
2026-11-06 22:20:06 +00:00
2026-11-07 17:46:39 +00:00
def test_chromium_folds_the_hierarchy_by_keyboard_reorders_agents_and_hands_an_office_over_with_a_signature ( self ):
signer = self . pin ()
trust = verifier . Trust ( approvers = self . approvers , replay = self . replay / "offices.json" , origin = self . origin )
runner = office_host . Offices ( trust , self . council , COUNCIL_SCRIPT , self . outside / "home" )
jobs , results = self . offices / "jobs" , self . offices / "results"
self . beside ( lambda : any ( jobs . iterdir ()) and office_host . cycle ( jobs , results , runner ))
private = "" . join ( line for line in signer . pem . read_text ( encoding = "utf-8" ) . splitlines () if not line . startswith ( "-----" ))
authenticator = { "credentialId" : base64 . b64encode ( signer . raw_id ) . decode (), "privateKey" : private , "rpId" : "localhost" }
local = f "http://localhost: { self . port } /"
steps = [
{ "width" : 1366 , "height" : 768 , "url" : f " { local } #agents/hierarchy" , "wait" : 1200 , "probe" : ORG_OPEN , "shot" : self . shot ( "1366-hierarchy-open" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "keys" : [ "Enter" ], "probe" : ORG_FOLDED , "shot" : None },
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "keys" : [ "Enter" ], "probe" : ORDER_MOVED , "shot" : self . shot ( "1366-hierarchy-order" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : OPEN_TAKEOVER , "shot" : self . shot ( "1366-hierarchy-takeover" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : CONFIRM_TAKEOVER , "shot" : self . shot ( "1366-hierarchy-takeover-result" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : AFTER_TAKEOVER , "shot" : self . shot ( "1366-hierarchy-after" )},
]
data = self . drive ( steps , authenticator )
( OUT / "chromium-hierarchy.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
opened , folded , moved , sheet , confirmed , after = data [ "results" ]
self . assertNotIn ( "error" , opened )
self . assertEqual ( opened [ "focused" ], "toggle:dimension:identity" )
2026-11-08 17:26:51 +00:00
self . assertEqual (( folded [ "focused" ], folded [ "expanded" ], folded [ "label" ], folded [ "hidden" ], folded [ "again" ]), ( "toggle:dimension:identity" , "false" , "Expand: identity" , True , "true" ))
2026-11-07 17:46:39 +00:00
self . assertNotIn ( "identity/oidc" , folded [ "seen" ])
before = folded [ "before" ]
self . assertEqual ( moved [ "after" ], [ before [ 1 ], before [ 0 ], * before [ 2 :]])
self . assertEqual (( moved [ "focused" ], moved [ "disabled" ]), ( f "down: { before [ 1 ] } " , False ))
2026-11-08 17:26:51 +00:00
self . assertTrue ( moved [ "toast" ] . startswith ( "Priority" ), moved )
2026-11-07 17:46:39 +00:00
self . assertIn (( "rasci.py" , [ "agents" , "order" , "," . join ( moved [ "after" ])]), [( Path ( script ) . name , args ) for script , args in self . scripts . calls ])
2026-11-08 17:26:51 +00:00
self . assertEqual (( sheet [ "title" ], sheet [ "options" ], sheet [ "work" ], sheet [ "focused" ]), ( "identity/oidc: Hand over" , [ "s1" ], "oidc line" , "SELECT" ))
self . assertEqual (( confirmed [ "result" ], confirmed [ "text" ]), ( "done" , "identity/oidcdone" ), confirmed )
self . assertIn ( "Hand over: doneidentity/oidc: done" , confirmed [ "toasts" ])
2026-11-07 17:46:39 +00:00
self . assertNotIn ( "error" , after )
claim = json . loads (( self . council / "identity" / "oidc" / "claim.json" ) . read_text ( encoding = "utf-8" ))
self . assertEqual (( claim [ "session" ], claim [ "work" ]), ( "s1" , "oidc line" ))
written = [ json . loads ( path . read_text ( encoding = "utf-8" )) for path in results . glob ( "*.json" )]
self . assertEqual ([[( item [ "office" ], item [ "action" ], item [ "status" ]) for item in result [ "items" ]] for result in written ], [[( "identity/oidc" , "takeover" , "done" )]])
2026-11-07 18:14:21 +00:00
def inbox_work ( self ) -> None :
stage = { "title" : "Stage line" , "status" : "running" , "worker" : "Stage (this session)" , "roles" : { "Stage (this session)" : "RA" }}
2026-11-08 17:26:51 +00:00
greet = { "title" : SLOW , "workdir" : str ( self . outside ), "description" : "counts" , "check" : "manual" , "status" : "queued" , "roles" : { "Operator" : "R" , "Skills session" : "A" }}
2026-11-07 18:14:21 +00:00
push = { "title" : "git push" , "workdir" : "/repo" , "description" : "ship" , "check" : "manual" , "status" : "queued" , "roles" : { "Operator" : "R" , "Skills session" : "A" }}
self . write_council ({ "rasci.json" : { "work/stage" : stage , "operator/greet" : greet , "operator/push" : push }})
arguments = { "kind" : "ask" , "tool" : "Bash" , "pattern" : "make deploy" , "scope" : "/repo*" , "expires" : int ( time . time ()) + 7 * 86400 , "agent" : "Skills" , "why" : "deploy runs after every green gate" }
self . assertFalse ( self . tool ( "rule_request" , arguments )[ "isError" ])
def layout ( self , probe : dict ) -> None :
self . assertNotIn ( "error" , probe )
2026-11-08 17:26:51 +00:00
self . assertEqual ( sorted ( probe [ "kinds" ]), [ "Ask" , "Operator" , "Operator" , "Review" , "Rule request" ])
self . assertIn ([ "Deny" , "Deny via …" ], probe [ "splits" ])
2026-11-07 18:14:21 +00:00
self . assertEqual ( len ( probe [ "splits" ]), 4 )
2026-11-08 17:26:51 +00:00
self . assertEqual ( probe [ "fields" ], [ "Kind" , "Tool (glob)" , "Pattern (glob)" , "Directory (glob)" , "Expires on" ])
self . assertEqual ( probe [ "dry" ], "Dry run: the rule would have granted 1 of 1 asks from the last 7 days." )
2026-11-07 18:14:21 +00:00
self . assertEqual ( probe [ "accept" ], 1 )
self . assertTrue ( probe [ "fits" ] and probe [ "followed" ], probe )
self . assertEqual (( probe [ "unlabelled" ], probe [ "wide" ]), ([], False ))
def test_chromium_runs_a_command_into_its_console_denies_by_call_and_accepts_a_rule ( self ):
self . inbox_work ()
signer = self . pin ()
trust = [ "--approvers" , str ( self . approvers ), "--replay" , str ( self . replay / "runner.json" )]
def run_jobs () -> None :
if any ( job [ "status" ] == "queued" for job in self . httpd . hub . jobs . listed ()):
subprocess . run ([ sys . executable , str ( RUNNER ), "--port" , str ( self . port ), "--interval" , "0.1" , * trust , "--once" ], capture_output = True , timeout = 120 , check = False )
self . beside ( run_jobs )
private = "" . join ( line for line in signer . pem . read_text ( encoding = "utf-8" ) . splitlines () if not line . startswith ( "-----" ))
authenticator = { "credentialId" : base64 . b64encode ( signer . raw_id ) . decode (), "privateKey" : private , "rpId" : "localhost" }
inbox = f "http://localhost: { self . port } /#stream/inbox"
steps = [
{ "width" : 1366 , "height" : 768 , "url" : inbox , "wait" : 1200 , "probe" : RUN_START , "shot" : self . shot ( "1366-inbox-console-running" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : RUN_HELD , "shot" : self . shot ( "1366-inbox-console-scrolled-up" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : RUN_DONE , "shot" : self . shot ( "1366-inbox-console-done" )},
]
for width , height in WIDTHS :
fresh = f "http://localhost: { self . port } /? { width } #stream/inbox"
steps . append ({ "width" : width , "height" : height , "url" : fresh , "wait" : 1200 , "probe" : LAYOUT , "shot" : self . shot ( f " { width } -inbox-console" )})
steps . append ({ "width" : width , "height" : height , "url" : None , "wait" : 0 , "probe" : DENY_SHEET , "shot" : self . shot ( f " { width } -inbox-deny-sheet" )})
steps += [
{ "width" : 1366 , "height" : 768 , "url" : inbox , "wait" : 1200 , "probe" : DENY , "shot" : self . shot ( "1366-inbox-deny-undo" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : DENIED , "shot" : None },
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : ACCEPT , "shot" : self . shot ( "1366-inbox-rule-accepted" )},
]
data = self . drive ( steps , authenticator )
( OUT / "chromium-inbox.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
started , held , done , * rest = data [ "results" ]
self . assertNotIn ( "error" , started )
2026-11-08 17:26:51 +00:00
self . assertIn ( "takes effect in" , started [ "toast" ])
self . assertTrue ( started [ "running" ] . startswith ( "running" ), started )
2026-11-07 18:14:21 +00:00
self . assertTrue ( started [ "overflows" ] and started [ "followed" ], started )
self . assertLess ( held [ "held" ], 50 , held )
2026-11-08 17:26:51 +00:00
self . assertEqual (( done [ "last" ], done [ "lines" ], done [ "followed" ]), ( "finished" , 81 , True ))
self . assertRegex ( done [ "done" ], r "^doneExit code 0 after \d+ s$" )
self . assertIn ( "Close" , done [ "actions" ])
2026-11-07 18:14:21 +00:00
layouts , sheets = rest [ 0 : 6 : 2 ], rest [ 1 : 6 : 2 ]
for probe , sheet , ( width , _ ) in zip ( layouts , sheets , WIDTHS , strict = True ):
with self . subTest ( width = width ):
self . layout ( probe )
2026-11-08 17:26:51 +00:00
self . assertTrue ( probe [ "console" ] . startswith ( "doneExit code 0" ), probe )
self . assertEqual ( sheet [ "title" ], "Deny" )
2026-11-07 18:14:21 +00:00
self . assertEqual ( sheet [ "channels" ], [[ "post" , True ], [ "call" , False ], [ "newsletter" , False ], [ "broadcast" , False ], [ "pan-pan" , False ], [ "security" , False ]])
2026-11-08 17:26:51 +00:00
self . assertEqual ( sheet [ "hints" ][: 3 ], [ "to Skills, when its turn ends" , "to Skills, at once, wakes it" , "to all, when their turn ends" ])
2026-11-07 18:14:21 +00:00
self . assertEqual (( sheet [ "reason" ], sheet [ "unlabelled" ], sheet [ "wide" ]), ( True , [], False ))
denied , gone , accepted = rest [ 6 :]
2026-11-08 17:26:51 +00:00
self . assertTrue ( denied [ "undo" ] . startswith ( "Denial (Call): Bash: make deploy" ), denied )
2026-11-07 18:14:21 +00:00
self . assertFalse ( denied [ "sheet" ])
self . assertTrue ( gone [ "gone" ])
2026-11-08 17:26:51 +00:00
self . assertEqual (( accepted [ "toast" ], accepted [ "left" ]), ( "Created: Rule: ask Bash make deploy in /repo*" , False ))
2026-11-07 18:14:21 +00:00
ask = self . httpd . hub . asks . listed ()[ 0 ]
2026-11-08 17:26:51 +00:00
self . assertEqual (( ask [ "status" ], ask [ "reason" ], ask [ "channel" ]), ( "denied" , "make the gate green first" , "call" ))
2026-11-07 18:14:21 +00:00
calls = [ args for script , args in self . scripts . calls if script . endswith ( "radio.py" )]
self . assertEqual ( calls [ - 1 ][: 4 ], [ "send" , "call" , "--to" , "Skills session" ])
2026-11-08 17:26:51 +00:00
self . assertTrue ( calls [ - 1 ][ - 1 ] . endswith ( "Reason: make the gate green first" ), calls [ - 1 ])
2026-11-07 18:14:21 +00:00
self . assertEqual ([( rule [ "kind" ], rule [ "pattern" ], rule [ "proof" ] is not None ) for rule in self . httpd . hub . rules . listed ()], [( "ask" , "make deploy" , True )])
self . assertEqual ([( job [ "status" ], job [ "code" ]) for job in self . httpd . hub . jobs . listed ()], [( "done" , 0 )])
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_draws_the_inbox_console_and_the_rule_request_at_three_widths ( self ):
self . inbox_work ()
now = time . time ()
fields = { "task" : "operator/greet" , "command" : SLOW , "directory" : str ( self . outside ), "sha256" : "" , "status" : "done" , "proof" : None }
2026-11-08 17:26:51 +00:00
output = "" . join ( f "Row { number } \n " for number in range ( 1 , 81 )) + "finished \n "
2026-11-07 18:14:21 +00:00
self . httpd . hub . jobs . add ({ ** fields , "code" : 0 , "output" : output , "created_ts" : now - 20 , "started" : now - 19 , "finished" : now - 10 , "dismissed" : False })
shots = [( width , height , OUT / f "firefox- { width } -inbox-console.png" ) for width , height in WIDTHS ]
steps = [{ "width" : width , "height" : height , "url" : f " { self . base } #stream/inbox" , "wait" : 1200 , "probe" : LAYOUT , "shot" : str ( shot )} for width , height , shot in shots ]
data = self . drive ( steps , None , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ))
( OUT / "firefox-inbox.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ([ error for error in data [ "errors" ] if "(style-src-elem)" not in error and "/favicon.ico" not in error ], [])
for probe , ( width , _ , shot ) in zip ( data [ "results" ], shots , strict = True ):
with self . subTest ( width = width ):
self . layout ( probe )
2026-11-08 17:26:51 +00:00
self . assertEqual ( probe [ "console" ], "doneExit code 0 after 9 s" )
2026-11-07 18:14:21 +00:00
self . assertTrue ( shot . is_file () and shot . stat () . st_size > 1000 , shot )
2026-11-09 06:53:55 +00:00
def choice_steps ( self , browser : str , signs : bool , questions : dict [ int , str ]) -> list [ dict ]:
flag = "true" if signs else "false"
local = f "http://localhost: { self . port } /"
steps = []
for index , ( width , height ) in enumerate ( WIDTHS ):
cards = len ( WIDTHS ) - index if signs else len ( WIDTHS )
steps += [
{ "width" : width , "height" : height , "url" : f " { local } ? { width } #stream/inbox" , "wait" : 1200 , "probe" : CHOICE_VIEW . replace ( "@CARDS@" , str ( cards )), "shot" : str ( OUT / f " { browser } - { width } -tasks-choices.png" )},
{ "width" : width , "height" : height , "url" : None , "wait" : 0 , "probe" : CHOICE_SEND . replace ( "@SIGNS@" , flag ), "shot" : str ( OUT / f " { browser } - { width } -choice-request.png" )},
{ "width" : width , "height" : height , "url" : None , "wait" : 0 , "probe" : CHOICE_ANSWER . replace ( "@SIGNS@" , flag ) . replace ( "@QUESTION@" , questions [ width ]), "shot" : str ( OUT / f " { browser } - { width } -choice-answer.png" )},
* ({ "width" : width , "height" : height , "url" : f " { local } ? { width } #tasks/ { view } " , "wait" : 900 , "probe" : TASKS_FIT , "shot" : str ( OUT / f " { browser } - { width } -tasks- { view } -fit.png" )} for view in TASK_VIEWS ),
]
return steps
def requested ( self , sent : dict , signs : bool ) -> None :
"""Args:
sent: the CHOICE_SEND probe: two agents ticked, then every channel
button pressed once.
"""
labels = dict ( zip ( CHOICE_CHANNELS , [ "Post" , "Call" , "Newsletter" , "Broadcast" , "Pan-Pan" , "Sécurité" , "Mayday" ], strict = True ))
self . assertEqual ( len ( sent [ "picked" ]), 2 , sent )
self . assertEqual (( sent [ "counted" ], sent [ "fields" ], sent [ "tall" ], sent [ "wide" ]), ( "2" , 0 , False , False ))
2026-11-09 22:22:46 +00:00
self . assertEqual ( sent [ "opened" ], [ True , len ( CHOICE_CHANNELS )])
2026-11-09 06:53:55 +00:00
self . assertEqual ([ run [ "channel" ] for run in sent [ "runs" ]], CHOICE_CHANNELS )
for run in sent [ "runs" ]:
with self . subTest ( channel = run [ "channel" ]):
to = sent [ "picked" ] if run [ "channel" ] in ( "post" , "call" ) else [ "*" ]
signed = [[ "/api/choices/sign" , [ "class" , "to" ], run [ "channel" ], to ]]
self . assertEqual (( run [ "disabled" ], run [ "asked" ], len ( run [ "question" ])), ( False , 1 , int ( run [ "channel" ] == "mayday" )), run )
title = f "Choice request ( { labels [ run [ 'channel' ]] } ) to { ', ' . join ( to ) if to != [ '*' ] else 'all' } "
self . assertEqual ( run [ "posted" ], [ * signed , [ "/api/choices" , [ "class" , "proof" , "to" ], run [ "channel" ], to ]] if signs else signed )
self . assertTrue (( run [ "toast" ] or "" ) . startswith ( f " { title } : { 'sent' if signs else 'not signed' } " ), run )
def answered ( self , result : dict , signs : bool ) -> None :
self . assertNotIn ( "error" , result )
2026-11-09 22:22:46 +00:00
self . assertEqual (( result [ "title" ], result [ "asked" ], result [ "closed" ], result [ "tall" ], result [ "wide" ]), ( "Wait: your passkey signs this answer, a post to Stage" , 1 , [ False , 0 ], False , False ))
2026-11-09 06:53:55 +00:00
signed = [[ "/api/sign" , [ "items" ], None , None ]]
self . assertEqual ( result [ "posted" ], [ * signed , [ "/api/inbox/approve" , [ "items" , "proof" ], None , None ]] if signs else signed )
self . assertTrue (( result [ "toast" ] or "" ) . startswith ( "Choice: Wait" + ( "" if signs else ": not signed" )), result )
self . assertEqual ( result [ "gone" ], True if signs else None )
def check_choices ( self , browser : str , driver : str , binary : str , signs : bool ) -> None :
"""Args:
signs: cdp.mjs signs every prompt with its virtual passkey; bidi.mjs
has no virtual authenticator, so the probes answer each prompt
the way a canceled passkey dialog does.
"""
self . inbox_work ()
self . create_ask ({ ** ASK , "command" : "make choices" , "timeout" : 3600 })
authenticator = self . authenticator ()
questions = { width : f "Pick a name at { width } px?" for width , _ in WIDTHS }
decisions = { width : self . tool ( "decision_request" , { "question" : question , "options" : OPTIONS , "agent" : "Stage" , "recommended" : "" })[ "structuredContent" ][ "id" ] for width , question in questions . items ()}
data = self . drive ( self . choice_steps ( browser , signs , questions ), authenticator if signs else None , driver , binary )
( OUT / f " { browser } -choices.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ([ error for error in data [ "errors" ] if "(style-src-elem)" not in error and "/favicon.ico" not in error ], [])
per = 3 + len ( TASK_VIEWS )
for index , ( width , _ ) in enumerate ( WIDTHS ):
view , sent , answer , * fitted = data [ "results" ][ per * index : per * ( index + 1 )]
with self . subTest ( browser = browser , width = width ):
self . assertNotIn ( "error" , view )
self . assertEqual ( view [ "kinds" ], [ "Ask" , "Choice" , "Operator" , "Review" , "Rule request" ])
self . assertEqual ( view [ "filter" ], [ "Waits on Operator" , "true" ])
self . assertEqual ( view [ "channels" ], [[ channel , label , channel in ( "post" , "call" )] for channel , label in zip ( CHOICE_CHANNELS , [ "Post" , "Call" , "Newsletter" , "Broadcast" , "Pan-Pan" , "Sécurité" , "Mayday" ], strict = True )])
2026-11-09 22:22:46 +00:00
self . assertEqual (( view [ "fields" ], view [ "choices" ], view [ "multi" ], view [ "unlabelled" ], view [ "tall" ], view [ "wide" ]), ( 0 , 0 , [ "Request choices" , False , 0 , "0" , 0 ], [], False , False ))
self . assertEqual ( view [ "bar" ], [ "Tasks" , view [ "bar" ][ 1 ], [ "toolbar" , "chips" , "body" ]])
self . assertEqual ( view [ "bar" ][ 1 ] == 1 , width != 390 , view [ "bar" ])
2026-11-09 06:53:55 +00:00
self . assertGreaterEqual ( len ( view [ "agents" ]), 2 , view )
self . assertIn ([ "Choice" , [ "Merge now" , "Wait" ]], view [ "cards" ])
self . requested ( sent , signs )
self . answered ( answer , signs )
for fit , name in zip ( fitted , TASK_VIEWS , strict = True ):
self . assertEqual (( fit [ "hash" ], fit [ "bar" ], fit [ "choices" ], fit [ "tall" ], fit [ "wide" ]), ( f "#tasks/ { name } " , CHOICE_CHANNELS , 0 , False , False ), fit )
2026-11-09 22:22:46 +00:00
self . assertEqual (( fit [ "rows" ] == 1 , fit [ "layout" ]), ( width != 390 , [ "toolbar" , "chips" , "body" ]), fit )
2026-11-09 06:53:55 +00:00
if name == "board" :
self . assertEqual ( fit [ "columns" ], [ "queued" , "running" , "blocked" , "done" , "stopped" ])
hub = self . httpd . hub
if not signs :
self . assertEqual (( self . scripts . calls , hub . messages . listed ()), ([], []))
self . assertEqual ([ hub . decisions . get ( decision )[ "status" ] for decision in decisions . values ()], [ "pending" ] * len ( WIDTHS ))
return
requests = [ record for record in hub . messages . listed () if record [ "text" ] == CHOICE_REQUEST ]
self . assertEqual (( len ( requests ), sorted ({ record [ "class" ] for record in requests })), ( len ( WIDTHS ) * 9 , sorted ( CHOICE_CHANNELS )))
for record in requests :
self . assertTrue ( self . tool ( "operator_message" , { "id" : record [ "id" ], "text" : CHOICE_REQUEST , "to" : record [ "to" ]})[ "structuredContent" ][ "match" ], record [ "id" ])
radio = [ args for script , args in self . scripts . calls if script == skill_scripts . RADIO ]
self . assertEqual ( len ( radio ), len ( requests ) + len ( WIDTHS ))
for width , decision in decisions . items ():
with self . subTest ( width = width ):
state = self . tool ( "decision_wait" , { "id" : decision , "timeout_seconds" : 0 })[ "structuredContent" ]
self . assertEqual (( state [ "status" ], state [ "choice" ], state [ "answer" ]), ( "decided" , "Wait" , f "Choice for decision { decision } ( { questions [ width ] } ): Wait" ))
found = self . tool ( "operator_message" , { "id" : state [ "message" ], "text" : state [ "answer" ], "to" : "Stage" })[ "structuredContent" ]
self . assertEqual (( found [ "match" ], found [ "class" ], found [ "to" ]), ( True , "post" , "Stage" ))
self . assertIn ([ "post" , "--to" , "Stage" , "--session" , "Operator" , f "[kvb-orchestrator msg { state [ 'message' ] } ] { state [ 'answer' ] } " ], radio )
def test_chromium_requests_choices_on_every_channel_and_signs_a_choice_at_three_widths ( self ):
self . check_choices ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ), signs = True )
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_asks_the_passkey_for_every_choice_request_and_answer_and_sends_nothing_unsigned ( self ):
self . check_choices ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ), signs = False )
def plan_work ( self , now : float ) -> None :
register = json . loads (( self . council / "rasci.json" ) . read_text ( encoding = "utf-8" ))
register [ "work/feature" ][ "start" ] = iso ( now - 1800 )
register [ "work/stage" ][ "start" ] = iso ( now - 900 )
register . update ({
"work/done-a" : { "title" : "Done A" , "status" : "done" , "worker" : "Stage" , "branch" : "feature/base" , "start" : iso ( now - 36000 ), "end" : iso ( now - 28800 ), "roles" : { "Stage" : "RA" }, "estimates" : { "Stage" : { "value" : 5 , "complexity" : 2 }}},
"work/done-b" : { "title" : "Done B" , "status" : "done" , "worker" : "Docs" , "start" : iso ( now - 25200 ), "end" : iso ( now - 10800 ), "roles" : { "Docs" : "RA" }, "estimates" : { "Docs" : { "value" : 3 , "complexity" : 2 }}},
"work/done-c" : { "title" : "Done C" , "status" : "done" , "worker" : "Docs" , "start" : iso ( now - 14400 ), "end" : iso ( now - 10800 ), "roles" : { "Docs" : "RA" }},
"work/next" : { "title" : "Next step" , "status" : "queued" , "after" : [ "work/feature" ], "roles" : { "Skills" : "R" , "Maintainer" : "A" }, "estimates" : { "Skills" : { "value" : 8 , "complexity" : 3 }}},
"work/child" : { "title" : "Child of the base" , "status" : "queued" , "base" : "feature/base" , "roles" : { "Docs" : "RA" }},
})
queue = json . loads (( self . council / "queue.json" ) . read_text ( encoding = "utf-8" ))
queue [ "entries" ] += [
{ "id" : "deploy-4" , "seq" : 4 , "kind" : "deploy" , "task" : "work/stage" , "workdir" : "/repo" , "session" : "s2" , "name" : "Stage" , "hotfix" : None , "status" : "active" , "enqueued" : now - 400 , "granted" : now - 300 , "cmd" : "make deploy" },
{ "id" : "test-5" , "seq" : 5 , "kind" : "test" , "task" : "work/docs" , "workdir" : "/repo" , "session" : "s2" , "name" : "Docs" , "hotfix" : None , "status" : "waiting" , "enqueued" : now - 60 , "granted" : None , "cmd" : "make test" },
]
self . write_council ({ "rasci.json" : register , "queue.json" : queue })
def plan_steps ( self , browser : str , signs : bool ) -> list [ dict ]:
local = f "http://localhost: { self . port } /"
flag = "true" if signs else "false"
steps = []
for width , height in WIDTHS :
bulk = BULK . replace ( "@SIGNS@" , flag ) . replace ( "@VALUE@" , PLAN_VALUES [ width ])
steps += [
{ "width" : width , "height" : height , "url" : f " { local } ? { width } #tasks/gantt" , "wait" : 1200 , "probe" : GANTT , "shot" : str ( OUT / f " { browser } - { width } -tasks-gantt.png" )},
{ "width" : width , "height" : height , "url" : f " { local } ? { width } #tasks/burn" , "wait" : 900 , "probe" : BURN , "shot" : str ( OUT / f " { browser } - { width } -tasks-burn.png" )},
{ "width" : width , "height" : height , "url" : None , "wait" : 0 , "probe" : POINTS_ON , "shot" : None },
{ "width" : width , "height" : height , "url" : f " { local } ? { width } -reloaded#tasks/burn" , "wait" : 1200 , "probe" : REQUEST , "shot" : str ( OUT / f " { browser } - { width } -tasks-burn-points.png" )},
{ "width" : width , "height" : height , "url" : f " { local } ? { width } -points#tasks/gantt" , "wait" : 1200 , "probe" : GANTT , "shot" : str ( OUT / f " { browser } - { width } -tasks-gantt-points.png" )},
{ "width" : width , "height" : height , "url" : f " { local } ? { width } -list#tasks/list" , "wait" : 1200 , "probe" : bulk , "shot" : str ( OUT / f " { browser } - { width } -tasks-business-value.png" )},
]
return steps
def planned ( self , probe : dict , points : bool ) -> None :
hour = 3600
edges = [[ "after" , "work/feature" , "work/next" ], [ "base" , "work/done-a" , "work/child" ], [ "queue" , "queue/deploy-4" , "queue/gate-3" ], [ "queue" , "queue/gate-3" , "queue/test-5" ]]
self . assertEqual ( probe [ "forecast" ], [ "always" , [[ "always" , "always" ], [ "points" , "points only" ], [ "real" , "real only" ]]])
self . assertEqual ( probe [ "modes" ], [[ "none" , str ( not points ) . lower ()], [ "points" , str ( points ) . lower ()]])
self . assertEqual (( probe [ "request" ], probe [ "inner" ]), ( points , True ))
self . assertEqual ( sorted ([ kind , start , end ] for kind , start , end , _ in probe [ "edges" ]), sorted ( edges ))
self . assertEqual ({ tip for * _ , tip in probe [ "edges" ]}, { "url(#gantt-tip)" })
bars = { task : ( forecast , start , end , tag ) for task , forecast , start , end , tag in probe [ "bars" ]}
self . assertNotIn ( "operator/push" , bars )
self . assertEqual (( bars [ "work/old" ][ 0 ], bars [ "work/old" ][ 3 ]), ( False , "circle" ))
self . assertEqual ([ bars [ task ][ 0 ] for task in ( "work/feature" , "work/stage" , "queue/deploy-4" , "work/done-a" )], [ False ] * 4 )
self . assertGreaterEqual ( bars [ "work/feature" ][ 2 ] - bars [ "work/feature" ][ 1 ], 1800 )
forecasts = ( "work/next" , "work/docs" , "work/child" , "queue/gate-3" , "queue/test-5" )
lengths = { task : round ( bars [ task ][ 2 ] - bars [ task ][ 1 ], 3 ) for task in forecasts }
self . assertEqual ( lengths , { "work/next" : ( 4.5 if points else 2 ) * hour , "work/docs" : ( 3 if points else 2 ) * hour , "work/child" : 2 * hour , "queue/gate-3" : 2 * hour , "queue/test-5" : 2 * hour })
self . assertTrue ( all ( bars [ task ][ 0 ] for task in forecasts ))
self . assertEqual (( bars [ "work/next" ][ 1 ], bars [ "queue/test-5" ][ 1 ]), ( bars [ "work/feature" ][ 2 ], bars [ "queue/gate-3" ][ 2 ]))
self . assertTrue ( probe [ "hatched" ] and all ( probe [ "hatched" ]), probe [ "hatched" ])
self . assertEqual ( probe [ "real" ], 0 )
only = { task : ( round ( length , 3 ), tag ) for task , length , tag in probe [ "pointsOnly" ]}
dot = ( 0 , "circle" )
self . assertEqual ( only , { "work/next" : ( 4.5 * hour , "rect" ) if points else dot , "work/docs" : ( 3 * hour , "rect" ) if points else dot , "work/child" : dot , "queue/gate-3" : dot , "queue/test-5" : dot })
def check_plan ( self , browser : str , driver : str , binary : str , signs : bool ) -> None :
"""Args:
signs: cdp.mjs signs the bulk business value with its virtual
passkey; bidi.mjs has no virtual authenticator, so the probe
answers the prompt the way a canceled passkey dialog does.
"""
self . plan_work ( time . time ())
authenticator = self . authenticator ()
data = self . drive ( self . plan_steps ( browser , signs ), authenticator if signs else None , driver , binary )
( OUT / f " { browser } -plan.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ([ error for error in data [ "errors" ] if "(style-src-elem)" not in error and "/favicon.ico" not in error ], [])
rows = self . snapshot ()[ "tasks" ]
ranked = list ( enumerate ( rows ))
status_up = [ row [ "task" ] for _ , row in sorted ( ranked , key = lambda pair : ( STATUS_ORDER . index ( pair [ 1 ][ "status" ]), pair [ 0 ]))]
status_down = [ row [ "task" ] for _ , row in sorted ( ranked , key = lambda pair : ( - STATUS_ORDER . index ( pair [ 1 ][ "status" ]), pair [ 0 ]))]
for index , ( width , _ ) in enumerate ( WIDTHS ):
gantt , burn , points_on , request , gantt_points , bulk = data [ "results" ][ 6 * index : 6 * ( index + 1 )]
value = PLAN_VALUES [ width ]
items = [[ "work/next" , f "estimate work/next Operator --value { value } " ], [ "work/child" , f "estimate work/child Operator --value { value } " ]]
with self . subTest ( browser = browser , width = width ):
for probe in ( gantt , burn , points_on , request , gantt_points , bulk ):
self . assertNotIn ( "error" , probe )
2026-11-09 22:22:46 +00:00
self . assertEqual (( probe [ "tall" ], probe [ "wide" ], probe [ "layout" ]), ( False , False , [ "toolbar" , "chips" , "body" ]), probe )
self . assertEqual ( probe [ "rows" ] == 1 , width != 390 , probe )
2026-11-09 06:53:55 +00:00
self . planned ( gantt , points = False )
self . planned ( gantt_points , points = True )
self . assertEqual (( burn [ "modes" ], burn [ "request" ]), ([[ "none" , "true" ], [ "points" , "false" ]], False ))
self . assertEqual ( burn [ "scope" ][ 0 ], "all" )
self . assertEqual ( burn [ "scope" ][ 1 ][: 2 ], [ "all" , "filter" ])
self . assertIn ( "agent:Docs" , burn [ "scope" ][ 1 ])
self . assertIn ( "line:identity/oidc" , burn [ "scope" ][ 1 ])
self . assertEqual ( burn [ "series" ], [[ "scope" , 9 ], [ "done" , 4 ], [ "remaining" , 5 ]])
self . assertEqual ( burn [ "agent" ], [[ "scope" , 5 ], [ "done" , 2 ], [ "remaining" , 3 ]])
self . assertEqual ( points_on [ "before" ], { "modes" : [[ "none" , "true" ], [ "points" , "false" ]], "request" : False })
self . assertEqual (( points_on [ "modes" ], points_on [ "request" ]), ([[ "none" , "false" ], [ "points" , "true" ]], True ))
self . assertEqual ( request [ "modes" ], [[ "none" , "false" ], [ "points" , "true" ]])
self . assertEqual ( request [ "series" ], [[ "scope" , 12 ], [ "done" , 4 ], [ "remaining" , 8 ], [ "unestimated" , 2 ]])
self . assertTrue ( request [ "toast" ] . startswith ( f "Request estimates: { 'done' if signs else 'not signed' } " ), request [ "toast" ])
self . assertEqual ( bulk [ "sorts" ], [ "prio" , "wsjf" , "value" , "complexity" , "status" , "start" , "end" , "agent" , "title" ])
self . assertEqual (( bulk [ "prio" ], bulk [ "status" ], bulk [ "statusDown" ]), ([ row [ "task" ] for row in rows ], status_up , [ status_down , "Descending" ]))
self . assertEqual (( bulk [ "picks" ], bulk [ "armed" ]), ([ "work/feature" , "work/next" , "work/docs" , "work/child" , "work/stage" ], [ False , "2" ]))
self . assertTrue ( bulk [ "toast" ] . startswith ( f "Business value { value } for 2 tasks: { 'handed to the runner' if signs else 'not signed' } " ), bulk [ "toast" ])
signed = [[ "/api/sign" , [ "items" ], items ]]
self . assertEqual (( bulk [ "asked" ], bulk [ "posted" ]), ( 1 , [ * signed , [ "/api/tasks" , [ "items" , "proof" ], items ]] if signs else signed ))
self . assertEqual (( bulk [ "after" ], bulk [ "reset" ]), ([ True , 0 ] if signs else [ False , 2 ], True ))
self . assertEqual ([ args for script , args in self . scripts . calls if script == skill_scripts . ESTIMATE ], [[ "request" , "--session" , "Operator" ]] * ( len ( WIDTHS ) if signs else 0 ))
self . assertEqual ( self . httpd . hub . settings . get ()[ "estimation" ], "none" )
jobs = self . runner_jobs ()
if not signs :
self . assertEqual ( jobs , [])
register = json . loads (( self . council / "rasci.json" ) . read_text ( encoding = "utf-8" ))
self . assertEqual ([ task for task , row in register . items () if "Operator" in row . get ( "estimates" , {})], [])
return
self . assertEqual ([ job [ "argv" ][ - 1 ] for job in jobs ], [ PLAN_VALUES [ width ] for width , _ in WIDTHS for _ in range ( 2 )])
self . run_runner ()
register = json . loads (( self . council / "rasci.json" ) . read_text ( encoding = "utf-8" ))
self . assertEqual ({ task : register [ task ][ "estimates" ][ "Operator" ] for task in ( "work/next" , "work/child" )}, { "work/next" : { "value" : 34 , "at" : register [ "work/next" ][ "estimates" ][ "Operator" ][ "at" ]}, "work/child" : { "value" : 34 , "at" : register [ "work/child" ][ "estimates" ][ "Operator" ][ "at" ]}})
rows = { row [ "task" ]: row for row in self . snapshot ()[ "tasks" ]}
self . assertEqual (( rows [ "work/next" ][ "wsjf" ], rows [ "work/child" ][ "wsjf" ], rows [ "work/child" ][ "value" ]), ({ "value" : 34 , "complexity" : 3 , "score" : 11.33 }, None , 34 ))
def test_chromium_draws_the_gantt_and_burn_switches_the_estimation_requests_estimates_and_signs_a_bulk_business_value_at_three_widths ( self ):
self . check_plan ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ), signs = True )
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_draws_the_gantt_and_burn_and_asks_the_passkey_for_a_bulk_business_value_storing_nothing_unsigned ( self ):
self . check_plan ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ), signs = False )
2026-11-09 22:22:46 +00:00
def trend_work ( self , now : float ) -> None :
day = 86400
register = {
"work/t1" : { "title" : "First" , "status" : "done" , "worker" : "Stage" , "start" : iso ( now - 10 * day ), "end" : iso ( now - 8 * day ), "roles" : { "Stage" : "RA" }, "estimates" : { "Stage" : { "value" : 5 , "complexity" : 1 }}},
"work/t2" : { "title" : "Second" , "status" : "done" , "worker" : "Docs" , "start" : iso ( now - 10 * day ), "end" : iso ( now - 6 * day ), "roles" : { "Docs" : "RA" }, "estimates" : { "Docs" : { "value" : 3 , "complexity" : 2 }}},
"work/t3" : { "title" : "Third" , "status" : "done" , "worker" : "Docs" , "branch" : "feature/t3" , "start" : iso ( now - 10 * day ), "end" : iso ( now - 4 * day ), "roles" : { "Docs" : "RA" }, "estimates" : { "Docs" : { "value" : 3 , "complexity" : 3 }}},
"work/t4" : { "title" : "Fourth" , "status" : "running" , "worker" : "Stage" , "start" : iso ( now - day ), "roles" : { "Stage" : "RA" }, "estimates" : { "Stage" : { "value" : 8 , "complexity" : 5 }}},
"work/t5" : { "title" : "Fifth" , "status" : "queued" , "after" : [ "work/t4" ], "roles" : { "Skills" : "R" }, "estimates" : { "Skills" : { "value" : 8 , "complexity" : 3 }}},
"work/t6" : { "title" : "Sixth" , "status" : "queued" , "base" : "feature/t3" , "roles" : { "Docs" : "RA" }},
}
queue = { "seq" : 2 , "entries" : [
{ "id" : "deploy-1" , "seq" : 1 , "kind" : "deploy" , "task" : "work/t4" , "workdir" : "/repo" , "session" : "s2" , "name" : "Stage" , "hotfix" : None , "status" : "active" , "enqueued" : now - 400 , "granted" : now - 300 , "cmd" : "make deploy" },
{ "id" : "test-2" , "seq" : 2 , "kind" : "test" , "task" : "work/t5" , "workdir" : "/repo" , "session" : "s2" , "name" : "Docs" , "hotfix" : None , "status" : "waiting" , "enqueued" : now - 60 , "granted" : None , "cmd" : "make test" },
]}
self . write_council ({ "rasci.json" : register , "queue.json" : queue })
def trend_steps ( self , browser : str ) -> list [ dict ]:
local = f "http://localhost: { self . port } /"
steps = []
for width , height in WIDTHS :
days = str ( TREND_DAYS [ width ])
steps += [
{ "width" : width , "height" : height , "url" : f " { local } ? { width } #tasks/list" , "wait" : 1200 , "probe" : ETA_LIST , "shot" : str ( OUT / f " { browser } - { width } -tasks-eta.png" )},
{ "width" : width , "height" : height , "url" : f " { local } ? { width } #tasks/burn" , "wait" : 900 , "probe" : TREND_SET . replace ( "@MODE@" , "regression" ) . replace ( "@DAYS@" , days ), "shot" : str ( OUT / f " { browser } - { width } -tasks-burn-regression.png" )},
{ "width" : width , "height" : height , "url" : f " { local } ? { width } -regression#tasks/burn" , "wait" : 1200 , "probe" : TREND_RELOADED , "shot" : None },
{ "width" : width , "height" : height , "url" : None , "wait" : 0 , "probe" : TREND_SET . replace ( "@MODE@" , "velocity" ) . replace ( "@DAYS@" , days ), "shot" : str ( OUT / f " { browser } - { width } -tasks-burn-velocity.png" )},
{ "width" : width , "height" : height , "url" : f " { local } ? { width } -velocity#tasks/burn" , "wait" : 1200 , "probe" : TREND_RELOADED , "shot" : None },
{ "width" : width , "height" : height , "url" : None , "wait" : 0 , "probe" : STEP_TIPS , "shot" : str ( OUT / f " { browser } - { width } -tasks-burn-steps.png" )},
{ "width" : width , "height" : height , "url" : f " { local } ? { width } -3d#tasks/3d" , "wait" : 1200 , "probe" : TASKS_3D , "shot" : str ( OUT / f " { browser } - { width } -tasks-3d.png" )},
{ "width" : width , "height" : height , "url" : f " { local } ? { width } -camera#tasks/3d" , "wait" : 900 , "probe" : CAMERA , "shot" : str ( OUT / f " { browser } - { width } -tasks-3d-steady-camera.png" )},
]
return steps
def eta_checked ( self , probe : dict , mode : str , rows : dict , edges : list [ dict ], window : tuple [ float , float ]) -> None :
"""Args:
probe: task -> [start, eta, forecast, text, title] as the list shows it.
mode: the estimation mode the list was drawn in.
rows: the snapshot's task rows by task.
edges: the snapshot's task edges.
window: the earliest and latest time the page can have taken as now.
"""
day = 86400
points = { "none" : {}, "points" : { "work/t4" : 10 * day , "work/t5" : 6 * day }}[ mode ]
self . assertEqual ( sorted ( probe ), sorted ( rows ))
for task , ( start , eta , forecast , words , title ) in probe . items ():
with self . subTest ( mode = mode , task = task ):
row = rows [ task ]
self . assertTrue ( words . startswith ( "ETA " ) and words != "ETA – " , words )
if row [ "ended" ]:
self . assertEqual (( eta , forecast , title ), ( row [ "end" ], False , "ETA: the end of the ended task" ))
continue
self . assertTrue ( forecast )
self . assertAlmostEqual ( eta - start , points . get ( task , 4 * day ), delta = 0.01 )
ahead = [ probe [ edge [ "from" ]][ 1 ] for edge in edges if edge [ "to" ] == task and edge [ "from" ] in probe and edge [ "from" ] != task ]
if row [ "start" ] is not None :
self . assertEqual ( start , row [ "start" ])
elif ahead and max ( ahead ) > window [ 1 ]:
self . assertAlmostEqual ( start , max ( ahead ), delta = 0.01 )
else :
self . assertTrue ( window [ 0 ] <= start <= window [ 1 ], ( start , window ))
def trend_forecast ( self , points : list [ list [ float ]], mode : str , days : int ) -> tuple [ float , float | None ]:
"""Args:
points: [t, remaining] of the drawn burndown points.
Returns:
The trend's start and the time its remaining line reaches 0
(None when it never falls), computed here independently of plan.js.
"""
last_t , last_y = points [ - 1 ]
if mode == "regression" :
mean_t = sum ( t for t , _ in points ) / len ( points )
mean_y = sum ( y for _ , y in points ) / len ( points )
slope = sum (( t - mean_t ) * ( y - mean_y ) for t , y in points ) / sum (( t - mean_t ) ** 2 for t , _ in points )
return points [ 0 ][ 0 ], ( mean_t - mean_y / slope if slope < 0 else None )
start = max ( points [ 0 ][ 0 ], last_t - days * 86400 )
before = [ y for t , y in points if t <= start ][ - 1 ]
slope = ( last_y - before ) / ( last_t - start )
return start , ( last_t - last_y / slope if slope < 0 else None )
def trended ( self , probe : dict , mode : str , days : int ) -> None :
start , forecast = self . trend_forecast ( probe [ "points" ], mode , days )
self . assertEqual ( probe [ "value" ], mode )
self . assertEqual ( probe [ "days" ], [ days , mode == "velocity" ])
self . assertEqual ( sorted ( series for series , * _ in probe [ "lines" ]), [ "done" , "remaining" , "scope" ])
for _ , begin , end in probe [ "lines" ]:
self . assertAlmostEqual ( begin , start , delta = 0.01 )
self . assertAlmostEqual ( end , forecast if forecast is not None else probe [ "points" ][ - 1 ][ 0 ], delta = 0.01 )
if forecast is None :
self . assertEqual (( probe [ "note" ], probe [ "marker" ]), ([ None , "No forecast: the trend never reaches 0 remaining, so done never meets scope" ], None ))
return
self . assertAlmostEqual ( probe [ "note" ][ 0 ], forecast , delta = 0.01 )
self . assertAlmostEqual ( probe [ "marker" ], forecast , delta = 0.01 )
self . assertTrue ( probe [ "note" ][ 1 ] . startswith ( "Forecast: " ), probe [ "note" ])
def check_trend_eta_3d ( self , browser : str , driver : str , binary : str ) -> None :
self . trend_work ( time . time ())
begun = time . time ()
data = self . drive ( self . trend_steps ( browser ), None , driver , binary , init = GRAPH_HOOK )
ended = time . time ()
( OUT / f " { browser } -trend-eta-3d.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ([ error for error in data [ "errors" ] if "(style-src-elem)" not in error and "/favicon.ico" not in error ], [])
snapshot = self . snapshot ()
rows = { row [ "task" ]: row for row in snapshot [ "tasks" ]}
edges = snapshot [ "task_edges" ]
self . assertEqual ( sorted ([ edge [ "kind" ], edge [ "from" ], edge [ "to" ]] for edge in edges ), [[ "after" , "work/t4" , "work/t5" ], [ "base" , "work/t3" , "work/t6" ], [ "queue" , "queue/deploy-1" , "queue/test-2" ]])
trends = [[ "off" , "off" ], [ "regression" , "linear regression" ], [ "velocity" , "velocity" ]]
per = 8
for index , ( width , _ ) in enumerate ( WIDTHS ):
eta , regression , reloaded , velocity , again , tips , shown , camera = data [ "results" ][ per * index : per * ( index + 1 )]
days = TREND_DAYS [ width ]
kept = TREND_DAYS [ WIDTHS [ index - 1 ][ 0 ]] if index else 7
with self . subTest ( browser = browser , width = width ):
for probe in ( eta , regression , reloaded , velocity , again , tips , shown ):
self . assertNotIn ( "error" , probe )
self . assertEqual (( probe [ "tall" ], probe [ "wide" ], probe [ "layout" ]), ( False , False , [ "toolbar" , "chips" , "body" ]), probe )
self . assertEqual ( probe [ "rows" ] == 1 , width != 390 , probe [ "rows" ])
for mode in ( "none" , "points" , "back" ):
self . eta_checked ( eta [ mode ], "points" if mode == "points" else "none" , rows , edges , ( begun - 1 , ended + 1 ))
self . assertEqual ( eta [ "modes" ], [[ "none" , "true" ], [ "points" , "false" ]])
self . assertEqual (( regression [ "before" ][ "value" ], regression [ "before" ][ "options" ], regression [ "before" ][ "note" ], regression [ "before" ][ "lines" ]), ( "off" , trends , None , []))
self . assertEqual ( regression [ "before" ][ "days" ], [ kept , False ])
self . assertEqual ( regression [ "toasts" ], [ "Trend: linear regression" ])
self . trended ( regression [ "after" ], "regression" , kept )
self . trended ( reloaded , "regression" , kept )
self . assertEqual ( velocity [ "toasts" ], [ f "Trend: velocity over { kept } days" , f "Trend: velocity over { days } days" ])
self . trended ( velocity [ "after" ], "velocity" , days )
self . trended ( again , "velocity" , days )
self . assertEqual ([ step [ "t" ] for step in tips [ "steps" ]], [ t for t , _ in again [ "points" ]])
listed = [[ sorted ( filter ( None , step [ key ] . split ( " " ))) for key in ( "started" , "ended" , "joined" )] for step in tips [ "steps" ]]
self . assertEqual ( listed , [[[ "work/t1" , "work/t2" , "work/t3" ], [], [ "work/t5" , "work/t6" ]], [[], [ "work/t1" ], []], [[], [ "work/t2" ], []], [[], [ "work/t3" ], []], [[ "work/t4" ], [], []], [[], [], []]])
self . assertEqual ([ step [ "series" ] for step in tips [ "steps" ]], [[ "scope" , "done" , "remaining" ]] * len ( tips [ "steps" ]))
self . assertIn ( "Ended: work/t1" , tips [ "steps" ][ 1 ][ "tip" ])
self . assertIn ( "No work package started, ended or joined the scope here" , tips [ "steps" ][ - 1 ][ "tip" ])
self . assertEqual (( tips [ "reset" ], tips [ "note" ], tips [ "lines" ]), ( "Trend: off" , False , 0 ))
self . tasks_3d ( shown , snapshot )
self . assertTrue ( camera [ "hooked" ], camera )
self . assertGreater ( math . dist ( camera [ "built" ], camera [ "moved" ]), 1 , camera )
for refresh in camera [ "refreshes" ]:
self . assertEqual (( refresh [ "rebuilt" ], refresh [ "settled" ]), ( True , True ), camera )
self . assertLess ( math . dist ( refresh [ "camera" ], camera [ "moved" ]), 0.01 , camera )
self . assertEqual ( self . httpd . hub . settings . get ()[ "trend" ], { "mode" : "off" , "days" : TREND_DAYS [ WIDTHS [ - 1 ][ 0 ]]})
def tasks_3d ( self , shown : dict , snapshot : dict ) -> None :
tasks = sorted ( row [ "task" ] for row in snapshot [ "tasks" ])
colors = { "after" : shown [ "palette" ][ "violet" ], "base" : shown [ "palette" ][ "info" ], "queue" : shown [ "palette" ][ "warn" ]}
edges = sorted ([ edge [ "from" ], edge [ "to" ], edge [ "kind" ], colors [ edge [ "kind" ]]] for edge in snapshot [ "task_edges" ])
self . assertEqual ( len ( set ( colors . values ())), 3 , colors )
for layout , word in (( "force" , "Force" ), ( "time" , "Time" )):
drawn = shown [ layout ]
with self . subTest ( layout = layout ):
self . assertEqual ( drawn [ "note" ], f " { len ( tasks ) } nodes · { len ( edges ) } edges · { word } " )
self . assertEqual ( drawn [ "layouts" ], [[ "none" , str ( layout == "force" ) . lower ()], [ "time" , str ( layout == "time" ) . lower ()]])
self . assertEqual ( sorted ( node for node , * _ in drawn [ "nodes" ]), tasks )
self . assertEqual ( sorted ( drawn [ "links" ]), edges )
self . assertEqual ( drawn [ "label" ], "work/t5 · Fifth" )
self . assertEqual ([ fz for _ , fz , _ in shown [ "force" ][ "nodes" ]], [ None ] * len ( tasks ))
timed = sorted ( shown [ "time" ][ "nodes" ], key = lambda node : node [ 2 ])
self . assertTrue ( all ( fz is not None for _ , fz , _ in timed ), timed )
self . assertEqual ([ fz for _ , fz , _ in timed ], sorted ( fz for _ , fz , _ in timed ))
self . assertLess ( timed [ 0 ][ 1 ], timed [ - 1 ][ 1 ])
popup = shown [ "popup" ]
self . assertEqual (( popup [ "open" ], popup [ "title" ], popup [ "id" ], popup [ "picked" ], popup [ "open_button" ], popup [ "line" ]), ( True , "Fifth" , "work/t5" , "work/t5" , "Open task" , True ), popup )
self . assertTrue ( popup [ "eta" ], popup )
self . assertEqual (( popup [ "closed" ], shown [ "back" ]), ([ False , None ], f " { len ( tasks ) } nodes · { len ( edges ) } edges · Force" ))
def test_chromium_draws_the_trend_forecast_point_tips_eta_and_tasks_3d_at_three_widths ( self ):
self . check_trend_eta_3d ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ))
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_draws_the_trend_forecast_point_tips_eta_and_tasks_3d_at_three_widths ( self ):
self . check_trend_eta_3d ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ))
2026-11-07 23:43:26 +00:00
def test_chromium_pins_a_task_priority_after_signature_and_the_runner_applies_it ( self ):
signer = self . pin ()
private = "" . join ( line for line in signer . pem . read_text ( encoding = "utf-8" ) . splitlines () if not line . startswith ( "-----" ))
authenticator = { "credentialId" : base64 . b64encode ( signer . raw_id ) . decode (), "privateKey" : private , "rpId" : "localhost" }
local = f "http://localhost: { self . port } /"
data = self . drive ([{ "width" : 1366 , "height" : 768 , "url" : f " { local } #tasks/list" , "wait" : 1200 , "probe" : PIN , "shot" : self . shot ( "1366-tasks-priority-signed" )}], authenticator )
( OUT / "chromium-task-priority.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
pinned = data [ "results" ][ 0 ]
self . assertNotIn ( "error" , pinned )
self . assertEqual (( pinned [ "title" ], pinned [ "preview" ][ 0 ]), ( "work/docs" , "rasci.py set work/docs --prio 2" ))
2026-11-08 17:26:51 +00:00
self . assertEqual ( pinned [ "toasts" ], [ "Priority work/docs: handed to the runnerrasci.py set work/docs --prio 2" ])
2026-11-07 23:43:26 +00:00
jobs = self . runner_jobs ()
self . assertEqual ([( job [ "id" ], job [ "kind" ], job [ "argv" ]) for job in jobs ], [( pinned [ "jobs" ], "task" , [ "set" , "work/docs" , "--prio" , "2" ])])
self . run_runner ()
register = json . loads (( self . council / "rasci.json" ) . read_text ( encoding = "utf-8" ))
self . assertEqual (( register [ "work/docs" ][ "prio" ], register [ "work/feature" ][ "prio" ]), ( 2 , 1 ))
self . assertEqual ([( row [ "task" ], row [ "prio" ]) for row in self . snapshot ()[ "tasks" ][: 2 ]], [( "work/feature" , 1 ), ( "work/docs" , 2 )])
2026-11-06 22:20:06 +00:00
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
2026-11-07 15:03:14 +00:00
def test_firefox_walks_every_screen_at_three_widths ( self ):
shots = [( width , height , screen , OUT / f "firefox- { width } - { screen . replace ( '/' , '-' ) } .png" ) for width , height in WIDTHS for screen in SCREENS ]
for * _ , shot in shots :
shot . unlink ( missing_ok = True )
steps = [{ "width" : width , "height" : height , "url" : f " { self . base } # { screen } " , "wait" : 900 , "probe" : PROBE , "shot" : str ( shot )} for width , height , screen , shot in shots ]
data = self . drive ( steps , None , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ))
( OUT / "firefox-walk.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
blocked = [ error for error in data [ "errors" ] if error . startswith ( "Content-Security-Policy" )]
self . assertEqual ([ error for error in data [ "errors" ] if error not in blocked ], [])
# 3d-force-graph injects its own <style>, which the unchanged CSP blocks; the page carries those rules itself.
self . assertEqual ([ error for error in blocked if "(style-src-elem)" not in error and "/favicon.ico" not in error ], [])
for probe , ( width , _ , screen , shot ) in zip ( data [ "results" ], shots , strict = True ):
with self . subTest ( width = width , screen = screen ):
self . assertNotIn ( "error" , probe )
self . assertEqual ( probe [ "hash" ], f "# { screen } " )
2026-11-07 23:43:26 +00:00
self . assertEqual (( probe [ "tabs" ], probe [ "subs" ]), ( TABS , SUBS ))
2026-11-07 15:03:14 +00:00
self . assertEqual ( labelled ( probe ), { "unlabelled" : [], "broken" : []})
self . assertFalse ( probe [ "wide" ], "the page scrolls sideways" )
2026-11-08 21:18:55 +00:00
self . assertEqual (( probe [ "panel" ], probe [ "nodeChips" ]), ( 0 , 0 ))
2026-11-07 15:03:14 +00:00
self . assertTrue ( shot . is_file () and shot . stat () . st_size > 1000 , shot )
if screen . endswith ( "/3d" ):
self . assertTrue ( probe [ "canvas" ], probe [ "note" ])
2026-11-06 22:20:06 +00:00
if __name__ == "__main__" :
unittest . main ()