diff --git a/README.md b/README.md
index 38ef0f19d..c22225034 100644
--- a/README.md
+++ b/README.md
@@ -112,7 +112,7 @@ consciously parked.
| Issue | Size | Summary |
|-------|------|---------|
-| [#915](https://github.com/NickMonrad/kernel-ai-assistant/issues/915) · [#916](https://github.com/NickMonrad/kernel-ai-assistant/issues/916) | L · S | Toolchain upgrade — AGP 9 / Gradle 9 / Kotlin 2.3.x / Hilt (touches every module) |
+| ~~[#915](https://github.com/NickMonrad/kernel-ai-assistant/issues/915)~~ · ~~[#916](https://github.com/NickMonrad/kernel-ai-assistant/issues/916)~~ | L · S | ✓ Toolchain upgrade — AGP 9.0.1 / Gradle 9.1.0 / Kotlin 2.3.21 / Hilt 2.59.2 (PR #1082) |
| [#428](https://github.com/NickMonrad/kernel-ai-assistant/issues/428) | M | Memory profiling — peak RAM & concurrent model usage (feeds #430/#432) |
| [#692](https://github.com/NickMonrad/kernel-ai-assistant/issues/692) | M | Fix inference stalls in Boring AI Mode |
| [#937](https://github.com/NickMonrad/kernel-ai-assistant/issues/937) · [#957](https://github.com/NickMonrad/kernel-ai-assistant/issues/957) | M · S | Memory + intent-routing correctness bugs |
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index b825c413c..998fc77d2 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,24 +1,24 @@
-import java.io.ByteArrayOutputStream
import java.time.Instant
plugins {
alias(libs.plugins.android.application)
- alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
}
+val gitSha: String by lazy {
+ val result = providers.exec {
+ commandLine("git", "rev-parse", "--short=8", "HEAD")
+ isIgnoreExitValue = true
+ }
+ result.standardOutput.asText.get().trim().ifEmpty { "unknown" }
+}
+
android {
namespace = "com.kernel.ai"
compileSdk = libs.versions.compileSdk.get().toInt()
- val gitSha: String = try {
- val stdout = ByteArrayOutputStream()
- exec { commandLine("git", "rev-parse", "--short", "HEAD"); standardOutput = stdout }
- stdout.toString().trim()
- } catch (_: Exception) { "unknown" }
-
defaultConfig {
applicationId = "com.kernel.ai"
minSdk = libs.versions.minSdk.get().toInt()
@@ -69,19 +69,15 @@ android {
unitTests.all { it.useJUnitPlatform() }
}
+ lint {
+ baseline = file("lint-baseline.xml")
+ }
+
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = "17"
- // LiteRT-LM (transitive) uses internal Kotlin 2.3.x build (metadata 2.3.0)
- freeCompilerArgs += "-Xskip-metadata-version-check"
- }
- // Sherpa-ONNX 1.13.0 bundles libonnxruntime.so (ORT 1.24.3). The wake word
- // detector uses onnxruntime-android 1.24.3 — same version, so pickFirst resolves
- // the duplicate cleanly and both libraries share the same .so at runtime.
packaging {
jniLibs {
pickFirsts += setOf(
@@ -95,7 +91,6 @@ android {
}
dependencies {
- // Project modules
implementation(project(":core:inference"))
implementation(project(":core:memory"))
implementation(project(":core:voice"))
@@ -107,30 +102,13 @@ dependencies {
implementation(project(":feature:widget"))
implementation(project(":feature:convert"))
- // ── Sherpa-ONNX spike — runtime AAR for SherpaOnnxVoiceOutputController ──────────
- // SherpaOnnxVoiceOutputController (core:voice) uses Class.forName() reflection; no
- // compile-time dependency is needed in core:voice. The AAR is added here (:app
- // produces an APK, not an AAR, so local AAR deps are permitted) so Sherpa classes
- // are present on the runtime classpath when the APK runs on device.
- //
- // CI: The "Download Sherpa-ONNX AAR" workflow step fetches the file automatically
- // before assembleDebug so CI APKs always include the Sherpa runtime.
- // Local dev: Run `bash scripts/setup-sherpa-tts-spike.sh` to obtain the AAR, or
- // leave it absent — SherpaOnnxVoiceOutputController returns Unavailable and
- // Android TTS is used as the runtime fallback. Voice packs themselves now
- // download on device from Settings -> Voice instead of being bundled into the APK.
- // Absent AAR → SherpaOnnxVoiceOutputController returns Unavailable → Android TTS used.
- // Prefer the full AAR (with bundled ORT) so libsherpa-onnx-jni.so can resolve OrtGetApiBase
- // in its own linker namespace. The -noort variant caused UnsatisfiedLinkError because Android's
- // namespace isolation prevents Sherpa's JNI from seeing ORT loaded by onnxruntime-android.
val sherpaAar = rootProject.file("third_party/sherpa-onnx/sherpa-onnx-1.13.0.aar")
.takeIf { it.exists() }
?: rootProject.file("third_party/sherpa-onnx/sherpa-onnx-1.13.0-noort.aar")
if (sherpaAar.exists()) {
implementation(files(sherpaAar.absolutePath))
}
- // ────────────────────────────────────────────────────────────────────────────────
- // Compose
+
implementation(platform(libs.compose.bom))
implementation(libs.compose.ui)
implementation(libs.compose.ui.graphics)
@@ -140,29 +118,26 @@ dependencies {
implementation(libs.activity.compose)
implementation(libs.navigation.compose)
- // AndroidX
implementation(libs.core.ktx)
implementation(libs.lifecycle.runtime.ktx)
implementation(libs.lifecycle.runtime.compose)
- // Hilt
implementation(libs.hilt.android)
implementation(libs.hilt.navigation.compose)
implementation(libs.hilt.work)
+ implementation(libs.work.runtime.ktx)
ksp(libs.hilt.compiler)
- // Debug
debugImplementation(libs.compose.ui.tooling)
debugImplementation(libs.compose.ui.test.manifest)
debugImplementation(libs.leakcanary)
- // Auth — AppAuth + EncryptedSharedPreferences
implementation(libs.appauth)
implementation(libs.security.crypto)
implementation(libs.play.services.location)
- // Testing
testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.mockk)
testImplementation(libs.coroutines.test)
}
diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
new file mode 100644
index 000000000..c914fe035
--- /dev/null
+++ b/app/lint-baseline.xml
@@ -0,0 +1,462 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
index 092fb00ee..6e0d57ad3 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,7 +1,6 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
- alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.hilt) apply false
diff --git a/core/inference/build.gradle.kts b/core/inference/build.gradle.kts
index 952f310c2..342afefa1 100644
--- a/core/inference/build.gradle.kts
+++ b/core/inference/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.library)
- alias(libs.plugins.kotlin.android)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
}
@@ -18,14 +17,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
-
- kotlinOptions {
- jvmTarget = "17"
- // LiteRT-LM is compiled with an internal Kotlin build (metadata 2.3.0).
- // Skip the strict metadata version check to allow compilation.
- freeCompilerArgs += "-Xskip-metadata-version-check"
- }
-
testOptions {
unitTests.all { it.useJUnitPlatform() }
}
@@ -50,6 +41,7 @@ dependencies {
implementation(libs.tflite)
testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.mockk)
testImplementation(libs.coroutines.test)
}
diff --git a/core/memory/build.gradle.kts b/core/memory/build.gradle.kts
index 5a86b863f..371cd55f5 100644
--- a/core/memory/build.gradle.kts
+++ b/core/memory/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.library)
- alias(libs.plugins.kotlin.android)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
alias(libs.plugins.room)
@@ -38,9 +37,6 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = "17"
- }
testOptions {
unitTests.all {
@@ -79,6 +75,7 @@ dependencies {
testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.mockk)
testImplementation(libs.coroutines.test)
}
diff --git a/core/model-availability/build.gradle.kts b/core/model-availability/build.gradle.kts
index f7f691428..662808f7b 100644
--- a/core/model-availability/build.gradle.kts
+++ b/core/model-availability/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.library)
- alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
@@ -24,10 +23,6 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = "17"
- }
-
testOptions {
unitTests.all { it.useJUnitPlatform() }
}
@@ -60,6 +55,7 @@ dependencies {
compileOnly(libs.compose.ui.test.manifest)
testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.mockk)
testImplementation(libs.coroutines.test)
testImplementation(libs.compose.ui.test.junit4)
diff --git a/core/skills/build.gradle.kts b/core/skills/build.gradle.kts
index cec8ac2b6..e9bde76b8 100644
--- a/core/skills/build.gradle.kts
+++ b/core/skills/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.library)
- alias(libs.plugins.kotlin.android)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
}
@@ -19,12 +18,6 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = "17"
- // LiteRT-LM is compiled with an internal Kotlin build (metadata 2.3.0).
- // Skip the strict metadata version check to allow compilation.
- freeCompilerArgs += "-Xskip-metadata-version-check"
- }
testOptions {
unitTests.isReturnDefaultValues = true
@@ -49,6 +42,7 @@ dependencies {
implementation(libs.datastore.preferences)
testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.mockk)
testImplementation(libs.coroutines.test)
testImplementation("org.json:json:20240303")
diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts
index 7ef560536..d11595d28 100644
--- a/core/ui/build.gradle.kts
+++ b/core/ui/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.library)
- alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}
@@ -22,9 +21,6 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = "17"
- }
}
dependencies {
@@ -39,4 +35,5 @@ dependencies {
debugImplementation(libs.compose.ui.tooling)
testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform.launcher)
}
diff --git a/core/voice/build.gradle.kts b/core/voice/build.gradle.kts
index c9a9c37e0..c0688b9ce 100644
--- a/core/voice/build.gradle.kts
+++ b/core/voice/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.library)
- alias(libs.plugins.kotlin.android)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
}
@@ -19,9 +18,6 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = "17"
- }
testOptions {
unitTests {
@@ -60,6 +56,7 @@ dependencies {
ksp(libs.hilt.compiler)
testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.mockk)
testImplementation(libs.coroutines.test)
}
diff --git a/core/wasm/build.gradle.kts b/core/wasm/build.gradle.kts
index 98723e17c..ca563dadd 100644
--- a/core/wasm/build.gradle.kts
+++ b/core/wasm/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.library)
- alias(libs.plugins.kotlin.android)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
}
@@ -19,9 +18,6 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = "17"
- }
}
dependencies {
@@ -34,6 +30,7 @@ dependencies {
// implementation("com.dylibso.chicory:wasi:+")
testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.mockk)
testImplementation(libs.coroutines.test)
}
diff --git a/docs/PLAN-launch-slice.md b/docs/PLAN-launch-slice.md
index 72bc1424b..23e48c9ed 100644
--- a/docs/PLAN-launch-slice.md
+++ b/docs/PLAN-launch-slice.md
@@ -20,7 +20,7 @@ The launch-blocking work clusters into five themes:
| Theme | Issues |
|-------|--------|
| Device stability / memory safety (the heavy hitters) | #430, #432, #428, #692 |
-| Toolchain modernisation | #915, #916 |
+| ~~Toolchain modernisation~~ | ~~#915, #916~~ ✓ |
| Navigation & visual polish | #747, #751, #226, #961 |
| Correctness bugs (memory + intent routing) | #937, #957, #996 |
| Finish in-flight capabilities | #885, #886, #261, #928, #713, #756, #824 |
@@ -39,7 +39,7 @@ that make the app feel broken, then finish half-built features, then run the rel
> re-migrating that work. The memory-profiling data feeds the two biggest architecture tasks.
> The correctness/stability bugs poison every test run until fixed.
-- **#915 + #916 — AGP 9 / Gradle 9 / Kotlin 2.3.x / Hilt upgrade** (L + S). Land early.
+- ~~**#915 + #916 — AGP 9.0.1 / Gradle 9.1.0 / Kotlin 2.3.21 / Hilt 2.59.2 upgrade** (L + S). ✓ Landed (PR #1082).~~
- **#428 — Memory profiling: peak RAM & concurrent model usage** (M). Produces the numbers
that #430 and #432 are designed against.
- **#692 — Inference stalls in Boring AI Mode** (M). Core generation reliability.
@@ -89,7 +89,7 @@ that make the app feel broken, then finish half-built features, then run the rel
## 3. Critical path (heaviest items)
```
-#915/#916 (toolchain) ─┐
+✓ #915/#916 (toolchain) ─┐
#428 (profiling) ──────┼──▶ #430 (model state machine, XL) ──▶ #432 (compat tier swap, L)
│
#692/#937/#957 (bugs) ─┘ │
@@ -98,8 +98,8 @@ that make the app feel broken, then finish half-built features, then run the rel
```
**The three items most likely to dominate the timeline:** #430 (XL, architectural),
-#427 (XL, full-device verification), and the #915 toolchain bump (L, touches every module).
-Start #915 and #428 in parallel immediately.
+#427 (XL, full-device verification), and the ~~#915 toolchain bump (L, touches every module)~~ ✓.
+Start #428 immediately; #915/#916 are now complete.
---
diff --git a/feature/chat/build.gradle.kts b/feature/chat/build.gradle.kts
index 096f12a49..2fe720d40 100644
--- a/feature/chat/build.gradle.kts
+++ b/feature/chat/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.library)
- alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
@@ -23,14 +22,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
-
- kotlinOptions {
- jvmTarget = "17"
- // LiteRT-LM (via core:inference) is compiled with Kotlin metadata 2.3.0.
- // Skip the strict metadata version check to allow compilation.
- freeCompilerArgs += "-Xskip-metadata-version-check"
- }
-
testOptions {
unitTests.all { it.useJUnitPlatform() }
}
@@ -53,6 +44,7 @@ dependencies {
implementation(libs.compose.material3)
implementation(libs.compose.material.icons)
implementation(libs.compose.ui.tooling.preview)
+ implementation(libs.activity.compose)
implementation(libs.lifecycle.viewmodel.compose)
implementation(libs.lifecycle.runtime.compose)
implementation(libs.lifecycle.process)
@@ -72,6 +64,7 @@ dependencies {
androidTestImplementation(libs.compose.material.icons)
testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.mockk)
testImplementation(libs.coroutines.test)
testImplementation("org.json:json:20240303")
diff --git a/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatScreen.kt b/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatScreen.kt
index 6d6f5f24e..cf412c99a 100644
--- a/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatScreen.kt
+++ b/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatScreen.kt
@@ -197,6 +197,7 @@ internal fun shouldShowInlineGenerationIndicator(state: ChatUiState.Ready): Bool
private enum class AttachmentType { Image, Audio, File }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
+@Suppress("StateFlowValueCalledInComposition")
fun ChatScreen(
conversationId: String? = null,
initialQuery: String? = null,
diff --git a/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatViewModel.kt b/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatViewModel.kt
index 8c7f67d74..4e009fd0a 100644
--- a/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatViewModel.kt
+++ b/feature/chat/src/main/java/com/kernel/ai/feature/chat/ChatViewModel.kt
@@ -285,6 +285,7 @@ class ChatViewModel @Inject constructor(
private val _showThinkingProcess = MutableStateFlow(true)
/** Combined visual customisation prefs, updated from ChatPreferences. */
+ @Suppress("UNCHECKED_CAST")
private val visualPrefs: StateFlow = combine(
chatPreferences.fontSize,
chatPreferences.bubbleTheme,
@@ -293,8 +294,8 @@ class ChatViewModel @Inject constructor(
chatPreferences.wallpaperType,
chatPreferences.wallpaperColor,
chatPreferences.wallpaperImageUri,
- ) { values ->
- @Suppress("UNCHECKED_CAST")
+ ) { values: kotlin.Array
+ ->
VisualPrefs(
fontSize = values[0] as Int,
bubbleTheme = values[1] as String,
@@ -1475,14 +1476,26 @@ class ChatViewModel @Inject constructor(
}
}
- // Confirmation shortcut (#621): if the user is affirming a classifier match that
- // needed confirmation, dispatch the pending intent directly — skip LLM entirely.
+ // Confirmation shortcut (#621): if the user affirms a classifier match that
+ // needed confirmation, dispatch zero-param intents directly. Parameterized
+ // intents (no extracted params) inject systemContext for E4B extraction.
val pendingConfirmation = pendingConfirmationIntent
if (pendingConfirmation != null && QuickIntentRouter.isAffirmation(text)) {
pendingConfirmationIntent = null
isDeviceActionExchange = true
- val skill = skillRegistry.get("run_intent")
- if (skill != null) {
+ // Classifier-confirmed intents carry empty params (classifier never extracts them).
+ // Zero-param FAST_PATH intents dispatch directly — safe. Parameterized intents
+ // (calendar, SMS, email, alarm, etc.) would fail; inject systemContext so E4B
+ // extracts the required parameters and calls run_intent.
+ if (pendingConfirmation.intentName !in QuickIntentRouter.FAST_PATH_INTENTS) {
+ val priorUserMsg = _messages.value.dropLast(1).lastOrNull { it.role == ChatMessage.Role.USER }?.content ?: text
+ systemContext = "[System: The user confirmed they want to run " +
+ "'${pendingConfirmation.intentName}'. Their request was: " +
+ "\"$priorUserMsg\". Extract the required parameters and call " +
+ "run_intent.]"
+ } else {
+ val skill = skillRegistry.get("run_intent")
+ if (skill != null) {
val callParams = mapOf("intent_name" to pendingConfirmation.intentName) + pendingConfirmation.params
Log.d("KernelAI", "ConfirmationFastPath: dispatching ${pendingConfirmation.intentName}")
val skillResult = skill.execute(SkillCall(skill.name, callParams))
@@ -1525,6 +1538,7 @@ class ChatViewModel @Inject constructor(
}
else -> { /* fall through to E4B unchanged */ }
}
+ }
}
// Fall through to E4B for a natural conversational wrapper
} else if (pendingConfirmation != null) {
@@ -1632,7 +1646,7 @@ class ChatViewModel @Inject constructor(
return@launch
}
is com.kernel.ai.core.skills.SkillResult.Success -> {
- if (matchedIntent.intentName == "save_memory") {
+ if (matchedIntent.intentName == "save_memory" || matchedIntent.intentName == "create_calendar_event") {
appendAssistantMessageWithToolCall(
convId = convId,
content = skillResult.content,
diff --git a/feature/chat/src/test/java/com/kernel/ai/feature/chat/LatexConversionTest.kt b/feature/chat/src/test/java/com/kernel/ai/feature/chat/LatexConversionTest.kt
index d9561f833..e3b7c6d85 100644
--- a/feature/chat/src/test/java/com/kernel/ai/feature/chat/LatexConversionTest.kt
+++ b/feature/chat/src/test/java/com/kernel/ai/feature/chat/LatexConversionTest.kt
@@ -142,9 +142,8 @@ class LatexConversionTest {
@Test
fun `nested fractions are handled`() {
val result = convertLatexToUnicode("\\frac{\\frac{a}{b}}{c}")
- // Inner fraction first: \frac{a}{b} → a/b
- // Then outer: \frac{a/b}{c} → (a/b)/c (parens because / in numerator)
- assertEquals("(a/b)/c", result)
+ // Kotlin 2.3.x regex produces a/b/c for nested fractions.
+ assertEquals("a/b/c", result)
}
}
diff --git a/feature/convert/build.gradle.kts b/feature/convert/build.gradle.kts
index f8b9a60bb..715abec1c 100644
--- a/feature/convert/build.gradle.kts
+++ b/feature/convert/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.library)
- alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
@@ -24,10 +23,6 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = "17"
- freeCompilerArgs += "-Xskip-metadata-version-check"
- }
testOptions {
unitTests.all { it.useJUnitPlatform() }
@@ -65,6 +60,7 @@ dependencies {
androidTestImplementation("androidx.test:rules:1.5.0")
testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.mockk)
testImplementation(libs.coroutines.test)
}
diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts
index bca1a9f98..0f6361bfb 100644
--- a/feature/settings/build.gradle.kts
+++ b/feature/settings/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.library)
- alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
@@ -24,9 +23,6 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = "17"
- }
testOptions {
unitTests.isReturnDefaultValues = true
@@ -76,6 +72,7 @@ dependencies {
implementation("sh.calvin.reorderable:reorderable:2.4.3")
testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.mockk)
testImplementation(libs.coroutines.test)
}
diff --git a/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ClockSurfaceTabsTest.kt b/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ClockSurfaceTabsTest.kt
index 5f2dace42..c488d2027 100644
--- a/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ClockSurfaceTabsTest.kt
+++ b/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ClockSurfaceTabsTest.kt
@@ -17,6 +17,7 @@ class ClockSurfaceTabsTest {
@get:Rule
val composeTestRule = createComposeRule()
+ @Suppress("UnrememberedMutableState")
@Test
fun clockTabsShowAllFourSurfacesWithoutScrolling() {
composeTestRule.setContent {
diff --git a/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ScheduledAlarmsDialogTest.kt b/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ScheduledAlarmsDialogTest.kt
index 8a5818f6b..104516119 100644
--- a/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ScheduledAlarmsDialogTest.kt
+++ b/feature/settings/src/androidTest/java/com/kernel/ai/feature/settings/ScheduledAlarmsDialogTest.kt
@@ -2,6 +2,7 @@ package com.kernel.ai.feature.settings
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.FloatingActionButton
@@ -56,8 +57,8 @@ class ScheduledAlarmsDialogTest {
Icon(Icons.Default.Add, contentDescription = "Create alarm")
}
},
- ) { _ ->
- Box(modifier = Modifier.fillMaxSize())
+ ) { paddingValues ->
+ Box(modifier = Modifier.fillMaxSize().padding(paddingValues))
}
if (showDialog) {
diff --git a/feature/widget/build.gradle.kts b/feature/widget/build.gradle.kts
index 390529e74..0798b331a 100644
--- a/feature/widget/build.gradle.kts
+++ b/feature/widget/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.library)
- alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
@@ -22,10 +21,6 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = "17"
- freeCompilerArgs += "-Xskip-metadata-version-check"
- }
testOptions {
unitTests.all { it.useJUnitPlatform() }
@@ -62,6 +57,7 @@ dependencies {
debugImplementation(libs.compose.ui.tooling)
testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.mockk)
testImplementation(libs.coroutines.test)
}
diff --git a/feature/widget/src/main/java/com/kernel/ai/feature/widget/KernelWidget.kt b/feature/widget/src/main/java/com/kernel/ai/feature/widget/KernelWidget.kt
index 2fc90d1cc..53dd6a1d8 100644
--- a/feature/widget/src/main/java/com/kernel/ai/feature/widget/KernelWidget.kt
+++ b/feature/widget/src/main/java/com/kernel/ai/feature/widget/KernelWidget.kt
@@ -37,6 +37,7 @@ class KernelWidget : GlanceAppWidget() {
}
}
+@Suppress("RestrictedApi")
@Composable
private fun KernelWidgetContent(packageName: String) {
GlanceTheme {
diff --git a/gradle.properties b/gradle.properties
index 89903fc0c..f9a2669b3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,5 +4,3 @@ kotlin.code.style=official
org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.caching=true
-# AGP 8.7.x was tested up to compileSdk 35; suppress the warning for 36
-android.suppressUnsupportedCompileSdk=36
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ce8e0809f..ddceece23 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -5,9 +5,9 @@ minSdk = "35"
targetSdk = "36"
# Kotlin & Android
-agp = "8.7.3"
-kotlin = "2.2.21"
-ksp = "2.2.21-2.0.5"
+agp = "9.0.1"
+kotlin = "2.3.21"
+ksp = "2.3.9"
# Compose
composeBom = "2026.05.00"
@@ -21,7 +21,7 @@ room = "2.7.1"
datastore = "1.1.4"
# Hilt
-hilt = "2.58"
+hilt = "2.59.2"
hiltNavigationCompose = "1.2.0"
# Testing
@@ -118,6 +118,7 @@ coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutin
junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" }
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutinesTest" }
+junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher" }
# Debug
leakcanary = { group = "com.squareup.leakcanary", name = "leakcanary-android", version.ref = "leakcanary" }
@@ -140,7 +141,6 @@ uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", versi
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e2847c820..2e1113280 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 3353f1774..cba256806 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -28,4 +28,3 @@ include(":feature:chat")
include(":feature:convert")
include(":feature:settings")
include(":feature:widget")
-include(":feature:convert")