A node edit that changes only the top-level Node.parent field (re-homing
a node under a new parent in the swarm tree) persisted correctly via
ClientNodeManager.submit(...), but the Server-view canvas did not
re-draw the parent/child orbit — the node stayed visually under its old
parent until an unrelated change or a full reload forced a relayout.
The canvas layout is recomputed by ClientScreen’s
LaunchedEffect(structure, wiringRevision, viewMode), where:
structure = ClientNodeManager.swarm, a Set<String> of node ids.
It only re-emits when the set of ids changes.wiringRevision = ClientNodeManager.wiringRevision, the “meta changed
but the id set didn’t” signal.A parent change keeps the same id set (no node added/removed), so
structure doesn’t re-emit. And submit() — unlike updateMetaData(),
which bumps _wiringRevision — does not bump the revision. So neither
trigger fires and computeNodePositions never re-runs. The per-node
MutableStateFlow<Node> does update (the chip repaints), which is why the
change looked “saved” while the tree geometry stayed stale.
shared/.../manager/ClientNodeManager.kt — added a public
notifyWiringChanged() that does _wiringRevision.update { it + 1 }
(the same field updateMetaData already bumps), so callers using the
plain submit() path can request a relayout without routing a tree
change through the meta-write path.composeApp/.../connect/ConnectNodesWizard.kt — after submitting a
MAKE_PARENT connect, call nodeManager.notifyWiringChanged().submit() does not relayout the canvas; updateMetaData() does. Any
mutation that changes node geometry inputs (parent edges, and anything
else computeNodePositions/computeFlowPositions reads) but not the
swarm id set must bump wiringRevision — call notifyWiringChanged()
after submit(), or route through updateMetaData().structure, wiringRevision) are an allow-list of
invalidation signals, not a general “node changed” observer. When adding
a new kind of structural edit, ask “which of these two re-emits?” — if
neither, the canvas won’t move.