ketoy-dev 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +101 -0
  3. package/SECURITY.md +34 -0
  4. package/dist/ketoy.js +3165 -0
  5. package/dist/ketoy.js.map +1 -0
  6. package/package.json +78 -0
  7. package/skills/ketoy/README.md +50 -0
  8. package/skills/ketoy/SKILL.md +148 -0
  9. package/skills/ketoy/examples/capabilities-stubs.kt +60 -0
  10. package/skills/ketoy/examples/hilt-config.kt +192 -0
  11. package/skills/ketoy/examples/no-hilt-config.kt +101 -0
  12. package/skills/ketoy/examples/todo-screen.kt +156 -0
  13. package/skills/ketoy/guides/build-and-analyze.md +87 -0
  14. package/skills/ketoy/guides/diagnose-errors.md +129 -0
  15. package/skills/ketoy/guides/init-project.md +127 -0
  16. package/skills/ketoy/guides/migrate.md +190 -0
  17. package/skills/ketoy/guides/publish-deferred.md +46 -0
  18. package/skills/ketoy/guides/safe-edits.md +141 -0
  19. package/skills/ketoy/reference/architecture-cheatsheet.md +122 -0
  20. package/skills/ketoy/reference/capabilities.md +122 -0
  21. package/skills/ketoy/reference/forbidden-apis.md +149 -0
  22. package/skills/ketoy/reference/supported-composables.md +80 -0
  23. package/skills/ketoy/reference/supported-constructors.md +57 -0
  24. package/skills/ketoy/reference/supported-modifiers.md +76 -0
  25. package/skills/ketoy/templates/app-build.gradle.kts.tmpl +109 -0
  26. package/skills/ketoy/templates/ketoy-capabilities.json.tmpl +21 -0
  27. package/skills/ketoy/templates/manifest-snippet.xml.tmpl +33 -0
  28. package/templates/HelloKetoyScreen.kt.tmpl +51 -0
  29. package/templates/MainActivity.kt.tmpl +53 -0
  30. package/templates/MyApplication.kt.tmpl +88 -0
  31. package/templates/ketoy-capabilities.json.tmpl +5 -0
@@ -0,0 +1,149 @@
1
+ # Forbidden APIs in KBC Source
2
+
3
+ KBC bytecode is **sandboxed**. The compiler plugin rejects entire categories of calls. This is the source of truth for "what can't I do" — every entry here corresponds to a typed diagnostic in `KetoyCompilationError`.
4
+
5
+ ## 1. Direct Android / AndroidX APIs
6
+
7
+ **Banned**: any FQ starting with `android.` or `androidx.` **outside the registered Compose surface**.
8
+
9
+ ```kotlin
10
+ // ❌ KetoyBC: Direct access to android.widget.Toast.makeText
11
+ Toast.makeText(context, "hi", Toast.LENGTH_SHORT).show()
12
+
13
+ // ❌ KetoyBC: Direct access to android.content.Context.getSystemService
14
+ val vm = context.getSystemService(VIBRATOR_SERVICE)
15
+
16
+ // ✅ via capability
17
+ toast("hi") // stub at 0x0901
18
+ ```
19
+
20
+ The diagnostic includes a fix example pointing at the equivalent capability.
21
+
22
+ ## 2. Reflection
23
+
24
+ **Banned**: `kotlin.reflect.*`, `java.lang.Class.get*`, `java.lang.reflect.*`.
25
+
26
+ ```kotlin
27
+ // ❌ KetoyBC: Reflection is not allowed in KBC source
28
+ val klass = MyClass::class
29
+ val type = typeOf<MyClass>()
30
+ val fields = obj.javaClass.declaredFields
31
+
32
+ // ✅ Use sealed classes + when() exhaustive checks
33
+ ```
34
+
35
+ KBC has no class metadata at runtime — reflection literally cannot work. Use sealed classes or enums for type-discriminated logic.
36
+
37
+ ## 3. File I/O
38
+
39
+ **Banned**: `java.io.*`, `java.nio.*`, `kotlin.io.*` (which wraps `java.io`).
40
+
41
+ ```kotlin
42
+ // ❌ KetoyBC: File I/O is not allowed in KBC source
43
+ File("config.json").readText()
44
+ FileInputStream(path).use { ... }
45
+
46
+ // ✅ via storage capability
47
+ val raw = kvGet("config_json") as String?
48
+ ```
49
+
50
+ For persistent state use `KV_GET/SET/OBSERVE` (DataStore-backed in the standard library) or `DB_QUERY/OBSERVE` (Room-backed via `KBCRoomBridge`).
51
+
52
+ ## 4. Unstructured concurrency
53
+
54
+ **Banned**: `GlobalScope`, `runBlocking`, `kotlinx.coroutines.CoroutineScope(...)` factory.
55
+
56
+ ```kotlin
57
+ // ❌ KetoyBC: GlobalScope is forbidden — use viewModelScope
58
+ GlobalScope.launch { fetchData() }
59
+
60
+ // ❌ KetoyBC: runBlocking is forbidden
61
+ val result = runBlocking { suspendFn() }
62
+
63
+ // ✅ inside a @KetoyComposable, use LaunchedEffect
64
+ LaunchedEffect(key) {
65
+ val result = fetchData() // a @KetoyCapabilityStub suspend fn
66
+ vmSetState("result", result)
67
+ }
68
+ ```
69
+
70
+ Allowed: `viewModelScope.launch`, `coroutineScope { ... }` builder (structured), `LaunchedEffect`, `withContext(Dispatchers.IO)` (via `WITH_CONTEXT` opcode + dispatcher capabilities).
71
+
72
+ ## 5. Direct construction of non-allowed types
73
+
74
+ If a constructor isn't in `supported-constructors.md`, the compiler plugin throws `KetoyBC: Direct construction of <type> in KBC source`. The most common culprits:
75
+
76
+ ```kotlin
77
+ // ❌ Brush gradients not on catalog
78
+ Brush.linearGradient(listOf(Color.Red, Color.Blue))
79
+
80
+ // ❌ Arbitrary data classes (try to construct UiState(...) inside a Composable)
81
+ data class UiState(val name: String)
82
+ UiState("hi")
83
+ ```
84
+
85
+ Fix: pass the value in via state (`vmGetState`) or define an adapter.
86
+
87
+ ## 6. `@Composable` without `@KetoyComposable`
88
+
89
+ ```kotlin
90
+ // ❌ KetoyBC: Unregistered composable com.example.MyHelper
91
+ @Composable
92
+ fun MyHelper() { Text("hi") }
93
+
94
+ @KetoyComposable
95
+ @Composable
96
+ fun Root() {
97
+ MyHelper() // crashes here
98
+ }
99
+ ```
100
+
101
+ **Why**: bare `@Composable` is treated as an external call that needs an adapter (which doesn't exist for arbitrary user code). Either:
102
+ - Add `@KetoyComposable` to the helper (it becomes a KBC function), OR
103
+ - Inline the helper into the calling screen.
104
+
105
+ This is intentional — Phase 9A tightened detection so `MyHelper` doesn't silently become uncallable.
106
+
107
+ ## 7. Transitive closure errors
108
+
109
+ Closure validation walks every helper reachable from a `@KetoyComposable` / `@KetoyEntryPoint` / `@KetoyViewModel` root. If a helper does something forbidden, the error carries a breadcrumb:
110
+
111
+ ```
112
+ KetoyBC: Direct access to android.util.Log.d in helper
113
+ Reached via: TodoListScreen → renderHeader → logHeaderShown
114
+ ...
115
+ ```
116
+
117
+ **Fix at the leaf**, not the root. The breadcrumb points you there.
118
+
119
+ **Important**: same-module top-level functions that are NEVER called from a KBC root are **left alone** — they compile to plain DEX. The closure walk only validates code reachable from a KBC root. So you can have native helpers and KBC helpers side-by-side in `:app`.
120
+
121
+ ## 8. Lambda captures (closure conversion)
122
+
123
+ KBC supports closure conversion for `val`/captured reads, BUT:
124
+ - Captures are snapshotted **eagerly** at the call site. Mutating the parent `val` later does not affect the lambda.
125
+ - Only outer-scope `val`s and parameter reads work. `var` writes from inside a lambda don't propagate.
126
+ - Deeply nested lambdas with many captures slow down compilation. Keep capture sets small.
127
+
128
+ ```kotlin
129
+ // ✅ works — captured outer val
130
+ @KetoyComposable
131
+ fun Screen() {
132
+ val name = "World"
133
+ Column {
134
+ Text("Hello, $name!") // name captured into the Column content lambda
135
+ }
136
+ }
137
+
138
+ // ❌ won't update the outer var
139
+ fun Screen() {
140
+ var count = 0
141
+ Button(onClick = { count++ }) { Text("$count") } // count writes lost
142
+ }
143
+
144
+ // ✅ use state instead
145
+ fun Screen() {
146
+ val count = vmGetState("count") as Int? ?: 0
147
+ Button(onClick = { vmSetState("count", count + 1) }) { Text("$count") }
148
+ }
149
+ ```
@@ -0,0 +1,80 @@
1
+ # Supported Composables (as of 0.2.1-alpha)
2
+
3
+ Every composable callable from KBC source must have a registered adapter. The canonical list is in `ketoy-adapters-material3/src/main/resources/adapter-scan-roots.txt`. Below is the human-readable summary. App-specific adapters live at `0x4000+` registered through a host scan-roots file.
4
+
5
+ If a composable is **not** in this list, KBC compilation will reject the call with `KetoyBC: Unregistered composable <FQ>`. The fix is one of: (a) it's supported under a different overload — disambiguate; (b) add a custom adapter via `KBCComposableAdapter.register(...)`; (c) keep the screen native.
6
+
7
+ ## Layout & containers
8
+
9
+ | FQ | Adapter ID | Notes |
10
+ |---|---|---|
11
+ | `androidx.compose.material3.Text` | `0x0001` | String overload, 17 params (post-1.5). AnnotatedString form not supported. |
12
+ | `androidx.compose.foundation.layout.Column` | `0x0002` | |
13
+ | `androidx.compose.foundation.layout.Row` | `0x0003` | |
14
+ | `androidx.compose.foundation.layout.Box` | `0x0004` | |
15
+ | `androidx.compose.foundation.layout.Spacer` | `0x000B` | |
16
+ | `androidx.compose.material3.Scaffold` | `0x0007` | `topBar`, `bottomBar`, `floatingActionButton`, `snackbarHost` content slots all supported. |
17
+ | `androidx.compose.material3.Surface` | `0x0008` | Non-clickable overload (modifier-first). Clickable overload not yet supported. |
18
+ | `androidx.compose.material3.Card` | `0x0009` | 6-param shape/colors/elevation/border form. |
19
+
20
+ ## Lazy lists — NOT supported
21
+
22
+ `LazyColumn`, `LazyRow` are explicitly deferred (KNOWN_ISSUES #5/#6). The content slot is `LazyListScope.() -> Unit`, a receiver-scoped composable that the KSP codegen doesn't yet handle. **Workaround**: use `Column` with `verticalArrangement` for small lists; for large lists keep the screen native and dispatch through navigation.
23
+
24
+ ## Buttons & interaction
25
+
26
+ | FQ | Adapter ID | Notes |
27
+ |---|---|---|
28
+ | `androidx.compose.material3.Button` | `0x000C` | |
29
+ | `androidx.compose.material3.IconButton` | `0x000F` | |
30
+ | `androidx.compose.material3.Checkbox` | `0x0014` | |
31
+ | `androidx.compose.material3.Switch` | `0x0016` | |
32
+
33
+ ## Text input
34
+
35
+ | FQ | Adapter ID | Notes |
36
+ |---|---|---|
37
+ | `androidx.compose.material3.TextField` | `0x0012` | String overload only. AnnotatedString and TextFieldValue not supported. |
38
+
39
+ ## Media
40
+
41
+ | FQ | Adapter ID | Notes |
42
+ |---|---|---|
43
+ | `androidx.compose.foundation.Image` | `0x0024` | Use `painterResource(R.drawable.X)`; host must register `X` in `MaterialDrawableResolver`. |
44
+ | `coil.compose.AsyncImage` | (see scan-roots) | The painters-and-callbacks overload. |
45
+ | `androidx.compose.material3.Icon` | `0x0019` | Use `Icons.<Style>.<Name>`; host must register the icon in `MaterialIconsResolver`. Material icons-extended required on classpath. |
46
+
47
+ ## TopAppBar family
48
+
49
+ | FQ | Adapter ID |
50
+ |---|---|
51
+ | `androidx.compose.material3.TopAppBar` | (see scan-roots) |
52
+
53
+ Note: `TopAppBarDefaults.topAppBarColors(...)` is **constructed** via `CONSTRUCT_JVM`, not called as a composable. See `supported-constructors.md`.
54
+
55
+ ## How to verify a composable is supported
56
+
57
+ ```bash
58
+ grep -i 'YourComposable' /path/to/ketoyvm/ketoy-adapters-material3/src/main/resources/adapter-scan-roots.txt
59
+ ```
60
+
61
+ If grep returns nothing → not supported. Don't invent.
62
+
63
+ ## Adding a custom adapter (app-specific)
64
+
65
+ Custom composables live in your app's KBC scope at `0x4000+`. Pattern:
66
+
67
+ ```kotlin
68
+ // In your app's Hilt module or App.onCreate:
69
+ KBCComposableAdapter.register(
70
+ id = 0x4001,
71
+ fqName = "com.yourapp.ui.MyChart",
72
+ ) { p ->
73
+ MyChart(
74
+ data = p.getAnyOrNull(0) as List<Float>? ?: emptyList(),
75
+ modifier = p.getModifier(1),
76
+ )
77
+ }
78
+ ```
79
+
80
+ Then declare a matching `@KetoyCapabilityStub`-like function in KBC source — or use a custom adapter scan-roots file feeding KSP. See `docs/adapters/custom-composable-adapters.md` in the Ketoy repo for the full flow.
@@ -0,0 +1,57 @@
1
+ # Supported Constructors (CONSTRUCT_JVM)
2
+
3
+ When KBC source writes `TextStyle(fontSize = 16.sp, color = Color.White)` or `RoundedCornerShape(40.dp)`, the compiler plugin emits a `CONSTRUCT_JVM` opcode targeting a registered constructor adapter. Only the constructors listed here are supported.
4
+
5
+ If your KBC code constructs an object whose constructor isn't on this list, you'll get `KetoyBC: Direct construction of <FQ> in KBC source`. The fix is either: (a) use a supported alternative; (b) add a custom `KBCConstructorAdapter` in your host; (c) move the construction host-side and pass via state.
6
+
7
+ ## Built-in constructors
8
+
9
+ | FQ / Factory | Notes |
10
+ |---|---|
11
+ | `androidx.compose.ui.text.TextStyle` | The full kitchen-sink TextStyle ctor. |
12
+ | `androidx.compose.foundation.text.KeyboardOptions` | |
13
+ | `androidx.compose.foundation.text.KeyboardActions` | |
14
+ | `androidx.compose.foundation.shape.RoundedCornerShape` | Both `(Dp)` and `(Int percent)` forms. |
15
+ | `androidx.compose.ui.text.input.PasswordVisualTransformation` | |
16
+ | `androidx.compose.material3.ButtonDefaults.buttonColors` (factory) | Pass as named args; supported via top-level factory pattern. |
17
+ | `androidx.compose.material3.CardDefaults.cardColors` (factory) | |
18
+ | `androidx.compose.material3.TopAppBarDefaults.topAppBarColors` (factory) | |
19
+ | `androidx.compose.ui.graphics.Color(Long)` | The `Color(0xFFAARRGGBB)` literal form. |
20
+
21
+ ## Atomic-collapse encoder shortcuts
22
+
23
+ The compiler plugin recognizes these patterns and rewrites them inline — they look like constructor calls in source but don't emit `CONSTRUCT_JVM`:
24
+
25
+ | Source pattern | Wire encoding | Host resolution |
26
+ |---|---|---|
27
+ | `Color.Red`, `Color.White`, etc. (12 named) | `KBCValue.ColorARGB` literal | inline |
28
+ | `Color(0xFFAARRGGBB)` | `KBCValue.ColorARGB` literal | inline |
29
+ | `16.dp`, `8.dp`, etc. | `KBCValue.Dp(Float)` | inline |
30
+ | `12.sp` | `KBCValue.Sp(Float)` | inline |
31
+ | `FontWeight.Bold`, `.Medium`, etc. (9) | `KBCValue.FontWeightInt` | inline |
32
+ | `FontStyle.Italic` / `.Normal` | `KBCValue.FontStyleId` | inline |
33
+ | `TextAlign.Center` etc. (6) | `KBCValue.TextAlignId` | inline |
34
+ | `TextOverflow.Ellipsis` etc. | `KBCValue.TextOverflowId` | inline |
35
+ | `Arrangement.Center` etc. (12) | `KBCValue.HorizontalArrangementId` / `VerticalArrangementId` | inline |
36
+ | `Alignment.Center` etc. (15) | `KBCValue.HorizontalAlignmentId` / `VerticalAlignmentId` / `ContentAlignmentId` | inline |
37
+ | `ContentScale.Fit` etc. (7) | `KBCValue.ContentScaleId` | inline |
38
+ | `KeyboardType.Password` etc. (8) | `KBCValue.KeyboardTypeId` | inline |
39
+ | `ImeAction.Done` etc. (8) | `KBCValue.ImeActionId` | inline |
40
+ | `FontFamily.Default` / `.SansSerif` / `.Serif` / `.Monospace` / `.Cursive` | `KBCValue.FontFamilyTokenId` | inline |
41
+ | `FontFamily(Font(R.font.X))` | `KBCValue.StringLiteral("X")` | Host `MaterialFontFamilyResolver` |
42
+ | `painterResource(R.drawable.X)` | `KBCValue.StringLiteral("X")` | Host `MaterialDrawableResolver` |
43
+ | `Icons.<Style>.<Name>` | `KBCValue.StringLiteral("Style.Name")` | Host `MaterialIconsResolver` |
44
+
45
+ ## NOT supported
46
+
47
+ - Arbitrary data class construction (`MyConfig(...)` — define a `KBCComposableAdapter` parameter instead, or pass via state).
48
+ - `Modifier.then(otherModifier)` from outside the supported builder set.
49
+ - `Brush.linearGradient(...)`, `Brush.verticalGradient(...)` — gradient brushes not on the catalog yet.
50
+ - Custom `Shape` implementations.
51
+ - `AnnotatedString.Builder` and `buildAnnotatedString { }`.
52
+
53
+ ## Verifying a constructor is supported
54
+
55
+ ```bash
56
+ grep -i 'YourType' /path/to/ketoyvm/ketoy-adapters-material3/src/main/resources/adapter-scan-roots.txt | grep '^constructor='
57
+ ```
@@ -0,0 +1,76 @@
1
+ # Supported Modifiers
2
+
3
+ The `KBCModifierIRWalker` recognizes a fixed set of `Modifier.*` builder calls and encodes them as `KBCModifierOp` entries in the bundle's modifier table. Anything outside this list either passes through as a no-op (rare) or breaks the build with `Unregistered composable <Modifier helper FQ>`.
4
+
5
+ ## Layout
6
+
7
+ | Builder | Notes |
8
+ |---|---|
9
+ | `Modifier.fillMaxSize()` / `fillMaxSize(fraction)` | |
10
+ | `Modifier.fillMaxWidth()` / `fillMaxWidth(fraction)` | |
11
+ | `Modifier.fillMaxHeight()` / `fillMaxHeight(fraction)` | |
12
+ | `Modifier.size(Dp)` / `size(width = ..., height = ...)` | |
13
+ | `Modifier.width(Dp)` | |
14
+ | `Modifier.height(Dp)` | |
15
+ | `Modifier.padding(Dp)` | All sides |
16
+ | `Modifier.padding(horizontal = ..., vertical = ...)` | |
17
+ | `Modifier.padding(start = ..., top = ..., end = ..., bottom = ...)` | Partial defaults supported (e.g. `padding(top = 24.dp)`) |
18
+ | `Modifier.padding(paddingValues = pv)` | PaddingValues from `Scaffold` content slot — register-passthrough |
19
+ | `Modifier.weight(Float)` | Inside `Row`/`Column` only; layout-scope handled |
20
+ | `Modifier.offset(x = Dp, y = Dp)` | |
21
+
22
+ ## Appearance
23
+
24
+ | Builder | Notes |
25
+ |---|---|
26
+ | `Modifier.background(color = Color)` | Color must be supported token / `Color(0xFFAARRGGBB)` |
27
+ | `Modifier.background(brush = ...)` | NOT supported (Brush construction outside catalog) |
28
+ | `Modifier.border(width = Dp, color = Color)` | |
29
+ | `Modifier.clip(shape = ...)` | Shape must be `RoundedCornerShape(...)` |
30
+ | `Modifier.alpha(Float)` | |
31
+ | `Modifier.zIndex(Float)` | |
32
+ | `Modifier.aspectRatio(Float)` | |
33
+
34
+ ## Interaction
35
+
36
+ | Builder | Notes |
37
+ |---|---|
38
+ | `Modifier.clickable { ... }` | Lambda dispatches via VM. No `indication` / `interactionSource` overload. |
39
+ | `Modifier.testTag(String)` | |
40
+
41
+ ## NOT supported
42
+
43
+ - `Modifier.then(otherModifier)` from outside the catalog.
44
+ - `Modifier.semantics { ... }`.
45
+ - `Modifier.layoutId(...)`, `ConstraintLayout` modifiers.
46
+ - `Modifier.shadow(...)` with elevation Dp — limited support; verify with a tiny test.
47
+ - `Modifier.draw*` modifiers (`drawBehind`, `drawWithContent`).
48
+ - `Modifier.pointerInput(...)` / gesture detectors.
49
+ - Custom `LayoutModifier` implementations.
50
+
51
+ ## Patterns
52
+
53
+ Chained modifiers with mixed token kinds:
54
+
55
+ ```kotlin
56
+ Modifier
57
+ .fillMaxWidth()
58
+ .padding(horizontal = 20.dp, vertical = 12.dp)
59
+ .clip(shape = RoundedCornerShape(12.dp))
60
+ .background(color = Color(0xFFEEEEFF))
61
+ ```
62
+
63
+ Partial-default named-arg padding (this works since Phase D of ADR-0001):
64
+
65
+ ```kotlin
66
+ Modifier.padding(top = 48.dp, start = 16.dp, end = 16.dp) // bottom defaults to 0.dp
67
+ ```
68
+
69
+ Layout-scope `weight`:
70
+
71
+ ```kotlin
72
+ Row(modifier = Modifier.fillMaxWidth()) {
73
+ Card(modifier = Modifier.weight(3f)) { ... }
74
+ Card(modifier = Modifier.weight(2f)) { ... }
75
+ }
76
+ ```
@@ -0,0 +1,109 @@
1
+ // REFERENCE SHAPE — NOT A TARGET TO WRITE WHOLESALE.
2
+ //
3
+ // This is the *final shape* of an `app/build.gradle.kts` after Ketoy
4
+ // integration. Use it as a guide for surgical edits via the safe-edits
5
+ // strategy (skills/ketoy/guides/safe-edits.md). If the user has an
6
+ // existing `app/build.gradle.kts` — and they almost always do — you
7
+ // MUST edit it surgically: insert the plugin, append the dependencies,
8
+ // append the `ketoy { }` block. Overwriting will destroy their existing
9
+ // dependencies, signing config, build types, flavors, ProGuard rules,
10
+ // Crashlytics integration, custom tasks, etc.
11
+ //
12
+ // Only write this file verbatim if the project does not yet have an app
13
+ // module (e.g. fresh `gradle init` skeleton). Even then, you're better
14
+ // off generating the module via Android Studio and editing it surgically.
15
+ //
16
+ // {{PROJECT_NAME}} — placeholders: {{NAMESPACE}}, {{APPLICATION_ID}},
17
+ // {{MIN_SDK}} (>=26), {{TARGET_SDK}}, {{COMPILE_SDK}}, {{VERSION_CODE}},
18
+ // {{VERSION_NAME}}
19
+
20
+ plugins {
21
+ alias(libs.plugins.android.application)
22
+ alias(libs.plugins.kotlin.android)
23
+ alias(libs.plugins.kotlin.compose)
24
+ // Hilt — keep only if the project uses Hilt:
25
+ // alias(libs.plugins.ksp)
26
+ // alias(libs.plugins.hilt)
27
+ id("dev.ketoy.compiler")
28
+ }
29
+
30
+ android {
31
+ namespace = "{{NAMESPACE}}"
32
+ compileSdk = {{COMPILE_SDK}}
33
+
34
+ defaultConfig {
35
+ applicationId = "{{APPLICATION_ID}}"
36
+ minSdk = {{MIN_SDK}} // Ketoy requires API 26+
37
+ targetSdk = {{TARGET_SDK}}
38
+ versionCode = {{VERSION_CODE}}
39
+ versionName = "{{VERSION_NAME}}"
40
+ }
41
+
42
+ compileOptions {
43
+ sourceCompatibility = JavaVersion.VERSION_17
44
+ targetCompatibility = JavaVersion.VERSION_17
45
+ }
46
+ kotlinOptions {
47
+ jvmTarget = "17"
48
+ }
49
+ buildFeatures {
50
+ compose = true
51
+ }
52
+ }
53
+
54
+ dependencies {
55
+ // ─── Ketoy ─────────────────────────────────────────────────
56
+ implementation(platform("dev.ketoy.vm:ketoy-bom:0.2.1-alpha"))
57
+ implementation("dev.ketoy.vm:ketoy-runtime")
58
+ implementation("dev.ketoy.vm:ketoy-capabilities-core")
59
+ implementation("dev.ketoy.vm:ketoy-capabilities-navigation")
60
+ implementation("dev.ketoy.vm:ketoy-adapters-material3")
61
+ implementation("dev.ketoy.vm:ketoy-annotations")
62
+ // For Hilt projects, also:
63
+ // implementation("dev.ketoy.vm:ketoy-hilt")
64
+ testImplementation("dev.ketoy.vm:ketoy-test")
65
+
66
+ // ─── AndroidX + Compose ──────────────────────────────────────
67
+ implementation(libs.androidx.core.ktx)
68
+ implementation(libs.androidx.lifecycle.runtime.ktx)
69
+ implementation(libs.androidx.activity.compose)
70
+ implementation(platform(libs.androidx.compose.bom))
71
+ implementation(libs.androidx.compose.ui)
72
+ implementation(libs.androidx.compose.material3)
73
+ // Required if your KBC screens reference Icons.<Style>.<Name>:
74
+ implementation(libs.androidx.compose.material.icons.extended)
75
+ implementation(libs.androidx.navigation.compose)
76
+
77
+ // ─── DataStore (used by storage capabilities) ───────────────
78
+ implementation(libs.androidx.datastore.preferences)
79
+
80
+ // ─── Coroutines ──────────────────────────────────────────────
81
+ implementation(libs.kotlinx.coroutines.android)
82
+ }
83
+
84
+ // ─── KetoyBC compiler plugin configuration ──────────────────────
85
+ //
86
+ // `exportFromAppModule = true` makes the compiler plugin attach to
87
+ // compileReleaseKotlin and emit KBC bytecode only for the transitive
88
+ // call closure rooted in `@KetoyComposable` / `@KetoyEntryPoint` /
89
+ // `@KetoyViewModel` declarations in THIS module. Native code in the
90
+ // same module compiles to DEX as plain Kotlin, untouched.
91
+ //
92
+ // Bundle lands at: app/src/main/assets/ketoy/main.ktx
93
+ ketoy {
94
+ exportFromAppModule.set(true)
95
+ bundleId.set("main")
96
+ bundleVariant.set("release")
97
+ capabilityRegistryFile.set(file("ketoy-capabilities.json"))
98
+ debugMode.set(true)
99
+
100
+ // Ed25519 raw 32-byte seed for signing. Generate via:
101
+ // openssl genpkey -algorithm Ed25519 -outform DER -out key.der
102
+ // tail -c 32 key.der > app/keys/sample-private.key
103
+ // Matching public key at app/src/main/assets/ketoy/keys/sample-public.key.
104
+ // The private key is .gitignored.
105
+ val signingKey = file("keys/sample-private.key")
106
+ if (signingKey.exists()) {
107
+ signingKeyFile.set(signingKey)
108
+ }
109
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "version": 1,
3
+ "allowedStdlibFqNames": [],
4
+ "capabilities": [
5
+ { "id": 1792, "name": "NAV_PUSH",
6
+ "fqName": "{{PACKAGE}}.ketoyscreens.navPush",
7
+ "kind": "SYNC", "required": true },
8
+ { "id": 1793, "name": "NAV_POP",
9
+ "fqName": "{{PACKAGE}}.ketoyscreens.navPop",
10
+ "kind": "SYNC", "required": true },
11
+ { "id": 2560, "name": "VM_GET_STATE",
12
+ "fqName": "{{PACKAGE}}.ketoyscreens.vmGetState",
13
+ "kind": "SYNC", "required": true },
14
+ { "id": 2561, "name": "VM_SET_STATE",
15
+ "fqName": "{{PACKAGE}}.ketoyscreens.vmSetState",
16
+ "kind": "SYNC", "required": true },
17
+ { "id": 2562, "name": "VM_OBSERVE_STATE",
18
+ "fqName": "{{PACKAGE}}.ketoyscreens.vmObserveState",
19
+ "kind": "FLOW", "required": true }
20
+ ]
21
+ }
@@ -0,0 +1,33 @@
1
+ <!--
2
+ SURGICAL EDIT ONLY: add the `android:name` attribute to the user's EXISTING
3
+ <application> tag. Do NOT replace the whole <application> block — it
4
+ carries android:icon, android:theme, android:label, android:allowBackup,
5
+ possibly tools:replace, network security config, FileProviders, etc.
6
+
7
+ If the existing <application> already has android:name pointing at a
8
+ non-Ketoy class, stop and ask whether to extend that class or repoint.
9
+
10
+ For Hilt projects, use the Hilt-aware Application class.
11
+
12
+ Non-Hilt:
13
+ -->
14
+ <application
15
+ android:name=".App"
16
+ android:allowBackup="true"
17
+ android:icon="@mipmap/ic_launcher"
18
+ android:label="@string/app_name"
19
+ android:theme="@style/Theme.{{APP_NAME}}">
20
+ <!-- ...activities... -->
21
+ </application>
22
+
23
+ <!--
24
+ Hilt:
25
+ -->
26
+ <application
27
+ android:name=".MyApplication"
28
+ android:allowBackup="true"
29
+ android:icon="@mipmap/ic_launcher"
30
+ android:label="@string/app_name"
31
+ android:theme="@style/Theme.{{APP_NAME}}">
32
+ <!-- ...activities... -->
33
+ </application>
@@ -0,0 +1,51 @@
1
+ package {{PACKAGE}}
2
+
3
+ import androidx.compose.foundation.layout.Arrangement
4
+ import androidx.compose.foundation.layout.Column
5
+ import androidx.compose.foundation.layout.Spacer
6
+ import androidx.compose.foundation.layout.fillMaxSize
7
+ import androidx.compose.foundation.layout.height
8
+ import androidx.compose.foundation.layout.padding
9
+ import androidx.compose.material3.Text
10
+ import androidx.compose.runtime.Composable
11
+ import androidx.compose.ui.Alignment
12
+ import androidx.compose.ui.Modifier
13
+ import androidx.compose.ui.graphics.Color
14
+ import androidx.compose.ui.text.font.FontWeight
15
+ import androidx.compose.ui.unit.dp
16
+ import androidx.compose.ui.unit.sp
17
+ import dev.ketoy.annotations.KetoyComposable
18
+ import dev.ketoy.annotations.KetoyEntryPoint
19
+
20
+ /**
21
+ * Default KBC entry-point shipped by `ketoy init`.
22
+ *
23
+ * The KetoyBC compiler plugin compiles this function (and every other
24
+ * `@KetoyComposable` / `@KetoyEntryPoint` reachable from it) into the
25
+ * `main.ktx` bundle written to `assets/ketoy/main.ktx` at release build
26
+ * time. At runtime, `KetoyScreen(entryPoint = "HelloKetoyScreen", ...)`
27
+ * loads + executes it.
28
+ */
29
+ @KetoyEntryPoint
30
+ @KetoyComposable
31
+ @Composable
32
+ fun HelloKetoyScreen(modifier: Modifier = Modifier) {
33
+ Column(
34
+ modifier = Modifier.fillMaxSize().padding(24.dp),
35
+ verticalArrangement = Arrangement.Center,
36
+ horizontalAlignment = Alignment.CenterHorizontally,
37
+ ) {
38
+ Text(
39
+ text = "Hello, Ketoy!",
40
+ fontSize = 28.sp,
41
+ fontWeight = FontWeight.Bold,
42
+ color = Color(0xFF4F378B),
43
+ )
44
+ Spacer(Modifier.height(8.dp))
45
+ Text(
46
+ text = "This screen ships as KBC bytecode inside the APK.",
47
+ fontSize = 14.sp,
48
+ color = Color.DarkGray,
49
+ )
50
+ }
51
+ }
@@ -0,0 +1,53 @@
1
+ package {{PACKAGE}}
2
+
3
+ import android.os.Bundle
4
+ import androidx.activity.ComponentActivity
5
+ import androidx.activity.compose.setContent
6
+ import androidx.activity.enableEdgeToEdge
7
+ import androidx.compose.foundation.layout.Arrangement
8
+ import androidx.compose.foundation.layout.Column
9
+ import androidx.compose.foundation.layout.fillMaxSize
10
+ import androidx.compose.material3.MaterialTheme
11
+ import androidx.compose.material3.Surface
12
+ import androidx.compose.material3.Text
13
+ import androidx.compose.runtime.CompositionLocalProvider
14
+ import androidx.compose.ui.Alignment
15
+ import androidx.compose.ui.Modifier
16
+ import dev.ketoy.runtime.bundle.KetoyBundleSource
17
+ import dev.ketoy.runtime.compose.KetoyScreen
18
+ import dev.ketoy.runtime.compose.LocalKetoyBundleLoader
19
+ import dev.ketoy.runtime.compose.LocalKetoyRuntime
20
+
21
+ class MainActivity : ComponentActivity() {
22
+
23
+ override fun onCreate(savedInstanceState: Bundle?) {
24
+ super.onCreate(savedInstanceState)
25
+ enableEdgeToEdge()
26
+ val app = application as MyApplication
27
+ setContent {
28
+ MaterialTheme {
29
+ Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
30
+ CompositionLocalProvider(
31
+ LocalKetoyRuntime provides app.ketoyRuntime,
32
+ LocalKetoyBundleLoader provides app.ketoyBundleLoader,
33
+ ) {
34
+ KetoyScreen(
35
+ entryPoint = "HelloKetoyScreen",
36
+ bundleSource = KetoyBundleSource.Asset("ketoy/main.ktx"),
37
+ ) {
38
+ // Native fallback — rendered when the .ktx bundle is absent,
39
+ // incompatible, or corrupt. Replace with your own native screen.
40
+ Column(
41
+ modifier = Modifier.fillMaxSize(),
42
+ verticalArrangement = Arrangement.Center,
43
+ horizontalAlignment = Alignment.CenterHorizontally,
44
+ ) {
45
+ Text("Hello Android")
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }