| name | visualizing-recomposition-cascades |
| description | Use this skill to drive the active investigation features of the `skydoves/compose-stability-analyzer` IntelliJ / Android Studio plugin: the static Recomposition Cascade visualizer that walks the call graph from a root `@Composable`, and the live Recomposition Heatmap that streams `D/Recomposition` events from a connected device's logcat into block inlays above each instrumented composable. Covers the `Compose Stability Analyzer` tool window's three tabs (Explorer, Cascade, Heatmap), the cascade analyzer's static PSI walk with depth cap and cycle detection, the ADB logcat command the heatmap consumes, and the toggle/clear actions. Use when the user asks "what gets dragged in if I change this composable", "how many times did this recompose during that scroll", "what is the blast radius of this state change", or mentions the cascade tab, the heatmap tab, the recomposition inlays, or `Toggle Recomposition Heatmap`. |
| license | Apache-2.0. See LICENSE for complete terms. |
| metadata | {"author":"Jaewoong Eum (skydoves)","keywords":["jetpack-compose","performance","stability","recomposition","cascade","heatmap","intellij-plugin","logcat","trace-recomposition","compose-stability-analyzer"]} |
Visualizing Recomposition Cascades — static blast radius and live heatmap inside the IDE
Passive gutter icons answer "is this composable skippable" (covered by ../using-stability-analyzer-ide-plugin/SKILL.md). The active investigation tools answer two harder questions. The Cascade visualizer walks the static call graph from a root @Composable and shows what else might recompose if the root recomposes. The Live Heatmap subscribes to the device's logcat and renders the actual recomposition counts as block inlays above each instrumented composable in the editor. Together they close the loop between static analysis and runtime evidence.
Both features live inside the same IntelliJ plugin and surface through the right-anchored Compose Stability Analyzer tool window, which has three tabs: Explorer, Cascade, and Heatmap.
When to use this skill
- The developer asks "what gets dragged in if I change this composable" — use the Cascade tab.
- The developer asks "how many times did this recompose during that scroll" or "which composable is hot during this animation" — use the Heatmap tab.
- Triaging a recomposition spike surfaced by
../../recomposition/debugging-recompositions/SKILL.md and needing the call-site context.
- Preparing a refactor and wanting the blast radius before touching a shared composable.
- The user mentions the Cascade tab, the Heatmap tab, recomposition inlays, Analyze Recomposition Cascade, Toggle Recomposition Heatmap, or Clear Recomposition Data.
When NOT to use this skill
- Pure source-level diagnosis. The gutter icons and the
UnstableComposable inspection from ../using-stability-analyzer-ide-plugin/SKILL.md are enough.
- CI gating against stability regressions. Use
../enforcing-stability-in-ci/SKILL.md.
- Full release-grade scroll/startup measurement with FrameTimingMetric. Use
../../measurement/generating-baseline-profiles/SKILL.md. Toggle the heatmap off before such runs.
- Conceptual questions about how the compiler classifies a type. Use
../understanding-stability-inference/SKILL.md.
Prerequisites
- The IDEA plugin from
../using-stability-analyzer-ide-plugin/SKILL.md already installed and the Compose Stability Analyzer tool window visible in the right rail.
- For the Cascade workflow: nothing extra; it is pure static analysis on the open project's PSI.
- For the Heatmap workflow:
- At least one composable annotated with
@TraceRecomposition (cross-link ../../measurement/tracing-recompositions-at-runtime/SKILL.md for the runtime setup).
- An Android device or emulator connected via ADB.
- The app installed and running.
ComposeStabilityAnalyzer.setEnabled(true) called in Application.onCreate (gated on BuildConfig.DEBUG or a dedicated flag — see ../../measurement/tracing-recompositions-at-runtime/SKILL.md).
Workflow A — Cascade visualizer
A1. Place the caret inside a @Composable
The action Analyze Recomposition Cascade (id com.skydoves.compose.stability.idea.cascade.AnalyzeCascadeAction) is enabled only when the caret is inside a @Composable function. It is registered in the Editor popup; right-click anywhere inside the composable body to surface it.
A2. Run the action
Right-click → Analyze Recomposition Cascade. The right-anchored Compose Stability Analyzer tool window opens to the Cascade tab and renders a tree rooted at the selected function.
Internals to know when triaging the result:
- The walk starts from the selected
KtCallExpression and descends into call targets resolved via resolveMainReference().
- Maximum recursion depth is capped at 10 levels, so cascades from very wide composables stop at depth 10 rather than expanding indefinitely.
- Cycle detection uses a visited set of fully qualified names; recursive composables (e.g. tree renderers) appear once and are not re-expanded.
- Each node carries a stability badge from the same engine that drives the gutter icons in
../using-stability-analyzer-ide-plugin/SKILL.md.
A3. Walk and triage
Walk depth-first. For each node:
- Read the stability badge. A red node downstream of the root means recomposing the root will recompose that subtree without skipping.
- Double-click a node to navigate to its source.
- For high-fan-out branches, prune by stabilizing parameters (
../stabilizing-compose-types/SKILL.md) or by deferring state reads inside hot leaves (../../recomposition/deferring-state-reads/SKILL.md).
A4. Treat the cascade as a static estimate, not a runtime guarantee
The cascade is a static call-graph walk. Whether each downstream node actually recomposes at runtime depends on stability and skipping decisions made by the compiler and runtime. Confirm with the live heatmap (Workflow B) before drawing conclusions about real cost.
Workflow B — Live Heatmap
B1. Annotate target composables with @TraceRecomposition
The heatmap is fed by logcat lines emitted by the @TraceRecomposition runtime. Without instrumented composables, no inlays appear. Use the Add @TraceRecomposition intention from ../using-stability-analyzer-ide-plugin/SKILL.md, or add the annotation manually. Full runtime setup (gating, state tracing, throttling) lives in ../../measurement/tracing-recompositions-at-runtime/SKILL.md.
B2. Run the app and confirm logcat output
Run a debug build on the connected device. Open the Logcat tool window and filter on tag Recomposition. Confirm D/Recomposition lines stream in when the instrumented composables recompose. The exact format the plugin's parser expects:
[Recomposition #3] UserProfile (tag: user-screen) (2.30ms)
├─ [param] user: User changed (User@abc123 → User@def456)
├─ [param] count: Int stable (42)
├─ [state] counter: Int changed (5 → 6)
└─ Unstable parameters: [user]
If the log lines are not appearing, the runtime is misconfigured — go back to ../../measurement/tracing-recompositions-at-runtime/SKILL.md before continuing.
B3. Toggle the heatmap on
In the editor right-click → Toggle Recomposition Heatmap (action id com.skydoves.compose.stability.idea.heatmap.ToggleHeatmapAction). The action also lives under the Code menu.
The plugin's AdbLogcatService spawns this exact ADB command to subscribe:
adb logcat -s Recomposition:D -T 1
-s Recomposition:D filters to the tag at debug level; -T 1 starts from the most recent line. If multiple devices are connected, a popup chooser appears.
B4. Read the inlays
HeatmapInlayManager places block inlays above each @Composable declaration that has streamed at least one event. Each inlay shows:
- The recomposition count since the heatmap was toggled on (or since the last Clear Recomposition Data).
- A color-coded background reflecting the relative heat.
Click an inlay to switch the tool window to the Heatmap tab and focus that composable's event history. Hover an inlay for a 5-second tooltip with the latest data, served from AdbLogcatService.getHeatmapData(name).
B5. Reset accumulated counts
Run Code → Clear Recomposition Data (action id com.skydoves.compose.stability.idea.heatmap.ClearHeatmapDataAction) to reset the counters between scenarios. Use this when comparing two interactions back-to-back (e.g. a list scroll vs. a search filter) without restarting the app.
B6. Toggle off before measurement
Toggle the heatmap action again to stop the listener. The heatmap drives logcat I/O and PSI work on every recomposition; MUST toggle it off before running Macrobenchmark or Baseline Profile generation per ../../measurement/generating-baseline-profiles/SKILL.md, or the overhead will skew the timing results.
Tool window structure
The right-anchored tool window with id Compose Stability Analyzer (icon /icons/stability.svg) ships with three tabs registered by StabilityToolWindowFactory:
| Tab | Backing class | Purpose |
|---|
| Explorer | StabilityToolWindow | Hierarchical tree by module → package → file → composable, with stability badges. Double-click navigates to the source. Use this as a survey view across a module before drilling into Cascade or Heatmap. |
| Cascade | CascadePanel | Populated when Analyze Recomposition Cascade runs. Shows the static call-graph tree from the selected root. |
| Heatmap | HeatmapPanel | Populated when the live heatmap is toggled on. Shows the live event log per composable. |
Use the Explorer tab as the entry point; drop into Cascade for a target; enable Heatmap to confirm.
Patterns
Pattern: heatmap with no @TraceRecomposition produces no inlays
// WRONG
1. Toggle Recomposition Heatmap on.
2. ADB connects, logcat reader starts.
3. Scroll the screen aggressively.
4. No inlays appear above any composable.
5. Developer concludes "the heatmap is broken".
// WRONG because: the heatmap is fed by logcat lines emitted by `@TraceRecomposition`.
// Without at least one instrumented composable there is nothing to render.
// Add `@TraceRecomposition` per `../../measurement/tracing-recompositions-at-runtime/SKILL.md`,
// rebuild, and toggle again.
@TraceRecomposition(traceStates = true)
@Composable
fun PriceTicker(price: Price) {
Text(price.formatted)
}
Pattern: do not run the heatmap during Macrobenchmark
// WRONG
1. Open Macrobenchmark scroll journey.
2. Toggle Recomposition Heatmap on (forgot from earlier session).
3. Run the benchmark.
4. FrameTimingMetric numbers regress vs. baseline; team panics.
// WRONG because: the heatmap drives logcat I/O and PSI updates on every recomposition.
// That overhead skews FrameTimingMetric and any release-grade measurement.
// Toggle the heatmap off before running `../../measurement/generating-baseline-profiles/SKILL.md`
// or any Macrobenchmark journey.
// RIGHT
1. Toggle Recomposition Heatmap off (verify no inlays in editor).
2. Run the Macrobenchmark journey.
3. Compare FrameTimingMetric vs. the committed baseline.
Pattern: do not trust the cascade as a runtime guarantee
// WRONG
Cascade from PriceTicker shows 12 downstream composables.
Developer concludes "all 12 will recompose every tick" and refactors all 12.
// WRONG because: the cascade is a static call-graph walk. Whether each downstream node
// actually recomposes depends on stability and skipping at runtime. Several of the 12
// may already be skipping today; refactoring them is wasted work.
// Confirm with the live heatmap before drawing conclusions.
// RIGHT — the loop: cascade for blast radius, heatmap for confirmation, fix, re-measure
1. Analyze Recomposition Cascade from PriceTicker -> 12 nodes flagged.
2. Annotate the top 3 (by suspected hotness) with `@TraceRecomposition`.
3. Toggle Recomposition Heatmap on, run a representative scenario.
4. Inlays show 2 nodes with high counts; the other 10 stayed cold.
5. Fix the 2 hot nodes via `../stabilizing-compose-types/SKILL.md`.
6. Toggle the heatmap off, re-measure with `../../measurement/generating-baseline-profiles/SKILL.md`.
Pattern: prune by stability, not by structure
// WRONG
Cascade shows a wide subtree; developer wraps every leaf in `key { ... }` to force isolation.
// WRONG because: `key` does not make an unstable parameter stable. The leaves still
// receive unstable arguments; the parent still recomposes them. The fix is upstream:
// stabilize the type per `../stabilizing-compose-types/SKILL.md` so the existing
// skipping logic can do its job.
// RIGHT
Identify the unstable parameter via the cascade node's badge.
Stabilize the type at its declaration site.
Re-run the cascade to confirm the badge flipped to stable.
Mandatory rules
- MUST annotate target composables with
@TraceRecomposition before toggling the heatmap. Without it, the logcat stream is empty and no inlays render.
- MUST toggle the heatmap off before running Macrobenchmark or Baseline Profile generation per
../../measurement/generating-baseline-profiles/SKILL.md. Logcat overhead skews timing.
- MUST NOT ship
ComposeStabilityAnalyzer.setEnabled(true) in production. Gate on BuildConfig.DEBUG or a dedicated flag per ../../measurement/tracing-recompositions-at-runtime/SKILL.md.
- MUST NOT read the cascade as a runtime guarantee. It is a static blast-radius estimate; confirm with the heatmap before refactoring.
- MUST NOT chase a green cascade as a goal. Skippability is a diagnostic, not a KPI; some downstream nodes legitimately need to recompose.
- PREFERRED: start every investigation from the Explorer tab to scan the module, drop into Cascade for a target root, then enable the Heatmap to confirm the cascade's predictions against runtime evidence.
- PREFERRED: run Clear Recomposition Data between scenarios when comparing two interactions back-to-back, instead of restarting the app.
- PREFERRED: keep the heatmap session focused on a single screen at a time. Inlays accumulate across scenarios and the signal-to-noise ratio drops fast.
Verification
References
For the passive in-editor features (gutter icons, hover, inline hints, inspection) of the same plugin, see ../using-stability-analyzer-ide-plugin/SKILL.md. For the runtime instrumentation that emits the D/Recomposition lines the heatmap consumes, see ../../measurement/tracing-recompositions-at-runtime/SKILL.md. For CI-side gating against stability regressions, see ../enforcing-stability-in-ci/SKILL.md. For the upstream type fixes that prune cascade branches, see ../stabilizing-compose-types/SKILL.md. For deferring state reads inside hot leaves discovered via the heatmap, see ../../recomposition/deferring-state-reads/SKILL.md. For Macrobenchmark journeys that MUST run with the heatmap off, see ../../measurement/generating-baseline-profiles/SKILL.md.