This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
JetLime is a Kotlin Multiplatform (KMP) Compose Multiplatform library that renders customizable timeline UIs. Targets: Android, iOS (x64/arm64/simulator), Desktop (JVM), JS (IR), WasmJs. The published artifact is io.github.pushpalroy:jetlime.
The repo has two Gradle modules:
:jetlime— the library (all platform source insrc/commonMain/kotlin/com/pushpal/jetlime/; Android-specific manifest + instrumented tests insrc/androidMainandsrc/androidTest).:sample:composeApp— a sample app that consumes:jetlimeviaimplementation(project(":jetlime"))and runs on all five targets (Android, iOS via CocoaPods, Desktop, Web-JS, Web-WASM). JDK 17 is required for builds; CI uses JDK 21 only for the Spotless lint job. Kotlin 2.3.20, Compose Multiplatform 1.10.3,androidTargetcompileSdk 36 / minSdk 23.
Format / lint (required before PR — CI runs spotlessCheck):
./gradlew spotlessApply # auto-fix
./gradlew spotlessCheck # verify
Spotless applies ktlint + io.nlopez.compose.rules:ktlint with a mandatory MIT license header from spotless/copyright.kt. .editorconfig enforces 2-space indent, max_line_length=100, trailing commas, and allow-lists LocalJetLimeStyle for the Compose ktlint rule (compose_allowed_composition_locals).
Library tests (Compose UI tests — the real ones live in jetlime/src/androidTest/, not src/test/):
./gradlew :jetlime:connectedAndroidTest # all instrumented tests (needs emulator/device)
./gradlew :jetlime:connectedAndroidTest --tests "com.pushpal.jetlime.JetLimeColumnTest.jetLimeColumn_displaysItems"
Sample app per-platform builds (wrap the right gradle tasks and copy outputs to distributions/):
./scripts/build_android.sh # :sample:composeApp:assembleDebug
./scripts/build_ios.sh # xcodebuild on sample/iosApp/iosApp.xcworkspace
./scripts/build_macos.sh # :sample:composeApp:packageUberJarForCurrentOS
./scripts/build_web_js.sh # :sample:composeApp:jsBrowserDistribution
./scripts/build_web_wasm.sh # :sample:composeApp:wasmJsBrowserDistribution
API docs (Dokka V2 — output is synced into the root docs/ directory that GitHub Pages serves):
./scripts/run_dokka.sh # wraps :jetlime:syncDokkaToDocs --no-configuration-cache
Publishing (see gradle.properties for required credentials — mavenCentralUsername, signing.*):
./gradlew publishToMavenLocal # test locally via ~/.m2
./gradlew publishAndReleaseToMavenCentral --no-configuration-cache
To test a local publish, uncomment the maven coordinate in sample/composeApp/build.gradle.kts and add mavenLocal() to settings.gradle.kts.
Compose compiler metrics/reports:
./gradlew assembleRelease -PcomposeCompilerReports=true # outputs under jetlime/build/compose_compiler/
The library is tiny (~11 files in commonMain) and built around three layers:
- List containers (
JetLimeList.kt) —JetLimeColumn/JetLimeRowwrapLazyColumn/LazyRow. They install aJetLimeStylevia theLocalJetLimeStyleCompositionLocaland compute anEventPosition(START/MIDDLE/END) for each index viaEventPosition.dynamic(index, listSize). The arrangement (VERTICALvsHORIZONTAL) is stamped on the style here and is howJetLimeEventdispatches toVerticalEventvsHorizontalEvent. - Events (
JetLimeEvent.kt,JetLimeExtendedEvent.kt) —JetLimeEventis a single-slot composable that usesModifier.drawBehindto paint the connecting line(s) and the point circle/icon. The per-itemJetLimeEventStylecarries both the event'sEventPosition(used to decide whether to draw the up/down or left/right connector segments viaisNotStart()/isNotEnd()) and aPointPlacement(START/CENTER/END) that governs where the point sits within the item box and how the connector is split into two segments that meet at the point.JetLimeExtendedEvent(vertical-only) adds a second slot (additionalContent) rendered on the opposite side of the line; it uses a customLayoutandBoxWithConstraintscapped byJetLimeEventDefaults.AdditionalContentMaxWidth. - Style + defaults —
JetLimeStyle(list-level: line brush, thickness,pathEffect,contentDistance,itemSpacing, alignment) andJetLimeEventStyle(per-event point visuals) are@Immutable.JetLimeDefaults/JetLimeEventDefaultsexpose thecolumnStyle()/rowStyle()/eventStyle()/pointAnimation()/lineGradientBrush()/lineSolidBrush()factory helpers — always extend these rather than constructing style classes directly (the constructors areinternal).
- Line segments are drawn with Compose
drawLine, branching onPointPlacementandEventPosition. ForCENTER/ENDplacement, the code draws two separate segments (start→point and point→end) and skips the relevant half at the first/last item. ForSTARTplacement (default), a single segment extends from the point past the item box, with apointStartFactor = 1.1foverdraw so adjacent items' lines visually join. - RTL mirroring is handled explicitly in
HorizontalEventand inJetLimeExtendedEvent. In horizontal RTL, thexOffsetis flipped assize.width - logicalXOffsetand segment start/end Xs swap sides. Extended vertical usesLocalLayoutDirection+absolutePaddingso physical LEFT/RIGHT alignment is preserved regardless of layout direction. Any change to line/point drawing must keep both LTR and RTL visually correct — see the RTL tests inJetLimeColumnTest/JetLimeRowTest. VerticalAlignment.LEFT/RIGHTandHorizontalAlignment.TOP/BOTTOMare physical sides (viaabsolutePadding), not start/end-relative.
The library version appears in several places that must be kept in sync on release:
jetlime/build.gradle.kts—mavenPublishing.coordinates(..., "X.Y.Z")andcocoapods { version = "X.Y.Z" }jetlime/jetlime.podspecscripts/add_git_tag.sh—TAG="X.Y.Z"README.mdinstallation snippet After publishing,scripts/add_git_tag.shcreates and pushes theX.Y.Zgit tag onmain.