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>
63 lines
3.2 KiB
JavaScript
63 lines
3.2 KiB
JavaScript
function stored(key, fallback) {
|
|
try {
|
|
const text = window.localStorage.getItem(key);
|
|
return text === null ? fallback : JSON.parse(text);
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
function keep(key, value) {
|
|
try {
|
|
window.localStorage.setItem(key, JSON.stringify(value));
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function validViews(value) {
|
|
const names = new Set(KINDS.map(([id]) => id));
|
|
if (!Array.isArray(value)) {
|
|
return [];
|
|
}
|
|
return value.filter((view) => view && typeof view.name === "string" && view.name && Array.isArray(view.kinds) && view.kinds.length && view.kinds.every((name) => names.has(name)))
|
|
.map((view) => ({name: view.name.slice(0, 60), kinds: view.kinds, waiting: view.waiting === true, q: typeof view.q === "string" ? view.q : "", agent: typeof view.agent === "string" ? view.agent : ""}));
|
|
}
|
|
|
|
let state = JSON.parse(document.getElementById("state").textContent);
|
|
const KINDS = Object.entries(state.kinds);
|
|
const ui = {screen: "stream", views: {agents: "orbit", artifacts: "tree"}, preset: DAY, from: null, to: null, asOf: null, playing: false, busy: false, offline: false, agent: null, order: null, draft: "", node: null, cursor: 0, selected: new Set(), snoozed: false, review: null, file: 0};
|
|
const filter = {view: "fuer-dich", saved: "", kinds: new Set(state.views["fuer-dich"].kinds), waiting: true, q: "", agent: "", session: "", sub: "", parts: new Set(PARTS.map(([id]) => id))};
|
|
const stream = {key: null, events: [], entries: [], last: 0, sessions: {}, busy: false, again: false, dom: null, error: ""};
|
|
const tree = {data: null, busy: false, q: "", open: new Set(), dom: null};
|
|
const palette = {items: [], index: 0, input: null, list: null, plan: null, box: null};
|
|
const known = {inbox: null, alarms: null, radio: null, batches: new Set()};
|
|
const motion = {current: new Map(), from: new Map(), target: new Map(), start: 0, duration: 0, frame: 0, asOf: undefined};
|
|
const drawn = {screen: "", sig: ""};
|
|
const files = new Map();
|
|
const pageMedia = new Set(state.media);
|
|
const sky = {canvas: document.getElementById("sky"), stars: Ambience.stars(320, 7), mode: "", frame: 0};
|
|
const music = {gain: null, filter: null, drone: [], timer: 0, tension: 0, ducked: 0};
|
|
const radio = {element: null, url: "", level: 0, timer: 0, playing: false, waiting: false};
|
|
const speech = {pending: [], busy: false, warned: 0};
|
|
const mic = {recorder: null, held: false, busy: false, timer: 0};
|
|
const space = {
|
|
views: {graph: {...state.settings.space.graph}, sky: {...state.settings.space.sky}}, glides: {graph: null, sky: null}, edits: 0, saved: 0, timer: 0,
|
|
scene: null, sceneCursor: null, cam: null, frame: 0, look: false, compass: null, subs: new Map(), loading: false, version: 0,
|
|
cursor: {from: 0, to: null, start: 0}, style: null, styleKey: "", trackAsked: 0,
|
|
};
|
|
let userViews = validViews(stored(STORE.views, stored("kvb-hub.views", [])));
|
|
let settings = state.settings;
|
|
let settingsSig = JSON.stringify(settings);
|
|
let drawerDom = null;
|
|
let timeDom = null;
|
|
let graphDom = null;
|
|
let reviewDom = null;
|
|
let inboxDom = null;
|
|
let replay = 0;
|
|
let positions = new Map();
|
|
let inboxSig = "";
|
|
let audio = null;
|
|
let searchTimer = 0;
|