The first-time “Create Your First Project” onboarding overlay (OnboardingOverlay)
appeared too often and felt nagging — it could re-show whenever a connected server
happened to have no Project node, including for users who were already navigating an
existing swarm or running multiple servers.
The show condition in StartupOverlayDispatch only checked swarmStable, that some
server was in NONE/INFO state, and !hasProjects. hasProjects was a swarm-wide
project-type check, so a server with other node types (or a multi-server setup, or a user
mid-click) still satisfied it. There was no signal that the user was a genuine first-timer.
Extracted a pure onboardingServer(...) decision function from the composable and tightened
the gate to fire only for a single, freshly connected, empty server with nothing selected:
exactly one server (servers.singleOrNull()), that server in NONE/INFO state, its
nodeManager.children(...) empty (replaces the narrower !hasProjects check — any child node
now suppresses the prompt), and selectedNodeId == null (collected reactively in
ClientScreen). No state is persisted; the gate is purely derived from live swarm state.
The decision lives in a pure, parameterized function (childrenOf/selectedNodeId injected),
so OnboardingGateTest locks each gate down without Compose. Mirrors the existing
selectLayoutRoots / NodeLayoutFiltersTest seam — when a visibility rule has more than one
condition, extract it from the composable and assert each condition in isolation rather than
burying boolean logic inside an if in a @Composable.