refactor(frontend): extract the agent roster into dashboard/state.js (#1451)
First step of splitting the 4.3k-line tabs.js monolith into per-tab modules. The live agent roster (containersState) is the one piece of genuinely cross-domain state — read by the container tree, the capabilities + tool-group matrices, the schedule target-chips, the container-load poll, the selection bar and the operator inbox. Move it (and its snapshot-sync) into a new dashboard/state.js that each domain module imports, so the upcoming per-tab extractions have a single source of truth to depend on instead of a shared IIFE closure. Minimal by design — only the roster moves; the container row cache and the apply-handlers (which also re-render) stay with the container domain. Behaviour-preserving: the imported Map is mutated in place exactly as before. esbuild inlines state.js into the existing tabs.js bundle, so the static output is unchanged. Build green.
This commit is contained in:
parent
377229ab6d
commit
59054b1f57
2 changed files with 34 additions and 13 deletions
25
frontend/packages/dashboard/src/state.js
Normal file
25
frontend/packages/dashboard/src/state.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// 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);
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import {
|
|||
openStream, renderServerWarnings,
|
||||
} from './common.js';
|
||||
import { createTabStrip } from '@hive/shared/tabs.js';
|
||||
import { containersState, syncContainersFromSnapshot } from './state.js';
|
||||
|
||||
// mdNode (in common.js) reads `window.marked` for the markdown side
|
||||
// panel preview path. Set it here on the dashboard entry so file
|
||||
|
|
@ -149,23 +150,18 @@ window.marked = marked;
|
|||
}
|
||||
});
|
||||
|
||||
// Derived container state — cold-loaded from /api/state.containers,
|
||||
// then mutated live by `container_state_changed` (upsert by name)
|
||||
// and `container_removed` (drop by name). The coordinator's rescan
|
||||
// helper fires these after every mutation site + on a periodic poll
|
||||
// in crash_watch. Keyed by ContainerView.name so the lifecycle
|
||||
// forms' POST → 200 → matching event flips the row without a
|
||||
// snapshot refetch.
|
||||
const containersState = new Map();
|
||||
// The live agent roster (`containersState`) + its snapshot-sync now
|
||||
// live in `./state.js` — it's the one piece of cross-domain state, read
|
||||
// by nearly every tab. It's still mutated live by `container_state_changed`
|
||||
// (upsert by name) and `container_removed` (drop by name) via the apply
|
||||
// handlers just below; the imported binding reflects those in place.
|
||||
//
|
||||
// Keyed container row cache. Maps agent name → { el: <li>, fingerprint }.
|
||||
// Allows renderContainers to skip rebuilding rows whose displayed state
|
||||
// hasn't changed — prevents full-wipe flicker + avoids redundant async
|
||||
// dashboard-state fetches on every SSE event.
|
||||
// dashboard-state fetches on every SSE event. Container-domain only,
|
||||
// so it stays here (not in state.js).
|
||||
const containerRowCache = new Map();
|
||||
function syncContainersFromSnapshot(s) {
|
||||
containersState.clear();
|
||||
for (const c of s.containers || []) containersState.set(c.name, c);
|
||||
}
|
||||
function applyContainerStateChanged(ev) {
|
||||
if (!ev.container || !ev.container.name) return;
|
||||
containersState.set(ev.container.name, ev.container);
|
||||
|
|
|
|||
Loading…
Reference in a new issue