Releases: AkshayChordiya/Pose
Release list
0.6.3 — Keep the theme through incremental builds
Fixed
-
Editing a composable no longer strips the theme from every preview in the module.
getSymbolsWithAnnotationonly returns symbols from KSP's dirty file set. Nothing in
your source connects a composable to the module's@PoseSetupobject - that link only
exists in the code Pose emits, asAppPose.Theme { … }- so on any incremental round
that didn't happen to touch the config file, Pose couldn't see it. It took the
no-config path: warnedPG024and regenerated every preview unthemed, and they
stayed that way until the next clean build. Same symptom as 0.6.1's missing config
object, except nothing was missing.Generated files now name the setup object's file as a KSP
Dependenciessource, which
keeps it in the dirty set. Previews stay isolating (aggregating = false), so
incremental builds cost the same as before.Only the config object needs this. Parameter types, sealed subtypes and
PreviewParameterProviderclasses are all named somewhere in your own source, so
Kotlin already dirties the composable when they change. -
Tripleparameters no longer refuse withPG001.Triplewas listed in the
planner's collection-like set - so it never got aPreviewParameterProviderslot - but
the resolver had no branch for it, only forPair. It fell through to structural
synthesis, which walksTriple's own constructor and finds the type variables
A/B/Crather than the arguments supplied at the use site, then refused on
.first.Triple<Int, Int, Int>now resolves toTriple(0, 0, 0), each slot going
through the ordinary ladder so nested types recurse as they do everywhere else.Reported in #1.
Added
@PoseSetup(showBackground = …)- controls whether Pose's default light/dark pair stampsshowBackground = true. Still on by default, since a composable that paints nothing of its own renders transparent and reads as broken against Studio's dark chrome. Turn it off when what you render already paints its own background. Ignored whenpreviewsis set, your own annotations decide everything then. Emitted as an omission rather thanshowBackground = false, which is already@Preview's default.
0.6.2 — Don't build preview data in production
Changed
-
PG001andPG010now suggestget() = sequenceOf(...)rather than a plainval.sequenceOf(a, b, c)evaluates its arguments eagerly — aSequenceis lazy about iteration, not construction — so the previously-suggested form built every sample when the companion object initialised. That happens the first time anything touches the type, in production, for data only the preview panel reads. The getter defers it entirely, and release builds never call it since generated previews live in the debug variant.Same correction applied to the README and refusal catalog, both of which now explain why rather than just showing the corrected code.
Thanks to feedback on the 0.6.x docs for catching this.
Docs
- README leads with a demo GIF.
- Documents putting a
PreviewParameterProviderinsrc/debug/kotlinas the stronger option when you'd rather keep sample data out of production source altogether.
0.6.1 — Tell people when the theme is missing
Upgrading to 0.6.0 removed pose.themeFqName. If you didn't also add a @PoseSetup object, Pose carried on generating previews with no theme and said nothing - they render in Compose's baseline palette, which looks like a Pose bug rather than missing configuration. Snapshot suites see it as a wall of unexplained golden diffs.
No behavior changes; this release is entirely about making that state visible.
Added
-
PG024- warns when a module generates previews but has no@PoseSetupobject, naming the exact config to add:[PG024] generated 14 preview(s) in this module but found no @PoseSetup object, so they render with Compose's default theme rather than yours. Add: @PoseSetup internal object AppPose : PoseConfig { @Composable override fun Theme(content: @Composable () -> Unit) = AppTheme(content) }Always a warning, never an error - generation succeeded, it just may not look right. Unthemed previews are legitimate for a design-system module, so declaring
@PoseSetup internal object AppPose : PoseConfigwith noThemeoverride is the explicit opt-out and silences it. Not governed bypose.strict, because bulk mode forcesstrict = falseand that's exactly where losing a whole module's theme hurts most.
Changed
-
PG023now names the migration for each option removed in 0.6.0 instead of just reporting it as unrecognised:[PG023] `pose.themeFqName` was removed in 0.6.0 and is being ignored — previews will render UNTHEMED until you add a @PoseSetup object overriding `Theme`.
0.6.0 - Type-safe configuration
Configuration moves out of Gradle strings and into Kotlin for type-safety. Breaking - the pose.* options it replaces are removed rather than deprecated; see Removed below for the one-block migration.
Added
-
@PoseSetup+PoseConfig- one config object per module:@PoseSetup(generateForAllPublicComposables = true) internal object FeaturePose : PoseConfig { @Composable override fun Theme(content: @Composable () -> Unit) { AppTheme { content() } } }
Pose never learns your theme's name - it emits
FeaturePose.Theme { … }. The reference toAppThemeis ordinary Kotlin, so the compiler checks it and the IDE refactors it. Renaming a theme can no longer break previews. -
Scalars (
generateForAllPublicComposables,previews,provideInspectionMode,maxPreviewsPerComposable,maxDepth,collectionSize) live on the@PoseSetupannotation rather than as interface properties, because KSP can read annotation arguments but not property initializers. -
Shared config across modules via ordinary inheritance - put overrides on an
abstract classin your design-system module, extend it per module. -
PG023flags misspelledpose.*keys with a "did you mean" suggestion. Previously a typo silently took the default. -
PG019–PG022cover config-object misuse: duplicate setup objects,@PoseSetupon a non-object, an unreachable (private) object, and one that doesn't implementPoseConfig.
Changed
-
Bulk mode now skips wrapper-shaped composables - any composable whose required parameters are all
@Composablecontent lambdas. Themes, surfaces and providers render nothing but synthesized empty content, so they were never useful previews. This replaces the 0.4.2 "skip the composable matchingpose.themeFqName" rule, which can't work once the theme is invoked inside an override body KSP can't see. -
@PoseSamplereplacesPoseProvider(…, forParam = "…"). Binding a provider to one specific parameter now happens on that parameter:@Pose @Composable fun ArticleComparison( @PoseSample(ArticleSamples::class) left: Article, @PoseSample(FeaturedArticleSamples::class) right: Article, ) { … }
forParamnamed a parameter by string from a distance — exactly the failure mode this release removes everywhere else. Renaming the parameter silently rebound it to the wrong provider (via generic-type fallback) or dropped it to structural synthesis. The annotation travels with the declaration, so there's nothing to desync.
Removed
PoseProvideris gone. WithoutforParamit was a single-field wrapper around aKClass, soproviderstakes classes directly:@Pose(providers = [ArticleSamples::class])instead of@Pose(providers = [PoseProvider(ArticleSamples::class)]).
Breaking. Every pose.* KSP option that shapes previews is gone - @PoseSetup replaces all of them, and keeping two ways to configure the same thing wasn't worth it at 0.x. Passing a removed key now warns via PG023 rather than being silently ignored.
| Removed | Replacement |
|---|---|
pose.themeFqName |
override fun Theme(content) on your PoseConfig |
pose.previewWrapperFqName |
override fun Wrapper(content) |
pose.generatePreviewsForAllPublicComposables |
@PoseSetup(generateForAllPublicComposables = true) |
pose.provideInspectionMode |
@PoseSetup(provideInspectionMode = …) |
pose.maxPreviewsPerComposable |
@PoseSetup(maxPreviewsPerComposable = …) |
pose.maxDepth |
@PoseSetup(maxDepth = …) |
pose.collectionSize |
@PoseSetup(collectionSize = …) |
pose.strict, pose.verboseSkips survive - they're build-behavior knobs with no annotation equivalent, and you may want them to differ between a local build and CI.
PG011 and PG018 retire with the string options they validated; PoseConfig.Theme / .Wrapper are compiler-checked, so there's nothing left to validate at build time.
Migration replace the ksp { arg(...) } block with a config object:
// Before
ksp {
arg("pose.themeFqName", "com.example.ui.AppTheme")
arg("pose.generatePreviewsForAllPublicComposables", "true")
}
// After
@PoseSetup(generateForAllPublicComposables = true)
internal object AppPose : PoseConfig {
@Composable
override fun Theme(content: @Composable () -> Unit) = AppTheme(content)
}- The
annotationsartifact now applies the Compose compiler plugin and takescompose-runtimeascompileOnly, soPoseConfigcan declare@Composablemembers. The published POM is unchanged —compileOnlydoesn't publish, so the artifact still has no Compose dependency and the marker annotations stay usable without Compose on the classpath.
Fixed
- Bulk mode enabled via
@PoseSetupnow correctly coercesstricttofalse.Diagnosticswas built from pre-merge options, so the coercion was skipped when bulk arrived from the config object rather than a Gradle arg.
Unchanged
- The IntelliJ plugin. It locates generated files by path and function name, neither of which changed. No plugin release needed.
v0.5.0
LocalInspectionMode + custom CompositionLocals
Added
- Generated previews now provide
LocalInspectionMode = true. Every generated preview is wrapped inCompositionLocalProvider(LocalInspectionMode provides true). Studio's preview renderer already sets this, but snapshot runners (Paparazzi, Roborazzi,com.android.compose.screenshot) leave itfalse— so a composable branching onLocalInspectionMode.currenttook its production path there, attempting real network calls with dummy URLs and producing blank snapshots. Opt out withpose.provideInspectionMode = false. pose.previewWrapperFqName- point at your own composable to supply arbitraryCompositionLocals (fake image loaders, no-op analytics, locale providers). Same trailing-lambda contract aspose.themeFqName. Nesting, outermost first: inspection-mode provider → your wrapper → theme → target. The wrapper sits inside Pose's provider so it keeps the final say over any local Pose also sets.PG018diagnostic + refusal-catalog entry for an unresolvablepreviewWrapperFqName.
Changed
- Generated output changes for every user - the extra
CompositionLocalProviderlayer appears in all generated previews. Behaviourally a no-op in Studio (which already sets the local); the difference shows up in snapshot tests, which is the point. No action needed unless you were relying on snapshot tests exercising the non-inspection path — in that case setpose.provideInspectionMode = false. - Emission is skipped silently when
androidx.compose.ui.platform.LocalInspectionModeisn't on the compile classpath (a module withcompose-runtimebut nocompose-ui), so no build can break on the new reference.
Tests
- New
InspectionModeTest— 6 cases covering default-on, opt-out, nesting order vs the theme, wrapper emission, full four-layer nesting order, and the PG018 refusal. - 4 new
OptionsTestcases for the two new options. 60 processor tests total.
v0.4.0
Now published on Maven:
implementation("io.github.akshaychordiya.pose:annotations:0.4.0") and you're off 🚀
What Pose does
Auto-generates Jetpack Compose @Preview functions from your @Composable code at build time — deterministic, offline, no LLM, never touches your source. Annotate, build, generated previews land in build/generated/.
Install
plugins { id("com.google.devtools.ksp") }
dependencies {
implementation("io.github.akshaychordiya.pose:annotations:0.4.0")
kspDebug ("io.github.akshaychordiya.pose:processor:0.4.0")
implementation ("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
}
ksp {
arg("pose.themeFqName", "com.example.ui.AppTheme")
}Requires Kotlin 2.0+, KSP 2.x, AGP 8.2+, JDK 17+.
Highlights in this release
Zero-config previews — @Pose on any public composable emits a light+dark pair with showBackground = true. Structural synthesis walks data classes, sealed hierarchies, primitives, and 50+ well-known types (Color, Dp, Flow<T>, Result<T>, java.time.*, Uri, ...).
Sample-data ladder — three levels of investment:
- Nothing — structural synth handles it.
companion.previewSamples: Sequence<T>— wired through@PreviewParameterfor every composable that takesT.@Pose(providers = [PoseProvider(FooSamples::class)])— composable-scopedPreviewParameterProviderbindings, withforParamfor disambiguating same-typed parameters.
- Sealed fan-out — one preview per subtype, named
Foo__Preview_Loading,_Success,_Errorso the preview panel reads cleanly. - Bulk opt-in — flip
pose.generatePreviewsForAllPublicComposables = trueand every public@Composable Unitin the module gets a preview.@PoseIgnoreopts individual composables back out. - Actionable refusals — every diagnostic names the composable, gives a concrete fix (rewrite skeleton, provider snippet, or config option), and links to the refusal catalog.
- Snapshot-testing multiplier — every
@Posecomposable becomes a free visual regression test via Paparazzi, Roborazzi, or Google'scom.android.compose.screenshot. Wiring recipe in the README.
Coming next (0.5.0)
Kotlin compiler plugin for in-source @Pose gutter markers, so you don't have to jump into build/generated/ to open a preview.