Use this skill to instrument a Jetpack Compose composable with `@TraceRecomposition` from `skydoves/compose-stability-analyzer` so per-recomposition diffs (which state or parameter changed, what value transition) print to logcat under the `Recomposition` tag. Works in release-with-debug-symbols builds where Android Studio Layout Inspector cannot reach, and feeds the IntelliJ / Android Studio plugin's live recomposition heatmap (green under 10, yellow 10–50, red 50+). Covers the Gradle plugin setup, the `ComposeStabilityAnalyzer.setEnabled(BuildConfig.DEBUG)` runtime gate that keeps the instrumentation out of production, and the handoff to debug-time Layout Inspector and CI `stabilityCheck`. Use when the user mentions `@TraceRecomposition`, "trace recomposition", "compose-stability-analyzer", "recomposition logcat", "recomposition heatmap", "release-mode recomposition counts", or needs to confirm a stability fix in a release-like build.
npx skills add https://github.com/skydoves/compose-performance-skills --skill tracing-recompositions-at-runtime
@TraceRecomposition, logcat, and the live heatmapLayout Inspector counts recompositions and surfaces Argument Change Reasons, but it works only in debug, where Live Literals and the interpreted Compose runtime inflate counts. @TraceRecomposition from skydoves/compose-stability-analyzer instruments a composable at compile time and emits per-recomposition diffs (which state changed, what value transition) to logcat under the Recomposition tag. The instrumentation works in any build the developer enables it for — including release-with-debug-symbols — and feeds the IntelliJ / Android Studio plugin's live recomposition heatmap.
This skill is the release-mode complement to ../../recomposition/debugging-recompositions/SKILL.md. Layout Inspector is debug-only, fast to set up, and good for the first triage. @TraceRecomposition is the ground-truth confirmation: instrument the suspect composable, ship a release+R8 build of the dev APK, run the user journey, and read the per-recomposition log lines.
@TraceRecomposition, "trace recomposition", "compose-stability-analyzer", "recomposition logcat", "recomposition heatmap", or "release-mode recomposition".../../recomposition/debugging-recompositions/SKILL.md.../../stability/enforcing-stability-in-ci/SKILL.md (stabilityCheck).../generating-baseline-profiles/SKILL.md with MacrobenchmarkRule + FrameTimingMetric.com.github.skydoves.compose.stability.analyzer (latest, v0.7.3+) added to the module that owns the composables to instrument.Application subclass declared in the manifest, so ComposeStabilityAnalyzer.setEnabled(...) can be called from onCreate().org.jetbrains.kotlin.plugin.compose applied (Kotlin 2.0+).BuildConfig field or feature flag the runtime toggle can read. BuildConfig.DEBUG works; a custom BuildConfig.ENABLE_RECOMPOSITION_TRACE is preferred for production-style profiling builds.compose-stability-analyzer plugin installed from the JetBrains marketplace (or built locally from the GitHub repo).../../recomposition/debugging-recompositions/SKILL.md so the developer has already named the suspect composable in debug before reaching for runtime tracing.In the module's build.gradle.kts:
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
alias(libs.plugins.compose.stability.analyzer)
}
In gradle/libs.versions.toml:
[versions]
composeStabilityAnalyzer = "0.7.3"
[plugins]
compose-stability-analyzer = { id = "com.github.skydoves.compose.stability.analyzer", version.ref = "composeStabilityAnalyzer" }
Same build.gradle.kts, alongside the plugins block:
composeStabilityAnalyzer {
enabled.set(true) // compile-time switch; runtime toggle below gates emission
}
enabled.set(true) controls whether the compiler weaves in the instrumentation. With enabled.set(false) no @TraceRecomposition annotations have any effect. Leaving it on across all build types is fine — the runtime toggle is the actual production gate.
import com.skydoves.compose.stability.runtime.TraceRecomposition
@TraceRecomposition(traceStates = true)
@Composable
fun PriceTicker(price: Price) {
Text(price.formatted)
}
traceStates = true extends the diff to mutableStateOf reads inside the composable body, not just parameters. Start with true for first investigation; flip to false once the cause is known to keep logs compact.
Application.onCreate()import com.skydoves.compose.stability.runtime.ComposeStabilityAnalyzer
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
ComposeStabilityAnalyzer.setEnabled(BuildConfig.DEBUG)
}
}
Without this call, every annotated composable still emits to logcat — including in release. Gate the toggle behind BuildConfig.DEBUG or a custom BuildConfig.ENABLE_RECOMPOSITION_TRACE so the production APK is silent.
adb logcat -s Recomposition:D
Sample output for an animated PriceTicker whose price changes from 99.0 to 99.5 (illustrative — exact log shape depends on the analyzer version):
D/Recomposition: [Recomposition #1] PriceTicker
D/Recomposition: ├─ [param] price: Price changed (Price(99.0) → Price(99.5))
D/Recomposition: [Recomposition #2] PriceTicker
D/Recomposition: ├─ [param] price: Price unchanged (skipped via strong-skipping equals)
The number after # is a per-instance counter — cumulative across the lifetime of the composable's restart scope. A composable that prints [Recomposition #50] while only being on screen for two seconds is the smoking gun.
Install the Compose Stability Analyzer plugin from JetBrains Marketplace. With the plugin installed and the app running, the editor gutter next to each @TraceRecomposition-annotated composable shows a color-coded badge. Indicative thresholds (illustrative — see the plugin's settings panel for the current bands):
Click the badge to jump to a side panel listing each [Recomposition #N] entry with its diff. The panel mirrors logcat but groups by composable instance so the developer can spot which LazyColumn row is misbehaving without scrolling logcat.
Runtime tracing names the composable and the changing parameter. The fix lives elsewhere:
../../stability/diagnosing-compose-stability/SKILL.md and ../../stability/stabilizing-compose-types/SKILL.md.Flow → ../../recomposition/using-strong-skipping-correctly/SKILL.md and ../../side-effects/collecting-flows-safely/SKILL.md.../../recomposition/deferring-state-reads/SKILL.md.../../recomposition/debugging-recompositions/SKILL.md.Once the fix lands, re-run the trace; the post-fix logcat should show one initial [Recomposition #1] and no subsequent entries during the same scenario.
@TraceRecomposition is for diagnosis. Preventing the next regression is a CI concern: enable stabilityCheck in CI per ../../stability/enforcing-stability-in-ci/SKILL.md, which fails the build when a previously-skippable composable becomes non-skippable.
// RIGHT
@TraceRecomposition(traceStates = true)
@Composable
fun PriceTicker(price: Price) {
Text(price.formatted)
}
Sample logcat output (illustrative — exact log shape depends on the analyzer version; the price changes once per second; the surrounding row recomposes once per parent tick):
D/Recomposition: [Recomposition #3] PriceTicker
D/Recomposition: ├─ [param] price: Price changed (Price(99.0) → Price(99.5))
D/Recomposition: [Recomposition #4] PriceTicker
D/Recomposition: ├─ [param] price: Price unchanged
D/Recomposition: ├─ [reason] parent restart scope re-invoked; strong-skipping equals matched
The second entry is the desirable shape: parent ticked, the equals() guard fired, body skipped.
// WRONG
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
// ComposeStabilityAnalyzer.setEnabled(...) is never called.
// Every annotated composable emits to logcat in every build, including release.
}
}
// WRONG because: shipping with tracing enabled adds logcat I/O on every recomposition,
// which adds nontrivial overhead on hot composables (LazyColumn rows in particular)
// and pollutes user-installed-app logs on shared devices.
// RIGHT
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
ComposeStabilityAnalyzer.setEnabled(BuildConfig.DEBUG)
}
}
For a release-with-debug-symbols profiling APK, prefer a dedicated flag over BuildConfig.DEBUG:
// RIGHT — explicit profiling flag, decoupled from debug
ComposeStabilityAnalyzer.setEnabled(BuildConfig.ENABLE_RECOMPOSITION_TRACE)
@TraceRecomposition annotations in production releases// WRONG
// Production release with @TraceRecomposition still annotated on hot composables and
// ComposeStabilityAnalyzer.setEnabled(true) hard-coded in Application.
// WRONG because: logcat I/O on every recomposition adds nontrivial overhead on hot
// composables. The instrumentation also captures parameter values into log strings,
// which can leak PII if a composable receives a user model.
// RIGHT — annotation present, runtime gate keeps it dormant in release
@TraceRecomposition(traceStates = true)
@Composable
fun PriceTicker(price: Price) { Text(price.formatted) }
// Application:
ComposeStabilityAnalyzer.setEnabled(BuildConfig.DEBUG) // dormant in release
traceStates once the cause is known// First investigation — verbose
@TraceRecomposition(traceStates = true)
@Composable
fun Feed(state: FeedState) { /* ... */ }
// Cause identified, fix shipped, keep the annotation as a tripwire — quieter
@TraceRecomposition(traceStates = false)
@Composable
fun Feed(state: FeedState) { /* ... */ }
traceStates = true logs every mutableStateOf read transition inside the body. That is gold for first triage and noise once the cause is known. Toggling to false keeps the per-recomposition counter (still useful as a tripwire) without the per-state diff.
@TraceRecomposition finds the regression a developer is chasing right now. It does nothing about the regression a teammate ships next week. Pair it with stabilityCheck:
# RIGHT — both layers in place
- @TraceRecomposition annotates suspect composables; runtime toggle gated on BuildConfig.DEBUG.
- ./gradlew :app:stabilityCheck runs in CI per ../../stability/enforcing-stability-in-ci/SKILL.md.
- The .stability baseline updates only after a deliberate review.
# WRONG — diagnosis without prevention
- @TraceRecomposition is the only mechanism in place.
- Next PR introduces an unstable type; nothing in CI catches it; the perf regression
ships and is found again at runtime weeks later.
ComposeStabilityAnalyzer.setEnabled(...) on a build config field (BuildConfig.DEBUG or a dedicated BuildConfig.ENABLE_RECOMPOSITION_TRACE) or a feature flag. Never hard-code setEnabled(true).stabilityCheck CI gate from ../../stability/enforcing-stability-in-ci/SKILL.md. Runtime tracing is for diagnosis; CI gating prevents the next regression. One without the other is half a workflow.@TraceRecomposition instrumentation enabled. The annotation may remain on composables, but ComposeStabilityAnalyzer.setEnabled(...) MUST resolve to false in the production build.[Recomposition #N] count as a hard SLO without a context (which scenario? which device? release or debug?). Track the count delta across a fixed scenario instead — "post-fix the price-ticker scenario emits 1 entry vs pre-fix 30".PriceTicker recomposes per parent tick because price is reported as Changed, but the Price data class is @Immutable and equals() should match"). "It recomposes a lot" is not a finding.traceStates = true for first investigation (richer logs); set to false once the cause is known so the trace becomes a quieter tripwire.@TraceRecomposition annotations on a small, deliberate set of composables (the screen's hot composables, the LazyColumn row composable). Annotating every composable defeats the signal-to-noise ratio of the heatmap.com.github.skydoves.compose.stability.analyzer plugin applied to the module that owns the composables to instrument.composeStabilityAnalyzer { enabled.set(true) } configured.Application.onCreate() calls ComposeStabilityAnalyzer.setEnabled(BuildConfig.DEBUG) (or another build-flag gate). The call is not hard-coded true.@TraceRecomposition(traceStates = true).adb logcat -s Recomposition:D prints [Recomposition #N] <ComposableName> lines while reproducing the scenario.changed (oldValue → newValue) or unchanged.adb logcat -s Recomposition:D prints nothing — the instrumentation is dormant../gradlew :app:stabilityCheck) is configured per ../../stability/enforcing-stability-in-ci/SKILL.md so the next regression is caught before it ships.skydoves/compose-stability-analyzer (Gradle plugin + runtime + IDE plugin) — https://github.com/skydoves/compose-stability-analyzerFor the debug-time entry point with Layout Inspector and Argument Change Reasons, see ../../recomposition/debugging-recompositions/SKILL.md. For the CI gate that prevents the next stability regression, see ../../stability/enforcing-stability-in-ci/SKILL.md. For acting on the cause once the offending parameter is named, see ../../stability/diagnosing-compose-stability/SKILL.md, ../../stability/stabilizing-compose-types/SKILL.md, ../../recomposition/using-strong-skipping-correctly/SKILL.md, and ../../recomposition/deferring-state-reads/SKILL.md. For end-to-end user-perceived perf measurement (frame timing, cold startup), see ../generating-baseline-profiles/SKILL.md.
Use when the user asks to run Gemini CLI for code review, plan review, or big context (>200k) processing. Ideal for comprehensive analysis requiring large context windows. Uses Gemini 3 Pro by default for state-of-the-art reasoning and coding.
Claude Skills meta-skill: extract domain material (docs/APIs/code/specs) into a reusable Skill (SKILL.md + references/scripts/assets), and refactor existing Skills for clarity, activation reliability, and quality gates.
> Pedantic backend pre-commit and atomic commit Skill for Django/Optimo-style repos. Enforces local AGENTS.md / CLAUDE.md, pre-commit hooks, .security/* helpers, and Monty’s backend engineering taste – with no AI signatures in commit messages.
Automatically backs up files, saves diffs, uses agents/skills, and ensures modular code (<200 lines) before any implementation. Use this skill for ALL code changes to ensure safe, reversible, and clean implementations.
Use when you've developed a broadly useful skill and want to contribute it upstream via pull request - guides process of branching, committing, pushing, and creating PR to contribute skills back to upstream repository
Default reference pipeline for the code-migration taskKind — code-import → design-extract → token-map → rewrite-plan → patch-edit ↔ build-test devloop → diff-review → handoff.
Configure human-in-the-loop gating for AI agent review actions in Claude Code. Use when setting up a project where an agent may post PR reviews, comments, merges, or edit CI configuration, and you want a cryptographically auditable approval trail with Cedar-enforced gates.
> Check installed community skills for updates. Shows a diff and requires explicit approval before applying. Use when the user says "check for updates", "update my skills", "anything new for my installed skills", or when invoked from the registry-sync agent.
Take skydoves/tracing-recompositions-at-runtime from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.