Jetpack Compose Builder — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Jetpack Compose Builder (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Build the state and data flow first, the pixels second - most Compose bugs are state-ownership and recomposition bugs, not layout bugs. The costly failure is a screen that works in a demo, then recomposes entire subtrees on every keystroke or scroll frame because state lives in the wrong place and parameters are unstable.
Before writing a composable, establish:
Decide which state is UI-local (remember) versus screen-level (ViewModel exposing StateFlow, collected via collectAsStateWithLifecycle()). Hold mutable state in the lowest common ancestor that needs it - no lower (parent cannot drive it) and no higher (recomposes too wide a scope).
Pass immutable state down as parameters; emit changes up via lambdas (onValueChange, onClick). A composable that owns its own remember { mutableStateOf } cannot be controlled, previewed, or tested by its parent - hoist it. Every leaf composable must render in a @Preview without a ViewModel.
Use remember to cache allocations and computations across recompositions, rememberSaveable to survive config change and process death. Key remember, LaunchedEffect, and derivedStateOf on the inputs they depend on; LaunchedEffect(Unit) where an id should restart the effect is a bug. Wrap derived values whose input changes faster than the output (scroll offset to "show FAB" boolean) in derivedStateOf so downstream recomposition happens only when the result changes.
LaunchedEffect for scoped suspend work, rememberCoroutineScope for event-driven launches, DisposableEffect for cleanup, SideEffect to publish Compose state to non-Compose code. Never launch coroutines or write state in the composable body - state writes during composition throw or loop.
Use LazyColumn/LazyRow with a stable item key (server id, not index) so reorders animate and item state survives. Hoist scroll position with rememberLazyListState when it must be observed or controlled. Prefer slot APIs (content lambdas) over boolean flags. Never nest a lazy list inside a scrollable of the same axis.
Apply the recomposition cost rules below, cheapest first: push state reads down, defer reads past composition, then fix stability.
Capture recomposition counts in Layout Inspector and skippability in the Compose compiler metrics report (enable the compiler's reports output and read the -classes.txt for "unstable" parameters). Fix the composable that actually recomposes too often. Add stability annotations only against that evidence. If frames still miss budget after recomposition is contained, move to mobile-perf-profiler for a full trace.
Compose skips a composable only when the call site's arguments are all stable and unchanged. Work through these in order:
Red line: if Layout Inspector shows a composable recomposing on interactions that do not concern it (typing in an unrelated field recomposes the whole list), that is the bug to fix - not a candidate for micro-tuning.
Bad - unstable params, inline work, index keys:
@Composable
fun MessageList(vm: ChatViewModel) { // child reaches into ViewModel
val messages by vm.messages.collectAsState()
LazyColumn {
items(messages.size) { i -> // index key: reorders reset state
val m = messages[i]
Row {
Text(SimpleDateFormat("HH:mm").format(m.sentAt)) // allocation + parse per recomposition
Text(m.body)
Button(onClick = { vm.retry(m) }) { Text("Retry") } // lambda captures vm per pass
}
}
}
}Good - stateless, stable keys, display-ready data, hoisted events:
@Immutable
data class MessageUi(val id: String, val timeLabel: String, val body: String)
@Composable
fun MessageList(
messages: ImmutableList<MessageUi>,
onRetry: (String) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(modifier) {
items(messages, key = { it.id }) { m ->
MessageRow(message = m, onRetry = onRetry) // previews without a ViewModel
}
}
}Time formatting moved to the ViewModel, identity keys keep row state across reorders, and every parameter is stable so unchanged rows skip.
Produce the screen as a state-hoisted composable tree: an immutable UI-state class, a stateless screen composable taking state plus event lambdas, preview functions for the key states (loading, content, error), and - for any performance claim - the Layout Inspector recomposition counts or compiler-metrics lines that justify it.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.