diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml
index 7bc1f18..d980a56 100644
--- a/.github/workflows/gradle-wrapper-validation.yml
+++ b/.github/workflows/gradle-wrapper-validation.yml
@@ -15,5 +15,5 @@ jobs:
- name: Checkout latest code
uses: actions/checkout@v4.1.1
- name: Validate Gradle Wrapper
- uses: gradle/wrapper-validation-action@v1
+ uses: gradle/actions/wrapper-validation@v4
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index c224ad5..739bc36 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,7 @@
-
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 5e3bc86..668a0c8 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,4 +1,5 @@
import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.konan.properties.Properties
plugins {
@@ -18,7 +19,7 @@ android {
defaultConfig {
minSdk = libs.versions.min.sdk.version.get().toInt()
- targetSdk = libs.versions.compile.sdk.version.get().toInt()
+ targetSdk = libs.versions.target.sdk.version.get().toInt()
namespace = "dev.simonas.quies"
applicationId = AppCoordinates.APP_ID
@@ -38,13 +39,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
- composeOptions {
- kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
- }
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_21.toString()
- freeCompilerArgs = listOf("-Xcontext-receivers")
- }
signingConfigs {
getByName("debug") {
keyAlias = "androiddebugkey"
@@ -105,6 +99,13 @@ android {
}
}
+kotlin {
+ compilerOptions {
+ jvmTarget = JvmTarget.JVM_21
+ freeCompilerArgs.add("-Xcontext-receivers")
+ }
+}
+
dependencies {
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat)
@@ -127,6 +128,7 @@ dependencies {
implementation(libs.hilt.navigation.compose)
implementation(libs.javapoet)
implementation(libs.kotlin.math)
+ implementation(platform(libs.compose.bom))
implementation(platform(libs.firebase.bom))
implementation(project(":data"))
implementation(libs.datastore)
@@ -147,6 +149,7 @@ dependencies {
testImplementation(libs.turbine)
testImplementation(libs.truth)
+ androidTestImplementation(platform(libs.compose.bom))
androidTestImplementation(libs.hilt.android)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.ext.junit.ktx)
diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
index df51d81..30320c5 100644
--- a/app/lint-baseline.xml
+++ b/app/lint-baseline.xml
@@ -1,4 +1,103 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/androidTest/java/dev/simonas/quies/UITest.kt b/app/src/androidTest/java/dev/simonas/quies/UITest.kt
index 1a0e200..c4bfa6f 100644
--- a/app/src/androidTest/java/dev/simonas/quies/UITest.kt
+++ b/app/src/androidTest/java/dev/simonas/quies/UITest.kt
@@ -11,7 +11,6 @@ import dev.simonas.quies.storage.Store
import dev.simonas.quies.utils.instrumentationExec
import javax.inject.Inject
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineDispatcher
import org.junit.Before
import org.junit.Rule
import org.junit.rules.RuleChain
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index 033156d..b4e9aee 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -14,12 +14,6 @@ dependencies {
implementation(libs.javapoet)
}
-tasks.withType().configureEach {
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_21.toString()
- }
-}
-
kotlin {
jvmToolchain(21)
}
\ No newline at end of file
diff --git a/data/build.gradle.kts b/data/build.gradle.kts
index 2c78e55..39f9b78 100644
--- a/data/build.gradle.kts
+++ b/data/build.gradle.kts
@@ -1,4 +1,3 @@
-@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
id("com.android.library")
kotlin("android")
@@ -13,7 +12,6 @@ android {
minSdk = libs.versions.min.sdk.version.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- consumerProguardFiles("consumer-rules.pro")
externalNativeBuild {
cmake {
cppFlags += ""
diff --git a/gradle.properties b/gradle.properties
index ac26ecb..4bee7aa 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -7,7 +7,15 @@ org.gradle.caching=true
org.gradle.vfs.watch=true
android.useAndroidX=true
-# Automatically convert third-party libraries to use AndroidX
-android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
+android.defaults.buildfeatures.resvalues=true
+android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
+android.enableAppCompileTimeRClass=false
+android.usesSdkInManifest.disallowed=false
+android.uniquePackageNames=false
+android.dependency.useConstraints=true
+android.r8.strictFullModeForKeepRules=false
+android.r8.optimizedResourceShrinking=false
+android.builtInKotlin=false
+android.newDsl=false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index faf8bbc..6d4d30d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,24 +1,23 @@
[versions]
-agp = "8.13.0"
-androidx_activity_compose = "1.8.0"
-androidx_test = "1.5.0"
-androidx_test_ext = "1.1.5"
-appcompat = "1.6.1"
+agp = "9.2.1"
+androidx_activity_compose = "1.13.0"
+androidx_test = "1.7.0"
+androidx_test_ext = "1.3.0"
+appcompat = "1.7.1"
benmanesversion = "0.47.0"
colormath = "3.3.2"
colormath-ext-jetpack-compose = "3.3.2"
-compile_sdk_version = "36"
-compose-lifecycle = "2.6.2"
-compose-navigation = "2.7.5"
-compose_compiler = "1.5.4"
-compose_material3 = "1.1.2"
+compile_sdk_version = "37"
+compose-lifecycle = "2.10.0"
+compose-navigation = "2.9.8"
+compose_bom = "2026.05.00"
constraint_layout = "2.1.4"
-core_ktx = "1.10.1"
-coroutines = "1.7.3"
+core_ktx = "1.18.0"
+coroutines = "1.10.2"
datastore = "1.1.1"
datastore-preferences = "1.1.1"
-detekt = "1.23.1"
-espresso_core = "3.5.1"
+detekt = "1.23.8"
+espresso_core = "3.7.0"
firebase_analytics = "22.0.0"
firebase_bom = "33.0.0"
firebase_crashlytics = "19.0.0"
@@ -28,22 +27,21 @@ hilt = "2.56.2"
hilt-navigation-compose = "1.2.0"
javapoet = "1.13.0"
junit = "4.13.2"
-junit-jupiter = "5.8.1"
+junit-jupiter = "5.11.4"
kaml = "0.55.0"
kotlin = "2.2.20"
kotlin-math = "1.5.3"
-ksp = "2.2.20-2.0.2"
+ksp = "2.3.2"
ktlint_gradle = "11.5.0"
min_sdk_version = "28"
-mixpanel-android = "7.5.2"
+mixpanel-android = "8.2.1"
mockito-android = "5.4.0"
mockito-kotlin = "5.1.0"
play_publisher = "3.9.1"
simple_flank = "0.4.0"
-target_sdk_version = "35"
+target_sdk_version = "37"
truth = "1.1.5"
turbine = "1.0.0"
-org-jetbrains-kotlin-jvm = "1.8.0"
material = "1.12.0"
[libraries]
@@ -59,13 +57,14 @@ androidx_test_runner = { module = "androidx.test:runner", version.ref = "android
colormath = { module = "com.github.ajalt.colormath:colormath", version.ref = "colormath" }
colormath-ext-jetpack-compose = { module = "com.github.ajalt.colormath:colormath-ext-jetpack-compose", version.ref = "colormath-ext-jetpack-compose" }
compose-navigation = { module = "androidx.navigation:navigation-compose", version.ref = "compose-navigation" }
-compose_foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose_compiler" }
+compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" }
+compose_foundation = { module = "androidx.compose.foundation:foundation" }
compose_lifecycle = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "compose-lifecycle" }
-compose_material3 = { module = "androidx.compose.material3:material3", version.ref = "compose_material3" }
-compose_ui = { module = "androidx.compose.ui:ui", version.ref = "compose_compiler" }
-compose_ui_test_junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose_compiler" }
-compose_ui_test_manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose_compiler" }
-compose_ui_tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose_compiler" }
+compose_material3 = { module = "androidx.compose.material3:material3" }
+compose_ui = { module = "androidx.compose.ui:ui" }
+compose_ui_test_junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
+compose_ui_test_manifest = { module = "androidx.compose.ui:ui-test-manifest" }
+compose_ui_tooling = { module = "androidx.compose.ui:ui-tooling" }
coroutines_android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
coroutines_core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines_test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
@@ -104,7 +103,6 @@ ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint.gradle" }
play_publisher = { id = "com.github.triplet.play", version.ref = "play_publisher" }
simple_flank = { id = "io.github.flank.gradle.simple-flank", version.ref = "simple_flank" }
versions = { id = "com.github.ben-manes.versions", version.ref = "benmanesversion" }
-org-jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "org-jetbrains-kotlin-jvm" }
com-android-library = { id = "com.android.library", version.ref = "agp" }
-kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version = "1.9.0" }
+kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 033e24c..d997cfc 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d41cb63..c61a118 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,6 @@
-#Sun Mar 03 13:55:21 EET 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index fcb6fca..739907d 100755
--- a/gradlew
+++ b/gradlew
@@ -1,7 +1,7 @@
#!/bin/sh
#
-# Copyright © 2015-2021 the original authors.
+# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# SPDX-License-Identifier: Apache-2.0
+#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -83,7 +85,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -111,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -144,7 +146,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC3045
+ # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -152,7 +154,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC3045
+ # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -169,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
- CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -201,16 +202,15 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-# Collect all arguments for the java command;
-# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-# shell script including quotes and variable substitutions, so put them in
-# double quotes to make sure that they get re-expanded; and
-# * put everything else in single quotes, so that it's not re-expanded.
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
- -classpath "$CLASSPATH" \
- org.gradle.wrapper.GradleWrapperMain \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
diff --git a/gradlew.bat b/gradlew.bat
index 6689b85..e509b2d 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -57,22 +59,21 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/maintenance/01-stale-dependencies.md b/maintenance/01-stale-dependencies.md
new file mode 100644
index 0000000..48178f3
--- /dev/null
+++ b/maintenance/01-stale-dependencies.md
@@ -0,0 +1,107 @@
+# Section 1 — Stale Dependencies
+
+## 1. Goal
+
+Bring the version catalog at `gradle/libs.versions.toml` in line with current stable releases of AndroidX, Compose-adjacent, Kotlin coroutines, JUnit/test, detekt, and SDK libraries. Out-of-date pins predate the project's Kotlin 2.2.20 / AGP 8.13 / JDK 21 baseline and are blocking idiomatic APIs (typed Navigation, `collectAsStateWithLifecycle`, predictive back, Material3 1.4 components). Each batch is shippable independently and verifiable via existing gradle tasks. No Compose BOM rework, no CI/Gradle wrapper changes — those land in later sections.
+
+## 2. Inventory
+
+| Catalog key | Current pin | Latest stable (May 2026) | Risk / notes |
+|---|---|---|---|
+| `androidx_activity_compose` | 1.8.0 | **1.13.0** | Source-breaking: `ActivityResultLauncher.getContract` is now an abstract Kotlin property. `enableEdgeToEdge()` re-invoked on config changes (1.13). New `PredictiveBackHandler` composable. Pulls in `core:core-pip`. |
+| `appcompat` | 1.6.1 | **1.7.1** | Patch-only risk. 1.7.0 required Activity 1.8 (already satisfied post-bump). 1.7.1 fixes `AppCompatActivity` × NavigationEvent interaction. |
+| `core_ktx` | 1.10.1 | **1.18.0** | Multi-major jump. From 1.19.0-alpha onward `core-ktx` becomes an empty alias for `core`; staying on stable 1.18 sidesteps that. Watch for tightened nullability in `Bundle`/`Intent` extensions. |
+| `compose-lifecycle` (lifecycle-runtime-compose) | 2.6.2 | **2.10.0** | minSdk floor moves to **API 23** (project is 28 — fine). 2.7+ promoted `collectAsStateWithLifecycle` to stable; 2.8 added KMP source set; 2.10 adds `rememberLifecycleOwner`. ViewModel `KClass` overloads added. |
+| `compose-navigation` | 2.7.5 | **2.9.8** | Major: 2.8 introduced type-safe `@Serializable` routes (additive — string routes still work). 2.8.1 closed an implicit deeplink security hole — verify any deeplinks declare `` explicitly. 2.8.x **requires Compose runtime ≥ 1.7.2** (handled by Section 2's BOM). |
+| `compose_material3` | 1.1.2 | **1.4.0** | Largest visual-risk bump. 1.2 typography line-height adjustments, 1.3 added new components, 1.4 has `MaterialTheme.LocalMaterialTheme.current` access path; checkbox sizing/colors realigned to spec (flag `isCheckboxStylingFixEnabled` available). Removed deprecated `DropdownMenuItem` overload, deprecated `FilterChip`/`AssistChip` `horizontalSpacing`. UI smoke required. |
+| `coroutines` | 1.7.3 | **1.10.2** *(verify)* | 1.8 changed `Dispatchers.Main` initialisation (immediate-by-default) and removed several deprecated APIs; `runBlockingTest` deprecated path removed; new `kotlinx-coroutines-test` `runTest` semantics (timeout default 60s). 1.9+ requires Kotlin 2.0+. Inspect tests using `runTest` / `TestCoroutineScheduler`. |
+| `detekt` | 1.23.1 | **1.23.8** *(verify)* | 1.23.x is the current stable stream; 2.x is in beta. Stay on 1.23.8 for Kotlin 2.2 compiler compat (1.23.1 predates Kotlin 2.0). **`detekt-formatting` plugin must move in lockstep** — already shares `detekt` version.ref. |
+| `androidx_test` | 1.5.0 | **1.7.0** | minSdk floor 21 (fine). Internal Kotlin upgrade to 1.9. |
+| `androidx_test_ext` | 1.1.5 | **1.3.0** | Aligns with `androidx_test` 1.7.0. |
+| `espresso_core` | 3.5.1 | **3.7.0** | Robolectric+Espresso deadlock fix. Move with the test bundle. |
+| `junit-jupiter` | 5.8.1 | **5.11.x** *(verify)* | 5.10 dropped the legacy vintage runner default; AGP 8.13 supports JUnit5 platform natively. Confirm test discovery still works (`testDebugUnitTest`). Currently only consumed in `app` test source set. |
+| `mixpanel-android` | 7.5.2 | **8.x** *(verify)* | Major bump. 8.x changed init API (`MixpanelAPI.getInstance(...)` signature added trackAutomaticEvents bool), and dropped some legacy endpoints. Audit every callsite before bumping. |
+| `org-jetbrains-kotlin-jvm` (version) | 1.8.0 | **DELETE** | Confirmed dead: no module applies `org.jetbrains.kotlin.jvm`. Remove both the `[versions]` entry (line 46) and the `[plugins]` alias (line 107). Leftover from kotlin-android-template. |
+| `kotlinx-serialization` plugin | hardcoded 1.9.0 | track `kotlin` version | Used by `data/build.gradle.kts`. Plugin coordinate `org.jetbrains.kotlin.plugin.serialization` ships with the Kotlin compiler — must equal Kotlin version. Change line 109 to `version.ref = "kotlin"`. |
+
+Cross-reference (NOT in scope): `compose_foundation`, `compose_ui`, `compose_ui_tooling`, `compose_ui_test_junit4`, `compose_ui_test_manifest` all incorrectly point at `version.ref = "compose_compiler"` (1.5.4). Section 2 replaces these with the Compose BOM.
+
+## 3. Sequencing
+
+Six batches, intended to land as separate PRs. Each is independently revertible.
+
+**Batch A — Catalog hygiene (zero runtime change).**
+- Delete `org-jetbrains-kotlin-jvm` version + plugin entries.
+- Repoint `kotlinx-serialization` plugin to `version.ref = "kotlin"`.
+- Justification: pure cleanup, no library bytecode shifts. Land first to de-noise diffs.
+
+**Batch B — Detekt toolchain.**
+- `detekt` 1.23.1 → 1.23.8 (auto-propagates to `detekt-formatting`).
+- Justification: build-tool only; no app code change. Validates the 2.2 Kotlin compiler is honoured by detekt's parser.
+
+**Batch C — Foundational AndroidX (no Compose API surface).**
+- `appcompat` 1.6.1 → 1.7.1
+- `core_ktx` 1.10.1 → 1.18.0
+- `androidx_activity_compose` 1.8.0 → 1.13.0
+- Justification: `appcompat` 1.7+ pulls Activity ≥ 1.8 transitively; bumping all three at once avoids resolved-version surprises. None are Compose-runtime-pinned.
+
+**Batch D — Coroutines.**
+- `coroutines` 1.7.3 → latest 1.10.x stable.
+- Justification: behavioural changes around `Dispatchers.Main.immediate` and `runTest` warrant isolating from UI library bumps so any test failures are unambiguously attributable.
+
+**Batch E — Compose-adjacent (NOT Compose UI itself — that's Section 2).**
+- `compose-lifecycle` 2.6.2 → 2.10.0
+- `compose-navigation` 2.7.5 → 2.9.8
+- `compose_material3` 1.1.2 → 1.4.0
+- Justification: these three change Compose-rendered surfaces. **Compat constraint**: navigation 2.8.x requires Compose runtime ≥ 1.7.2. The current `compose_compiler = 1.5.4` references resolve to Compose UI 1.5.4 — Section 2's BOM will fix this. **Hold this batch until Section 2 lands**, OR temporarily pin Compose libraries to ≥ 1.7.2 explicitly. Material3 1.4 also wants Compose ≥ 1.7. Note this dependency in the PR description.
+
+**Batch F — Test stack.**
+- `androidx_test` 1.5.0 → 1.7.0
+- `androidx_test_ext` 1.1.5 → 1.3.0
+- `espresso_core` 3.5.1 → 3.7.0
+- `junit-jupiter` 5.8.1 → 5.11.x
+- Justification: AndroidX test artifacts share a release train and must move together. JUnit 5 lumps in here because it only affects the same source sets.
+
+**Batch G — Mixpanel (independent vendor).**
+- `mixpanel-android` 7.5.2 → 8.x.
+- Justification: vendor SDK with non-trivial init API change. Land last and behind a manual verification build (analytics events visible in Mixpanel UI debug view).
+
+Ordering rationale: A → B → C → D → F → E → G. E waits on Section 2 unless explicit Compose pins are added; G is last because runtime regressions only show in release telemetry.
+
+## 4. Per-batch verification
+
+Common gates for every batch:
+```
+./gradlew detekt lint testDebugUnitTest assembleDebug
+```
+Plus `./gradlew dependencies --configuration releaseRuntimeClasspath | grep -i ` to confirm resolved version matches the catalog (no transitive override surprises).
+
+Per-batch additions:
+
+- **A (catalog hygiene)**: `./gradlew help` to confirm the catalog still parses; `./gradlew :data:tasks` confirms serialization plugin still resolves.
+- **B (detekt)**: `./gradlew detekt` on every module; spot-check that `config/detekt/detekt.yml` rule IDs all still exist (run with `--build-upon-default-config` if needed).
+- **C (foundational AndroidX)**: full `./gradlew lint` (lint-baseline diffs are the canary); manual smoke: launch debug build, exercise back gesture (predictive back from Activity 1.13), edge-to-edge insets on a Pixel emulator API 33+.
+- **D (coroutines)**: `./gradlew testDebugUnitTest` with `-Pkotlinx.coroutines.test.default_timeout=60s` if any test hangs; audit any `Dispatchers.Main.immediate` assumptions; grep for `runBlockingTest` (deprecated) and `TestCoroutineDispatcher` (removed).
+- **E (Compose-adjacent)**: full instrumented run via Flank: `./gradlew :app:runFlank`; UI smoke on every top-level screen for typography/spacing regressions (Material3 1.2 line-height, 1.4 checkbox); navigate every nav graph entry to confirm string routes still resolve.
+- **F (test stack)**: `./gradlew testDebugUnitTest connectedDebugAndroidTest`; if connected tests are skipped locally, run Flank.
+- **G (mixpanel)**: build debug, exercise tracked flows, verify event arrival in Mixpanel debug/live view; confirm `MixpanelAPI.getInstance` callsite signature matches 8.x.
+
+## 5. Risks & rollback
+
+Migration gotchas to anticipate:
+
+- **Material3 1.1 → 1.4**: typography line-height changes (1.2), checkbox/indicator size (1.4 — flagged via `isCheckboxStylingFixEnabled`), removed deprecated chip params. UI-test screenshot diffs likely; budget time for visual review.
+- **Coroutines 1.8+**: `Dispatchers.Main` is now `Main.immediate` by default in some contexts; tests that asserted dispatcher ordering may flake. `runBlockingTest` removed — migrate to `runTest`.
+- **Lifecycle 2.7+**: `collectAsStateWithLifecycle` is the canonical replacement for `collectAsState` for cold flows; current code likely still uses `collectAsState` — bump does not force a rewrite, but lint may warn.
+- **Navigation 2.8.1 deeplink security fix**: any implicit deep links not declared via `` will stop matching. Audit `AndroidManifest.xml` and `composable(...)` deeplink params.
+- **Activity 1.13**: source-breaking change to `ActivityResultLauncher.getContract` for any Kotlin code that overrides it.
+- **Mixpanel 8.x**: init signature changed; missed callsite = silent telemetry loss in release builds.
+
+Rollback per batch: each batch is a single PR/commit touching only `gradle/libs.versions.toml` (plus, for Batch G/E, possibly a few callsites). Revert is `git revert `. Renovate's automerge is scoped to minor — if Renovate races a manual major bump, lock the major bump behind a `packageRules` entry in `renovate.json` for that batch's duration (note: editing `renovate.json` is a Section 4 concern; for this section, just revert the catalog entry).
+
+## 6. Out of scope
+
+- Compose BOM adoption and replacement of `version.ref = "compose_compiler"` on `compose_foundation`/`compose_ui`/`compose_ui_tooling`/`compose_ui_test_*` → **Section 2**.
+- GitHub Actions runner versions, action SHAs, JDK setup → **Section 3**.
+- `gradle-wrapper.properties` Gradle version, `renovate.json` rules, Ben-Manes `dependencyUpdates` config → **Section 4**.
+- Firebase BOM, Hilt, KSP, AGP, Kotlin compiler, `kaml`, `colormath`, `datastore`, `kotlin-math`, `truth`, `turbine`, `mockito-*`, `simple_flank`, `play_publisher`, `ktlint_gradle`, `benmanesversion`, `material` (Google MDC) — these are either current or covered elsewhere. Material (`com.google.android.material:material` 1.12.0) is current stable; do not touch.
diff --git a/maintenance/02-build-config.md b/maintenance/02-build-config.md
new file mode 100644
index 0000000..3b71f3a
--- /dev/null
+++ b/maintenance/02-build-config.md
@@ -0,0 +1,247 @@
+# Section 2 — Build-config maintenance
+
+## Goal
+
+Clean up build-config drift in `gradle/libs.versions.toml`, `app/build.gradle.kts`, `data/build.gradle.kts`, `buildSrc/build.gradle.kts`, and `gradle.properties`. The work removes obsolete suppressions, dead version aliases, deprecated DSL, and incorrectly-pinned Compose library versions. Ordering favours behaviour-neutral cleanups first; the Compose BOM migration lands last because it actually changes the bytecode shipped to users.
+
+---
+
+## 1. Compose libraries pinned to compiler version → Compose BOM
+
+**Problem.** `compose_foundation`, `compose_ui`, `compose_ui_tooling`, `compose_ui_test_junit4`, `compose_ui_test_manifest` all reference `version.ref = "compose_compiler"` (= `1.5.4`). The Kotlin Compose compiler plugin and Compose runtime/UI libraries are decoupled since Kotlin 2.0; the app is currently shipping Compose UI **1.5.4** (released Oct 2023) on Kotlin 2.2.20.
+
+**Files to change.**
+- `gradle/libs.versions.toml:13` (drop `compose_compiler`), `:62`, `:65`, `:66`, `:67`, `:68` (rewrite library entries).
+- `app/build.gradle.kts:108–160` (dependencies block; add `platform(libs.compose.bom)` and androidTest counterpart).
+
+**Concrete change.**
+
+`gradle/libs.versions.toml` — add a single BOM coordinate, keep `compose_material3` versioned only if you intentionally want a different track (BOM `2026.05.00` ships material3 `1.4.0`, so prefer dropping the explicit pin):
+
+```toml
+# [versions]
+compose_bom = "2026.05.00"
+# remove: compose_compiler = "1.5.4"
+# remove: compose_material3 = "1.1.2" (let BOM manage it)
+
+# [libraries]
+compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" }
+compose_foundation = { module = "androidx.compose.foundation:foundation" }
+compose_material3 = { module = "androidx.compose.material3:material3" }
+compose_ui = { module = "androidx.compose.ui:ui" }
+compose_ui_tooling = { module = "androidx.compose.ui:ui-tooling" }
+compose_ui_test_junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
+compose_ui_test_manifest = { module = "androidx.compose.ui:ui-test-manifest" }
+```
+
+`app/build.gradle.kts` — add the BOM platform under `dependencies` next to the Firebase BOM (line 130):
+
+```kotlin
+implementation(platform(libs.compose.bom))
+androidTestImplementation(platform(libs.compose.bom))
+```
+
+No other dependency lines change — the unversioned coordinates in the catalog resolve through the BOM.
+
+**Verification.**
+- `./gradlew :app:dependencies --configuration releaseRuntimeClasspath | grep "androidx.compose"` — every Compose artifact should show `1.11.1` (or whatever the BOM resolves to), not `1.5.4`.
+- `./gradlew :app:assembleDebug` succeeds.
+- `./gradlew :app:lintDebug` — Compose lint should not regress.
+- Smoke-run the app; visually confirm onboarding screens, navigation, and material3 surfaces still render.
+
+---
+
+## 2. Remove dead `composeOptions { kotlinCompilerExtensionVersion = … }`
+
+**Problem.** Once `org.jetbrains.kotlin.plugin.compose` is applied (it is — `gradle/libs.versions.toml:110`, `app/build.gradle.kts:12`), AGP ignores `composeOptions.kotlinCompilerExtensionVersion`. The Compose compiler version is governed by the Kotlin version.
+
+**Files to change.** `app/build.gradle.kts:41–43`.
+
+**Concrete change.**
+
+```kotlin
+// before (lines 41-43)
+composeOptions {
+ kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
+}
+// after
+// (delete the whole block)
+```
+
+The `compose_compiler` version entry is already removed in item 1.
+
+**Verification.**
+- `./gradlew :app:assembleDebug` — build succeeds.
+- `./gradlew :app:tasks` — no warnings about `kotlinCompilerExtensionVersion` being ignored.
+
+---
+
+## 3. Disable Jetifier
+
+**Problem.** `android.enableJetifier=true` rewrites every transitive jar at build time to translate `android.support.*` → `androidx.*`. Every dependency in this project is AndroidX-native; Jetifier is dead weight.
+
+**Files to change.** `gradle.properties:11`.
+
+**Concrete change.**
+
+```properties
+# before
+android.enableJetifier=true
+# after
+# (delete line; android.useAndroidX=true on line 9 stays)
+```
+
+**Verification.**
+- Add the `com.github.plnice.canidropjetifier` plugin to root `build.gradle.kts` temporarily (or run as a one-shot init script), then `./gradlew canIDropJetifier`. Expect zero hits.
+- Alternative one-liner without adding a plugin: `./gradlew :app:dependencies | grep -i "support-"` — should return nothing referring to `com.android.support:*`.
+- Full clean build: `./gradlew clean :app:assembleRelease`.
+
+---
+
+## 4. `-Xcontext-receivers` → context parameters (or keep flag)
+
+**Problem.** `-Xcontext-receivers` is the experimental Kotlin 1.x flag. Kotlin 2.2 promotes the feature under a new flag, `-Xcontext-parameters`, with a slightly different declaration syntax. Specifying both flags is a compile error.
+
+**Files to change.** `app/build.gradle.kts:46`.
+
+**Usage audit (already run).**
+- `app/src/main/.../onboarding/OnboardingComponents.kt` — 8 `context(DrawScope)` declarations, including one on the public `interface CanvasComponent.draw(...)`.
+- `app/src/test/.../utils/TurbineKtx.kt:9` — `context(TestScope)` on `suspend inline fun Flow.testLast`.
+
+This is a real refactor (interface signature + every call site). **Recommend two-step:**
+
+**Step 4a (this pass — zero behavioural change).** Keep `-Xcontext-receivers` but stop putting it in `freeCompilerArgs` via the deprecated `kotlinOptions` block; move to the modern `kotlin { compilerOptions { … } }` DSL so the next refactor only edits one line.
+
+```kotlin
+// before (app/build.gradle.kts:44-47, inside android { })
+kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_21.toString()
+ freeCompilerArgs = listOf("-Xcontext-receivers")
+}
+
+// after — delete the block above; add at module top-level:
+kotlin {
+ compilerOptions {
+ jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
+ freeCompilerArgs.add("-Xcontext-receivers")
+ }
+}
+```
+
+**Step 4b (separate ticket, out of scope here).** Migrate the 9 declaration sites to context parameters (`context(scope: DrawScope)`), flip the flag to `-Xcontext-parameters`. IntelliJ has an assisted migration. Touches a public interface, so do it in isolation.
+
+**Verification.**
+- `./gradlew :app:compileDebugKotlin` — no deprecation warnings about `kotlinOptions`.
+- `./gradlew :app:compileReleaseKotlin --warning-mode all 2>&1 | grep -i context` — only the expected experimental-feature notice.
+
+---
+
+## 5. Drop dead `target_sdk_version` catalog entry
+
+**Problem.** `app/build.gradle.kts:21` and `data/build.gradle.kts:10` (compileSdk) both read `compile.sdk.version`; nothing references `target.sdk.version`.
+
+**Files to change.** `gradle/libs.versions.toml:43`.
+
+**Concrete change.**
+
+```toml
+# before
+target_sdk_version = "35"
+# after
+# (delete)
+```
+
+**Verification.**
+- `grep -rn "target.sdk.version\|target_sdk_version" .` returns nothing.
+- `./gradlew help` — catalog resolves cleanly.
+
+---
+
+## 6. Drop `@Suppress("DSL_SCOPE_VIOLATION")` and TODO
+
+**Problem.** KTIJ-19369 was resolved years ago; the suppression and TODO are noise.
+
+**Files to change.** `data/build.gradle.kts:1`.
+
+**Concrete change.**
+
+```kotlin
+// before
+@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
+plugins {
+// after
+plugins {
+```
+
+**Verification.**
+- `./gradlew :data:assembleDebug` succeeds.
+- IDE re-syncs without surfacing a DSL-scope warning.
+
+---
+
+## 7. `buildSrc` — drop `kotlinOptions` block
+
+**Problem.** `buildSrc/build.gradle.kts:17–21` configures `tasks.withType { kotlinOptions { jvmTarget = … } }` but `kotlin { jvmToolchain(21) }` (lines 23–25) already pins both source/target levels for compile tasks. The `kotlinOptions` DSL is deprecated.
+
+**Files to change.** `buildSrc/build.gradle.kts:17–21`.
+
+**Concrete change.**
+
+```kotlin
+// before
+tasks.withType().configureEach {
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_21.toString()
+ }
+}
+
+kotlin {
+ jvmToolchain(21)
+}
+// after — delete the tasks.withType block; keep:
+kotlin {
+ jvmToolchain(21)
+}
+```
+
+**Verification.**
+- `./gradlew help` — buildSrc still compiles, no deprecation warning.
+- `./gradlew :app:assembleDebug` builds (proves Hilt/AGP plugins from buildSrc still resolve correctly).
+
+---
+
+## Suggested order
+
+Land in two PRs.
+
+**PR 1 — risk-free cleanups (items 5, 6, 7, 2, 3 in that order).**
+1. Item 5 — delete unused `target_sdk_version` catalog entry.
+2. Item 6 — strip the `@Suppress` / TODO from `data/build.gradle.kts`.
+3. Item 7 — remove the deprecated `kotlinOptions` block from `buildSrc/build.gradle.kts`.
+4. Item 2 — delete the dead `composeOptions {}` block.
+5. Item 3 — disable Jetifier (after running `canIDropJetifier` audit).
+
+**PR 2 — behaviour-affecting changes (items 4a, 1).**
+6. Item 4a — migrate `kotlinOptions` block in `app/build.gradle.kts` to the `kotlin { compilerOptions {} }` DSL while keeping `-Xcontext-receivers`.
+7. Item 1 — Compose BOM migration. Highest risk: bumps Compose UI from 1.5.4 to 1.11.x and material3 from 1.1.2 to 1.4.0; ship after manual smoke-test.
+
+(Item 4b — context-parameters migration — is a separate refactor, tracked elsewhere.)
+
+---
+
+## Risks
+
+- **Compose 1.5.4 → 1.11.x (item 1) is a six-version jump.** Likely API surface deltas: `pullRefresh` graduations, `BasicTextField` rewrite, deprecated `NavHost` overloads, `LazyLayout` API shifts. Plan one cycle of manual smoke-testing onboarding, navigation, and any custom `DrawScope` work in `OnboardingComponents.kt`.
+- **material3 1.1.2 → 1.4.0** changes default tonal/elevation palettes; visual diffs in surfaces, buttons, dialogs are likely. Take screenshots before/after.
+- **Jetifier off (item 3)** — if any *runtime* (not compile-time) reflection-based dep references `android.support.*`, this can fail at runtime. Mitigate by running existing instrumentation tests post-change.
+- **`-Xcontext-receivers` removal accidentally tripped** during DSL migration in item 4a will break `OnboardingComponents.kt` and `TurbineKtx.kt` compilation. Build before committing.
+- **Compose compiler / Kotlin alignment** — once item 1 lands, Compose UI version is no longer cross-checked against the Kotlin Compose plugin in the catalog. Document in `README.md` or `TROUBLESHOOTING.md` that bumping Kotlin requires verifying Compose BOM compatibility.
+
+---
+
+## Out of scope
+
+- Section 1: stale dependency bumps (covered in `01-stale-dependencies.md`).
+- Section 3: CI/CD workflow changes (covered in `03-ci-workflows.md`).
+- Section 4: Gradle wrapper upgrade, AGP/Kotlin version bumps, Renovate config (covered in `04-misc.md`).
+- Item 4b: context-parameters source migration (separate refactor ticket).
diff --git a/maintenance/03-ci-workflows.md b/maintenance/03-ci-workflows.md
new file mode 100644
index 0000000..06a4384
--- /dev/null
+++ b/maintenance/03-ci-workflows.md
@@ -0,0 +1,77 @@
+# Plan: CI Workflows
+
+## 1. Goal
+
+Refresh the four GitHub Actions workflows under `.github/workflows/` to use currently-supported action versions, fix a copy-paste workflow name in `post-merge.yaml`, migrate off the deprecated `gradle/gradle-build-action` and standalone `gradle/wrapper-validation-action`, and clean up a no-op conditional. No behavioural changes to what gets built/tested/published.
+
+## 2. Per-file change list
+
+### 2.1 `.github/workflows/pre-merge.yaml`
+
+Diffs:
+- L14, L29: `actions/checkout@v4.1.1` → `actions/checkout@v4` (float to major). Alternative: pin to a full SHA; see Open Questions.
+- L16, L31: `actions/setup-java@v4.1.0` → `actions/setup-java@v4`.
+- L21, L36: `gradle/gradle-build-action@v3.1.0` → `gradle/actions/setup-gradle@v4` (action was renamed/relocated; v3 of the old action is the last release).
+- L11, L26: remove `if: ${{ !contains(github.event.head_commit.message, 'ci skip') }}` — `head_commit` is null on `pull_request` events, so this never triggers. See Open Questions for the alternative (use PR title).
+
+Side effects:
+- `gradle/actions/setup-gradle@v4` enables Gradle's built-in build cache reporting and uses a slightly different cache key scheme than `gradle-build-action@v3`. First run on the new action produces a cache miss; subsequent runs should hit. No config required — defaults are fine for this repo's two-step (`detekt lint`, then `compileX testDebugUnitTest flankRun`) usage.
+- `setup-gradle@v4` adds a job summary by default with build scan / cache stats. Harmless; can be silenced with `add-job-summary: 'never'` if noisy.
+
+### 2.2 `.github/workflows/post-merge.yaml`
+
+Diffs:
+- L1: `name: Pre Merge Checks` → `name: Post Merge Checks` (or `Validate Build`). User to pick — recommend `Post Merge Checks` for symmetry with `pre-merge.yaml`.
+- L14: `actions/checkout@v4.1.1` → `actions/checkout@v4`.
+- L16: `actions/setup-java@v4.1.0` → `actions/setup-java@v4`.
+- L21: `gradle/gradle-build-action@v3.1.0` → `gradle/actions/setup-gradle@v4`.
+- L11: the `head_commit` conditional is valid here (this workflow runs on `push`), so leave it. But note it skips the *whole job* — if intent was per-step skipping, that's fine; just be aware merge-commit messages on main rarely contain `ci skip` in practice.
+
+Side effects: same `setup-gradle@v4` notes as above.
+
+### 2.3 `.github/workflows/deploy.yml`
+
+Diffs:
+- L14: `actions/checkout@v4.1.1` → `actions/checkout@v4`.
+- L16: `actions/setup-java@v4.1.0` → `actions/setup-java@v4`.
+- L21: `gradle/gradle-build-action@v3.1.0` → `gradle/actions/setup-gradle@v4`.
+- L11: `head_commit` conditional — for tag pushes, `head_commit` is populated from the tagged commit, so the check works. Low value (you don't typically tag a release with `ci skip` in the message), but harmless. Suggest leaving it to keep the diff minimal.
+
+Side effects: none beyond the `setup-gradle@v4` cache-key change. `publishBundle` does not interact with the action upgrades.
+
+### 2.4 `.github/workflows/gradle-wrapper-validation.yml`
+
+Diffs:
+- L16: `actions/checkout@v4.1.1` → `actions/checkout@v4`.
+- L18: `gradle/wrapper-validation-action@v1` → `gradle/actions/wrapper-validation@v4`. The standalone action is in maintenance mode and has been merged into the umbrella `gradle/actions` repo. Behaviour is equivalent (verifies `gradle/wrapper/gradle-wrapper.jar` checksum against known-good list).
+- L19: trailing whitespace cleanup (cosmetic).
+
+Side effects: none — same validation, same exit codes.
+
+## 3. Open questions (for the user to decide)
+
+1. **Pin actions to SHAs vs. float to major?** Floating to `@v4` (`actions/checkout`, `actions/setup-java`, `gradle/actions/*`) is simpler and gets security patches for free. Pinning to a full 40-char SHA (e.g. `actions/checkout@a5ac7e5...`) is the OpenSSF Scorecard recommendation and protects against tag-rewrite supply-chain attacks. For a personal/small project, floating to major is the pragmatic default. Decide once and apply uniformly.
+2. **`pre-merge.yaml` trigger `branches: '*'`** — this fires on every PR regardless of base branch, including stacked feature-to-feature PRs. Most repos use `branches: [main]` to only run on PRs targeting main. Is the wildcard intentional (e.g. you stack PRs)? If not, narrow to `main`.
+3. **`if: ${{ !contains(github.event.head_commit.message, 'ci skip') }}` on `pre-merge.yaml`** — this is a no-op on `pull_request` events (`head_commit` is null, so `contains(null, 'ci skip')` is false, so the negation is true, so the job always runs — i.e. it works by accident). Three options:
+ - (a) Remove it from `pre-merge.yaml` entirely (cleanest).
+ - (b) Replace with `!contains(github.event.pull_request.title, 'ci skip')` so the skip mechanism actually works for PRs.
+ - (c) Leave as is. Recommend (a) unless you actively use `ci skip` on PRs.
+4. **Workflow name for `post-merge.yaml`** — `Post Merge Checks` vs. `Validate Build`. No functional difference; pick one.
+
+## 4. Verification
+
+1. Open a throwaway branch (`chore/ci-bumps`) with the diffs and push a no-op commit (e.g. whitespace in `README.md`). Confirm:
+ - `Pre Merge Checks` runs both jobs to completion when the branch is opened as a PR.
+ - `Validate Gradle Wrapper` runs and succeeds on both `pull_request` and (after merge) `push` to main.
+2. Inspect the `Setup Gradle` step's job summary — `gradle/actions/setup-gradle@v4` emits a summary with cache restore/save sizes. First run = miss, second run on the same branch = hit.
+3. Merge to main and confirm `Post Merge Checks` (renamed) runs `testDebugUnitTest flankRun` end-to-end.
+4. (Optional, when you cut the next release) tag `vX.Y.Z` on a throwaway and observe `Deploy` running through `publishBundle`. Skip if you don't want to burn a Play Store internal-track upload — the deploy workflow shares the same three action upgrades as the others, so success on `pre-merge` + `post-merge` is strong evidence it'll work.
+5. Check the Actions tab for any deprecation warnings — should be zero after this pass.
+
+## 5. Out of scope
+
+- Bumping the Gradle wrapper version itself — covered in Section 4.
+- Bumping the JDK version — already on Zulu 21, current LTS, no change needed.
+- Migrating from Flank to GMD (Gradle Managed Devices) or another runner — orthogonal, larger discussion.
+- Reworking the `data/downloadProdSource.sh` / `replaceSecrets.sh` flow used by `post-merge` and `deploy` — separate concern.
+- Adding concurrency groups, build matrices, or caching of `~/.android` — possible follow-up, not part of this maintenance pass.
diff --git a/maintenance/04-misc.md b/maintenance/04-misc.md
new file mode 100644
index 0000000..dc58d41
--- /dev/null
+++ b/maintenance/04-misc.md
@@ -0,0 +1,120 @@
+# Section 4 — Miscellaneous Maintenance Items
+
+## Goal
+
+Resolve a set of small, mostly-independent maintenance items that fall outside the dependency-bump and lint-cleanup work covered in Sections 1–3: Gradle wrapper refresh, Renovate config modernization, optional release version bump, lint baseline regeneration, type-safe project accessor cleanup, README/troubleshooting hygiene, and a `local.properties` gitignore confirmation.
+
+---
+
+## 1. Gradle wrapper upgrade
+
+- **Problem:** `gradle-wrapper.properties` pins Gradle 8.13 (Mar 2024 era for the file timestamp). Latest stable 8.x is **8.14.5** (May 2026). AGP 8.13 (`gradle/libs.versions.toml:2`) requires minimum Gradle 8.13 and is forward-compatible within the 8.x line; Gradle 9.x is **not** an option without an AGP 9.x bump (out of scope here).
+- **Files to change:**
+ - `/Users/simonas/Projects/quies/gradle/wrapper/gradle-wrapper.properties:4`
+- **Concrete change:** Update `distributionUrl` to `https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip`. Run `./gradlew wrapper --gradle-version 8.14.5 --distribution-type bin` so the wrapper jar / scripts get regenerated consistently. Re-run once (`./gradlew wrapper`) to lock the version.
+- **Verification:** `./gradlew --version` reports 8.14.5; `./gradlew :app:assembleDebug` and `./gradlew :app:lintDebug` succeed.
+
+---
+
+## 2. Renovate config modernization
+
+- **Problem:** `renovate.json` extends `config:base`, which Renovate has deprecated in favor of `config:recommended`. The preset `:automergeMinor` still works but warrants explicit re-confirmation since it was inherited from the `kotlin-android-template` upstream and auto-merging minors on a small Android codebase has nontrivial risk (e.g., AGP/Kotlin minors).
+- **Files to change:**
+ - `/Users/simonas/Projects/quies/renovate.json` (entire file, 6 lines)
+- **Concrete change:** Replace `"config:base"` with `"config:recommended"`. Decide whether to keep `:automergeMinor` (see Open Questions). If kept, consider scoping it away from `agp`, `kotlin`, and `gradle` packages via a `packageRules` block.
+- **Verification:** Push a branch and open a Renovate PR or run the Renovate dry-run / config-validator (`npx --package renovate -c renovate-config-validator`). Confirm no config-deprecation warnings appear in the next Renovate dashboard issue.
+
+---
+
+## 3. App version bump (decision-gated)
+
+- **Problem:** `Coordinates.kt` shows `APP_VERSION_NAME = "1.0.1"`, `APP_VERSION_CODE = 33`. If this maintenance pass is shipped to Play (internal track at minimum, given `play { track.set("internal") }` in `app/build.gradle.kts:182`), the version must bump. Not all maintenance passes ship.
+- **Files to change:**
+ - `/Users/simonas/Projects/quies/buildSrc/src/main/java/Coordinates.kt:5-6`
+- **Concrete change:** Bump to `APP_VERSION_NAME = "1.0.2"` and `APP_VERSION_CODE = 34`. Skip if not shipping.
+- **Verification:** `./gradlew :app:assembleRelease` produces an AAB whose `versionCode` matches; confirm via `aapt dump badging` or Play console upload preflight.
+
+---
+
+## 4. Lint baseline regeneration (depends on Sections 1 & 2)
+
+- **Problem:** `app/build.gradle.kts:98` references `app/lint-baseline.xml`. After the dependency bumps in Sections 1–2 land, new lint findings introduced by upgraded androidx/Compose/Hilt/etc. will be silently absorbed by the old baseline rather than surfacing for review. Regenerating ensures `warningsAsErrors = true` (line 96) actually catches new issues.
+- **Files to change:**
+ - `/Users/simonas/Projects/quies/app/lint-baseline.xml` (delete, then regenerate)
+- **Concrete change:**
+ 1. Delete `app/lint-baseline.xml`.
+ 2. Run `./gradlew :app:updateLintBaseline` (Gradle 8.x AGP task). If unavailable, temporarily comment out `baseline = ...` and run `./gradlew :app:lintDebug -Dlint.baselines.continue=true` to write a new baseline at `app/build/reports/lint-results-debug-baseline.xml`, then move it to `app/lint-baseline.xml`.
+ 3. Diff the new baseline against the old one. Triage any *newly-quieted* findings — fix in code rather than baselining when feasible.
+ 4. Commit the regenerated baseline.
+- **Verification:** `./gradlew :app:lintRelease` passes with `warningsAsErrors = true`. New baseline diff committed alongside dep bumps.
+- **Sequencing note:** Must run **after** Sections 1 (deps) and 2 (lint cleanup) merge, otherwise the baseline captures findings that those sections will eliminate.
+
+---
+
+## 5. Type-safe project accessors
+
+- **Problem:** `settings.gradle.kts:18` enables `TYPESAFE_PROJECT_ACCESSORS`, but `app/build.gradle.kts:131` still uses the stringy form `implementation(project(":data"))`. Either use the preview or drop the flag.
+- **Files to change:**
+ - `/Users/simonas/Projects/quies/app/build.gradle.kts:131`
+ - (or alternatively) `/Users/simonas/Projects/quies/settings.gradle.kts:18`
+- **Concrete change:** **Preferred:** replace `implementation(project(":data"))` with `implementation(projects.data)`. Sweep other `build.gradle.kts` modules (`data/`, `buildSrc/`) for similar string accessors. **Alternative:** delete the `enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")` line if the team prefers stable-only features.
+- **Verification:** `./gradlew :app:dependencies | grep -i ':data'` resolves the module dependency identically; `./gradlew :app:assembleDebug` succeeds.
+
+---
+
+## 6. README expansion (optional)
+
+- **Problem:** `README.md` is 18 lines: a logo, two epigraphs, and a single acknowledgement line pointing at the upstream template. There's no build/setup, no description of what the app does, no badge/CI block. Acceptable for a personal project; suboptimal for onboarding or open-source visibility.
+- **Files to change:**
+ - `/Users/simonas/Projects/quies/README.md`
+- **Concrete change (if pursued):** Add sections — *What it does* (1 paragraph), *Build & run* (`./gradlew :app:installDebug`, JDK 21, Android SDK requirements per `compileSdk`), *Module layout* (`app/`, `data/`, `buildSrc/`), *CI/CD* (link to `.github/workflows`), preserve the epigraphs and template acknowledgement.
+- **Verification:** `markdownlint README.md` clean (if used); manual readability review.
+- **Optional flag:** Skip if the user prefers the current minimalist tone.
+
+---
+
+## 7. `local.properties` gitignore confirmation
+
+- **Problem:** `local.properties` exists at repo root and would leak the local SDK path / arbitrary developer overrides if tracked.
+- **Files to change:** None expected — confirmation only.
+- **Concrete change:** Confirmed gitignored. `.gitignore:38` lists `local.properties`, and `.gitignore:254` lists `/*/local.properties` for module-level files. The file is **not** tracked by git (it appears in `ls -la` only because gitignore doesn't delete files; it just excludes them from indexing).
+- **Verification:** Run `git ls-files | grep local.properties` — should return empty. Run `git check-ignore -v local.properties` — should print the matching `.gitignore` rule. No action needed unless either check fails.
+
+---
+
+## 8. `TROUBLESHOOTING.md` review
+
+- **Problem:** The doc has two sections, both stale:
+ 1. *"agp incompatibility with IntelliJ IDEA"* references AGP 8.0.0 vs IDEA's max-supported 7.4.0 — irrelevant in 2026 with AGP 8.13.
+ 2. *"Use correct JVM version"* shows `jvmTarget = JavaVersion.VERSION_17` and a sample error listing JVM targets up to 18 — but `app/build.gradle.kts:38-45` now uses **JDK 21**.
+- **Files to change:**
+ - `/Users/simonas/Projects/quies/TROUBLESHOOTING.md`
+- **Concrete change:** Either (a) delete the file entirely (preferred — both items are obsolete and the IDE-version pain is a non-issue at this AGP/IDE pairing), or (b) rewrite: drop section 1 outright; in section 2 update the example to `VERSION_21` and update the error message snippet to a current-era one (or just say "your local JDK must be ≥ 21; check `java --version`").
+- **Verification:** Manual read; ensure no other doc references `TROUBLESHOOTING.md` (grep the repo).
+
+---
+
+## Sequencing
+
+- **Item 4** (lint baseline regen) **must run after Sections 1 and 2** are merged so the regenerated baseline reflects the post-bump, post-cleanup state.
+- **Items 1, 2, 3, 5, 6, 7, 8** are mutually independent and can ship in any order or as a single PR.
+- Suggested grouping for one PR: 1 + 2 + 5 + 8 (all small mechanical edits). Items 3, 6 are decision-gated (see below). Item 7 needs no change. Item 4 lands in a follow-up.
+
+---
+
+## Open questions for the user
+
+1. **Item 3 (release):** Is this maintenance pass shipping to Play? If yes, bump to `1.0.2` / `34`. If no, leave as-is.
+2. **Item 2 (Renovate automerge):** Keep `:automergeMinor` for all packages, restrict it (exclude `agp`, `kotlin`, `gradle`, `compose-*`), or drop it? Recommendation: restrict — minor bumps to AGP/Kotlin can break Compose compiler compatibility.
+3. **Item 5 (preview flag):** Adopt `projects.data` everywhere, or drop the preview flag? Recommendation: adopt; the feature is widely used and unlikely to be removed.
+4. **Item 6 (README):** Expand with build/setup section, or keep the minimalist poetic tone?
+5. **Item 8 (TROUBLESHOOTING):** Delete entirely, or keep a slimmed-down version?
+
+---
+
+## Out of scope
+
+- Section 1 — dependency bumps in `gradle/libs.versions.toml`.
+- Section 2 — build-config cleanup (Compose BOM, Jetifier, dead `composeOptions`, `kotlinOptions` migration).
+- Section 3 — GitHub Actions / CI workflow upgrades.
+- AGP 9.x / Gradle 9.x migration (would require coordinated AGP, JDK, and possibly Kotlin upgrades; not a maintenance task).
diff --git a/settings.gradle.kts b/settings.gradle.kts
index f5cacee..441f73e 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -5,6 +5,9 @@ pluginManagement {
mavenCentral()
}
}
+plugins {
+ id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
+}
dependencyResolutionManagement {
repositories {