treefmt: apply prettier
Pure `nix fmt` output from the commit before this one — no hand edits. 203 files: 52 md, 42 tsx, 32 js, 32 css, 21 ts, 13 html, 8 json, 3 mjs. Reproduce with `nix develop -c nix fmt` on the parent commit; the result should be byte-identical to this tree. None of the 13 `.prettierignore` entries appears here — verified by intersecting the changed-file list against the ignore file, with a control proving the intersection finds a match when one exists.
This commit is contained in:
parent
5d24bedd60
commit
39b95c2ede
203 changed files with 10090 additions and 6085 deletions
|
|
@ -11,41 +11,47 @@
|
|||
// `dashboard.html` each load their own bundle.
|
||||
// SW4RM (containers) domain lives in `./swarm.js`.
|
||||
|
||||
import { marked } from 'marked';
|
||||
import { marked } from "marked";
|
||||
import { $, NOTIF, openStream, renderServerWarnings } from "./common.js";
|
||||
import { el } from "@hive/shared/dom.js";
|
||||
import { bindAsyncForms } from "@hive/shared/forms.js";
|
||||
import { createTabStrip } from "@hive/shared/tabs.js";
|
||||
import { containersState, syncContainersFromSnapshot } from "./state.js";
|
||||
import { fmtAgo, fmtDuration } from "./util.js";
|
||||
import {
|
||||
$,
|
||||
NOTIF,
|
||||
openStream, renderServerWarnings,
|
||||
} from './common.js';
|
||||
import { el } from '@hive/shared/dom.js';
|
||||
import { bindAsyncForms } from '@hive/shared/forms.js';
|
||||
import { createTabStrip } from '@hive/shared/tabs.js';
|
||||
import {
|
||||
containersState, syncContainersFromSnapshot,
|
||||
} from './state.js';
|
||||
import { fmtAgo, fmtDuration } from './util.js';
|
||||
import {
|
||||
applyCapabilitiesChanged, applyToolGroupsChanged,
|
||||
fetchAndRenderCapabilities, fetchAndRenderToolGroups,
|
||||
applyCapabilitiesChanged,
|
||||
applyToolGroupsChanged,
|
||||
fetchAndRenderCapabilities,
|
||||
fetchAndRenderToolGroups,
|
||||
initPermissions,
|
||||
} from './permissions.js';
|
||||
} from "./permissions.js";
|
||||
import {
|
||||
applySchedulesChanged,
|
||||
refreshSchedules, activeScheduleCount,
|
||||
} from './schedules.js';
|
||||
refreshSchedules,
|
||||
activeScheduleCount,
|
||||
} from "./schedules.js";
|
||||
import {
|
||||
initCall,
|
||||
refreshOperatorInbox, operatorInboxAppendFromEvent, operatorInboxCount,
|
||||
syncApprovalsFromSnapshot, applyApprovalAdded, applyApprovalResolved,
|
||||
renderApprovals, activeApprovalCount,
|
||||
} from './call.js';
|
||||
refreshOperatorInbox,
|
||||
operatorInboxAppendFromEvent,
|
||||
operatorInboxCount,
|
||||
syncApprovalsFromSnapshot,
|
||||
applyApprovalAdded,
|
||||
applyApprovalResolved,
|
||||
renderApprovals,
|
||||
activeApprovalCount,
|
||||
} from "./call.js";
|
||||
import {
|
||||
initJobqRollup, syncTransientsFromSnapshot,
|
||||
applyRebuildQueueChanged, applyContainerStateChanged, applyContainerRemoved,
|
||||
applyTransientSet, applyTransientCleared,
|
||||
initJobqRollup,
|
||||
syncTransientsFromSnapshot,
|
||||
applyRebuildQueueChanged,
|
||||
applyContainerStateChanged,
|
||||
applyContainerRemoved,
|
||||
applyTransientSet,
|
||||
applyTransientCleared,
|
||||
renderContainers,
|
||||
renderSelectionBar,
|
||||
} from './swarm.js';
|
||||
} from "./swarm.js";
|
||||
|
||||
// mdNode (in common.js) reads `window.marked` for the markdown side
|
||||
// panel preview path. Set it here on the dashboard entry so file
|
||||
|
|
@ -56,7 +62,7 @@ window.marked = marked;
|
|||
// Track which items we've already notified about so a re-render
|
||||
// doesn't re-fire for the same row. Keyed by stable ids; reset only
|
||||
// when the page reloads.
|
||||
const seenApprovals = new Set();
|
||||
const seenApprovals = new Set();
|
||||
let seededNotify = false;
|
||||
|
||||
function notifyDeltas(s) {
|
||||
|
|
@ -75,11 +81,17 @@ window.marked = marked;
|
|||
for (const a of approvals) {
|
||||
if (seenApprovals.has(a.id)) continue;
|
||||
seenApprovals.add(a.id);
|
||||
const verb = a.kind === 'spawn' ? 'spawn approval'
|
||||
: a.kind === 'init_config' ? 'config-init approval'
|
||||
: 'config commit';
|
||||
NOTIF.show('◆ approval #' + a.id, `${verb} for ${a.agent}`,
|
||||
'hyperhive:approval:' + a.id);
|
||||
const verb =
|
||||
a.kind === "spawn"
|
||||
? "spawn approval"
|
||||
: a.kind === "init_config"
|
||||
? "config-init approval"
|
||||
: "config commit";
|
||||
NOTIF.show(
|
||||
"◆ approval #" + a.id,
|
||||
`${verb} for ${a.agent}`,
|
||||
"hyperhive:approval:" + a.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -96,20 +108,21 @@ window.marked = marked;
|
|||
// the renderers so this loop can refresh them without a full re-render).
|
||||
setInterval(() => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
document.querySelectorAll('.approval-ts[data-requested-at]').forEach((node) => {
|
||||
const requestedAt = Number(node.getAttribute('data-requested-at'));
|
||||
if (!Number.isFinite(requestedAt)) return;
|
||||
const ageSec = Math.max(0, now - requestedAt);
|
||||
node.textContent = 'requested ' + fmtAgo(requestedAt);
|
||||
node.classList.toggle('stale', ageSec >= 3600);
|
||||
});
|
||||
document.querySelectorAll('.sched-due[data-due-at]').forEach((node) => {
|
||||
const dueAt = Number(node.getAttribute('data-due-at'));
|
||||
document
|
||||
.querySelectorAll(".approval-ts[data-requested-at]")
|
||||
.forEach((node) => {
|
||||
const requestedAt = Number(node.getAttribute("data-requested-at"));
|
||||
if (!Number.isFinite(requestedAt)) return;
|
||||
const ageSec = Math.max(0, now - requestedAt);
|
||||
node.textContent = "requested " + fmtAgo(requestedAt);
|
||||
node.classList.toggle("stale", ageSec >= 3600);
|
||||
});
|
||||
document.querySelectorAll(".sched-due[data-due-at]").forEach((node) => {
|
||||
const dueAt = Number(node.getAttribute("data-due-at"));
|
||||
if (!Number.isFinite(dueAt)) return;
|
||||
const dueIn = dueAt - now;
|
||||
node.textContent = dueIn <= 0
|
||||
? 'overdue ' + fmtAgo(dueAt)
|
||||
: fmtDuration(dueIn);
|
||||
node.textContent =
|
||||
dueIn <= 0 ? "overdue " + fmtAgo(dueAt) : fmtDuration(dueIn);
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
|
|
@ -119,12 +132,12 @@ window.marked = marked;
|
|||
// operator is typing in one of them, skip the refresh — the next
|
||||
// tick (or a manual action) will pick it up after they blur.
|
||||
const MANAGED_SECTION_IDS = [
|
||||
'containers-section',
|
||||
'inbox-section',
|
||||
'approvals-section',
|
||||
'schedules-section',
|
||||
'capabilities-section',
|
||||
'tool-groups-section',
|
||||
"containers-section",
|
||||
"inbox-section",
|
||||
"approvals-section",
|
||||
"schedules-section",
|
||||
"capabilities-section",
|
||||
"tool-groups-section",
|
||||
];
|
||||
// <details> sections that should survive a refresh need a stable
|
||||
// `data-restore-key` attribute. snapshotOpenDetails walks managed
|
||||
|
|
@ -138,7 +151,7 @@ window.marked = marked;
|
|||
for (const id of MANAGED_SECTION_IDS) {
|
||||
const sect = document.getElementById(id);
|
||||
if (!sect) continue;
|
||||
for (const d of sect.querySelectorAll('details[data-restore-key]')) {
|
||||
for (const d of sect.querySelectorAll("details[data-restore-key]")) {
|
||||
if (d.open) open.add(d.dataset.restoreKey);
|
||||
}
|
||||
}
|
||||
|
|
@ -149,7 +162,7 @@ window.marked = marked;
|
|||
for (const id of MANAGED_SECTION_IDS) {
|
||||
const sect = document.getElementById(id);
|
||||
if (!sect) continue;
|
||||
for (const d of sect.querySelectorAll('details[data-restore-key]')) {
|
||||
for (const d of sect.querySelectorAll("details[data-restore-key]")) {
|
||||
if (open.has(d.dataset.restoreKey)) d.open = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -159,7 +172,7 @@ window.marked = marked;
|
|||
const el_ = document.activeElement;
|
||||
if (!el_ || el_ === document.body) return false;
|
||||
const tag = el_.tagName;
|
||||
if (tag !== 'INPUT' && tag !== 'TEXTAREA' && tag !== 'SELECT') return false;
|
||||
if (tag !== "INPUT" && tag !== "TEXTAREA" && tag !== "SELECT") return false;
|
||||
return MANAGED_SECTION_IDS.some((id) => {
|
||||
const sect = document.getElementById(id);
|
||||
return sect && sect.contains(el_);
|
||||
|
|
@ -176,8 +189,8 @@ window.marked = marked;
|
|||
return;
|
||||
}
|
||||
try {
|
||||
const resp = await fetch('/api/state');
|
||||
if (!resp.ok) throw new Error('http ' + resp.status);
|
||||
const resp = await fetch("/api/state");
|
||||
if (!resp.ok) throw new Error("http " + resp.status);
|
||||
const s = await resp.json();
|
||||
// Stash the latest snapshot for any sub-widget that wants a
|
||||
// synchronous read (e.g. the compose autocomplete pulls agent
|
||||
|
|
@ -190,19 +203,18 @@ window.marked = marked;
|
|||
// come from HYPERHIVE_HIVE_NAME / HYPERHIVE_SWARM_NAME env vars
|
||||
// (set by services.hyperhive.{hiveName,swarmName} nix options).
|
||||
// When unset we fall back gracefully — the headline stays hidden.
|
||||
const hiveId = $('swarm-identity');
|
||||
const hiveId = $("swarm-identity");
|
||||
if (hiveId) {
|
||||
const hive = s.hive_name;
|
||||
const swarm = s.swarm_name;
|
||||
if (hive || swarm) {
|
||||
const label = swarm && hive ? `${swarm} / ${hive}`
|
||||
: hive || swarm;
|
||||
const label = swarm && hive ? `${swarm} / ${hive}` : hive || swarm;
|
||||
hiveId.textContent = label;
|
||||
hiveId.hidden = false;
|
||||
// Preserve any (N) call-count prefix already applied by
|
||||
// refreshTabCounts so the title doesn't flicker on reload.
|
||||
const existingPrefix = document.title.match(/^(\(\d+\) )/)?.[1] || '';
|
||||
document.title = existingPrefix + label + ' // h1ve-c0re';
|
||||
const existingPrefix = document.title.match(/^(\(\d+\) )/)?.[1] || "";
|
||||
document.title = existingPrefix + label + " // h1ve-c0re";
|
||||
}
|
||||
}
|
||||
const openDetails = snapshotOpenDetails();
|
||||
|
|
@ -235,9 +247,12 @@ window.marked = marked;
|
|||
// /api/state fetches are the initial cold load and the
|
||||
// post-submit refetch on forms without `data-no-refresh`
|
||||
// (tombstones, meta-input updates).
|
||||
if (pollTimer) { clearTimeout(pollTimer); pollTimer = null; }
|
||||
if (pollTimer) {
|
||||
clearTimeout(pollTimer);
|
||||
pollTimer = null;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('refreshState failed', err);
|
||||
console.error("refreshState failed", err);
|
||||
// Schedule a single retry on transient errors so the page
|
||||
// recovers from a brief network blip without making the
|
||||
// operator reload.
|
||||
|
|
@ -275,20 +290,20 @@ window.marked = marked;
|
|||
// and filter client-side — the dashboard ignores broker traffic
|
||||
// and the inbox ignores mutation events.
|
||||
const MUTATION_HANDLERS = {
|
||||
approval_added: applyApprovalAdded,
|
||||
approval_added: applyApprovalAdded,
|
||||
approval_resolved: applyApprovalResolved,
|
||||
transient_set: applyTransientSet,
|
||||
transient_set: applyTransientSet,
|
||||
transient_cleared: applyTransientCleared,
|
||||
container_state_changed: applyContainerStateChanged,
|
||||
container_removed: applyContainerRemoved,
|
||||
container_removed: applyContainerRemoved,
|
||||
// rebuild_queue_changed: refreshes the SW4RM queue-summary banner
|
||||
// (see swarm.js) — a payload-less push trigger, same treatment
|
||||
// /builds.html gives it for its JobqGraph mount handle's .refresh()
|
||||
// (its own separate subscription).
|
||||
rebuild_queue_changed: applyRebuildQueueChanged,
|
||||
schedules_changed: applySchedulesChanged,
|
||||
capabilities_changed: applyCapabilitiesChanged,
|
||||
tool_groups_changed: applyToolGroupsChanged,
|
||||
rebuild_queue_changed: applyRebuildQueueChanged,
|
||||
schedules_changed: applySchedulesChanged,
|
||||
capabilities_changed: applyCapabilitiesChanged,
|
||||
tool_groups_changed: applyToolGroupsChanged,
|
||||
};
|
||||
(function bindDashboardStream() {
|
||||
// Route through the SharedWorker so all open hyperhive tabs on the
|
||||
|
|
@ -305,24 +320,31 @@ window.marked = marked;
|
|||
// `/api/dashboard/stream` subscribers before this (subscription
|
||||
// discipline, part 1 of the dashboard-event-stream-split issue).
|
||||
const es = openStream(
|
||||
'/api/dashboard/stream?kinds=sent,approval_added,approval_resolved,' +
|
||||
'transient_set,transient_cleared,' +
|
||||
'container_state_changed,container_removed,rebuild_queue_changed,' +
|
||||
'schedules_changed,capabilities_changed,tool_groups_changed',
|
||||
"/api/dashboard/stream?kinds=sent,approval_added,approval_resolved," +
|
||||
"transient_set,transient_cleared," +
|
||||
"container_state_changed,container_removed,rebuild_queue_changed," +
|
||||
"schedules_changed,capabilities_changed,tool_groups_changed",
|
||||
);
|
||||
es.onmessage = (e) => {
|
||||
let ev;
|
||||
try { ev = JSON.parse(e.data); } catch { return; }
|
||||
try {
|
||||
ev = JSON.parse(e.data);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
// Broker `sent` frames aren't mutation events, but the operator
|
||||
// inbox cares about ones addressed to "operator".
|
||||
if (ev.kind === 'sent' && ev.to === 'operator') {
|
||||
if (ev.kind === "sent" && ev.to === "operator") {
|
||||
operatorInboxAppendFromEvent(ev);
|
||||
return;
|
||||
}
|
||||
const h = MUTATION_HANDLERS[ev.kind];
|
||||
if (!h) return; // broker rows + future kinds — dashboard doesn't care
|
||||
try { h(ev); }
|
||||
catch (err) { console.error('dashboard SSE handler', ev.kind, err); }
|
||||
try {
|
||||
h(ev);
|
||||
} catch (err) {
|
||||
console.error("dashboard SSE handler", ev.kind, err);
|
||||
}
|
||||
};
|
||||
es.onopen = () => {
|
||||
// Re-sync to recover events that fired during the SSE disconnect
|
||||
|
|
@ -334,7 +356,7 @@ window.marked = marked;
|
|||
};
|
||||
es.onerror = () => {
|
||||
// EventSource auto-reconnects; nothing to do beyond logging.
|
||||
console.debug('dashboard SSE error, will retry');
|
||||
console.debug("dashboard SSE error, will retry");
|
||||
};
|
||||
})();
|
||||
|
||||
|
|
@ -360,38 +382,41 @@ window.marked = marked;
|
|||
// Re-fetch on activation as a safety net: SSE covers live mutations,
|
||||
// re-sync covers disconnect windows / approval-path inserts that
|
||||
// don't yet emit.
|
||||
if (target === 'schedules') { refreshSchedules(); }
|
||||
if (target === "schedules") {
|
||||
refreshSchedules();
|
||||
}
|
||||
// Permissions tables: SSE covers worker-applied changes
|
||||
// (capabilities_changed / tool_groups_changed); re-fetch on
|
||||
// activation as a safety net for any gap between SSE events and
|
||||
// the cold-load snapshot.
|
||||
if (target === 'permissions') {
|
||||
if (target === "permissions") {
|
||||
initPermissions();
|
||||
fetchAndRenderCapabilities();
|
||||
fetchAndRenderToolGroups();
|
||||
}
|
||||
if (target === 'call') { refreshOperatorInbox(); }
|
||||
if (target === "call") {
|
||||
refreshOperatorInbox();
|
||||
}
|
||||
}
|
||||
|
||||
// Wire the shared tab strip now that activateTab + the lazy-load fns it
|
||||
// calls are defined. The strip resolves the active tab from the hash
|
||||
// (default SW4RM), toggles the active tab/pane + aria-selected, and
|
||||
// fires activateTab for the per-tab side-effects on every change.
|
||||
createTabStrip($('tabbar'), { defaultId: 'swarm', onShow: activateTab });
|
||||
createTabStrip($("tabbar"), { defaultId: "swarm", onShow: activateTab });
|
||||
|
||||
// Register the Y3R C4LL domain's count callback (call.js) — its live
|
||||
// mutations (inbox stream append, mark-read) trigger a tab-count refresh
|
||||
// through this instead of reaching back into the coordinator directly.
|
||||
initCall({ onCountsChanged: refreshTabCounts });
|
||||
|
||||
|
||||
// Tab count pills — pure derived data from the existing state
|
||||
// stores so SSE-driven updates flow through without extra plumbing.
|
||||
// Set `hidden` when the count is zero so the pill doesn't draw
|
||||
// attention to an empty room.
|
||||
|
||||
function setTabCount(tab, n) {
|
||||
const el_ = $('tab-count-' + tab);
|
||||
const el_ = $("tab-count-" + tab);
|
||||
if (!el_) return;
|
||||
el_.textContent = String(n);
|
||||
el_.hidden = n <= 0;
|
||||
|
|
@ -406,22 +431,20 @@ window.marked = marked;
|
|||
for (const c of containersState.values()) {
|
||||
if (c.needs_update) swarm++;
|
||||
}
|
||||
setTabCount('swarm', swarm);
|
||||
setTabCount("swarm", swarm);
|
||||
// Y3R C4LL — pending approvals + unread agent->operator messages.
|
||||
const callCount =
|
||||
activeApprovalCount() +
|
||||
operatorInboxCount();
|
||||
setTabCount('call', callCount);
|
||||
const callCount = activeApprovalCount() + operatorInboxCount();
|
||||
setTabCount("call", callCount);
|
||||
// Browser tab title prefix — lets the operator see the pending
|
||||
// call count without switching to the window. Strips any existing
|
||||
// `(N) ` prefix before re-applying so identity-title updates
|
||||
// (which run once on state load, not every tick) compose cleanly.
|
||||
const rawTitle = document.title.replace(/^\(\d+\) /, '');
|
||||
const rawTitle = document.title.replace(/^\(\d+\) /, "");
|
||||
document.title = callCount > 0 ? `(${callCount}) ${rawTitle}` : rawTitle;
|
||||
// SCH3DUL3S — count of schedules with at least one still-active
|
||||
// target (whole-schedule cancellation or all-targets-cancelled
|
||||
// means "not waiting on the worker"; those don't pull attention).
|
||||
setTabCount('schedules', activeScheduleCount());
|
||||
setTabCount("schedules", activeScheduleCount());
|
||||
}
|
||||
// Poll the state stores on a 1s tick to keep the pill counts in
|
||||
// sync. The state stores are mutated synchronously by every SSE
|
||||
|
|
|
|||
Loading…
Reference in a new issue