Documentation — install, VS Code Marketplace, and one-page-per-product reference for each data extension.
Render @Preview composables to PNG outside Android Studio, so AI coding
agents can see what they're changing. Works with Jetpack Compose (Android,
via Robolectric) and Compose Multiplatform Desktop (via ImageComposeScene).
Renders include paused-clock animation captures (GIF or single frame) and opt-in ATF accessibility checks with annotated overlays.
Also renders Android XML resources —
vector drawables, adaptive launcher icons, animated-vector drawables — and indexes the icon
attributes in AndroidManifest.xml so tooling can link manifest lines to the same rendered PNG.
Modules without any matching resources self-no-op, so this comes along for free with the plugin.
These tools give AI coding agents a tight, token-frugal feedback loop over
Compose UI — the way Playwright gave web agents one
over the DOM: act by a stable reference, observe structure rather than pixels,
and turn exploration into durable tests. The capabilities below are exposed by
the preview daemon's MCP server (and, where noted, the compose-preview CLI);
see docs/daemon/MCP.md for the full tool surface.
- Target by semantic ref, not pixels.
interactive/inputandrecord_previewaccept atarget(testTag/role+text/ a stable noderef) that the daemon resolves to the node's centre, so a click survives layout changes instead of breaking on a coordinate. Works on Android (Robolectric) and Desktop (Skiko). - Token-frugal observation.
render_preview observe=semantics|hashreturns thecompose/semanticstree + a hash + dimensions instead of a base64 PNG — typically a few hundred tokens versus ~1.5k. Fetch pixels only when you actually need to look. - Semantics diff.
diff_semantics(MCP) andcompose-preview diff-semantics(CLI) diff two semantics trees and report what changed semantically (text, label, role, testTag, overflow…), matched by stable ref — a deterministic, pixel-free regression signal, the Compose analogue of Playwright's aria-snapshot diff. - Matrix render.
render_matrixrenders one preview across a cross-product ofdevice × locale × uiMode × fontScalein a single call, returning a per-cell hash and which cells changed — "does this survive small screen + RTL- large font?" without N screenshots.
- Recording → test.
record_preview emitTest=trueturns a scripted interaction into a runnable Compose UI test (semantic targets becomeonNodeWithTag(...).performClick()steps). - Structured failures. A failed render reports a typed
kindplus a one-line fix hint for recognized signatures (classpath skew, Robolectric SDK mismatch, missing@Composable, …) instead of an opaque message.
Cost budget for these in docs/TOKEN_USAGE.md.
-
Agent skills — the
compose-previewandcompose-preview-reviewskill bundles live inyschimke/skills. Point any agent that can fetch a URL at them; each skill is a complete install-and-iterate playbook. Bootstrap a host machine (CLI + skills in one shot) with the installer inyschimke/skills:curl -fsSL https://raw.githubusercontent.com/yschimke/skills/main/scripts/install.sh | bash -
VS Code extension — published to the VS Code Marketplace and Open VSX (for VSCodium / Cursor / Windsurf). Install from inside the IDE: open the Extensions view (⇧⌘X / Ctrl+Shift+X), search Compose Preview, click Install. Source in
vscode-extension/. -
GitHub Actions — composite actions for CI:
install(CLI on$PATH),apply(unified pipeline — baselines on push, before/after PR comments, a11y + notification surfaces).
The plugin is published to Maven Central — no auth, no PAT.
// <module>/build.gradle.kts
plugins {
id("ee.schimke.composeai.preview") version "0.15.0"
}Working examples: samples/android/build.gradle.kts,
samples/wear/build.gradle.kts,
samples/cmp/build.gradle.kts.
You can apply the plugin dynamically without modifying the project's source code, useful for AI agents on the CLI, in CI, or when exploring the tool without committing changes. The compose-preview CLI ships a bundled Gradle init script and passes it via --init-script on every invocation, so projects that already apply com.android.application / com.android.library / org.jetbrains.compose pick up the preview plugin without an edit to build.gradle.kts:
compose-preview list # scan @Preview annotations
compose-preview render # render every @Preview to PNGFor direct ./gradlew use (e.g., a CI step that needs extra Gradle flags), materialise the same init script once and thread its path through each invocation:
INIT_SCRIPT="$(compose-preview init-script --path)"
./gradlew --init-script "$INIT_SCRIPT" :app:composePreviewDiscover
./gradlew --init-script "$INIT_SCRIPT" :app:composePreviewRenderAllVS Code users: the
Compose Previewextension already auto-injects via--init-scripton every Gradle invocation it makes — no extra setup needed.
The CLI's auto-inject script detects projects that already declare the plugin (either literally as id("ee.schimke.composeai.preview") version "..." or via a gradle/libs.versions.toml alias resolved through alias(libs.plugins.<x>)) and skips the classpath injection for those builds, so mixed setups work without conflicts.
Requires Java 17+, Gradle 8.13+, AGP 8.13.0+ (Android), Kotlin 2.0.21+,
Compose Multiplatform 1.10.3+ (Desktop). The bottom edge of the supported
consumer envelope is exercised on every push by the
agp8-min job against the fixture
under .github/ci/fixtures/agp8-min/;
the project's own build runs on a newer toolchain (see
docs/AGENTS.md).
Source under samples/. Rendered baselines (PNGs and animation
GIFs, regenerated on every push to main) are browsable inline on the
compose-preview/main
branch:
samples:android— phone, font-family showcase, scrolling captures, animation timelines.samples:wear— Wear OS Material 3 Expressive,EdgeButton, tile previews.samples:cmp— Compose Multiplatform Desktop.samples:remotecompose— Remote Compose againstwear-compose-remote-material3.samples:xr-spatial— Jetpack Compose for XR (androidx.xr.compose), 2D Home-Space fallback ofOrbiter/SpatialElevation/SpatialPanelcontent.
ATF a11y findings for the same samples are on the
compose-preview/a11y/main
branch.
The integration matrix renders the
plugin against real-world external Compose projects on every push to main.
Each render-enabled project publishes its own browsable
compose-preview/integration/<slug> branch — same gallery layout as
compose-preview/main, with a CI notes section recording any
workarounds the harness applied (consumer patches, stubbed credentials,
non-blocking status):
wear-os-samples(ComposeStarter) — full Android render + daemon round-trip, isolated-projects + configuration-cache.wear-os-samples(WearTilesKotlin) — Wear Tiles render path (kind: TILE).adaptive-apps-samples(AdaptiveJetStream) — XR spatial Compose, rendered on theandroidx.xr.composealpha14 baseline.
Real-world PRs opened by AI coding agents that used compose-preview to
verify their changes.
yschimke/meshcore-mobile#36— renders Play Store listing screenshots (phone + 7"/10" tablet) directly fromPlay Store — …@Previewcomposables, replacing hand-crafted PNGs.
Have one to add? Open a PR or an issue.
- Documentation site — installation, VS Code Marketplace, and the per-product data-extension reference.
- How it works — discovery, renderer, caching, project structure, plugin configuration.
- CI install action — pin the CLI on
$PATHin any GitHub Actions job, with version-catalog + Renovate recipes. - Cloud sandbox setup — Claude Code on the web, network allowlist.
- CI workflows —
compose-preview/mainbaselines, PR diff comments. - Development — building plugin, CLI, and extension from source; consuming
-SNAPSHOTbuilds. - Architecture (contributor) — class-by-class map of the four-stage pipeline.
- Releases · Changelog · License (Apache 2.0)
Use .github/workflows/codex-pr-review-reusable.yml to run AI PR review only after preview generation succeeds and with both code + visual context. The reusable workflow supports Codex, Claude, or Gemini based on which API key is configured (exactly one).
This repository wires the reusable workflow in .github/workflows/codex-pr-review.yml using a preview job plus a thin uses: call to the reusable workflow.
In this repository the unified .github/workflows/compose-preview.yml runs on every push to main, every PR, and workflow_dispatch. Consumer repos can split that into separate workflows (or keep one workflow per surface) based on their needs.
name: PR Review (Codex + Preview)
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
preview:
runs-on: ubuntu-latest
outputs:
preview_status: ${{ job.status }}
steps:
- uses: actions/checkout@v4
- run: ./gradlew :app:composePreviewRenderAll
- uses: actions/upload-artifact@v4
with:
name: compose-preview-images
path: app/build/compose-previews/renders
- uses: actions/upload-artifact@v4
with:
name: compose-preview-diff-images
path: app/build/compose-previews/diffs
- uses: actions/upload-artifact@v4
with:
name: compose-preview-metadata
path: app/build/compose-previews/**/*.json
codex-review:
needs: [preview]
uses: yschimke/compose-ai-tools/.github/workflows/codex-pr-review-reusable.yml@main
with:
preview_status: ${{ needs.preview.result }}
strict_mode: true
secrets:
codex_api_key: ${{ secrets.CODEX_API_KEY }}
claude_api_key: ${{ secrets.CLAUDE_API_KEY }}
gemini_api_key: ${{ secrets.GEMINI_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}- Agent selection is key-driven: set exactly one of
codex_api_key,claude_api_key, orgemini_api_key. - Codex has a built-in default command. Claude/Gemini require
claude_review_command/gemini_review_commandinputs unless you wrap them in your own caller. compose-preview-images: rendered head/PR preview images.compose-preview-diff-images: visual diffs (baseline vs PR/head), if your preview pipeline generates them.compose-preview-baseline: optional baseline images used to generate diffs.compose-preview-metadata: preview index and mapping files (for example: preview id → file path/module).
- Java 21 (
actions/setup-java,JAVA_HOMEfrom the action). - Android SDK (
android-actions/setup-android). compose-preview-reviewskill installation fromyschimke/skills.- Code diff capture (
git diff) plus artifact download for visual review.
- Preview failed/cancelled/skipped: workflow posts a blocked comment and does not run Codex visual review.
- Artifacts missing: workflow posts a blocked comment with “missing context” details.
- Strict mode enabled + blocking findings: reusable workflow fails its check.
- PR branch update (
update_pr_branch, defaulttrue): workflow attempts to commit.codex/review-output/{codex-review.md,codex-review.json}to the PR branch; for fork PRs or restricted tokens it skips with a warning. - No preview diffs available: Codex still reviews code + available preview images and explicitly marks missing visual-diff context.
needs:pattern (shown above): same workflow, same run.workflow_runpattern: trigger a second workflow after preview workflow completion and passpreview_status: successplus artifact names into the reusable workflow call.
## Codex PR Review
### Code findings
- [blocking] `ui/ProfileCard.kt:84` Null-state branch removed; can crash in empty profile payload.
- [warning] `ui/Theme.kt:42` Hard-coded color bypasses design token.
### Preview findings
- [blocking] `ProfileCard_Default.png` text overlaps avatar at 320dp width.
- [warning] `SettingsScreen_Dark.png` contrast drop on secondary action.
### Missing context / blocked checks
- Baseline metadata for `WearSummaryPreview` missing.
- Visual diff for `TabletLandscape` not present in uploaded artifacts.- Intentionally regress a composable (for example, shrink parent width and increase fixed text size to force clipping).
- Run your preview job to regenerate preview images and visual diffs.
- Open/update a PR and confirm:
- Preview job succeeds.
- Reusable Codex workflow runs after preview (
needs/workflow_rungate). - PR comment includes both code and preview findings.
- In strict mode, blocking visual regression findings fail the check.