Phase 3 local-first onboarding (bullet-journal walkthrough) required node creation without a server. The FlowStep.CreateNode and FlowStep.OpenEditor steps existed as unimplemented stubs; LocalSwarmHost and its storage + delivery path were missing.
No LocalSwarmHost existed — the design assumed all node writes went to a Ktor server. Local-only node types (Project, Journal) had no persistence or delivery path for offline clients. The FlowEngine also lacked ReturnToChooser to loop the user back to the flow menu after completing a walkthrough.
LocalSwarmHost (shared/.../localhost/) with createNode, updateNode, deleteNode, and init() (replays persisted local nodes into ClientNodeManager on startup).writeNode, readNodes, deleteNode defaults to FileOperations (delegates to existing update/load/delete).LocalSwarmHost in JVM/Android/iOS Koin modules; excluded from WASM (no FileOperations there). Injected everywhere as getOrNull() so WASM silently skips local creation.LocalSwarmHost.init() into ClientClientProcessor startup.FlowStep.ReturnToChooser (handled in FlowEngine.autoResolveLogicalSteps by resetting state to Idle).hidden: Boolean field to KrillFlow so what-next is loadable via ChainTo but invisible in FlowChooser.flows/bullet-journal.json (Phase 3 live, comingSoon: false) and flows/what-next.json (hidden utility flow).WalkthroughHost.kt: getKoin() is @Composable and cannot be called inside remember { }; extracted to composable scope first.FlowSheet.kt: added ReturnToChooser to the auto-resolve branch so the exhaustive when compiles.FlowRegistryTest Phase 2 ratchet: replaced phase3Plus_areMarkedComingSoon (which incorrectly expected bullet-journal to remain comingSoon=true) with phase4Plus_areMarkedComingSoon + dedicated bulletJournal_isLiveInPhase3 and whatNext_isHiddenAndParsesCorrectly tests.@Composable functions (getKoin(), koinInject(), etc.) cannot be called inside non-composable lambdas such as remember { }. Capture them at the composable level first.FlowStep subtype added to krill.zone.shared requires a corresponding branch in FlowSheet.kt’s when expression; Kotlin’s exhaustiveness check surfaces this at compile time but only when the when is an expression — keep it that way.comingSoon=true) must be updated in the same PR that promotes the flow to live; leaving the old assertion red is the gate working as intended.LocalSwarmHost’s selfId parameter (() -> String = installId) is the canonical seam for avoiding installId() calls in tests — repeat this pattern for any future component that needs device identity.