Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

Full release history for the Compose Skill Suite. The newest release is summarised under **What's new** in the [README](./README.md).

### 4.3.1 — 2026-07-06

**`compose-agent` authoring guidance for cross-phase back-writes + false leads, plus a suite-wide Strong Skipping correctness fix.**

Follow-up to 4.3.0: the audit could catch these after the fact, but the authoring skill stayed silent while writing. Now `compose-agent` warns up front, closing the drift the 4.3.0 cross-review flagged.

- **`compose-agent/performance.md`.** New **Never Back-Write Across Phases** section (layout→composition writes, cross-phase; snapshot-collection mutation in a `@Composable` body, composition-phase self-invalidation — with good/bad Kotlin) and an **Optimizations That Do Nothing** section (the false leads: `remember(index)` on pure fns, `remember`-ing an auto-memoized callback under Strong Skipping, identity caches for derived maps, hoisting without stabilizing captures). Grep triggers gain `onSizeChanged` / `onGloballyPositioned` / `onPlaced` and snapshot-collection mutation.
- **`compose-agent/SKILL.md`.** New Core Instruction, review-checklist line, and Review Process step 4 now names cross-phase back-writes.
- **`jetpack-compose-audit` Strong Skipping fix (correctness).** The 4.3.0 false-lead wording said manual callback `remember` could still matter "when the lambda captures an unstable value." Per the official [Strong Skipping docs](https://developer.android.com/develop/ui/compose/performance/stability/strongskipping), SSM memoizes lambdas **even with unstable captures**, so that carve-out could still bless the exact no-op the guard forbids. Removed suite-wide; the only remaining exception is SSM-off or a `@DontMemoize` / `@NonSkippableComposable` path.
- **Versions.** `compose-agent` → `4.3.1`, `jetpack-compose-audit` → `4.3.1`.

### 4.3.0 — 2026-07-06

**`jetpack-compose-audit` only — cross-phase back-write detection + false-lead guard.**
Expand Down
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
<img alt="Claude Code plugin" src="https://img.shields.io/badge/Claude%20Code-plugin-111827">
</p>

**`jetpack-compose-audit` 4.3.0 · 2026-07-06** — Cross-phase back-write detection (axis 3: layout callbacks writing state read in composition), a related composition-phase self-invalidation check (snapshot collections mutated in a composable body), and a **False Leads** scoring guard so the auditor stops crediting no-op "recomposition fixes." Adapted from [`chrisbanes/skills`](https://github.com/chrisbanes/skills) (Apache-2.0). `compose-agent` stays at `4.2.1`.
**`compose-agent` 4.3.1 · 2026-07-06** — Authoring guidance so the coding-agent skill *warns while you write*: **Never Back-Write Across Phases** (layout callbacks / snapshot-collection mutation feeding composition) and **Optimizations That Do Nothing** (the false leads). The 4.3.0 audit catches these after the fact; 4.3.1 stops you writing them.

**`jetpack-compose-audit` 4.3.1 · 2026-07-06** — Cross-phase back-write detection (axis 3: layout callbacks writing state read in composition), a related composition-phase self-invalidation check (snapshot collections mutated in a composable body), and a **False Leads** scoring guard so the auditor stops crediting no-op "recomposition fixes." Adapted from [`chrisbanes/skills`](https://github.com/chrisbanes/skills) (Apache-2.0).

**Version 4.2.0 · 2026-06-17** — Paging 3 in Compose: new `paging.md` reference (LLM guardrails, not API tour), audit hooks under existing Performance/State categories, planning doc at [`docs/paging-skill-plan.md`](./docs/paging-skill-plan.md). Validated through multi-agent cross-review. Both skills ship as `4.2.0`.

Expand All @@ -29,6 +31,18 @@ Authored and cross-reviewed with every frontier model — Claude Opus 4.8, GPT-5

## What's new

### 4.3.1 — 2026-07-06

**`compose-agent` — authoring guidance for cross-phase back-writes + false leads.**

- **`performance.md`.** New **Never Back-Write Across Phases** section (layout callbacks writing composition-read state; snapshot-collection mutation in a `@Composable` body, with good/bad Kotlin) and **Optimizations That Do Nothing** (the false leads). Grep triggers extended for `onSizeChanged` / `onGloballyPositioned` / `onPlaced` and snapshot-collection mutation.
- **`SKILL.md`.** New Core Instruction, review-checklist line, and Review Process step 4 names cross-phase back-writes.
- **Suite-wide Strong Skipping fix.** Removed a false-lead carve-out ("...unless the lambda captures an unstable value") that contradicted the [official Strong Skipping docs](https://developer.android.com/develop/ui/compose/performance/stability/strongskipping) — SSM memoizes lambdas even with unstable captures, so the only real exception is SSM-off / `@DontMemoize`.
- **Why.** Closes the drift the 4.3.0 cross-review flagged: the audit caught these after the fact, but the authoring skill stayed silent while writing the code. Now it warns up front.
- **Versions.** `compose-agent` → `4.3.1`, `jetpack-compose-audit` → `4.3.1`.

For release detail, see [`docs/release-notes-4.3.1.md`](./docs/release-notes-4.3.1.md).

### 4.3.0 — 2026-07-06

**`jetpack-compose-audit` — cross-phase back-writes + false-lead guard.**
Expand Down Expand Up @@ -365,6 +379,7 @@ compose-agent: find deprecated API in this module.
5. If it collects a `Flow`, is it `collectAsStateWithLifecycle()`?
6. Is the parameter order data → `modifier` → other → content slot last?
7. If it animates, is the API declarative first, remembered where needed, lifecycle-aware, and phase-correct?
8. Does any layout callback (`onSizeChanged` / `onGloballyPositioned` / `onPlaced`) or snapshot-collection mutation write state that is read back in composition? (cross-phase back-write / self-invalidation)

Any "no" without a reason → fixed before the code comes back. No extra prompt needed — loading the skill is enough.

Expand Down
4 changes: 2 additions & 2 deletions docs/release-notes-4.3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ A guard table in `scoring.md` Performance: plausible "recomposition fixes" that
- Identity/instance caches for read-only derived maps (use `remember(keys)`).
- Layout modifiers on both the measured and the sibling row (the sibling still reads size in composition).
- `assertRecompositionCount(Exactly(1))` forced on both rows when one correctly recomposes 0 times.
- Under **Strong Skipping**, manually `remember`-ing a callback the compiler already auto-memoizes — that lever only applies SSM-off, on `@NonSkippableComposable` / `@DontMemoize` paths, or when the lambda captures an unstable value.
- Under **Strong Skipping**, manually `remember`-ing a callback the compiler already auto-memoizes — that lever only applies SSM-off or on `@NonSkippableComposable` / `@DontMemoize` paths. (Historical note: 4.3.0 also listed "or when the lambda captures an unstable value" — **corrected in 4.3.1**; SSM memoizes lambdas even with unstable captures.)

The guard verifies against runtime recomposition counts / compiler reports rather than the presence of a pattern.

## Evals

`evals/evals.json` cases 12–14 lock in the new behavior: cross-phase layout→composition back-write (12), false leads that must not be credited or suggested under SSM (13), and composition-phase snapshot self-invalidation (14). Audit-only `compose-agent` has no cross-phase / false-lead guidance yet (tracked as follow-up).
`evals/evals.json` cases 12–14 lock in the new behavior: cross-phase layout→composition back-write (12), false leads that must not be credited or suggested under SSM (13), and composition-phase snapshot self-invalidation (14). Audit-only at 4.3.0; `compose-agent` gained matching authoring guidance in 4.3.1.

## Attribution

Expand Down
37 changes: 37 additions & 0 deletions docs/release-notes-4.3.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Release notes — 4.3.1 (2026-07-06)

**`compose-agent` authoring guidance + a suite-wide Strong Skipping correctness fix** (`jetpack-compose-audit` patched too).

Follow-up to 4.3.0. That release taught the **audit** to catch cross-phase back-writes and no-op "recomposition fixes"; this release teaches the **authoring** skill to avoid writing them in the first place. It closes the drift the 4.3.0 cross-review flagged: a `compose-agent` review could still recommend a pattern the audit rejects.

## `performance.md`

- **Never Back-Write Across Phases** — the reverse of "defer reads". Two shapes, each with good/bad Kotlin:
- Layout → composition: `onSizeChanged` / `onGloballyPositioned` / `onPlaced` writing state a sibling reads in composition. Fix: consume the measured value in layout/draw (`Layout` / `Modifier.layout { }`), or use `BoxWithConstraints` / `SubcomposeLayout` for genuine *parent* constraints — never round-trip a sibling's measured size through composition-read state.
- Snapshot-collection mutation in a `@Composable` body: `mutableStateListOf` / `mutableStateMapOf` (or `toMutableState*`) mutated and read in the same composition. Fix: derive from inputs with `remember(keys)`, or mutate from an event / `LaunchedEffect` / state holder.
- **Optimizations That Do Nothing** — the false leads, so the agent stops shipping them: `remember(index)` on a pure function, `remember`-ing an already auto-memoized callback under Strong Skipping, identity caches for read-only derived maps, hoisting without stabilizing captures. Prove a win with recomposition counts / compiler reports, not the presence of a pattern.
- **Grep triggers** extended: `onSizeChanged` / `onGloballyPositioned` / `onPlaced`, and `mutableStateListOf` / `mutableStateMapOf` / `toMutableStateList` / `toMutableStateMap`.

## `SKILL.md`

- New Core Instruction: no cross-phase back-writes, no no-op optimizations.
- Review Process step 4 now names cross-phase back-writes.
- New review-checklist line: does any layout callback or snapshot-collection mutation write state read back in composition?

## `jetpack-compose-audit` — Strong Skipping correctness fix

The 4.3.0 false-lead guard said manual callback `remember` could still matter "when the lambda captures an unstable value." That contradicts the official [Strong Skipping docs](https://developer.android.com/develop/ui/compose/performance/stability/strongskipping): SSM memoizes lambdas **even with unstable captures**, so the carve-out could still bless the exact no-op the guard forbids. Removed from `scoring.md` in all four places; the only remaining exception is SSM-off or a `@DontMemoize` / `@NonSkippableComposable` path.

The same correction lands in `compose-agent/performance.md`: the *Strong Skipping baseline*, *Lambdas In Composables*, and *Optimizations That Do Nothing* sections now agree. The old "lambdas passed across module boundaries to generic composables can miss memoization" bullet was dropped — under SSM, lambda memoization happens at the caller's compilation regardless of the callee's module/generics, so that case is obsolete; the real exceptions are SSM-off / `@DontMemoize` / `@NonSkippableComposable`.

## Eval coverage

The authoring rules share the audit acceptance evals (`jetpack-compose-audit/evals/evals.json` cases 12–14) — `compose-agent` mirrors the same rubric. A dedicated compose-agent *write-mode* eval harness is a tracked follow-up.

## Attribution

Continues the 4.3.0 adaptation from [`chrisbanes/skills`](https://github.com/chrisbanes/skills) `compose-recomposition-performance` (Apache-2.0), cited against `developer.android.com`.

## Versions

`compose-agent` → **4.3.1** and `jetpack-compose-audit` → **4.3.1** (SKILL + both `plugin.json` each).
2 changes: 1 addition & 1 deletion skills/compose-agent/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "compose-agent",
"version": "4.2.1",
"version": "4.3.1",
"description": "Agent skill that helps AI coding assistants write modern Jetpack Compose: correct state, effects, performance-aware modifiers, Navigation 3, Paging 3 in Compose, coroutines on lifecycle, animations, UI tests, focus/keyboard navigation, Compose Multiplatform boundaries, Android launch resources, and idiomatic Kotlin. Targets the mistakes LLMs actually make in Compose code.",
"author": {
"name": "Ivan Morgillo"
Expand Down
2 changes: 1 addition & 1 deletion skills/compose-agent/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "compose-agent",
"version": "4.2.1",
"version": "4.3.1",
"description": "Agent skill that helps AI coding assistants write modern Jetpack Compose: correct state, effects, performance-aware modifiers, Navigation 3, Paging 3 in Compose, coroutines on lifecycle, animations, UI tests, focus/keyboard navigation, Compose Multiplatform boundaries, Android launch resources, and idiomatic Kotlin. Targets the mistakes LLMs actually make in Compose code.",
"author": {
"name": "Ivan Morgillo"
Expand Down
7 changes: 5 additions & 2 deletions skills/compose-agent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ allowed-tools: Read, Glob, Grep, Edit, Write, Bash
argument-hint: "[focus area, e.g. 'state', 'effects', 'navigation', 'paging', 'lifecycle', 'animation', 'testing', 'focus', 'kmp']"
metadata:
author: Ivan Morgillo
version: "4.2.1"
version: "4.3.1"
---

# Compose Agent
Expand All @@ -27,7 +27,7 @@ If the repo pins older versions, match the repo — but call out what the modern
1. Check for **deprecated or soft-deprecated API** using `references/api.md`.
2. Validate **state and data flow** using `references/state.md`.
3. Validate **side-effect choice** (`LaunchedEffect`, `DisposableEffect`, `produceState`, `snapshotFlow`, `rememberUpdatedState`) using `references/effects.md`.
4. Review **composable performance** — stability, lambda modifiers, lazy list keys, deferred reads — using `references/performance.md`.
4. Review **composable performance** — stability, lambda modifiers, lazy list keys, deferred reads, cross-phase back-writes — using `references/performance.md`.
5. Review **modifier usage** — ordering, lambda-form, `Modifier.Node` over `composed { }` — using `references/modifiers.md`.
6. Review **navigation** using `references/navigation.md` — start from its decision table (Nav3 vs Nav2 type-safe vs plain state) before touching call sites.
7. Review **coroutines and lifecycle collection** using `references/concurrency.md`.
Expand All @@ -51,6 +51,8 @@ If doing a partial review, load only the relevant reference files — each refer
- Collect Flows with `collectAsStateWithLifecycle()` in UI code. Plain `collectAsState()` keeps collecting when the screen is not visible and burns battery and bandwidth. **Exception:** `Flow<PagingData<T>>` is collected with `collectAsLazyPagingItems()`, never `collectAsStateWithLifecycle()` — see `references/paging.md`.
- Prefer `rememberSaveable` over `remember` for UI state that should survive configuration change or process death, unless the value is unserializable or derivable.
- Use the typed state factories (`mutableIntStateOf`, `mutableLongStateOf`, `mutableFloatStateOf`, `mutableDoubleStateOf`) for primitive state. Raw `mutableStateOf<Int>(...)` boxes.
- Never write state a phase has already read. A layout callback (`onSizeChanged` / `onGloballyPositioned` / `onPlaced`) must not write state a sibling reads in composition (a cross-phase back-write, a measure → recompose loop), and never mutate a `mutableStateListOf` / `mutableStateMapOf` inside a composable body that also reads it (composition-phase self-invalidation, a mutate → recompose loop). Consume measured values in layout/draw, or mutate snapshot state from an event / `LaunchedEffect` / state holder. See `references/performance.md`.
- Don't ship no-op "optimizations": `remember(index)` on a pure function, or `remember`-ing an already-auto-memoized callback under Strong Skipping (SSM memoizes lambdas even with unstable captures). Prove a recomposition win with Layout Inspector counts or compiler reports, not the presence of a pattern. See `references/performance.md`.
- Never put a lambda in a `CompositionLocal`. Use explicit parameters.
- Do not introduce third-party libraries without asking first. The Accompanist libraries covering pager, swipe-refresh, flow layout, and system UI controller are **deprecated** — the functionality is in AndroidX now (`HorizontalPager`, `PullToRefreshBox`, `FlowRow`/`FlowColumn`, `enableEdgeToEdge()`).
- When an element's background reads from `MaterialTheme.colorScheme.*` (directly or via a blend), its text and icon colors must read from the same theming source. Hard-coded `Color.Black` / `Color.White` / raw ARGB literals over theme-driven backgrounds are dark-mode regressions. See `references/component-api.md`.
Expand Down Expand Up @@ -141,6 +143,7 @@ When the agent is **writing new code** rather than reviewing, the same rules app
- If it collects a Flow, is it `collectAsStateWithLifecycle()`? (A `Flow<PagingData<T>>` is the exception — it uses `collectAsLazyPagingItems()`.)
- Is the parameter order: data → modifier → other → content slot last?
- If it animates, is the API declarative first, remembered where needed, lifecycle-aware, and phase-correct?
- Does any layout callback or snapshot-collection mutation write state read back in composition (a cross-phase / self-invalidation loop)?
- If it pages, are keys stable, `LoadState` handled, and refresh/retry user-driven?
- If it needs focus, testing, or platform-specific behavior, did you load the focused reference before judging?

Expand Down
Loading
Loading