The header radio button opens the radio spot above the main area of the current tab. The Send button, the toolbar radio button and the Radio tab are gone, the mayday clears inside the spot without a Cancel mayday label, and the radio station probe waits for a visible row. 🎯 Fix: 92 % 💥 Break: 4 % 💸 Cost: 100 % ⏱️ Time: 100 % 🛡️ Security: 100 % 🏆 Score: 88 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
118 lines
4.3 KiB
JavaScript
118 lines
4.3 KiB
JavaScript
const navButtons = new Map();
|
|
|
|
function tabButton(id, label, hint) {
|
|
const badge = Object.hasOwn(state.badges, id) ? h("span", {class: "badge", hidden: true}) : null;
|
|
const button = h("button", {type: "button", id: `tab-${id}`, class: "icon-only", "aria-controls": "main", ...named(label, hint ? `${label} (${hint})` : label), on: {click: () => go(id, null)}}, icon(Nav.ICONS[id]), badge);
|
|
navButtons.set(id, {button, badge});
|
|
return button;
|
|
}
|
|
|
|
document.getElementById("tabs").append(...Nav.TABS.map(([id, label], index) => h("div", {class: "tab-group"},
|
|
tabButton(id, label, `key ${index + 1}`),
|
|
Nav.SUBS[id] ? h("div", {class: "subtabs"}, Nav.SUBS[id].map(([sub, name]) => tabButton(sub, name, ""))) : null)));
|
|
|
|
function measure() {
|
|
document.documentElement.style.setProperty("--head", `${document.getElementById("head").offsetHeight}px`);
|
|
}
|
|
|
|
function go(screen, view) {
|
|
const hash = Nav.hashOf(screen, view || ui.views[screen] || null);
|
|
if (window.location.hash === hash) {
|
|
apply();
|
|
} else {
|
|
window.location.hash = hash;
|
|
}
|
|
}
|
|
|
|
function apply() {
|
|
const route = Nav.route(window.location.hash);
|
|
if (route.redirect) {
|
|
window.history.replaceState(null, "", route.redirect);
|
|
}
|
|
if (route.spot) {
|
|
openSpot(true);
|
|
}
|
|
ui.screen = route.screen;
|
|
if (route.view) {
|
|
ui.views[route.screen] = route.view;
|
|
}
|
|
if (document.startViewTransition && drawn.screen && !calm.matches) {
|
|
document.startViewTransition(() => render(true));
|
|
} else {
|
|
render(true);
|
|
}
|
|
}
|
|
|
|
function signature() {
|
|
if (ui.screen === "agents") {
|
|
return JSON.stringify([ui.views.agents]);
|
|
}
|
|
return JSON.stringify(ui.screen === "artifacts" || ui.screen === "tasks" ? [ui.views[ui.screen]] : ui.screen === "rules" ? state.rules : []);
|
|
}
|
|
|
|
function screenOf() {
|
|
return {stream: streamScreen, inbox: tasksScreen, rules: rulesScreen, agents: agentsScreen, artifacts: artifactsScreen, tasks: tasksScreen}[ui.screen]();
|
|
}
|
|
|
|
function render(force) {
|
|
for (const [id, {button}] of navButtons) {
|
|
button.setAttribute("aria-current", ui.screen === id ? "page" : "false");
|
|
}
|
|
const sig = signature();
|
|
if (force || drawn.screen !== ui.screen || drawn.sig !== sig) {
|
|
graphDom = null;
|
|
stream.dom = null;
|
|
tree.dom = null;
|
|
inboxDom = null;
|
|
inboxSig = "";
|
|
taskView.dom = null;
|
|
const main = document.getElementById("main");
|
|
main.setAttribute("aria-labelledby", `tab-${ui.screen}`);
|
|
main.replaceChildren(screenOf());
|
|
document.body.classList.toggle("fit", ["tasks", "inbox"].includes(ui.screen) || (["agents", "artifacts"].includes(ui.screen) && !["tree", "hierarchy"].includes(ui.views[ui.screen])));
|
|
drawn.screen = ui.screen;
|
|
drawn.sig = sig;
|
|
if (g3.host && !g3.host.isConnected) {
|
|
dispose3d();
|
|
}
|
|
}
|
|
stage();
|
|
drawSpawn();
|
|
drawHierarchy(force);
|
|
drawToolbar();
|
|
drawInbox();
|
|
drawRadio();
|
|
drawTasks();
|
|
drawChoices();
|
|
drawStatus();
|
|
tick();
|
|
placePopup();
|
|
}
|
|
|
|
function drawStatus() {
|
|
for (const [id, count] of Object.entries(state.badges)) {
|
|
const {badge} = navButtons.get(id);
|
|
badge.textContent = String(count);
|
|
badge.hidden = count === 0;
|
|
badge.className = `badge${id === "inbox" && count ? " urgent" : ""}`;
|
|
}
|
|
const banner = document.getElementById("mayday");
|
|
const silence = state.silence;
|
|
banner.hidden = !silence;
|
|
banner.replaceChildren(...(silence ? [h("p", {class: "text"}, `MAYDAY · ${dash(silence.by)}: ${silence.text}`), iconBtn("bi-x-octagon", "cancel", "btn small", () => confirmAct("clear-mayday"), "cancel: end the mayday (radio.py clear)")] : []));
|
|
document.title = `${state.badges.inbox ? `(${state.badges.inbox}) ` : ""}Agent Orchestrator`;
|
|
window.setTimeout(measure, 0);
|
|
}
|
|
|
|
function toggleLeitstand() {
|
|
const on = !document.body.classList.contains("leitstand");
|
|
document.body.classList.toggle("leitstand", on);
|
|
if (on && document.documentElement.requestFullscreen && !document.fullscreenElement) {
|
|
document.documentElement.requestFullscreen().catch(() => null);
|
|
}
|
|
if (!on && document.fullscreenElement) {
|
|
document.exitFullscreen().catch(() => null);
|
|
}
|
|
window.setTimeout(measure, 50);
|
|
}
|