Skip to content

Releases: AkshayChordiya/Pose

0.6.3 — Keep the theme through incremental builds

Choose a tag to compare

@github-actions github-actions released this 14 Aug 17:30

Fixed

  • Editing a composable no longer strips the theme from every preview in the module.

    getSymbolsWithAnnotation only returns symbols from KSP's dirty file set. Nothing in
    your source connects a composable to the module's @PoseSetup object - that link only
    exists in the code Pose emits, as AppPose.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: warned PG024 and 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 Dependencies source, 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
    PreviewParameterProvider classes are all named somewhere in your own source, so
    Kotlin already dirties the composable when they change.

  • Triple parameters no longer refuse with PG001. Triple was listed in the
    planner's collection-like set - so it never got a PreviewParameterProvider slot - but
    the resolver had no branch for it, only for Pair. It fell through to structural
    synthesis, which walks Triple's own constructor and finds the type variables
    A/B/C rather than the arguments supplied at the use site, then refused on
    .first. Triple<Int, Int, Int> now resolves to Triple(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 stamps showBackground = 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 when previews is set, your own annotations decide everything then. Emitted as an omission rather than showBackground = false, which is already @Preview's default.

0.6.2 — Don't build preview data in production

Choose a tag to compare

@github-actions github-actions released this 10 Aug 09:03

Changed

  • PG001 and PG010 now suggest get() = sequenceOf(...) rather than a plain val. sequenceOf(a, b, c) evaluates its arguments eagerly — a Sequence is 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 PreviewParameterProvider in src/debug/kotlin as 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

Choose a tag to compare

@github-actions github-actions released this 07 Aug 17:42

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 @PoseSetup object, 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 : PoseConfig with no Theme override is the explicit opt-out and silences it. Not governed by pose.strict, because bulk mode forces strict = false and that's exactly where losing a whole module's theme hurts most.

Changed

  • PG023 now 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

Choose a tag to compare

@github-actions github-actions released this 07 Aug 16:43

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 to AppTheme is 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 @PoseSetup annotation 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 class in your design-system module, extend it per module.

  • PG023 flags misspelled pose.* keys with a "did you mean" suggestion. Previously a typo silently took the default.

  • PG019–PG022 cover config-object misuse: duplicate setup objects, @PoseSetup on a non-object, an unreachable (private) object, and one that doesn't implement PoseConfig.

Changed

  • Bulk mode now skips wrapper-shaped composables - any composable whose required parameters are all @Composable content 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 matching pose.themeFqName" rule, which can't work once the theme is invoked inside an override body KSP can't see.

  • @PoseSample replaces PoseProvider(…, 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,
    ) { … }

    forParam named 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

  • PoseProvider is gone. Without forParam it was a single-field wrapper around a KClass, so providers takes 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 annotations artifact now applies the Compose compiler plugin and takes compose-runtime as compileOnly, so PoseConfig can declare @Composable members. The published POM is unchanged — compileOnly doesn'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 @PoseSetup now correctly coerces strict to false. Diagnostics was 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

Choose a tag to compare

@AkshayChordiya AkshayChordiya released this 05 Aug 15:57

LocalInspectionMode + custom CompositionLocals

Added

  • Generated previews now provide LocalInspectionMode = true. Every generated preview is wrapped in CompositionLocalProvider(LocalInspectionMode provides true). Studio's preview renderer already sets this, but snapshot runners (Paparazzi, Roborazzi, com.android.compose.screenshot) leave it false — so a composable branching on LocalInspectionMode.current took its production path there, attempting real network calls with dummy URLs and producing blank snapshots. Opt out with pose.provideInspectionMode = false.
  • pose.previewWrapperFqName - point at your own composable to supply arbitrary CompositionLocals (fake image loaders, no-op analytics, locale providers). Same trailing-lambda contract as pose.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.
  • PG018 diagnostic + refusal-catalog entry for an unresolvable previewWrapperFqName.

Changed

  • Generated output changes for every user - the extra CompositionLocalProvider layer 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 set pose.provideInspectionMode = false.
  • Emission is skipped silently when androidx.compose.ui.platform.LocalInspectionMode isn't on the compile classpath (a module with compose-runtime but no compose-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 OptionsTest cases for the two new options. 60 processor tests total.

v0.4.0

Choose a tag to compare

@AkshayChordiya AkshayChordiya released this 03 Aug 20:40

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:

  1. Nothing — structural synth handles it.
  2. companion.previewSamples: Sequence<T> — wired through @PreviewParameter for every composable that takes T.
  3. @Pose(providers = [PoseProvider(FooSamples::class)]) — composable-scoped PreviewParameterProvider bindings, with forParam for disambiguating same-typed parameters.
  • Sealed fan-out — one preview per subtype, named Foo__Preview_Loading, _Success, _Error so the preview panel reads cleanly.
  • Bulk opt-in — flip pose.generatePreviewsForAllPublicComposables = true and every public @Composable Unit in the module gets a preview. @PoseIgnore opts 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 @Pose composable becomes a free visual regression test via Paparazzi, Roborazzi, or Google's com.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.