After the KMP refactor folio also runs on wasmJs and the justfile exposes just web, just web-build, and just test-ios. Surface all three.
Folio
A minimal Kotlin Multiplatform personal-ledger app: login with demo credentials, create accounts, add credits and debits. Shared UI across Android, iOS, and web (wasmJs via Compose for Web). Doubles as the example sanderling runs its property-based specs against.
Stack
- Kotlin Multiplatform + Compose Multiplatform (shared UI)
- SQLDelight for the data layer (unified across platforms)
- kotlinx.coroutines for state flows
- kotlinx.serialization for
@Serializableroute types
Prerequisites
just- JDK 17
- Android SDK (auto-discovered under
$ANDROID_HOME,~/Library/Android/sdk, or the Homebrew cask) - Xcode 16+ and
xcodegen(brew install xcodegen) for iOS
Android
just install # build + install on a booted emulator / device
just uninstall
just clean
iOS
just ios # default device: iPhone 17 Pro
IOS_DEVICE="iPhone 15" just ios # pick a different simulator
just ios regenerates app/iosApp/iosApp.xcodeproj from app/iosApp/project.yml,
builds the KMP framework (Shared.framework from :app:shared), links it
into the SwiftUI host, installs, and launches.
Web
just web # webpack dev server with COOP/COEP headers
just web-build # produce a webpack distributable bundle
just web runs :app:webApp:wasmJsBrowserDevelopmentRun --continuous, so
edits to shared code reload in the browser.
Demo credentials
email: [email protected]
password: ledger123
Run a sanderling test (Android)
just test
If no device is connected, sanderling boots the single AVD it finds. With multiple AVDs, pick one:
AVD=Pixel_7 just test
Persistent settings can live in .env alongside the justfile:
AVD=Pixel_7
DURATION=5m
Traces land in ./sanderling/runs/<timestamp>/.
Run a sanderling test (iOS)
just test-ios # default simulator: iPhone 17 Pro
IOS_DEVICE="iPhone 15" just test-ios # pick a different simulator
just test-ios boots the simulator if needed, runs just ios to install
and launch the app, then invokes sanderling test --platform ios. Same
DURATION, SEED, and OUTPUT env vars as the Android target.
How it connects to sanderling
- Each screen sets a stable Compose
testTag(HomeScreen,AccountCard,LedgerRow,TxnAmount, ...). The Sanderling SDK resolvestestTagtoresource-idon Android andaccessibilityIdentifieron iOS. - Identity for list items is the visible text content (account name; txn note + amount). No synthetic IDs encoded in semantics.
contentDescriptionis reserved for real accessibility labels, never as a data carrier.sanderling/spec.tsimports@sanderling/spec, reads state vias.ax.*, asserts properties, and weights the actions the fuzzer picks from.just testinvokessanderling testagainst the installed APK.