Nodes wired as source-observers (carrying meta.sources + InvocationTrigger.SOURCE_INVOKED) did not appear on the desktop canvas or Flow view when they lacked an effective parent-tree connection. They were queryable server-side via get_node but the UI treated them as invisible. Adding a parent edge to the Lambda (or any already-visible node) made them appear immediately.
computeNodePositions (now computeLayoutFromNodes) built a childrenMap keyed by node.parent and ran a BFS from root nodes (Servers, and the local Client when it has children). Any node whose parent field was blank, self-referential, or pointed to a node outside the BFS walk was never inserted into positions. The rendering code at ClientScreen.kt:781 (layout.positions[node.id]?.let { position -> … }) silently skips nodes absent from positions — so the node never rendered. The “targeting affinity” pass, which pulls source pairs together, also short-circuits on positions[node.id] ?: return@forEach, compounding the invisibility.
computeLayoutFromNodes (between the BFS layout and the targeting affinity pass). The pass iterates over all unpositioned nodes; any node whose meta.sources references at least one already-positioned node receives a canvas position near the centroid of those sources. The loop repeats until no new nodes are placed, so chains (A observes B observes C, none with parents) converge in O(depth) iterations.computeNodePositions into an internal fun computeLayoutFromNodes(nodes: List<Node>) to provide a testable seam that doesn’t require a real ClientNodeManager.SourceObserverCanvasVisibilityTest covering: server-child observer, blank-parent observer, self-referential-parent observer, chained multi-hop observers, and targeting-arc generation for orphan observers.internal functions so they are testable without a live NodeManager. Relying on integration-style mocks for layout math leads to untested corner cases.List<Node> directly in tests, call computeLayoutFromNodes, and assert assertContains(layout.positions.keys, node.id). No filesystem, no mocks, no virtual time needed.