Every entry on this page is grounded in the actual source - not the plan docs. “Supported” means the path is wired end-to-end: the compiler emits, the .ktx wire format carries it, the runtime resolves it, and an adapter consumes it. Audited against HEAD on 2026-05-17.
Fully routed Material3 + Foundation + Coil components, KSP-generated.
Named-arg modifier folding - padding, fill, size, background, clickable.
Network, storage, navigation, platform, dispatchers, Flow operators.
Logic, coroutine, Compose state, control flow, VM lifecycle.
KSP reads adapter-scan-roots.txt, resolves each FQ name against the real Compose / Material3 / Foundation / Coil declarations on the build classpath, and emits typed adapters. Every parameter on every adapter is wired - never a curated subset.
These IDs exist in KBCAdapterIds but aren’t in the scan-roots manifest yet. KBC source referencing them fails with KetoyCompilationError.UnregisteredComposable. Adding one is a one-line change to adapter-scan-roots.txt followed by ./gradlew :ketoy-adapters-material3:kspRelease.
The host registers custom adapters on KBCAdapterRegistry and supplies a matching FQ in its own scan-roots file. Additional adapter catalogs published by other Gradle modules are loaded via composeAdapterCatalogPath.
Complex Compose value types aren’t encoded inline. The compiler emits a CONSTRUCT_JVM opcode that builds the object via a registered constructor adapter and stores it in a register; the consuming COMPOSABLE_CALL reads it as KBCValue.Register(n).
IDs are reserved for TEXT_STYLE, SPAN_STYLE,PARAGRAPH_STYLE, TEXT_FIELD_VALUE,CUT_CORNER_SHAPE, SHADOW, OFFSET,BORDER_STROKE, ANNOTATED_STRING,VISUAL_TRANSFORM_PASSWORD, TEXT_SELECTION_COLORS,MUTABLE_INTERACTION_SOURCE, plus a coherent 0x0016…0x001Fblock for the remaining Material3 *Defaults factories.
TextStyle in particular needs the classifier to model FontSynthesis,BaselineShift, TextGeometricTransform,LocaleList, Shadow, TextDirection, andTextIndent before it can ship.
The IR walker extracts modifier builder calls at emit time into a KBCModifierDescriptor, pools it into the bundle’s modifier table, and references it via KBCValue.ModifierRef(idx). Argument resolution is named-arg - so K2’s partial-default lowering like padding(top = 48.dp, start = 16.dp) works correctly.
Weight is handled at the layout-scope level by Row / Column adapters via KBCParamSet.getWeightOrNull(idx) rather than during modifier folding.
Shapes - KBCShape covers:RectangleCircleRoundedCorner(size)RoundedCornerPercent(percent)CutCorner(size)
Compose value reads like FontWeight.Bold, Color.Red, 16.dp,Arrangement.SpaceEvenly all flow through a single ComposeTokenRegistryand encode as the matching KBCValue.*Id byte or inline literal.
Thin → Black
Named Compose colors + ARGB literals
Start, Center, SpaceBetween, SpaceEvenly…
Top/Center/Bottom × Start/Center/End
Fit, Crop, Inside, FillBounds…
Text, Email, Number, Password, Phone…
Done, Next, Search, Send, Go…
Full Material typography scale
-keep rulesMaterial icons flow through KBCValue.StringLiteral carrying the canonical FQ. The host registers a MaterialIconsResolver built via thematerialIconsResolver { } DSL with per-style helpers (registerFilled, registerRounded,registerOutlined, registerSharp,registerTwoTone, plus the five registerAutoMirrored* variants).
The compiler’s ComposeTokenRegistry pattern-matches all 10 Material icon style packages and 13 Icons.<get-Style> selectors. Missing-icon fallback is a 24×24 transparent PlaceholderImageVector- surfaces gaps visibly rather than crashing.
The KBC interpreter is register-based with 106 opcodes. The IR-to-KBC lowering handles every IR node listed below; everything else folds statically (Compose runtime infra), routes to an allowlisted intrinsic, or fails compilation with a typed KetoyCompilationError.
Int, Long, Float, Double, Boolean, String, Unit, null, Char. Full arithmetic, comparison, boolean ops, Int↔Long/Float/Double conversions, nullable boxing/unboxing.
Variadic STRING_CONCAT - every +, interpolation, or multi-arg concat collapses to one opcode. Per-bundle deduplicated string pool.
if, when, while, do-while, break, continue, return, throw, and try/catch.
Caveat: catch is currently catch-all - multi-catch collapses to the first handler. Rethrow inside a catch works correctly.
val onClick = { }, list.forEach { }, lambda parameters to adapters. Closures with captured outer-scope locals are fully supported via KBCValue.ClosureRef - capture values snapshot eagerly at slot resolution time.
Audited against 6 capture patterns: single, multi, navigation-onClick, nested two-level, branch, non-capturing baseline.
State-machine lowering with SUSPEND_POINT/RESUME_VALUE chains. Structured concurrency via SupervisorJob. CancellationException is preserved.
listOf, mapOf, mutableListOf intrinsified. Bigger stdlib ops (map, filter, reduce) flow through INVOKE_VIRTUAL against the KBC heap.
Both layers are load-bearing in production. The host owns lifecycle and persistence; KBC owns business logic.
Extends Android ViewModel, owns a KetoyVM per screen, instantiated by KetoyScreen via viewModel(factory).
State map is Map<String, Any?>. Persistable types - primitives + arrays - mirror to SavedStateHandle. Restore priority: SavedState > initialExtras > empty.
Base class developers extend when writing @KetoyViewModel-annotated classes. Exposes four lateinit var properties bound post-construction before init() fires.
open fun init() runs once after binding. Default no-op. Constructor parameters resolve via the host’s KetoyCapabilityProvider - capability IDs only, never Hilt graph access.
@KetoyViewModel
class CounterViewModel : KetoyBaseViewModel() {
override fun init() { setState("count", 0) }
fun increment() {
val current = getState("count") as? Int ?: 0
setState("count", current + 1)
}
}MutableStateFlow / MutableSharedFlow via STATE_FLOW_CREATE / SHARED_FLOW_CREATEviewModelScope.launch { } and async { }suspend functions, withContext(Dispatchers.IO), Flow.collectmap, filter, debounce, flatMapLatest, combine, take, distinctUntilChanged@KetoyCapabilityStub (Room DAO, Retrofit, DataStore)onCleared. Lifecycle is owned by KetoyVirtualViewModel; init() is the only entry. Cleanup is implicit via viewModelScope cancellation.These produce typed diagnostics with the offending FQ name, a rationale, a runnable fix example, and a docs URL.
The bridge between KBC and the host platform. KBC code never sees Hilt, Retrofit, OkHttp, Room DAOs, DataStore, or the Android Context - every interaction routes through a Short-ID-keyed lambda registered on CapabilityRegistry.
There is no special generator. The host writes a normal Room @Entity+ @Dao + @Database, injects the DAO into itsKetoyCapabilityProvider, and registers each operation under a0x4000+ ID via KBCRoomBridge helpers. The KBC side calls a @KetoyCapabilityStub-marked function and the compiler plugin rewrites the call to INVOKE_CAPABILITY_SUSPEND.
Retrofit, DataStore typed flows, Bluetooth, camera, WorkManager - same pattern, same wrap-as-capability flow.
Brotli-compressed, Ed25519-signed binary container. Format v2 - additive, so pre-2.1 readers parse v2 files with one field defaulted. 10 ordered sections.
KetoyBundleSource has 4 variants. Remote loading uses Ktor + OkHttp with on-device ETag cache; 304 serves cache; offline fallback to cache on network failure.
Ed25519 keypair generation via openssl genpkey (raw 32-byte seed). Signing plumbed through KetoyBundleTask. Verification: Ed25519 → KtxReader → BundleValidator inside KetoyRuntime.parseBundle.
DexMaker-backed JIT for whitelisted pure-logic functions - no capabilities, no coroutines, no Compose, no DIV/MOD on Int/Long. Activated on API 26+ when enableJIT = true. Translation failures fall back to the interpreter silently.
Speed gate: ≥1.5× over the interpreter.
Auto-enabled on debug builds (FLAG_DEBUGGABLE). Shows COMPOSABLE_CALL / CONSTRUCT_JVM / capability dispatch logs with human-readable names. Every emit site is gated - zero overhead in release.
FakeAdapterRegistry + FakeConstructorRegistry (record-only fakes), KBCBuilder + ParamBuilder sparse-encoding DSL, buildKBCBundle { } named-entry-point DSL, KetoyTestRuntime with descriptive entry-point errors.
id("dev.ketoy.compiler") from the Gradle plugin portal plus the BOM. Choose the mode that fits your project layout.
A separate library module holds the KetoyBC source. Produces .ktx in build/ketoy-bundles/. The consumer (:app) copies the asset.
ketoy { exportFromAppModule = true; bundleVariant = "release" } on the host APK. The compiler attaches only to the selected variant’s compile<Variant>Kotlin; output lands in src/main/assets/ketoy/<bundleId>.ktx and the plugin auto-wires the merge<Variant>Assets dependency.
./gradlew ketoyBundle produces a signed .ktx against the host-supplied private key. The runtime activation policy (PackageInfo.longVersionCode check, rollback, onBundleAppVersionMismatch callback) is plumbed through the format but not yet enforced at runtime - that’s a Phase 11B/D item.