chain table: the fenced Delete action + multi-node delete dialog (owner ruling)

"This is really the view deletion should be done in" — the chain table
becomes the graph's MANAGEMENT surface. The floating selection bar
gains Delete FENCED at its right end (owner-approved placement): a
divider quarantines the destructive action from Clear and Generate,
red destructive pair + trash icon, opening the SAME full-death confirm
dialog the canvas uses.

DeleteNodeDialog extended from one node to an array:
- one node renders exactly as always; many pluralize the title
  (Delete N propositions?) and list the names in the mono block,
  capped at 8 + 'and K more'
- the pre-flight impact read aggregates: summed edge counts, unioned
  referencing sessions (deduped, first-seen order); all-404 = gone
- confirm runs the per-node DELETEs SEQUENTIALLY — a failure stops
  the run (extraction_running is never plowed through); onDeleted
  now carries every id that actually died
- the chain table prunes its selection on delete and announces
  lh:graph-revision so the snapshot refetches

Drift fix found en route (surfaced as NaN in the aggregated facts):
the server's NodeDeletionImpact has been {edges, sessions} only since
the quiz-events ledger died in the practice-arc schema slim — the
dialog kept rendering quizEvents/observations as undefined. The dead
fact lines are removed; the dialog now shows exactly what the server
can honestly report.
This commit is contained in:
amosblomqvist
2026-09-30 01:03:59 +03:00
parent 4d58aa74b1
commit 40dc1b7f85
7 changed files with 221 additions and 65 deletions
+3 -3
View File
@@ -1537,18 +1537,18 @@ export function GraphView() {
{/* The full-death confirm dialog (node interlude) — portaled to
body by the component itself, lives here for state locality. */}
<DeleteNodeDialog
node={deletingNode}
nodes={deletingNode ? [deletingNode] : null}
joins={{
sessions: snapshot?.sessions ?? {},
fileSources: snapshot?.fileSources ?? {},
}}
onClose={() => setDeletingNode(null)}
onDeleted={(nodeId, graphRevision) => {
onDeleted={(nodeIds, graphRevision) => {
// Close the dialog; close the detail card if it pointed here
// (the refetch would close it anyway, but the same frame
// should not show a card for a node the user just killed).
setDeletingNode(null);
if (selected?.node.id === nodeId) setSelected(null);
if (selected && nodeIds.includes(selected.node.id)) setSelected(null);
// The wave-6 chain: every listener refetches on this. Clusters
// shrink / unrender below the minimum on the server's read.
window.dispatchEvent(new CustomEvent("lh:graph-revision", { detail: { graphRevision } }));