hyperhive/frontend/packages/dashboard/src/state.js
iris d0ec0d2896 dashboard: extract operator-inbox + approvals into call.js (#1451)
Begin splitting the tabs.js monolith: lift the Y3R C4LL domain into a new
call.js module (mirrors permissions.js / schedules.js).

- questionsState + QUESTION_HISTORY_LIMIT move to state.js: they're read by
  both the SW4RM container rows (per-agent question-count badges) and the
  Y3R C4LL questions domain, so they're cross-domain shared state.
- operator-inbox domain (state + refresh/render/mark/append) → call.js.
- approvals domain (state + sync/apply/render/diff-panel/history) → call.js.
- call.js exposes count getters (activeApprovalCount, operatorInboxCount);
  the entry's refreshTabCounts pulls them. Live-mutation paths call an
  injected onCountsChanged callback (registered via initCall at boot)
  instead of reaching into the coordinator (avoids a circular import).
- renderPeerHives, physically interleaved in the moved range but part of
  the SW4RM/peers domain, stays in tabs.js.

Behaviour-preserving; no visual change. Questions domain follows next.

(Recreated after a harness-restart git-object corruption; identical content.)
2026-06-16 10:22:53 +02:00

35 lines
1.9 KiB
JavaScript

// Shared dashboard state — the one piece of cross-domain state in the
// dashboard SPA: the live agent roster.
//
// The roster is read by nearly every tab (the container tree, the
// capabilities + tool-group matrices, the schedule target-chips, the
// container-load poll, the selection bar, and the operator inbox), so
// it lives here as a single source of truth that each domain module
// imports rather than threading through call signatures. Everything
// else in the dashboard is domain-local and lives with its own module.
//
// `containersState` is keyed by `ContainerView.name` so a lifecycle
// form's POST → 200 → matching SSE event can flip a single row without
// a full snapshot refetch. Mutated in place (`set` / `delete` / `clear`)
// by the container apply-handlers; consumers read it by reference, so
// the imported binding always reflects the latest roster.
export const containersState = new Map();
// Replace the whole roster from a fresh `/api/state` snapshot. Called by
// the entry's `refreshState` on cold load and after async-form submits;
// live single-row updates go through the container apply-handlers.
export function syncContainersFromSnapshot(s) {
containersState.clear();
for (const c of s.containers || []) containersState.set(c.name, c);
}
// Derived question state — the other genuinely cross-domain store. Owned by
// the Y3R C4LL questions domain (`call.js`: cold-loaded from /api/state, then
// mutated live by `question_added` / `question_resolved` events), but also
// read by the SW4RM container rows, which render per-agent asker/target
// question-count badges off `questionsState.pending`. It lives here for the
// same reason as `containersState`: a single source of truth both domains
// import by reference rather than threading through call signatures.
export const QUESTION_HISTORY_LIMIT = 20;
export const questionsState = { pending: [], history: [] };