Nodes added to the swarm via SSE (e.g., a new DataPoint appearing while an executor editor is open) did not appear in the NodeList composable until the user navigated away and back. Per-node value updates (snapshot readings, state changes) were still live because individual row composables use collectAsState() internally, but the structural membership of the list (which rows exist) was frozen at composition time.
NodeList read nodeManager.nodes() as a plain List<Node> snapshot during composition with no reactive subscription. There was no collectAsState() call on nodeManager.swarm and no remember key tied to the swarm, so Compose had no signal to re-enter the composable when the set of nodes changed. The NodeListScreen companion composable already used remember(swarm) correctly; NodeList (the picker/editor variant) was missing the same pattern.
ClientNodeManager.update() calls _swarm.update { it.plus(node.id) } on every node update. For new nodes this changes the set and triggers recomposition; for existing nodes the set is unchanged so the StateFlow emits no new value. This means remember(swarm) is sufficient for structural changes (add/remove) while per-node value changes continue to be handled by collectAsState() inside each row composable — the two mechanisms complement each other correctly.
Extracted the filtering logic from NodeList into internal fun nodeListFilter(...) so it can be tested without a Compose runtime. In the composable, added val swarm by nodeManager.swarm.collectAsState() and wrapped the node-list computation in remember(swarm, type, digitalOnly, showTrash, filterParent). The NodeItem lead in the Kraken report was refuted: the main graph view in ClientScreen.kt already calls nodeManager.readNodeState(nodeId).collectAsState() before passing node to NodeItem, so that path was already reactive.
nodeManager.nodes() must key that derivation on nodeManager.swarm.collectAsState() (or equivalent). NodeListScreen is the reference implementation.NodeListFilterTest covers nodeListFilter).StateFlow → collectAsState() → composable before assuming a missing abstraction. The absence of a ViewModel class is not itself a bug in the krill/Koin pattern.