Arkive Setup

SkillDev tools

Use when: (1) adding Arkive (com.infinum.arkive) to a project for the first time, (2) upgrading an existing Arkive installation, (3) generating and viewing the showcase for the first time. Installs the published Maven Central version, configures every module that has previews, and ends with a browsable showcase — setup is not done until the user has seen it. Not for the arkive repo itself (it has its own bootstrap, see its CLAUDE.md).

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Arkive Setup skill

What this skill tells your AI

The instructions your AI receives, as published by infinum/arkive in .agents/skills/setup/SKILL.md and read by ahel’s review.

Install Arkive on an Android project and get to a rendered, browsable showcase. Read references/arkive-cheatsheet.md first for paths, task names, and extension options used below.

The finish line is the showcase in a browser, not a green build. Setup that ends at "the plugin applied successfully" is unverified setup.

Step 1 — Resolve the version (never hardcode, never float)

Fetch https://repo1.maven.org/maven2/com/infinum/arkive/plugin/maven-metadata.xml and take the <latest> element. That concrete version is what goes in the build file.

  • Never write latest.release or a dynamic version into the consumer's build — builds must be reproducible. The skill chases latest at install time; the build pins.
  • Arkive requires 0.0.3 or newer (earlier versions have consumer-compat bugs and no verifyShowcase). If <latest> is older than 0.0.3, STOP and tell the user Arkive isn't ready to install yet.
  • Upgrading: compare the project's pinned version against <latest>, bump the pin, then re-sync with --refresh-dependencies once.

Step 2 — Discover the target modules

Arkive is applied per module. Unless the user scoped the request to specific modules, apply it to every module that has UI worth cataloguing:

  • Scan the whole project for @Preview usages. Every module with previewed composables (components or screens) gets the plugin. This includes KMP/CMP modules — previews in commonMain count (see the KMP section below).
  • A module with composables but no previews: do not invent previews silently — ask the user whether previews should be created for those components/screens, and which ones. Previews are content decisions, not setup plumbing.
  • Skip pure logic/data modules.

Step 3 — Pre-flight each module

Check before touching build files; each miss is a confusing failure later:

RequirementWhy
Kotlin 2.0+Published libraries have a Kotlin 2.0 language floor
KSP plugin (com.google.devtools.ksp) applied to the moduleArkive adds KSP dependencies but does not apply the KSP plugin
Android application/library module, or a KMP module (either the classic androidTarget() layout or com.android.kotlin.multiplatform.library)Tasks are registered per Android variant (the new KMP plugin has a single one, androidMain)
Existing Paparazzi/Roborazzi setups are fine — don't remove themArkive detects an already-applied engine plugin and leaves it alone; the only risk is two conflicting versions on the classpath
org.gradle.configureondemand in gradle.properties?If true, the plugin must ALSO be applied to the root project

Match the project's existing dependency style: if it uses a version catalog, add Arkive to libs.versions.toml and use the alias; otherwise use the plugins block directly:

plugins {
    id("com.infinum.arkive") version "<resolved version>"
}

Step 4 — Choose the snapshot engine (mandatory — the build fails without it)

Every module must select an engine; there is no default. Decide from the Gradle JDK, which you check first (./gradlew -version → the "Launcher JVM"/"Daemon JVM" line; this is the daemon's JDK, not the app's jvmToolchain):

  • Gradle JDK is 17–20 → choose Roborazzi yourself, don't ask. Paparazzi requires a JDK 21+ daemon and would fail the build. Tell the user what you chose and why, and that Paparazzi becomes available if they ever raise the Gradle JDK.
  • Gradle JDK is 21+ → ask the user which engine, with the trade-off stated briefly:
    • Roborazzi — real framework rendering via Robolectric; renders Compose Multiplatform resources; per-module device config (device.set("<robolectric qualifiers>")); ~1.8× slower per snapshot.
    • Paparazzi — layoutlib, pixel-identical to Android Studio previews, faster; cannot render CMP composeResources (those previews get skipped).
  • Exception that overrides the question: a CMP module whose previews use composeResources (stringResource/painterResource) must use Roborazzi — choose it for that module and explain why, whatever the JDK.

Engines are per module — a mixed project is fine. The arkive.engine Gradle property (root gradle.properties or -Parkive.engine=) overrides the DSL for the whole build; mention it if the team wants one org-wide choice.

Step 5 — Configure the extension

arkive {
    engine(Roborazzi) {                  // REQUIRED — from Step 4
        // device.set("w1280dp-h800dp-mdpi")  // optional: the (Robolectric) device to render
                                              // on; default is a Pixel-6-class phone. Match the
                                              // product: tablet app → tablet qualifiers, etc.
    }
    // or: engine(Paparazzi)
    multiModuleVariant.set("uatDebug")   // critical when the module has flavors — see below
    // enableVariants.set(true)          // richer catalogue, slower recording — ask, don't assume
    // designFileKey.set("...")          // Figma file key, if the team has one (enables /arkive:design-loop tier 1)
}

For Roborazzi, ask whether the module targets a phone, tablet, or desktop-sized screen and set device accordingly (e.g. 10" tablet w1280dp-h800dp-mdpi, desktop w1920dp-h1080dp-mdpi) — screens capture at device size, so the right canvas matters.

multiModuleVariant — get this exactly right. The root generateWebShowcase task builds each module's showcase for ONE variant and defaults to debug. If the module has product flavors, a bare debug variant does not exist, and the root task has nothing to generate for that module — it just silently misses the aggregated showcase. So: enumerate the module's actual variants (<flavor><BuildType>, e.g. uatDebug), pick the debug build type of the flavor the team develops against, and set it explicitly. If more than one flavor is plausible, ask which one — don't guess. No flavors → the default is fine and the line can be omitted. KMP modules: omit it — they have a single variant (androidMain) and the plugin defaults to it.

Leave snapshotRetention at its NONE default — golden testing is /arkive:snapshot-testing's job, and enabling it here without explaining it just surprises the team's git status.

Step 6 — Preview hygiene (two silent killers)

Private previews are dropped. The processor ignores private functions, and @Preview private fun ...Preview() is a very common pattern — those components silently never reach the catalogue. Find them in each target module and raise their visibility to internal (not public), telling the user which ones changed and why.

A module with no test sources records nothing. KSP skips a compilation with zero sources of its own (NO-SOURCE). A module whose test source set is empty never triggers Arkive's test processor, so the snapshot test is never generated and the module produces zero snapshots with no error anywhere. If a target module has no test sources, add the placeholder — same file everywhere, only the directory differs:

// android:     src/test/java/ArkivePlaceholder.kt
// classic KMP: src/androidUnitTest/kotlin/ArkivePlaceholder.kt
// new KMP:     src/androidHostTest/kotlin/ArkivePlaceholder.kt

// KSP skips a compilation with zero sources (NO-SOURCE), which would prevent Arkive's
// test processor from generating the snapshot test. Any real test serves the same purpose.
internal object ArkivePlaceholder

KMP / Compose Multiplatform modules

Arkive works on KMP modules that use the com.android.kotlin.multiplatform.library plugin (AGP 9+, KSP 2.3.6+). Previews in commonMain — plain CMP @Previews, @ArkiveComposable, @PreviewParameter in either the androidx or the jetbrains namespace — are recorded through the android target like any android preview. The full mechanics live in references/arkive-cheatsheet.md; what changes for setup:

  • One variant, named androidMain: the tasks are generateShowcaseAndroidMain / verifyShowcaseAndroidMain, goldens live in src/androidHostTest/snapshots, and multiModuleVariant needs no configuration.
  • Host tests must include android resources — check the module's androidLibrary block has it, add if missing:
    withHostTestBuilder {}.configure {
        isIncludeAndroidResources = true
    }
    
    (The plugin enables the library's androidResources itself — don't add that.)
  • The placeholder goes in src/androidHostTest/kotlin (see Step 6) — KMP modules rarely have host-test sources, so this is almost always needed.

Classic KMP layout (com.android.library + androidTarget(), any AGP 8+) is also supported and is even simpler: apply the plugin next to KSP and that's it — the usual per-variant tasks appear (generateShowcaseDebug, …), goldens live in src/androidUnitTest/snapshots, and the placeholder goes in src/androidUnitTest/kotlin. multiModuleVariant defaults to debug there; set it only when the module has flavors.

@ArkiveComposable in commonMain works on any Kotlin 2.0.21+ project (the annotations are built with the oldest supported Kotlin; klibs aren't forward-compatible). On an even older Kotlin the plugin wires the annotations into androidMain instead and logs a warning — write commonMain previews as plain @Preview in that case (collected all the same).

Step 7 — Annotations (the catalogue works without them, but recommend the upgrade)

Plain @Preview composables are collected by default — after Step 6, a project with previews gets a catalogue with zero further annotation work, including any name/group already set on the @Preview annotations themselves. That's the on-ramp; ship the first showcase on it.

Then tell the user the recommended standard is @ArkiveComposable: richer catalogue info (tags, designNodeId for the design loop, skip) and build-error validation instead of silently skipping broken previews. The naming/grouping conventions live in references/annotation-conventions.md (used by /arkive:annotate). During setup, don't mass-annotate beyond what the user asked for — recommend, offer, don't impose.

Step 8 — First run, and actually look at it

./gradlew generateWebShowcase

Recording every preview takes minutes on a real app — warn the user before running. Then:

  1. Confirm <root>/build/generated/arkive/showcase/arkive-showcase.json exists and that every module from Step 2 has a non-empty images/ directory in the output — a missing module usually means a wrong multiModuleVariant or the empty-test-sources trap from Step 6.
  2. Scan the build log for Arkive: no snapshot recorded for component warnings — each is a preview that crashed during recording and was dropped. Report them; don't let them pass silently. (The test-side Arkive: skipping component line with the crash message is test-JVM stdout — it only appears when running with --info.)
  3. Serve it in the background so the session isn't blocked: cd build/generated/arkive/showcase && python3 -m http.server 8090file:// does not work (the JSON is fetched).
  4. Open it for the user — don't just print the URL: open http://localhost:8090 (macOS) / xdg-open http://localhost:8090 (Linux) / start (Windows). Setup ends with the catalogue on the user's screen, and tell them the server keeps running so they can keep browsing (and how to stop it).
  5. Tell the user the no-server way to view it later: in Android Studio, right-click build/generated/arkive/showcase/index.htmlOpen In → Browser (the IDE's built-in web server serves it — double-clicking the file in Finder won't work).

Troubleshooting

SymptomCause / fix
Build fails: "no snapshot engine selected"The module never called engine(...) — add it (Step 4). The error message contains the exact block to paste
Build fails: "Paparazzi engine requires a JDK 21+ Gradle daemon"The Gradle JDK is older than 21 — switch the module to engine(Roborazzi), or raise the Gradle JDK (Studio: Settings → Build Tools → Gradle → Gradle JDK)
UnsupportedClassVersionError (class file 65) during syncSomething loaded Paparazzi classes on a <21 daemon — make sure the module selects engine(Roborazzi) and re-sync
CMP components skipped: "Android context is not initialized"The module is on Paparazzi but its previews use composeResources — switch that module to engine(Roborazzi)
First Roborazzi run stalls for ~a minuteRobolectric downloads the android-all jar for the pinned SDK (one-time, cached in ~/.m2); corporate proxies can redirect it via the robolectric.dependency.repo.url system property
Component skipped: "Compose did not get idle after N attempts in 60 SECONDS"The preview never settles (focus-request loops, non-clock polling) — a component bug; it's skipped safely but costs 60s per run. Fix the component or skip the preview
One engine misbehaves and the cause is unclearEngines are swappable: flip the module (or the whole build via -Parkive.engine=) to the other engine to isolate whether the problem is the engine or the preview — goldens re-record on switch
generateWebShowcase not foundconfigureondemand=true without the plugin on the root project; or plugin applied to a non-Android module
A flavored module is missing from the aggregated showcasemultiModuleVariant unset or naming a variant that doesn't exist — set it to the exact <flavor><BuildType>
A module generates no test and no snapshots at all, no errorsEmpty test source set — KSP never triggered; add the ArkivePlaceholder.kt from Step 6 (on KMP: in src/androidHostTest/kotlin)
KMP module: @ArkiveComposable unresolved in commonMain, log mentions Kotlin being olderConsumer's Kotlin predates the annotations' build Kotlin — plugin wired annotations to androidMain; use plain @Preview in commonMain
KMP module: every snapshot missing, log shows snapshot session finished with errors: <ns>.RHost tests can't see android resources — add isIncludeAndroidResources = true to withHostTestBuilder {}.configure { }
Showcase has no componentsPreviews are private (Step 6), or annotations are in a source set the debug variant doesn't compile
Change to arkive version "didn't take"Stale Gradle module cache — re-sync with --refresh-dependencies once
A component is missing from the catalogueIt crashed during recording — the build log has an Arkive: no snapshot recorded for component warning; re-run with --info for the test-side crash message, then fix the preview
Blank page when opening the showcaseOpened via file:// — serve over HTTP

Red flags — STOP

  • Writing any non-pinned version into a build file.
  • Declaring setup done without having generated the showcase and opened it in the user's browser.
  • Guessing a flavor for multiModuleVariant when several are plausible — ask.
  • Creating previews for un-previewed composables without asking.
  • Installing a version older than 0.0.3 because "it's what's published".
  • Restructuring the user's previews beyond visibility fixes and what setup needs.

Signals

GitHub stars
28
Forks
1
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
setup-infinum
Source
github.com/infinum/arkive