🧪 Test / 🧪 Lock + lint (push) Canceled after 0s
Requirement 001 decisions 52 to 58 (operator 2026-11-09): - Radio spot: the Agents toolbar radio button expands inline with the one composer (every channel, post and call recipients, Record, Send), a gear with the radio settings (tones, squelch, read aloud, moved out of the general settings) and Cancel mayday, which leaves the toolbar; the Radio tab keeps speakers, stations and the log plus a jump to the spot. - Day/Night menu: the badge and Drain, Suspend and Resume only; each explanation is the button's tooltip. - Tasks toolbar: views, filter, estimation, sort and business value in one wrapping row with a Request choices dropdown; the status chips stay the second row. - Burndown and burnup: a trend selector (off, linear regression, velocity over N days) stored in the hub settings, the trend lines and the forecast date on the chart, and a tooltip per point with the work packages that started, ended or joined there. - ETA: every list row shows its ETA (end when done, else start or now plus the forecast length behind its predecessors), sharing one predecessor walk with the Gantt. - Tasks 3D: a sixth view on the 3D engine with the dependency edges in the Gantt colours, layouts Force and Time. - Steady 3D camera: the view fits once per graph build, refreshes keep the operator's camera. - e2e: Firefox runs with its profile as cwd, so its crashreporter no longer drops core dumps into the repository. - A forecast beyond the last second a Date can hold is no forecast, so a nearly flat regression no longer aborts the Tasks render. Evidence: make test Ran 717 OK, ruff clean, markdownlint 0 errors; e2e 30 of 30 OK in Chromium and Firefox at 1920, 1366 and 390 px; MEDUSA 0 findings after one skeptic-accepted lexical EXA006. 🎯 Fix: 93 % 💥 Break: 4 % 💸 Cost: 100 % ⏱️ Time: – 🛡️ Security: 100 % 🏆 Score: 89 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
3.2 KiB
JavaScript
61 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", tasks: "list"}, preset: DAY, from: null, to: null, asOf: null, playing: false, busy: false, offline: false, agent: null, order: null, draft: "", spot: false, 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, model: 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, index: -1, pick: null, sig: "", state: "", short: "", note: "", told: null};
|
|
const speech = {pending: [], busy: false, warned: 0, current: null, source: null};
|
|
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, look: false, compass: null,
|
|
};
|
|
let userViews = validViews(stored(STORE.views, stored("kvb-hub.views", [])));
|
|
let settings = state.settings;
|
|
let settingsSig = JSON.stringify(settings);
|
|
let agentDom = 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;
|