$npx -y skills add skydoves/compose-performance-skills --skill stabilizing-compose-typesUse this skill to fix unstable Jetpack Compose types once a stability diagnosis has identified them. Covers the three-tier strategy — make the type truly stable with val plus immutable fields, mark with @Immutable or @Stable when the source is owned, and use stabilityConfiguratio
| 1 | # Stabilizing Compose Types — Three Tiers, In Order |
| 2 | |
| 3 | Once a diagnosis (see `../diagnosing-compose-stability/SKILL.md`) has named the unstable types, this skill walks Claude through fixing them. The strategy is a strict three-tier waterfall: (1) make the type **truly stable** by structural rewrite (`val` + immutable fields) — no annotation needed; (2) annotate with `@Immutable` or `@Stable` when the source is owned and the contract is honored; (3) use `stabilityConfigurationFile` for third-party or Java types. **DO NOT** invert this order — annotations are a contract, not a magic spell. |
| 4 | |
| 5 | ## When to use this skill |
| 6 | |
| 7 | - The compiler report (composables.txt / classes.txt) shows one or more `unstable` parameters. |
| 8 | - The developer asks how to stabilize a domain class, a `List<Foo>` parameter, `java.time.LocalDateTime`, or a third-party type. |
| 9 | - The developer mentions `@Immutable`, `@Stable`, `compose-stable-marker`, `compose-runtime-annotation`, `kotlinx.collections.immutable`, `stabilityConfigurationFiles`, or `stability_config.conf`. |
| 10 | - A `@TraceRecomposition` log shows recomposition happening because an argument is allocated fresh every recomposition. |
| 11 | |
| 12 | ## When NOT to use this skill |
| 13 | |
| 14 | - The unstable types are not yet identified — run `../diagnosing-compose-stability/SKILL.md` first. |
| 15 | - The symptom is a wrong-phase state read (`Modifier.alpha(state.value)`); use `../../recomposition/deferring-state-reads/SKILL.md`. |
| 16 | - The symptom is a `derivedStateOf` problem; use `../../recomposition/choosing-derivedstateof/SKILL.md`. |
| 17 | - A `Flow<T>` parameter is the cause; the fix is to collect upstream — see `../../side-effects/collecting-flows-safely/SKILL.md` rather than annotating `Flow` as stable. |
| 18 | |
| 19 | ## Prerequisites |
| 20 | |
| 21 | - `../diagnosing-compose-stability/SKILL.md` has been run; the developer has a concrete list of unstable types. |
| 22 | - Kotlin **2.0.0+** with `org.jetbrains.kotlin.plugin.compose` applied. Strong Skipping is on by default. |
| 23 | - For pure-Kotlin / data modules without `compose-runtime`: either `androidx.compose.runtime:runtime-annotation` (official) or `com.github.skydoves:compose-stable-marker` (legacy) on the classpath. Both expose `@Stable` / `@Immutable` / `@StableMarker` without dragging in the full runtime. |
| 24 | - For tier-3 fixes: Compose Compiler **1.5.5+** for `stabilityConfigurationFiles` DSL support (plural; the older singular `stabilityConfigurationFile` property is deprecated, see footnote below). |
| 25 | |
| 26 | ## Workflow — decision tree |
| 27 | |
| 28 | - [ ] **1. Do we own the source of the unstable type?** If yes, use tier 1 or tier 2. If no (Java stdlib, third-party SDK), jump to tier 3. |
| 29 | |
| 30 | - [ ] **2. Tier 1 — restructure to truly stable.** Can every property be a `val` of an already-stable type? If yes, **MUST** make that change first. No annotation is needed and no contract is implied. The compiler will infer stability automatically. |
| 31 | |
| 32 | ```kotlin |
| 33 | // WRONG |
| 34 | data class Snack( |
| 35 | var name: String, |
| 36 | val tags: Set<String>, |
| 37 | ) |
| 38 | // WRONG because: `var name` is an observable that does not notify Compose, and `Set<String>` is the standard library interface so its stability is Unknown — the data class is unstable on two axes. |
| 39 | ``` |
| 40 | |
| 41 | ```kotlin |
| 42 | // RIGHT |
| 43 | @Immutable |
| 44 | data class Snack( |
| 45 | val name: String, |
| 46 | val tags: ImmutableSet<String>, |
| 47 | ) |
| 48 | ``` |
| 49 | |
| 50 | - [ ] **3. Is the unstable property a collection?** Replace `kotlin.collections.List`, `Set`, `Map` with `kotlinx.collections.immutable.ImmutableList`, `ImmutableSet`, `ImmutableMap`. Build with `persistentListOf()`, `persistentSetOf()`, `persistentMapOf()` factories or `.toImmutableList()` adapters. The kotlinx-collections-immutable artifact ships a known-stable bitmask `0b1` recognized by the Compose Compiler. **PREFERRED:** prefer this over whitelisting `kotlin.collections.*` in `stability_config.conf` because the type system enforces immutability. |
| 51 | |
| 52 | - [ ] **4. Is the source a pure-Kotlin / data module that does not depend on `compose-r |