Files
skills/orchestrator/templates/page-space-steer.js
T
Kevin Veen-BirkenbachandClaude Opus 5.5 6f940b4415 feat(orchestrator): rename kvb-hub to Agent Orchestrator with the phase 2 surface
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>
2026-11-06 22:20:06 +00:00

195 lines
7.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const depth = () => (space.views.graph.axis === "spawn" ? "spawn" : "time");
function spaceTools() {
return h("div", {class: "space-tools"},
h("div", {class: "segmented", role: "group", "aria-label": "Tiefenachse"}, AXES.map(([id, label, hint]) => h("button", {type: "button", "aria-pressed": String(depth() === id), title: hint, "aria-label": `Tiefenachse ${label}: ${hint}`, on: {click: () => chooseAxis(id)}}, label))),
h("button", {type: "button", class: "btn small ghost", title: "Drehung, Zoom und Verschiebung zurücksetzen (Taste 0)", "aria-label": "3D-Ansicht zurücksetzen", on: {click: () => resetView("graph")}}, "⟲ Ansicht"),
h("span", {class: "muted", id: "space-help"}, "Ziehen dreht · Umschalt+Ziehen verschiebt · Rad oder zwei Finger zoomen · Pfeile, + / −, 0"));
}
const graph3d = () => ui.screen === "agents" && ui.views.agents === "3d";
const wantsTrack = () => graph3d() && depth() === "time";
function chooseAxis(id) {
if (id === depth()) {
return;
}
setView("graph", {...space.views.graph, axis: id}, false);
render(false);
}
function stage() {
if (!graphDom || ui.screen !== "agents" || ui.views.agents === "tree") {
return;
}
graphDom.pulses = null;
if (graph3d()) {
stageSpace();
return;
}
graphDom.stage.replaceChildren({orbit, chord, swimlane}[ui.views.agents]());
}
function drawSide() {
if (graphDom && ui.screen === "agents" && ui.views.agents !== "tree") {
graphDom.aside.replaceChildren(...side());
}
}
function seenView(kind) {
const glide = space.glides[kind];
const progress = glide && !calm.matches ? Math.min((performance.now() - glide.start) / GLIDE, 1) : 1;
if (progress >= 1) {
space.glides[kind] = null;
return space.views[kind];
}
return Space.tween(kind, glide.from, space.views[kind], Space.ease(progress));
}
function settle(kind) {
if (space.glides[kind]) {
space.views[kind] = Space.clampView(kind, seenView(kind));
space.glides[kind] = null;
}
}
function setView(kind, next, smooth) {
space.glides[kind] = smooth && !calm.matches ? {from: seenView(kind), start: performance.now()} : null;
space.views[kind] = Space.clampView(kind, next);
space.edits += 1;
window.clearTimeout(space.timer);
space.timer = window.setTimeout(persistViews, SAVE_DELAY);
if (kind === "graph") {
requestSpace();
} else {
redrawSky();
}
}
const nudge = (kind, change, smooth) => setView(kind, change(space.views[kind]), smooth);
const savedSpace = () => ({graph: Space.tidy(space.views.graph), sky: Space.tidy(space.views.sky)});
async function persistViews() {
const mark = space.edits;
if (await saveSettings({...settings, space: savedSpace()}) && space.edits === mark) {
space.saved = mark;
}
}
function resetView(kind) {
setView(kind, kind === "graph" ? {...Space.DEFAULTS.graph, axis: space.views.graph.axis} : {...Space.DEFAULTS.sky}, true);
}
function steerKey(event, kind, hooks) {
const arrows = {ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1]};
if (event.altKey || event.ctrlKey || event.metaKey) {
return;
}
if (Object.hasOwn(arrows, event.key)) {
const [x, y] = arrows[event.key];
if (event.shiftKey && hooks.panStep) {
hooks.panStep(x, y);
} else {
hooks.turnStep(x, y);
}
} else if (event.key === "+" || event.key === "=") {
hooks.zoomStep(ZOOM);
} else if (event.key === "-" || event.key === "_") {
hooks.zoomStep(1 / ZOOM);
} else if (event.key === "0") {
resetView(kind);
} else if (event.key === "Escape" && kind === "sky") {
skyLook(false);
} else {
return;
}
event.preventDefault();
event.stopPropagation();
}
function steer(element, kind, hooks) {
const pointers = new Map();
let travel = 0;
let spread = 0;
const gap = () => {
const [a, b] = [...pointers.values()];
return Math.hypot(a.x - b.x, a.y - b.y);
};
element.addEventListener("pointerdown", (event) => {
if (event.pointerType === "mouse" && event.button !== 0) {
return;
}
element.setPointerCapture(event.pointerId);
pointers.set(event.pointerId, {x: event.clientX, y: event.clientY});
settle(kind);
travel = pointers.size === 1 ? 0 : Infinity;
spread = pointers.size === 2 ? gap() : 0;
});
element.addEventListener("pointermove", (event) => {
const last = pointers.get(event.pointerId);
if (!last) {
return;
}
const dx = event.clientX - last.x;
const dy = event.clientY - last.y;
pointers.set(event.pointerId, {x: event.clientX, y: event.clientY});
travel += Math.abs(dx) + Math.abs(dy);
if (pointers.size === 2) {
const next = gap();
if (spread > 0 && next > 0) {
hooks.zoom(next / spread);
}
spread = next;
} else if (pointers.size === 1 && travel > 3) {
if (event.shiftKey && hooks.pan) {
hooks.pan(dx, dy);
} else {
hooks.rotate(dx, dy);
}
}
});
const release = (event) => {
if (!pointers.delete(event.pointerId)) {
return;
}
spread = pointers.size === 2 ? gap() : 0;
if (event.type === "pointerup" && !pointers.size && travel <= 3 && hooks.pick) {
const box = element.getBoundingClientRect();
hooks.pick(event.clientX - box.left, event.clientY - box.top);
}
};
element.addEventListener("pointerup", release);
element.addEventListener("pointercancel", release);
element.addEventListener("wheel", (event) => {
event.preventDefault();
settle(kind);
hooks.zoom(Math.exp(-event.deltaY * [1, 16, 400][event.deltaMode] * 0.0015));
}, {passive: false});
element.addEventListener("keydown", (event) => steerKey(event, kind, hooks));
}
const graphHooks = {
rotate: (dx, dy) => nudge("graph", (view) => ({...view, yaw: view.yaw - dx * 0.4, pitch: view.pitch + dy * 0.4}), false),
pan: (dx, dy) => nudge("graph", (view) => ({...view, pan_x: view.pan_x + dx / (space.cam ? space.cam.scale : 1), pan_y: view.pan_y + dy / (space.cam ? space.cam.scale : 1)}), false),
zoom: (factor) => nudge("graph", (view) => ({...view, zoom: view.zoom * factor}), false),
turnStep: (x, y) => nudge("graph", (view) => ({...view, yaw: view.yaw - x * TURN, pitch: view.pitch + y * TURN}), true),
panStep: (x, y) => nudge("graph", (view) => ({...view, pan_x: view.pan_x + x * NUDGE, pan_y: view.pan_y + y * NUDGE}), true),
zoomStep: (factor) => nudge("graph", (view) => ({...view, zoom: view.zoom * factor}), true),
pick: (x, y) => pickSpace(x, y),
};
const skyLens = () => Space.lens(seenView("sky"), window.innerWidth, window.innerHeight);
const skyHooks = {
rotate: (dx, dy) => {
const sight = skyLens();
nudge("sky", (view) => ({...view, azimuth: view.azimuth - dx / sight.kx, altitude: view.altitude + dy / sight.ky}), false);
},
pan: null,
zoom: (factor) => nudge("sky", (view) => ({...view, fov: view.fov / factor}), false),
turnStep: (x, y) => nudge("sky", (view) => ({...view, azimuth: view.azimuth + x * TURN, altitude: view.altitude - y * TURN}), true),
panStep: null,
zoomStep: (factor) => nudge("sky", (view) => ({...view, fov: view.fov / factor}), true),
pick: null,
};