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:
atlas 2026-09-02 14:29:33 +02:00
commit 39b95c2ede
203 changed files with 10090 additions and 6085 deletions

View file

@ -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