auditing-compose-performance — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited auditing-compose-performance (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.
This is the highest-level entry point in the compose-performance-skills library. When the developer's symptom is broad — "the app feels sluggish", "scroll is rough everywhere", "we're starting a perf sprint", "where do we even start?" — Claude enters here. The orchestrator does NOT replace the 25 focused skills; it sequences them through a four-phase loop and produces a written audit report at the end.
The phases are Measure → Diagnose → Fix → Verify, run in order, never skipped. Phase 1 establishes baseline numbers from a release + R8 build on a real device, because anything else is fiction. Phase 2 turns symptoms into named causes from the Compose Compiler reports, Layout Inspector, and runtime tracing. Phase 3 applies one targeted fix at a time, re-measuring between fixes so the delta of each change is provable. Phase 4 regenerates the Baseline Profile, locks in a CI stability gate, and commits the baseline files so regressions cannot slip back.
Perf work without measurement is guessing. Skydoves hot take #1 applies throughout: DO NOT chase 100% skippability — that is a diagnostic on a compiler report, not the goal. The goal is FrameTimingMetric and StartupTimingMetric improvement on a real device.
LazyColumn, derivedStateOf not firing, custom modifier recomposing). Go straight to the focused skill. See ../../INDEX.md for the symptom→skill map.../../INDEX.md../gradlew assembleRelease).../../measurement/generating-baseline-profiles/SKILL.md).stabilityConfigurationFile. Kotlin 2.0.20+ for Strong Skipping default. AGP 8.2+ for the Baseline Profile Generator template.Run all four phases in order. DO NOT skip ahead. After each Phase 3 fix, return to Phase 1 to re-measure and Phase 2 to re-diagnose before applying the next fix.
../../measurement/testing-compose-in-release-mode/SKILL.md.proguard-android-optimize.txt, resource shrinking on). Cross-link ../../build/configuring-r8-for-compose/SKILL.md.../../measurement/generating-baseline-profiles/SKILL.md.MacrobenchmarkRule + StartupTimingMetric under CompilationMode.Partial(BaselineProfileMode.Require). Run ≥10 iterations.FrameTimingMetric (P50, P90, P99). Run ≥5 iterations on the same device.// Phase 1 baseline scroll measurement (record P50/P90/P99 of frameDurationCpuMs)
@Test fun feedScroll() = rule.measureRepeated(
packageName = "com.example",
metrics = listOf(FrameTimingMetric()),
iterations = 5,
startupMode = StartupMode.WARM,
compilationMode = CompilationMode.Partial(BaselineProfileMode.Require),
) {
startActivityAndWait()
device.findObject(By.res("feed")).fling(Direction.DOWN)
}../../stability/diagnosing-compose-stability/SKILL.md.<module>-composables.txt. List every restartable-but-not-skippable composable and the unstable parameter that blocks skipping.<module>-classes.txt. List every unstable class with the offending field (a var, an unstable field type, an interface, etc.).runtime, unknown, "this looks stable but the compiler disagrees"), run ../../stability/understanding-stability-inference/SKILL.md to walk the 12-phase algorithm.../../recomposition/debugging-recompositions/SKILL.md.@TraceRecomposition. Cross-link ../../measurement/tracing-recompositions-at-runtime/SKILL.md.LazyColumn row beats a 10× recomposition on a one-off settings screen. DO NOT rank by compiler-report severity alone.For each ranked issue, pick the matching focused skill and apply the fix. PREFERRED: one PR per skill, so the diff is reviewable and bisectable. After each fix, re-run Phase 1 (measure) and Phase 2 (diagnose) to confirm the change moved the right needle and did not regress another.
data class, List/Set/Map parameter, java.time.LocalDateTime) → ../../stability/stabilizing-compose-types/SKILL.md.@DontMemoize or @NonSkippableComposable) → ../../recomposition/using-strong-skipping-correctly/SKILL.md.Modifier.offset { }, Modifier.graphicsLayer { }, Modifier.drawBehind { }) → ../../recomposition/deferring-state-reads/SKILL.md.derivedStateOf misuse (missing remember, captured non-state vars, used where input frequency does not exceed output frequency) → ../../recomposition/choosing-derivedstateof/SKILL.md.key and contentType for LazyColumn/LazyRow/LazyVerticalGrid, hoisting modifier chains out of the items lambda → ../../lists/optimizing-lazy-layouts/SKILL.md.../../lists/configuring-lazy-prefetch/SKILL.md.Modifier.composed { } to Modifier.Node + ModifierNodeElement → ../../modifiers/migrating-to-modifier-node/SKILL.md.clip after background) → ../../modifiers/ordering-modifier-chains/SKILL.md.collectAsState → collectAsStateWithLifecycle, hoist Flow<T> parameters out of composables, add .conflate() / .distinctUntilChanged()) → ../../side-effects/collecting-flows-safely/SKILL.md.LaunchedEffect vs RememberedEffect vs DisposableEffect vs SideEffect, stale callbacks via rememberUpdatedState) → ../../side-effects/using-efficient-effects/SKILL.md.../../measurement/generating-baseline-profiles/SKILL.md.stabilityDump once, then stabilityCheck on every PR). Cross-link ../../stability/enforcing-stability-in-ci/SKILL.md.app/src/main/generated/baselineProfiles/baseline-prof.txt (or wherever the consumer module placed it).app/stability/*.stability baseline files generated by :stabilityDump.Claude MUST produce this file at the end of the audit. Save it under docs/perf-audit-<date>-<module>.md (or wherever the project stores reports).
# Compose Performance Audit — <date> — <module>
## Environment
- Compose UI: <version>
- Compose Compiler: <version>
- Kotlin: <version>
- AGP: <version>
- Device: <model> / <API>
## Baseline (Phase 1)
- Cold startup median: <ms>
- Scroll FrameTimingMetric (P50/P90/P99): <ms> / <ms> / <ms>
- Baseline Profile present: yes/no
## Diagnosis (Phase 2)
- Restartable-not-skippable composables: <count>
- Unstable classes: <count>
- Top 5 recomposition hotspots: <list>
- Phase-misplaced reads: <count>
## Fixes applied (Phase 3)
| Skill | Change | Files | Macrobench delta |
| ----- | ------ | ----- | ---------------- |
| stability/stabilizing-compose-types | wrap List<Snack> with ImmutableList | feed/SnackList.kt | scroll P90 18ms → 12ms |
| recomposition/deferring-state-reads | offset(x.dp) → offset { } | hero/Hero.kt | scroll P99 33ms → 19ms |
| ... | ... | ... | ... |
## Verification (Phase 4)
- Cold startup median: <ms> (Δ <ms>)
- Scroll FrameTimingMetric (P50/P90/P99): <ms> / <ms> / <ms> (Δ ...)
- Baseline Profile regenerated: yes
- CI stability gate active: yes / no
## Open items / follow-ups
- <list>// WRONG
// "Scroll feels rough — let me wrap this List in ImmutableList and add @Immutable everywhere."
// WRONG because: no baseline number exists, so any later claim of improvement is unfalsifiable.// RIGHT
// 1. Run MacrobenchmarkRule on the suspect surface. Record P50/P90/P99 in the report.
// 2. Read the Compose Compiler report for the same surface. Identify the named cause.
// 3. Apply ONE targeted fix from a sibling skill.
// 4. Re-run the same Macrobenchmark. Compute the delta. Record it.WRONG: a single PR titled "perf improvements" that
- converts 4 data classes to ImmutableList parameters
- migrates 2 custom modifiers to Modifier.Node
- moves 3 graphicsLayer reads down a phase
- adds a Baseline Profile module
WRONG because: if the Macrobench delta is mixed (startup faster, scroll slower), the
audit cannot attribute the regression to a specific change. Bisect impossible.RIGHT: four PRs, each scoped to a single skill, each with its own Before/After
Macrobench number in the description. The audit report links to each PR in the
Phase 3 table.WRONG: "We got composables.txt skip rate from 71% to 100%. Audit complete."
WRONG because: skippability is a means. The end is FrameTimingMetric and
StartupTimingMetric improvement on a real device. A 100% skippable app can still
drop frames if the work happens in Layout or Draw.RIGHT: "Scroll P90 dropped from 18ms to 11ms. Cold startup median dropped from
820ms to 610ms. Skip rate improved as a side effect; we did not target it directly."| Capability | Min version |
|---|---|
| Strong Skipping default ON | Kotlin 2.0.20+ |
LazyLayoutCacheWindow | Compose Foundation 1.9+ |
| Pausable composition in lazy prefetch (default) | Compose Foundation 1.10+ |
stabilityConfigurationFile DSL | Compose Compiler 1.5.5+ |
| Baseline Profile Generator template | AGP 8.2+ |
| R8 full mode default | AGP 8.0+ |
Modifier.animateItem() GA | Compose UI 1.7+ |
rememberGraphicsLayer() | Compose UI 1.7+ |
FrameTimingMetric and StartupTimingMetric improvement, not a metric on the compiler report.app/stability/*.stability files committed; CI stabilityCheck gate is active.docs/perf-audit-<date>-<module>.md) and circulated.Phase 1 — Measure:
../../measurement/testing-compose-in-release-mode/SKILL.md — debug builds lie; measure release + R8.../../measurement/generating-baseline-profiles/SKILL.md — Baseline Profile Generator + Macrobenchmark end-to-end.../../build/configuring-r8-for-compose/SKILL.md — full mode, optimize ProGuard file, trust consumer rules.Phase 2 — Diagnose:
../../stability/diagnosing-compose-stability/SKILL.md — enable and read the Compose Compiler reports.../../stability/understanding-stability-inference/SKILL.md — 12-phase algorithm, $stable field, generic bitmasks.../../stability/using-stability-analyzer-ide-plugin/SKILL.md — IDE plugin for inline stability inspection.../../stability/visualizing-recomposition-cascades/SKILL.md — visualize how unstable parameters propagate through the composable tree.../../recomposition/debugging-recompositions/SKILL.md — Layout Inspector counts and Argument Change Reasons.../../measurement/tracing-recompositions-at-runtime/SKILL.md — @TraceRecomposition for release-grade tracing.Phase 3 — Fix:
../../stability/stabilizing-compose-types/SKILL.md — three-tier waterfall: rewrite, annotate, configure.../../recomposition/using-strong-skipping-correctly/SKILL.md — verify mode, audit lambda capture sites, escape hatches.../../recomposition/deferring-state-reads/SKILL.md — push reads down to Layout/Draw via lambda modifiers.../../recomposition/choosing-derivedstateof/SKILL.md — only when input frequency exceeds output frequency.../../recomposition/avoiding-subcomposition-pitfalls/SKILL.md — recognize when SubcomposeLayout / BoxWithConstraints cost outweighs benefit.../../lists/optimizing-lazy-layouts/SKILL.md — key, contentType, Modifier.animateItem(), hoisting.../../lists/configuring-lazy-prefetch/SKILL.md — LazyLayoutCacheWindow, pausable prefetch, NestedPrefetchScope.../../modifiers/migrating-to-modifier-node/SKILL.md — Modifier.Node + ModifierNodeElement over composed { }.../../modifiers/ordering-modifier-chains/SKILL.md — wrap-the-next-modifier mental model.../../side-effects/collecting-flows-safely/SKILL.md — collectAsStateWithLifecycle, hoist Flow<T> parameters.../../side-effects/using-efficient-effects/SKILL.md — pick the cheapest correct effect API.Phase 4 — Verify:
../../stability/enforcing-stability-in-ci/SKILL.md — stabilityDump baseline + stabilityCheck CI gate.../../measurement/generating-baseline-profiles/SKILL.md — regenerate after fixes; commit baseline-prof.txt.Hot-reload (developer-loop velocity, optional but recommended):
../../hot-reload/setting-up-compose-hotswan/SKILL.md — install and configure HotSwan for sub-second iteration.../../hot-reload/preserving-state-across-reloads/SKILL.md — keep remember/rememberSaveable state through a reload.../../hot-reload/understanding-hot-reload-limits/SKILL.md — what reloads cleanly vs what forces a full rebuild.../../hot-reload/iterating-with-ai-and-mcp/SKILL.md — pair HotSwan with an MCP-aware AI tool for tight loops.Symptom and API lookup: ../../INDEX.md.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.