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 {