Symptom

New feature: the node-editor header’s pencil/trash were replaced with a ProjectScreen-style avatar add-child menu; picking a type must create a child of the edited node and open the new node’s editor. The implementation plan’s createChildAndEdit did create(child); selectNode(child.id); executeCommand(Expand) and asserted (in a comment) that create() “optimistically adds the child to the local store so the selection resolves immediately.” It does not — so on first composition the new node would not be available and the editor would render blank.

Root cause

Two plan-vs-reality gaps, both caught only by holistic/code review (not by the per-task TDD, which tested the handler logic, not the runtime dispatch path):

  1. Async store insert. ClientNodeManager.create()submit()scope.launch { ... update() }. The local-store insert (nodes.getOrPut) runs inside a coroutine, so it is NOT synchronously visible after create() returns. KrillScreen’s Expand branch gates the editor render on nodeManager.nodeAvailable(child.id), which would be false on the composition triggered by the command flip.
  2. NodeChildren.load() returns pseudo-types. It mixes real child KrillApp types with MenuCommand.{Update,Delete,KeepBuildingSwarm}. ProjectScreen tolerates this because it routes through executeCommand, which has an isMenuOption() branch; the new createChildAndEdit does not, so a “Update”/”Delete” menu item would have been passed to buildChildNode, building a nonsensical node.

Fix

  1. Insert the child synchronously before navigating: create(child) (async server submit + parent-as-source default) then nodeManager.editing(child)editing()update()nodes.getOrPut is synchronous, so nodeAvailable(child.id) is true before the Expand command flips and KrillScreen recomposes. KrillScreen gates on availability only (not NodeState.EDITING), so the later async update(NONE) from create() is harmless.
  2. Filter the menu: nodeChildren.load(n).filterNot { it.isMenuOption() } so the editor avatar only offers real creatable child types. Parent- as-source needs no client code: buildChildNode yields empty sources, so the server’s existing withParentSourceDefault wires the parent on the create round-trip.

Prevention

Same class as feedback_verify_dispatch_entry_point / 2026-05-18-activity-pulse-wrong-stream: when a plan asserts a runtime property (“create() makes the node immediately available”), verify it against the actual dispatch path before relying on it — scope.launch inside a “create/submit/update” chain almost always means not synchronous. For Krill specifically: ClientNodeManager.create/submit is async; editing()/update() is the synchronous local-store seam, and KrillScreen editor routes gate on nodeAvailable(id). Also: any caller that consumes NodeChildren.load() and does NOT route through ScreenCore.executeCommand must filterNot { it.isMenuOption() }load() deliberately returns MenuCommand pseudo-types for the executeCommand menu contract.