Surface (requirement 001 phase 2): navigation Stream (Inbox, Regeln) / Agents / Artifacts with keys 1-3 and hash routes (#graph and #tree redirect), header with title, command palette and settings only, server-side badges (unsnoozed inbox, beats within 900 s, open jobs), a Mayday banner with "aufheben", four stream chips with agent:/art:/seit: search, Filter and time menus, the inbox in main, an agents toolbar (Funk an alle with Newsletter, Broadcast, Pan-Pan, Security, Mayday; Priorität; Tag/Nacht), Post or Call in the agent panel, Ambiente in the settings and read-aloud over the seven classes with Mayday locked. Rename (decision 9): hub/ is orchestrator/, the package kvb_orchestrator, the unit kvb-orchestrator-runner.service, scripts/orchestrator.sh, environment KVB_ORCHESTRATOR_*, make targets orchestrator-*, the MCP server, image and compose project kvb-orchestrator, the guard header X-Kvb-Orchestrator and the message marker [kvb-orchestrator msg <id>]. install runs migrate first: it stops the old container, removes its image and code, moves state, approvals and pinned keys, swaps the MCP registration, the PermissionRequest hook and the runner unit, and changes nothing on a second run (scratch-HOME test with stub docker, claude and systemctl). Saved views fall back to the old localStorage key once. MEDUSA acceptances carried to the renamed paths: 82 rows whose lines are unchanged (reason prefixed "renamed from <old path>, line unchanged"), and 7 rows whose text changed only in the product name, the package name, a temp prefix or a new class argument, each upheld by one kvb-think-criticize skeptic (approval.py:60, messaging.py:53, server.py:56, tests/hub_support.py:64, passkey.py:205, page-agent.js:75, language.py:46). Cost 101 %: the longer name adds 15 tokens of skill text; the operator approved it with the commit. 🎯 Fix: 92 % 💥 Break: 4 % 💸 Cost: 101 % ⏱️ Time: – 🛡️ Security: 100 % 💰 Value: ? 🏆 Score: 87 🚦 Gate: ⚠️ cost 101 % > 100: ask the operator before the commit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
88 lines
4.1 KiB
JavaScript
88 lines
4.1 KiB
JavaScript
function treeScreen() {
|
|
const search = h("input", {type: "search", placeholder: "Repo, Branch, Pfad oder Agent filtern …", value: tree.q, "aria-label": "Tree filtern", on: {input: (event) => {
|
|
tree.q = event.target.value;
|
|
drawTree();
|
|
}}});
|
|
const count = h("span", {class: "muted"});
|
|
const list = h("div", {class: "repos"});
|
|
tree.dom = {list, count};
|
|
window.setTimeout(() => {
|
|
drawTree();
|
|
fetchTree();
|
|
}, 0);
|
|
return h("div", {}, h("div", {class: "filters"}, search, count, btn("Neu laden", "btn small soft", fetchTree)), list);
|
|
}
|
|
|
|
async function fetchTree() {
|
|
if (tree.busy) {
|
|
return;
|
|
}
|
|
tree.busy = true;
|
|
try {
|
|
const response = await fetch("/api/tree", {cache: "no-store"});
|
|
if (response.ok) {
|
|
tree.data = await response.json();
|
|
}
|
|
} catch {
|
|
ui.offline = true;
|
|
}
|
|
tree.busy = false;
|
|
drawTree();
|
|
}
|
|
|
|
function worktreeRow(wt) {
|
|
const review = wt.review;
|
|
return h("div", {class: "wt"},
|
|
h("code", {}, wt.path),
|
|
h("span", {class: "muted", title: wt.head}, wt.head.slice(0, 10)),
|
|
wt.dirty === null ? pill("fehlt", "neutral") : wt.dirty ? pill("geändert", "warn") : pill("sauber", "ok"),
|
|
wt.workers.length ? h("span", {class: "who"}, wt.workers.map((name) => [face(name, false), person(name)])) : h("span", {class: "muted"}, "niemand"),
|
|
review ? [status(review.status), review.gate ? pill(review.gate === "pass" ? "Gate grün" : "Gate rot", review.gate === "pass" ? "ok" : "bad") : null] : null,
|
|
copyButton(wt.path));
|
|
}
|
|
|
|
function drawTree() {
|
|
if (!tree.dom) {
|
|
return;
|
|
}
|
|
if (!tree.data) {
|
|
tree.dom.list.replaceChildren(emptyState(tree.busy ? "Lese Repositories …" : "Kein Tree", "Die Repositories werden höchstens jede Minute neu gelesen."));
|
|
return;
|
|
}
|
|
const q = tree.q.trim().toLowerCase();
|
|
const hit = (...texts) => !q || texts.some((text) => String(text).toLowerCase().includes(q));
|
|
const repos = [];
|
|
for (const repo of tree.data.repos) {
|
|
const branches = repo.branches.filter((entry) => hit(repo.name, entry.name, ...entry.worktrees.flatMap((wt) => [wt.path, ...wt.workers])));
|
|
const detached = repo.detached.filter((wt) => hit(repo.name, wt.path, ...wt.workers));
|
|
if (branches.length || detached.length) {
|
|
repos.push({repo, branches, detached});
|
|
}
|
|
}
|
|
tree.dom.count.textContent = `${repos.length} Repositories · Stand ${clock.format(stampOf(tree.data.scanned))}`;
|
|
if (!repos.length) {
|
|
tree.dom.list.replaceChildren(emptyState("Nichts gefunden", "Kein Repository passt zum Filter."));
|
|
return;
|
|
}
|
|
tree.dom.list.replaceChildren(...repos.map(({repo, branches, detached}) => {
|
|
const trees = repo.branches.flatMap((entry) => entry.worktrees).concat(repo.detached);
|
|
const dirty = trees.filter((wt) => wt.dirty).length;
|
|
const details = h("details", {class: "card repo", open: Boolean(q) || tree.open.has(repo.path)},
|
|
h("summary", {}, h("span", {}, repo.name), counter(`${repo.branches.length} Branches`), counter(`${trees.length} Worktrees`), dirty ? pill(`${dirty} geändert`, "warn") : null),
|
|
branches.map((entry) => h("div", {class: "branch-row"},
|
|
h("div", {class: "branch-name"}, h("strong", {}, entry.name), entry.upstream ? h("span", {class: "muted"}, `↔ ${entry.upstream}`) : h("span", {class: "muted"}, "ohne Upstream"),
|
|
entry.ahead ? pill(`${entry.ahead} voraus`, "info") : null, entry.behind ? pill(`${entry.behind} zurück`, "warn") : null, entry.gone ? pill("Upstream weg", "bad") : null,
|
|
h("span", {class: "muted", title: entry.head}, entry.head.slice(0, 10))),
|
|
entry.worktrees.map(worktreeRow))),
|
|
detached.length ? h("div", {class: "branch-row"}, h("div", {class: "branch-name"}, h("strong", {}, "losgelöster HEAD")), detached.map(worktreeRow)) : null);
|
|
details.addEventListener("toggle", () => {
|
|
if (details.open) {
|
|
tree.open.add(repo.path);
|
|
} else {
|
|
tree.open.delete(repo.path);
|
|
}
|
|
});
|
|
return details;
|
|
}));
|
|
}
|