dashboard: tombstones + meta_inputs events — last /api/state refetches drop
new DashboardEvent::TombstonesChanged + MetaInputsChanged carry full snapshots (lists are tiny; snapshot beats diff for race avoidance). Coordinator-side helpers emit_tombstones_snapshot + emit_meta_inputs_snapshot fire from every mutation site: actions::destroy + post_purge_tombstone + actions::approve (spawn finalise consumes tombstone) + run_meta_update + auto_update::rebuild_agent (lock bumps). client adds derived stores + apply* handlers + drops the post-submit refetch on PURG3 (container row + tombstone row) and meta-update. after this commit /api/state is fetched exactly once per page session (cold load); every other change rides the SSE channel.
This commit is contained in:
parent
76e4034e01
commit
aed43ce4df
5 changed files with 123 additions and 24 deletions
|
|
@ -335,6 +335,32 @@
|
|||
if (containersState.delete(ev.name)) renderContainersFromState();
|
||||
}
|
||||
|
||||
// Derived tombstones + meta_inputs. Both are emitted as full
|
||||
// snapshots (not diffs) — the lists are tiny and recomputing
|
||||
// avoids ordering races between a same-tick destroy + purge.
|
||||
let tombstonesState = [];
|
||||
let metaInputsState = [];
|
||||
function syncTombstonesFromSnapshot(s) {
|
||||
tombstonesState = (s.tombstones || []).slice();
|
||||
}
|
||||
function syncMetaInputsFromSnapshot(s) {
|
||||
metaInputsState = (s.meta_inputs || []).slice();
|
||||
}
|
||||
function applyTombstonesChanged(ev) {
|
||||
tombstonesState = (ev.tombstones || []).slice();
|
||||
renderTombstonesFromState();
|
||||
}
|
||||
function applyMetaInputsChanged(ev) {
|
||||
metaInputsState = (ev.inputs || []).slice();
|
||||
renderMetaInputsFromState();
|
||||
}
|
||||
function renderTombstonesFromState() {
|
||||
renderTombstones({ tombstones: tombstonesState });
|
||||
}
|
||||
function renderMetaInputsFromState() {
|
||||
renderMetaInputs({ meta_inputs: metaInputsState });
|
||||
}
|
||||
|
||||
// Derived transient state — cold-loaded from /api/state.transients,
|
||||
// then mutated live by `transient_set` / `transient_cleared`. Keyed
|
||||
// by agent name so add/remove are O(1). `since_unix` is wall-clock so
|
||||
|
|
@ -512,14 +538,16 @@
|
|||
if (!c.is_manager) {
|
||||
// DESTR0Y is event-covered (ContainerRemoved); PURG3 also
|
||||
// wipes tombstone state which isn't event-derived yet, so it
|
||||
// keeps the post-submit refetch.
|
||||
// Both event-covered now (ContainerRemoved +
|
||||
// TombstonesChanged); no /api/state refetch needed.
|
||||
actions.append(
|
||||
form('/destroy/' + c.name, 'btn-destroy', 'DESTR0Y',
|
||||
'destroy ' + c.name + '? container is removed; state + creds kept.',
|
||||
{}, { noRefresh: true }),
|
||||
form('/destroy/' + c.name, 'btn-destroy', 'PURG3',
|
||||
'PURGE ' + c.name + '? container, config history, claude creds, '
|
||||
+ 'and notes are all WIPED. no undo.', { purge: 'on' }),
|
||||
+ 'and notes are all WIPED. no undo.',
|
||||
{ purge: 'on' }, { noRefresh: true }),
|
||||
);
|
||||
}
|
||||
li.append(actions);
|
||||
|
|
@ -683,8 +711,9 @@
|
|||
actions.append(respawn);
|
||||
actions.append(form(
|
||||
'/purge-tombstone/' + t.name, 'btn-destroy', 'PURG3',
|
||||
'PURGE ' + t.name + '? config history, claude creds, /state/ notes '
|
||||
+ 'are all WIPED. no undo.',
|
||||
'PURGE ' + t.name + '? config history, claude creds, '
|
||||
+ 'and notes are all WIPED. no undo.',
|
||||
{}, { noRefresh: true },
|
||||
));
|
||||
li.append(actions);
|
||||
ul.append(li);
|
||||
|
|
@ -1214,6 +1243,10 @@
|
|||
action: '/meta-update',
|
||||
class: 'meta-inputs-form',
|
||||
'data-async': '',
|
||||
// run_meta_update emits MetaInputsChanged once the lock
|
||||
// bump finishes; per-agent rebuilds fire their own
|
||||
// ContainerStateChanged. No /api/state refetch needed.
|
||||
'data-no-refresh': '',
|
||||
'data-confirm': 'update selected meta flake inputs + rebuild affected agents?',
|
||||
});
|
||||
const ul = el('ul', { class: 'meta-inputs' });
|
||||
|
|
@ -1419,6 +1452,8 @@
|
|||
// `transientsState` + `containersState`, not from `s.*`).
|
||||
syncTransientsFromSnapshot(s);
|
||||
syncContainersFromSnapshot(s);
|
||||
syncTombstonesFromSnapshot(s);
|
||||
syncMetaInputsFromSnapshot(s);
|
||||
renderContainers(s);
|
||||
renderTombstones(s);
|
||||
// Sync the derived approvals + questions stores from the
|
||||
|
|
@ -1513,6 +1548,8 @@
|
|||
transient_cleared: (ev) => { applyTransientCleared(ev); },
|
||||
container_state_changed: (ev) => { applyContainerStateChanged(ev); },
|
||||
container_removed: (ev) => { applyContainerRemoved(ev); },
|
||||
tombstones_changed: (ev) => { applyTombstonesChanged(ev); },
|
||||
meta_inputs_changed: (ev) => { applyMetaInputsChanged(ev); },
|
||||
},
|
||||
// Both history backfill and live frames flow through here, so the
|
||||
// inbox section ends up populated correctly on first paint and
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue