Symptom

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.

Root cause

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.

Fix

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.

Prevention

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.