Kraken’s nightly architectural scan flagged NodeItem.kt and GraphScreen.kt for “unstructured coroutine launches”: pointerInput’s awaitPointerEventScope { while (true) { ... } } loop and GraphScreen’s polling launches were claimed to run outside any composable-scoped CoroutineScope (implying GlobalScope/manual CoroutineScope(Dispatchers.Default) usage), risking orphaned work that outlives screen dismissal. The lead also claimed rememberCoroutineScope() in NodeItem.kt was declared but never used.
False positive. Both patterns are the standard, correct Compose idioms for their purpose:
Modifier.pointerInput(keys) { awaitPointerEventScope { while (true) { awaitPointerEvent() } } } is the canonical raw-gesture-detection loop — the same shape detectTapGestures/detectDragGestures use internally. The suspend block runs in a coroutine owned by the pointer-input modifier node itself; Compose cancels/relaunches it automatically when the composable leaves composition or the keys change. No manual Job bookkeeping is needed or expected.rememberCoroutineScope() in NodeItem.kt is in fact used (scope.launch { ... } inside the tap handler) — the local model’s claim it was unused was incorrect.GraphScreen.kt’s launch { ... } calls sit inside coroutineScope { } blocks in suspend functions that are only ever invoked from LaunchedEffect. This is textbook structured concurrency: coroutineScope doesn’t return until its children complete, and LaunchedEffect is cancelled on disposal/key change.grep -rn "GlobalScope\|CoroutineScope(Dispatchers" composeApp/src/commonMain/kotlin/krill/zone/app/ returns zero hits — no unscoped launches exist in this subtree.No code change was needed. The existing coroutine usage in NodeItem.kt and GraphScreen.kt is already correctly lifecycle-scoped.
If Kraken files a coroutine-structured-concurrency lead against a pointerInput { awaitPointerEventScope { while (true) { ... } } } block, check whether it’s a raw gesture-detection loop (the framework-recommended pattern) before treating the while (true) as an unbounded loop. Similarly, launch calls nested inside coroutineScope { } inside a suspend function called from LaunchedEffect are already structured — only flag launch/async that resolve to GlobalScope or a manually constructed CoroutineScope(...) not tied to composition.