Files
skills/orchestrator/templates/page-sky.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

207 lines
9.7 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 skyMoment = () => (live() ? Date.now() : (ui.asOf === null ? state.until : ui.asOf) * 1000);
function applyTheme(sun) {
const custom = settings.theme === "custom";
const mode = custom ? Ambience.modeOf(settings.colors.top, settings.colors.bottom) : settings.theme === "sky" ? Ambience.modeAt(sun.alt) : settings.theme;
const glass = Boolean(window.CSS && CSS.supports("(backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))")) && !window.matchMedia("(prefers-reduced-transparency: reduce)").matches;
const key = [mode, custom ? settings.colors.accent : "", glass].join("|");
if (key === sky.mode) {
return;
}
sky.mode = key;
const root = document.documentElement;
const tokens = Ambience.THEMES[mode];
for (const [name, value] of Object.entries(tokens)) {
root.style.setProperty(`--${name}`, value);
}
if (custom) {
root.style.setProperty("--accent", settings.colors.accent);
root.style.setProperty("--accent-soft", Ambience.css(Ambience.mix(Ambience.hex(settings.colors.accent), Ambience.hex(tokens.panel), 0.84)));
}
root.style.setProperty("--veil", `${glass ? Ambience.GLASS * 100 : 100}%`);
root.style.setProperty("--card", `${glass ? Ambience.CARD * 100 : 100}%`);
root.style.colorScheme = mode;
root.dataset.mode = mode;
requestSpace();
}
function drawMoon(paint, at, radius, moon) {
const tilt = (moon.tilt * Math.PI) / 180;
paint.save();
paint.translate(at.x, at.y);
paint.rotate(Math.atan2(-Math.cos(tilt), -Math.sin(tilt)));
const halo = paint.createRadialGradient(0, 0, radius, 0, 0, radius * 4);
halo.addColorStop(0, `rgb(235 240 255 / ${(0.28 * moon.lit).toFixed(3)})`);
halo.addColorStop(1, "rgb(235 240 255 / 0)");
paint.fillStyle = halo;
paint.fillRect(-radius * 4, -radius * 4, radius * 8, radius * 8);
paint.fillStyle = "rgb(60 68 92 / 0.55)";
paint.beginPath();
paint.arc(0, 0, radius, 0, Math.PI * 2);
paint.fill();
paint.fillStyle = "#f3f0e4";
paint.beginPath();
paint.arc(0, 0, radius, -Math.PI / 2, Math.PI / 2, false);
paint.ellipse(0, 0, Math.abs(2 * moon.lit - 1) * radius, radius, 0, Math.PI / 2, -Math.PI / 2, moon.lit < 0.5);
paint.closePath();
paint.fill();
paint.restore();
}
function drawSky() {
const ms = skyMoment();
const sun = Ambience.sun(ms, settings.latitude, settings.longitude);
const moon = Ambience.moon(ms, settings.latitude, settings.longitude);
applyTheme(sun);
const view = seenView("sky");
const facing = COMPASS[Space.compass(view.azimuth)];
const turned = ["azimuth", "altitude", "fov"].some((key) => Math.abs(view[key] - Space.DEFAULTS.sky[key]) >= 0.5);
const moonText = moon.alt > 0 ? ` · Mond ${Math.round(moon.lit * 100)} % ${moon.waxing ? "zunehmend" : "abnehmend"}` : "";
document.getElementById("sky-note").textContent = `${sun.alt > 0 ? "☀" : "☾"} ${STAGE_LABELS[Ambience.stage(sun.alt)]}${moonText}${turned ? ` · Blick ${facing[0]}` : ""}`;
if (space.look) {
const words = `Blick nach ${facing[1]} · ${((Math.round(view.azimuth) % 360) + 360) % 360}° · Höhe ${Math.round(view.altitude)}° · Sichtfeld ${Math.round(view.fov)}°`;
space.compass.textContent = words;
sky.canvas.setAttribute("aria-label", `Himmel, ${words}. Pfeiltasten drehen den Blick, Plus und Minus zoomen, 0 setzt zurück, Esc beendet.`);
}
const canvas = sky.canvas;
const paint = canvas.getContext("2d");
if (!paint) {
return;
}
const ratio = Math.min(window.devicePixelRatio || 1, 2);
const width = window.innerWidth;
const height = window.innerHeight;
if (canvas.width !== Math.round(width * ratio) || canvas.height !== Math.round(height * ratio)) {
canvas.width = Math.round(width * ratio);
canvas.height = Math.round(height * ratio);
}
paint.setTransform(ratio, 0, 0, ratio, 0, 0);
const sight = Space.lens(view, width, height);
const place = (alt, az) => Space.place(sight, alt, az);
const inside = (at, margin) => at.x >= -margin && at.x <= width + margin && at.y >= -margin && at.y <= height + margin;
const seen = (body, at) => body.alt > -2 && Math.abs(at.lon) <= view.fov / 2 + 10 && inside(at, 100);
const colors = settings.theme === "custom" ? {top: Ambience.hex(settings.colors.top), bottom: Ambience.hex(settings.colors.bottom)} : Ambience.skyAt(sun.alt);
const low = place(0, view.azimuth);
const high = place(90, view.azimuth);
const air = paint.createLinearGradient(low.x + (high.x - low.x) / Space.REACH, low.y + (high.y - low.y) / Space.REACH, low.x, low.y);
air.addColorStop(0, Ambience.css(colors.top));
air.addColorStop(1, Ambience.css(colors.bottom));
paint.fillStyle = air;
paint.fillRect(0, 0, width, height);
const dusk = Math.max(0, 1 - Math.abs(sun.alt) / 14);
const glowAt = place(Math.max(sun.alt, -3), sun.az);
if (dusk > 0 && Math.abs(glowAt.lon) <= 135) {
const x = Math.min(Math.max(glowAt.x, sight.cx - sight.kx * 100), sight.cx + sight.kx * 100);
const glow = paint.createRadialGradient(x, glowAt.y, 0, x, glowAt.y, width * 0.5);
glow.addColorStop(0, `rgb(255 186 128 / ${(0.45 * dusk).toFixed(3)})`);
glow.addColorStop(1, "rgb(255 186 128 / 0)");
paint.fillStyle = glow;
paint.fillRect(0, 0, width, height);
}
const light = Ambience.starlight(sun.alt);
if (light > 0) {
const spin = Ambience.sidereal(ms) + settings.longitude;
paint.fillStyle = "#ffffff";
for (const star of sky.stars) {
const spot = Ambience.horizontal(spin - star.ra, star.dec, settings.latitude);
const at = spot.alt > 1 ? place(spot.alt, spot.az) : null;
if (at && Math.abs(at.lon) <= view.fov / 2 && inside(at, 4)) {
paint.globalAlpha = light * (0.25 + 0.75 * star.glow);
paint.beginPath();
paint.arc(at.x, at.y, 0.5 + star.glow * 1.2, 0, Math.PI * 2);
paint.fill();
}
}
paint.globalAlpha = 1;
}
const sunAt = place(sun.alt, sun.az);
if (seen(sun, sunAt)) {
const at = sunAt;
const halo = paint.createRadialGradient(at.x, at.y, 0, at.x, at.y, 90);
halo.addColorStop(0, "rgb(255 236 200 / 0.55)");
halo.addColorStop(1, "rgb(255 236 200 / 0)");
paint.fillStyle = halo;
paint.fillRect(at.x - 90, at.y - 90, 180, 180);
paint.fillStyle = Ambience.css(Ambience.mix([255, 170, 90], [255, 246, 220], Math.min(Math.max(sun.alt / 20, 0), 1)));
paint.beginPath();
paint.arc(at.x, at.y, 15, 0, Math.PI * 2);
paint.fill();
}
const moonAt = place(moon.alt, moon.az);
if (seen(moon, moonAt)) {
drawMoon(paint, moonAt, 12, moon);
}
const edge = Space.horizon(sight, 96);
const bottom = Math.max(height, ...edge.map((at) => at.y)) + 2;
const trace = (lift) => edge.forEach((at, index) => (index ? paint.lineTo(at.x, at.y + lift) : paint.moveTo(at.x, at.y + lift)));
const ground = paint.createLinearGradient(0, low.y, 0, low.y + height - sight.cy);
ground.addColorStop(0, Ambience.css(Ambience.mix(colors.bottom, [4, 6, 10], 0.72)));
ground.addColorStop(1, Ambience.css(Ambience.mix(colors.bottom, [4, 6, 10], 0.9)));
paint.fillStyle = ground;
paint.beginPath();
trace(0);
paint.lineTo(edge[edge.length - 1].x, bottom);
paint.lineTo(edge[0].x, bottom);
paint.closePath();
paint.fill();
paint.strokeStyle = "rgb(255 255 255 / 0.18)";
paint.lineWidth = 1;
paint.beginPath();
trace(0.5);
paint.stroke();
paint.fillStyle = "rgb(255 255 255 / 0.5)";
paint.font = "600 11px system-ui, sans-serif";
paint.textAlign = "center";
COMPASS.forEach(([label], index) => {
const at = place(0, index * 45);
if (Math.abs(at.lon) <= view.fov / 2 + 0.5) {
paint.fillText(label, Math.min(Math.max(at.x, 14), width - 14), at.y + 16);
}
});
if (space.glides.sky) {
redrawSky();
}
}
function redrawSky() {
window.cancelAnimationFrame(sky.frame);
sky.frame = window.requestAnimationFrame(drawSky);
}
function skyLook(on) {
const canvas = sky.canvas;
space.look = on;
document.body.classList.toggle("sky-look", on);
document.getElementById("sky-panel").hidden = !on;
if (on) {
canvas.removeAttribute("aria-hidden");
canvas.setAttribute("tabindex", "0");
canvas.setAttribute("role", "application");
canvas.setAttribute("aria-roledescription", "Himmel");
drawSky();
canvas.focus();
return;
}
for (const name of ["tabindex", "role", "aria-roledescription", "aria-label"]) {
canvas.removeAttribute(name);
}
canvas.setAttribute("aria-hidden", "true");
document.getElementById("settings-open").focus();
}
function skyPanel() {
const panel = document.getElementById("sky-panel");
const step = (text, label, run) => h("button", {type: "button", class: "btn small", title: label, "aria-label": label, on: {click: run}}, text);
space.compass = h("span", {class: "compass", "aria-live": "polite"});
panel.append(space.compass,
step("◀", "Blick nach links drehen (Pfeil links)", () => skyHooks.turnStep(-1, 0)),
step("▶", "Blick nach rechts drehen (Pfeil rechts)", () => skyHooks.turnStep(1, 0)),
step("▲", "Blick heben (Pfeil hoch)", () => skyHooks.turnStep(0, -1)),
step("▼", "Blick senken (Pfeil runter)", () => skyHooks.turnStep(0, 1)),
step("−", "Sichtfeld weiten (Minus)", () => skyHooks.zoomStep(1 / ZOOM)),
step("+", "Hineinzoomen (Plus)", () => skyHooks.zoomStep(ZOOM)),
step("⟲ Zurücksetzen", "Blick zurücksetzen: Süden, Horizont, Sichtfeld 180° (Taste 0)", () => resetView("sky")),
step("Fertig", "Himmel verlassen (Esc)", () => skyLook(false)));
steer(sky.canvas, "sky", skyHooks);
}