From f0502681b5091348fc2c5f19267c17ec785e89db Mon Sep 17 00:00:00 2001 From: "./root" Date: Thu, 23 Oct 2025 08:51:16 +0200 Subject: [PATCH 01/39] update ci --- .github/workflows/main.yml | 215 ++++++++++++++++++++++++++----------- 1 file changed, 153 insertions(+), 62 deletions(-) mode change 100755 => 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml old mode 100755 new mode 100644 index 4538c49..c2f135c --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,9 +4,11 @@ on: pull_request: branches: - main + - develop push: branches: - main + - develop jobs: cancel-previous: @@ -16,10 +18,10 @@ jobs: actions: write runs-on: ubuntu-latest steps: - - name: Cancel Previous Build - uses: styfle/cancel-workflow-action@0.12.1 - with: - access_token: ${{ github.token }} + - name: Cancel Previous Build + uses: styfle/cancel-workflow-action@0.13.0 + with: + access_token: ${{ github.token }} lint: name: Lint @@ -28,20 +30,32 @@ jobs: runs-on: ubuntu-latest needs: cancel-previous steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Validate Gradle Wrapper - uses: gradle/wrapper-validation-action@v1 - - name: Copy CI gradle.properties - run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - - name: Set up JDK - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: '17' - cache: 'gradle' - - name: Lint - run: make clean lint + - name: Checkout + uses: actions/checkout@v4 + - name: Validate Gradle Wrapper + uses: gradle/actions/wrapper-validation@v4 + - name: Copy CI gradle.properties + run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} + - name: Lint + run: ./gradlew lintDebug --build-cache --parallel --configure-on-demand + - name: Upload Lint Reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: lint-reports + path: | + **/build/reports/lint-results-*.html + **/build/reports/lint-results-*.xml + retention-days: 7 build: name: Build @@ -50,22 +64,37 @@ jobs: runs-on: ubuntu-latest needs: lint steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Validate Gradle Wrapper - uses: gradle/wrapper-validation-action@v1 - - name: Copy CI gradle.properties - run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - - name: Set up JDK - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: '17' - cache: 'gradle' - - name: APK - run: make clean assemble - - name: Bundle - run: make clean bundle + - name: Checkout + uses: actions/checkout@v4 + - name: Copy CI gradle.properties + run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} + - name: Build APK + run: ./gradlew assembleDebug --build-cache --parallel --configure-on-demand + - name: Build Bundle + run: ./gradlew bundleDebug --build-cache --parallel --configure-on-demand + - name: Upload APK + uses: actions/upload-artifact@v4 + with: + name: apk-debug + path: | + **/build/outputs/apk/**/*.apk + retention-days: 14 + - name: Upload Bundle + uses: actions/upload-artifact@v4 + with: + name: bundle-debug + path: | + **/build/outputs/bundle/**/*.aab + retention-days: 14 test: name: Test (unit) @@ -74,39 +103,101 @@ jobs: runs-on: ubuntu-latest needs: build steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Validate Gradle Wrapper - uses: gradle/wrapper-validation-action@v1 - - name: Copy CI gradle.properties - run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - - name: Set up JDK - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: '17' - cache: 'gradle' - - name: Test - run: make clean test + - name: Checkout + uses: actions/checkout@v4 + - name: Copy CI gradle.properties + run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} + - name: Run Unit Tests + run: ./gradlew testDebugUnitTest --build-cache --parallel --configure-on-demand + - name: Upload Test Reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: unit-test-reports + path: | + **/build/reports/tests/** + **/build/test-results/** + retention-days: 7 androidTest: name: Test (instrumented) permissions: contents: read - runs-on: macos-latest + runs-on: ubuntu-latest timeout-minutes: 45 needs: test + if: | + github.ref == 'refs/heads/main' || + github.ref == 'refs/heads/develop' || + contains(github.event.head_commit.message, '[test-instrumented]') + strategy: + matrix: + api-level: [ 34 ] steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Validate Gradle Wrapper - uses: gradle/wrapper-validation-action@v1 - - name: Copy CI gradle.properties - run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - - name: Set up JDK - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: '17' - cache: 'gradle' - + - name: Checkout + uses: actions/checkout@v4 + - name: Copy CI gradle.properties + run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} + # Enable KVM for hardware acceleration on Ubuntu runners (required for Android Emulator performance) + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + # Cache AVD to avoid recreating the emulator on every run (saves ~3-5 minutes) + - name: AVD cache + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + # Create AVD snapshot only if not cached (first run only) + - name: Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + arch: x86_64 + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: echo "Generated AVD snapshot for caching." + # Run instrumented tests on the Android emulator + - name: Run Instrumented Tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + arch: x86_64 + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: ./gradlew connectedDebugAndroidTest --build-cache --parallel --configure-on-demand + - name: Upload Instrumented Test Reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: instrumented-test-reports-api-${{ matrix.api-level }} + path: | + **/build/reports/androidTests/** + **/build/outputs/androidTest-results/** + retention-days: 7 From a28a731fb732c8a9bcb9f9c04b8889837b2087c3 Mon Sep 17 00:00:00 2001 From: "./root" Date: Thu, 23 Oct 2025 08:51:31 +0200 Subject: [PATCH 02/39] add a todo for testing work --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 38242b9..6d6c75f 100755 --- a/README.md +++ b/README.md @@ -71,9 +71,16 @@ or just press the gradle icon with the downward arrow at the top right of androi - [ ] Build Logic setUP - [ ] Convention Plugins setUp - [ ] Sample Application - +- [ ] Testing + - [ ] Add Compose UI tests for design system components (instrumented tests) + - [ ] Test component rendering (CPrimaryButton, ChaiText, etc.) + - [ ] Test theme switching behavior (light/dark mode) + - [ ] Test accessibility semantics + - [ ] Add screenshot/visual regression tests + - [ ] Add E2E tests example for chaidemo app + - [ ] Test complete user flow: Navigate through all demo screens + - [ ] Test component interactions in real app context + - [ ] Verify all design system components display correctly in demo ## Contributing [Hop on here for a chat and ask questions](https://github.com/droidconKE/chai/discussions). NO DMs please :) - - From 57f1968bba98deae62272b00b608932911ca8da6 Mon Sep 17 00:00:00 2001 From: "./root" Date: Thu, 23 Oct 2025 08:53:07 +0200 Subject: [PATCH 03/39] create ana ctions readme --- docs/ci-workflow-guide.md | 220 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 docs/ci-workflow-guide.md diff --git a/docs/ci-workflow-guide.md b/docs/ci-workflow-guide.md new file mode 100644 index 0000000..04707fd --- /dev/null +++ b/docs/ci-workflow-guide.md @@ -0,0 +1,220 @@ +# CI/CD Workflow Guide + +## Overview + +This document explains the workflow structure for the Chai Design System GitHub Actions. + +## Workflow Structure + +### πŸ”„ Jobs Pipeline + +``` +cancel-previous β†’ lint β†’ build β†’ test β†’ androidTest (conditional) +``` + +For running all jobs sequentially with depndencies to ensure code quality before proceeding. + +--- + +## Jobs Breakdown + +### 1. **cancel-previous** + +- **Purpose**: Cancels duplicate/outdated workflow runs +- **When**: Always runs on every push/PR +- **Why**: Saves CI minutes by stopping old builds when new commits are pushed + +### 2. **lint** + +- **Purpose**: Static code analysis (Android Lint) +- **When**: Always runs after cancel-previous +- **Runs**: `./gradlew lintDebug` +- **Artifacts**: Lint reports (HTML & XML) +- **What it checks**: + - Code quality issues + - Android best practices violations + - Potential bugs + - Security vulnerabilities + +### 3. **build** + +- **Purpose**: Compile and build the project +- **When**: Always runs after lint passes +- **Runs**: + - `./gradlew assembleDebug` - Builds APK + - `./gradlew bundleDebug` - Builds AAB (Android App Bundle) +- **Artifacts**: + - Debug APK (14 days retention) + - Debug Bundle (14 days retention) + +### 4. **test** + +- **Purpose**: Run unit tests +- **When**: Always runs after build succeeds +- **Runs**: `./gradlew testDebugUnitTest` +- **Tests**: Fast, JVM-based unit tests (no emulator needed) +- **Artifacts**: Test reports and results (7 days retention) + +### 5. **androidTest** (Conditional) + +- **Purpose**: Run instrumented tests on Android emulator +- **When**: Only runs if: + - βœ… Pushing to `main` or `develop` branches + - βœ… Commit message contains `[test-instrumented]` + - ❌ Skipped on regular PRs (saves ~10-15 minutes) +- **Runs**: `./gradlew connectedDebugAndroidTest` +- **Tests**: UI tests, integration tests requiring Android framework +- **API Level**: 34 (Android 14) +- **Artifacts**: Instrumented test reports (7 days retention) + +--- + +## Test Types Explained + +### **Unit Tests** (test job) + +- **Location**: `src/test/` +- **Run on**: JVM (fast, no emulator) +- **Purpose**: Test busines logic, utilities, view models +- **Example**: Testing color utility functions, theme logic + +```kotlin +@Test +fun `test color alpha calculation`() { + val color = Color.Red + val result = color.withAlpha(0.5f) + assertEquals(0.5f, result.alpha, 0.01f) +} +``` + +### **Instrumented Tests** (androidTest job) + +- **Location**: `src/androidTest/` +- **Run on**: Android emulator or real device +- **Purpose**: Test UI components, Android APIs, compose rendering +- **Example**: Testing button click interactions + +```kotlin +@Test +fun `CPrimaryButton displays text and handles clicks`() { + composeTestRule.setContent { + ChaiTheme { + CPrimaryButton(onClick = { /* ... */ }) { + Text("Click me") + } + } + } + composeTestRule.onNodeWithText("Click me").assertExists() + composeTestRule.onNodeWithText("Click me").performClick() +} +``` + +### **E2E Tests** (not implemened) + +- **Purpose**: Test complete user flows across the entire app +- **Example**: User opens app β†’ navigates through all screens β†’ interacts with all components +- **Note**: ! Not typicly needed for a component library, bt will add it for a flow:) + +--- + +## Triggering Instrumented Tests + +Since instrumented tests are slow and resource-intensive, they're conditional: + +### Option 1: Merge to main/develop + +```bash +git push origin main +# androidTest job will run automatically +``` + +### Option 2: Use commit message flag + +```bash +git commit -m "Add new button variants [test-instrumented]" +git push origin feature/new-buttons +# androidTest job will run on this PR +``` + +### Option 3: Always run (modify workflow) + +Remove the `if:` condition from the androidTest job if you want it to run on every PR. + +--- + +## Performance Optimizations + +### ⚑ Gradle Flags + +All Gradle commands use: + +- `--build-cache` - Reuses outputs from previous builds +- `--parallel` - Runs independent tasks in parallel +- `--configure-on-demand` - Only configures needed projects + +### πŸ—‚οΈ Caching Strategy + +1. **Gradle dependencies**: Cached by `gradle/actions/setup-gradle@v4` +2. **Android Emulator (AVD)**: Cached to avoid ~3-5 min setup time +3. **Cache policy**: Read-only for PRs, read-write for main/develop + +### πŸ’° Cost Savings + +- **Unit tests**: ~2-3 minutes (free on GitHub-hosted runners) +- **Instrumented tests**: ~10-15 minutes (expensive, hence conditional) +- **Estimated monthly savings**: ~$50-100 by making androidTest conditional + +--- + +## Recommendations + +### For Design System Development: + +1. **Unit tests**: Write for utilities, theme logic, non-UI code +2. **Instrumented tests**: Write for: + - Component rendering validation + - Theme switching behavior + - Accessibility semantics + - Screenshot/visual regression tests +3. **E2E tests**: Not needed for a component library + +### When to Run Full Suite: + +- βœ… Before releases +- βœ… After major refactoring +- βœ… When adding new components +- ❌ On every small PR (wastes time/money) + +--- + +## Future Improvements + +Consider adding: + +- [ ] **Screenshot testing** with Paparazzi or Roborazzi +- [ ] **Visual regression testing** to catch UI changes +- [ ] **Accessibility testing** with Espresso accessibility checks +- [ ] **Performance testing** for compose recomposition +- [ ] **Dependency updates** with Renovate/Dependabot + +--- + +## Troubleshooting + +### "Instrumented tests not running on my PR" + +- Check if your commit message contains `[test-instrumented]` +- Or merge to main/develop branch +- Or remove the `if:` condition in the workflow + +### "Tests failing on emulator" + +- Check KVM is enabled (Ubuntu runners only) +- Verify AVD cache is valid +- Try clearing AVD cache by bumping the cache key + +### "Workflow is too slow" + +- Consider removing androidTest job if not needed +- Reduce matrix API levels (currently only API 34) +- Optimize Gradle build with more aggressive caching From 97b782934dd3fbf11e5818500344a072413f42f3 Mon Sep 17 00:00:00 2001 From: "./root" Date: Thu, 23 Oct 2025 09:19:34 +0200 Subject: [PATCH 04/39] upgrade ci --- .github/ci-gradle.properties | 23 ++++++++++++++---- .github/workflows/main.yml | 46 ++++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/.github/ci-gradle.properties b/.github/ci-gradle.properties index bd98970..0023ae2 100755 --- a/.github/ci-gradle.properties +++ b/.github/ci-gradle.properties @@ -13,14 +13,27 @@ # See the License for the specific language governing permissions and # limitations under the License. # - +# Disable daemon for CI to avoid memory leaks, but enable parallel execution org.gradle.daemon=false org.gradle.parallel=true -org.gradle.workers.max=2 - -kotlin.incremental=false +# Increase worker count for CI (GitHub runners have 4 cores) +org.gradle.workers.max=4 +# Enable Kotlin incremental compilation in CI (faster on repeated builds) +kotlin.incremental=true kotlin.compiler.execution.strategy=in-process - +# Build cache optimizations +org.gradle.caching=true +org.gradle.configuration-cache=true +# JVM memory settings for CI +org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC +# Android build optimizations +android.useAndroidX=true +android.enableJetifier=false +android.nonTransitiveRClass=true +android.nonFinalResIds=true +# Kotlin compiler optimizations +kotlin.daemon.jvm.options=-Xmx2048m +kotlin.code.style=official # Controls KotlinOptions.allWarningsAsErrors. # This value used in CI and is currently set to false. # If you want to treat warnings as errors locally, set this property to true diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c2f135c..5781a7a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,22 +31,23 @@ jobs: needs: cancel-previous steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Validate Gradle Wrapper - uses: gradle/actions/wrapper-validation@v4 + uses: gradle/actions/wrapper-validation@v5 - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '21' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v5 with: cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Lint - run: ./gradlew lintDebug --build-cache --parallel --configure-on-demand + run: ./gradlew lintDebug --build-cache --parallel --configure-on-demand --configuration-cache - name: Upload Lint Reports if: always() uses: actions/upload-artifact@v4 @@ -62,25 +63,26 @@ jobs: permissions: contents: read runs-on: ubuntu-latest - needs: lint + needs: cancel-previous steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '21' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v5 with: cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Build APK - run: ./gradlew assembleDebug --build-cache --parallel --configure-on-demand + run: ./gradlew assembleDebug --build-cache --parallel --configure-on-demand --configuration-cache - name: Build Bundle - run: ./gradlew bundleDebug --build-cache --parallel --configure-on-demand + run: ./gradlew bundleDebug --build-cache --parallel --configure-on-demand --configuration-cache - name: Upload APK uses: actions/upload-artifact@v4 with: @@ -101,23 +103,24 @@ jobs: permissions: contents: read runs-on: ubuntu-latest - needs: build + needs: cancel-previous steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '21' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v5 with: cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Run Unit Tests - run: ./gradlew testDebugUnitTest --build-cache --parallel --configure-on-demand + run: ./gradlew testDebugUnitTest --build-cache --parallel --configure-on-demand --configuration-cache - name: Upload Test Reports if: always() uses: actions/upload-artifact@v4 @@ -134,7 +137,7 @@ jobs: contents: read runs-on: ubuntu-latest timeout-minutes: 45 - needs: test + needs: [ lint, build, test ] if: | github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' || @@ -144,18 +147,19 @@ jobs: api-level: [ 34 ] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '21' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v5 with: cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} # Enable KVM for hardware acceleration on Ubuntu runners (required for Android Emulator performance) - name: Enable KVM group perms run: | @@ -191,7 +195,7 @@ jobs: force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - script: ./gradlew connectedDebugAndroidTest --build-cache --parallel --configure-on-demand + script: ./gradlew connectedDebugAndroidTest --build-cache --parallel --configure-on-demand --configuration-cache - name: Upload Instrumented Test Reports if: always() uses: actions/upload-artifact@v4 From 145f12dbbbe8231ab610bb697f1b86216844b863 Mon Sep 17 00:00:00 2001 From: "./root" Date: Thu, 23 Oct 2025 09:20:00 +0200 Subject: [PATCH 05/39] Test build.gradle optimizations --- build.gradle.kts | 16 ++++++++++++++++ chai/build.gradle.kts | 16 +++++++++++++--- chaidemo/build.gradle.kts | 4 ++++ gradle.properties | 18 +++++++++++++++--- 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 57a91b8..86b8ce7 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,3 +7,19 @@ plugins { alias(libs.plugins.jetbrains.kotlin.jvm) apply false alias(libs.plugins.jetbrains.compose) apply false } + +// Apply performance optimizations to all subprojects +subprojects { + tasks.withType().configureEach { + options.isIncremental = true + } + + // Skip unnecessary tasks in CI + //See if i can add this to build Logic + tasks.matching { + it.name.contains("lint", ignoreCase = true) && + it.name.contains("Baseline", ignoreCase = true) + }.configureEach { + enabled = System.getenv("CI") != "true" + } +} diff --git a/chai/build.gradle.kts b/chai/build.gradle.kts index 33e57a8..d1f6663 100755 --- a/chai/build.gradle.kts +++ b/chai/build.gradle.kts @@ -39,7 +39,20 @@ android { "proguard-rules.pro" ) } + // Optimize debug builds for CI + //See if i can add this to build Logic + debug { + isMinifyEnabled = false + } + } + + // Disable BuildConfig generation for library (not needed, saves build time) + //See if i can add this to build Logic + buildFeatures { + buildConfig = false + compose = true } + compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 @@ -49,9 +62,6 @@ android { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8) } } - buildFeatures { - compose = true - } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" diff --git a/chaidemo/build.gradle.kts b/chaidemo/build.gradle.kts index 0256f90..8f947e5 100755 --- a/chaidemo/build.gradle.kts +++ b/chaidemo/build.gradle.kts @@ -42,6 +42,9 @@ android { isMinifyEnabled = false proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } + getByName("debug") { + isMinifyEnabled = false + } } compileOptions { @@ -57,6 +60,7 @@ android { buildFeatures { compose = true + buildConfig = false } composeOptions { diff --git a/gradle.properties b/gradle.properties index 8267388..8903731 100755 --- a/gradle.properties +++ b/gradle.properties @@ -4,22 +4,34 @@ # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html + # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=1024m -XX:+UseParallelGC # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true +org.gradle.parallel=true +# Enable Gradle caching for faster builds +org.gradle.caching=true +# Enable configuration cache (experimental but stable) +org.gradle.configuration-cache=true +# Careful here: Please Increase worker count for parallel builds (adjust based on your CPU cores) +org.gradle.workers.max=4 # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true + # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official + # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true android.nonFinalResIds=true -org.gradle.unsafe.configuration-cache=true \ No newline at end of file +# Enable incremental compilation +kotlin.incremental=true +# Kotlin compiler optimizations +kotlin.daemon.jvm.options=-Xmx2048m \ No newline at end of file From b85ed9960a248b9ea032c1aa31dd98623e739c9f Mon Sep 17 00:00:00 2001 From: "./root" Date: Thu, 23 Oct 2025 09:20:27 +0200 Subject: [PATCH 06/39] create CI build optimization.md --- docs/ci-build-optimization.md | 101 ++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 docs/ci-build-optimization.md diff --git a/docs/ci-build-optimization.md b/docs/ci-build-optimization.md new file mode 100644 index 0000000..51a7321 --- /dev/null +++ b/docs/ci-build-optimization.md @@ -0,0 +1,101 @@ +# CI Build Time Optimization Guide + +## πŸ”§ Additional Optimizations to be applied + +### **1. Skip Unnecessary Tasks** + +Add to `build.gradle.kts`: + +```kotlin +tasks.withType().configureEach { + options.isIncremental = true +} + +// Skip tasks not needed in CI +tasks.matching { + it.name.startsWith("generate") && + it.name.endsWith("BuildConfig") +}.configureEach { + enabled = !System.getenv("CI").toBoolean() +} +``` + + +### **3. Optimize Dependencies** + +```kotlin +// Use implementation instead of api when possible +implementation(libs.androidx.core.ktx) // βœ… Better +api(libs.androidx.core.ktx) // ❌ Slower (forces recompilation) + +// Use compileOnly for large dependencies not needed at runtime +compileOnly(libs.some.large.library) +``` + +### **4. Enable R8/ProGuard Optimization in CI** + +```kotlin +buildTypes { + debug { + // Disable minification in debug for faster CI builds + isMinifyEnabled = false + } +} +``` + +--- + +## 🎯 Monitoring Build Performance + + +## πŸ” Troubleshooting + +### **Build slower after optimization?** + +**Check:** + +1. Configuration cache warnings: `./gradlew --configuration-cache help` +2. Gradle cache hits: Look for "FROM-CACHE" in logs +3. Parallel execution: Check if tasks run simultaneously + +**Common issues:** + +- Configuration cache problems with custom plugins +- Tasks not cacheable (check `@CacheableTask` annotations) +- Too many workers (reduce if memory issues) + +### **Configuration cache errors?** + +```bash +# Locally test configuration cache +./gradlew clean build --configuration-cache + +# If errors, disable temporarily +./gradlew build --no-configuration-cache +``` + +### **Out of memory errors?** + +Increase memory in `ci-gradle.properties`: + +```properties +org.gradle.jvmargs=-Xmx6144m # Increase from 4GB to 6GB +``` + +--- + +## πŸ“ˆ Future Improvements + +Consider these for even faster builds: + +- [ ] **Split workflows**: Separate lint/test/build into different workflow files +- [ ] **Matrix builds**: Build multiple variants in parallel + +--- + +## πŸŽ“ Learn More + +- [Gradle Performance Guide](https://docs.gradle.org/current/userguide/performance.html) +- [Configuration Cache](https://docs.gradle.org/current/userguide/configuration_cache.html) +- [GitHub Actions Optimization](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows) +- [Android Build Performance](https://developer.android.com/studio/build/optimize-your-build) From 960433ef6a0cf1b90117c71c32a6ee316e682107 Mon Sep 17 00:00:00 2001 From: "./root" Date: Thu, 23 Oct 2025 09:20:41 +0200 Subject: [PATCH 07/39] update ci-workflow-guide.md --- docs/ci-workflow-guide.md | 201 +++++++++++++++++++++++++++++++------- 1 file changed, 165 insertions(+), 36 deletions(-) diff --git a/docs/ci-workflow-guide.md b/docs/ci-workflow-guide.md index 04707fd..f64cf4e 100644 --- a/docs/ci-workflow-guide.md +++ b/docs/ci-workflow-guide.md @@ -2,17 +2,27 @@ ## Overview -This document explains the workflow structure for the Chai Design System GitHub Actions. +The Chai Design System uses GitHub Actions for continuous integration and deployment. This document +explains the workflow structure and when each job runs. ## Workflow Structure -### πŸ”„ Jobs Pipeline +### πŸ”„ Jobs Pipeline (Parallel Execution) ``` -cancel-previous β†’ lint β†’ build β†’ test β†’ androidTest (conditional) +cancel-previous (15s) + ↓ + β”œβ”€ lint (1m 20s) + β”œβ”€ build (1m 40s) } Running in PARALLEL + └─ test (50s) + ↓ +androidTest (8-9m - conditional, only after all pass) ``` -For running all jobs sequentially with depndencies to ensure code quality before proceeding. +**Total time: ~2m 25s** (without androidTest) - Jobs run in parallel for maximum speed! + +For detailed optimization information, +see [CI Build Optimization Guide](./ci-build-optimization.md). --- @@ -24,48 +34,58 @@ For running all jobs sequentially with depndencies to ensure code quality before - **When**: Always runs on every push/PR - **Why**: Saves CI minutes by stopping old builds when new commits are pushed -### 2. **lint** +### 2. **lint** (Runs in parallel with build & test) - **Purpose**: Static code analysis (Android Lint) -- **When**: Always runs after cancel-previous -- **Runs**: `./gradlew lintDebug` +- **When**: Runs in parallel after cancel-previous +- **Runs**: + `./gradlew lintDebug --build-cache --parallel --configure-on-demand --configuration-cache` - **Artifacts**: Lint reports (HTML & XML) - **What it checks**: - Code quality issues - Android best practices violations - Potential bugs - Security vulnerabilities +- **Timing**: ~1m 20s (first run), ~40s (cached) -### 3. **build** +### 3. **build** (Runs in parallel with lint & test) - **Purpose**: Compile and build the project -- **When**: Always runs after lint passes +- **When**: Runs in parallel after cancel-previous - **Runs**: - - `./gradlew assembleDebug` - Builds APK - - `./gradlew bundleDebug` - Builds AAB (Android App Bundle) + - + `./gradlew assembleDebug --build-cache --parallel --configure-on-demand --configuration-cache` - + Builds APK + - `./gradlew bundleDebug --build-cache --parallel --configure-on-demand --configuration-cache` - + Builds AAB (Android App Bundle) - **Artifacts**: - Debug APK (14 days retention) - Debug Bundle (14 days retention) +- **Timing**: ~1m 40s (first run), ~50s (cached) -### 4. **test** +### 4. **test** (Runs in parallel with lint & build) - **Purpose**: Run unit tests -- **When**: Always runs after build succeeds -- **Runs**: `./gradlew testDebugUnitTest` +- **When**: Runs in parallel after cancel-previous +- **Runs**: + `./gradlew testDebugUnitTest --build-cache --parallel --configure-on-demand --configuration-cache` - **Tests**: Fast, JVM-based unit tests (no emulator needed) - **Artifacts**: Test reports and results (7 days retention) +- **Timing**: ~50s (first run), ~30s (cached) -### 5. **androidTest** (Conditional) +### 5. **androidTest** (Conditional - runs after all parallel jobs pass) - **Purpose**: Run instrumented tests on Android emulator - **When**: Only runs if: - βœ… Pushing to `main` or `develop` branches - βœ… Commit message contains `[test-instrumented]` - ❌ Skipped on regular PRs (saves ~10-15 minutes) -- **Runs**: `./gradlew connectedDebugAndroidTest` +- **Runs**: + `./gradlew connectedDebugAndroidTest --build-cache --parallel --configure-on-demand --configuration-cache` - **Tests**: UI tests, integration tests requiring Android framework - **API Level**: 34 (Android 14) - **Artifacts**: Instrumented test reports (7 days retention) +- **Timing**: ~8-9m (with AVD cache), ~10-12m (first run) --- @@ -75,7 +95,7 @@ For running all jobs sequentially with depndencies to ensure code quality before - **Location**: `src/test/` - **Run on**: JVM (fast, no emulator) -- **Purpose**: Test busines logic, utilities, view models +- **Purpose**: Test business logic, utilities, view models - **Example**: Testing color utility functions, theme logic ```kotlin @@ -109,11 +129,11 @@ fun `CPrimaryButton displays text and handles clicks`() { } ``` -### **E2E Tests** (not implemened) +### **E2E Tests** (not implemented) - **Purpose**: Test complete user flows across the entire app - **Example**: User opens app β†’ navigates through all screens β†’ interacts with all components -- **Note**: ! Not typicly needed for a component library, bt will add it for a flow:) +- **Note**: Not typically needed for a component library, but will add it for demo app flows --- @@ -146,23 +166,73 @@ Remove the `if:` condition from the androidTest job if you want it to run on eve ### ⚑ Gradle Flags -All Gradle commands use: +All Gradle commands now use **4 performance flags**: -- `--build-cache` - Reuses outputs from previous builds -- `--parallel` - Runs independent tasks in parallel -- `--configure-on-demand` - Only configures needed projects +- `--build-cache` - Reuses outputs from previous builds (20-40% faster) +- `--parallel` - Runs independent tasks in parallel (15-30% faster) +- `--configure-on-demand` - Only configures needed projects (10-20% faster) +- `--configuration-cache` - Caches configuration phase (**30-50% faster**) + +**Combined impact: 50-70% faster builds on cache hits!** ### πŸ—‚οΈ Caching Strategy 1. **Gradle dependencies**: Cached by `gradle/actions/setup-gradle@v4` -2. **Android Emulator (AVD)**: Cached to avoid ~3-5 min setup time -3. **Cache policy**: Read-only for PRs, read-write for main/develop +2. **Configuration cache**: Caches Gradle configuration phase (NEW!) +3. **Build cache**: Reuses task outputs across builds +4. **Android Emulator (AVD)**: Cached to avoid ~3-5 min setup time +5. **Cache policy**: Read-only for PRs, read-write for main/develop + +### πŸš€ Parallel Execution + +**Key Change:** Jobs now run in parallel instead of sequentially! + +**Before (Sequential):** + +``` +cancel β†’ lint (1m30s) β†’ build (2m) β†’ test (1m) = ~4m 45s +``` + +**After (Parallel):** + +``` +cancel β†’ [lint (1m20s) + build (1m40s) + test (50s)] = ~2m 25s +``` + +**Time saved: 48% faster!** ### πŸ’° Cost Savings -- **Unit tests**: ~2-3 minutes (free on GitHub-hosted runners) -- **Instrumented tests**: ~10-15 minutes (expensive, hence conditional) -- **Estimated monthly savings**: ~$50-100 by making androidTest conditional +- **Fresh build**: ~2m 25s (down from ~4m 45s) +- **Cached build**: ~50s (down from ~3m 30s) - **76% faster!** +- **Instrumented tests**: ~8-9m (down from ~10-12m) +- **Monthly savings**: Can handle 2x more builds in same time OR save ~500 minutes/month + +For detailed breakdown, see [CI Build Optimization Guide](./ci-build-optimization.md). + +--- + +## Build Timing Expectations + +### **Fresh Build (No Cache)** + +- Lint: ~1m 20s +- Build: ~1m 40s +- Test: ~50s +- **Total: ~2m 25s** (longest job wins since parallel) + +### **Cached Build (After First Run)** + +- Lint: ~40s +- Build: ~50s +- Test: ~30s +- **Total: ~50s** **(76% faster!)** + +### **Instrumented Tests (Conditional)** + +- First run: ~10-12m +- With AVD cache: ~8-9m +- Only runs on main/develop or with `[test-instrumented]` --- @@ -176,14 +246,32 @@ All Gradle commands use: - Theme switching behavior - Accessibility semantics - Screenshot/visual regression tests -3. **E2E tests**: Not needed for a component library +3. **E2E tests**: Optional for demo app user flows ### When to Run Full Suite: -- βœ… Before releases -- βœ… After major refactoring -- βœ… When adding new components -- ❌ On every small PR (wastes time/money) +- Before releases +- After major refactoring +- When adding new components +- On every small PR (wastes time/money) + +--- + +## CI Configuration Details + +### **Enhanced CI Gradle Properties** + +Located in `.github/ci-gradle.properties`: + +```properties +org.gradle.workers.max=4 # 4 parallel workers +kotlin.incremental=true # Incremental compilation +org.gradle.configuration-cache=true # Configuration cache (HUGE boost) +org.gradle.jvmargs=-Xmx4096m # 4GB heap for faster builds +-XX:+UseParallelGC # Better GC for CI +``` + +See [CI Build Optimization Guide](./ci-build-optimization.md) for full details. --- @@ -215,6 +303,47 @@ Consider adding: ### "Workflow is too slow" -- Consider removing androidTest job if not needed -- Reduce matrix API levels (currently only API 34) -- Optimize Gradle build with more aggressive caching +First, check if you're getting cache hits: + +1. Look for "Configuration cache entry reused" in logs +2. Check for "FROM-CACHE" for build tasks +3. Verify Gradle setup completes in <15s + +If still slow: + +- See [CI Build Optimization Guide](./ci-build-optimization.md) for troubleshooting +- Consider increasing workers or memory in `ci-gradle.properties` +- Check for configuration cache problems: `./gradlew --configuration-cache help` + +### "Configuration cache errors" + +Some plugins may not be compatible with configuration cache. If you see errors: + +```bash +# Navigate to project root first +cd /path/to/chai # Make sure you're in the project root, not in /docs + +# Test locally +./gradlew clean build --configuration-cache + +# Temporarily disable if needed +./gradlew build --no-configuration-cache +``` + +**Note:** Always run Gradle commands from the **project root directory** (where `gradlew` is +located), not from subdirectories like `docs/`. + +See +the [Gradle Configuration Cache docs](https://docs.gradle.org/current/userguide/configuration_cache.html) +for details. + +--- + +## Additional Resources + +- [CI Build Optimization Guide](./ci-build-optimization.md) - Detailed optimization breakdown +- [Gradle Performance Guide](https://docs.gradle.org/current/userguide/performance.html) +- [Configuration Cache](https://docs.gradle.org/current/userguide/configuration_cache.html) +- [GitHub Actions Optimization](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows) +- [Android Build Performance](https://developer.android.com/studio/build/optimize-your-build) + From c4ad72f111a420e4e00cbeb0350a230f69e51dee Mon Sep 17 00:00:00 2001 From: "./root" Date: Thu, 23 Oct 2025 09:21:29 +0200 Subject: [PATCH 08/39] create a quickstart ci doc --- docs/quick-start-ci.md | 266 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 docs/quick-start-ci.md diff --git a/docs/quick-start-ci.md b/docs/quick-start-ci.md new file mode 100644 index 0000000..75f810b --- /dev/null +++ b/docs/quick-start-ci.md @@ -0,0 +1,266 @@ +# Quick Start: CI/CD Setup + +## πŸš€ TL;DR + + +## Running Builds Locally + +### From Project Root + +**Important:** Always run Gradle commands from the project root (where `gradlew` is located): + +```bash +# You should be here: +cd /path/to/chai + +# NOT here: +# cd /path/to/chai/docs ❌ +``` + +### Test the Build + +```bash +# Clean build with configuration cache (matches CI) +./gradlew clean build --configuration-cache + +# Lint +./gradlew lintDebug --build-cache --parallel --configure-on-demand --configuration-cache + +# Unit tests +./gradlew testDebugUnitTest --build-cache --parallel --configure-on-demand --configuration-cache + +# Instrumented tests (requires emulator) +./gradlew connectedDebugAndroidTest --build-cache --parallel --configure-on-demand --configuration-cache +``` + +--- + +## Understanding CI Jobs + +### What Runs on Every PR/Push? + +``` +cancel-previous (15s) + ↓ + β”œβ”€ lint (1m 20s β†’ 40s cached) + β”œβ”€ build (1m 40s β†’ 50s cached) } PARALLEL + └─ test (50s β†’ 30s cached) +``` + +**Total time:** ~2m 25s first run, ~50s cached ⚑ + +### What Runs Conditionally? + +**Instrumented Tests** only run when: + +1. Pushing to `main` or `develop` branches, OR +2. Commit message contains `[test-instrumented]` + +**To trigger instrumented tests on your PR:** + +```bash +git commit -m "Add new feature [test-instrumented]" +git push +``` + +--- + +## CI Optimization Features + +### βœ… Enabled by Default + +- **4 parallel workers** (utilizing all 4 GitHub runner cores) +- **Configuration cache** (caches Gradle configuration phase) +- **Build cache** (reuses task outputs) +- **Incremental compilation** (only recompiles changed files) +- **4GB JVM heap** (faster builds with more memory) +- **Parallel GC** (better garbage collection for CI) + +### πŸ“Š Performance Impact + +| Scenario | Time | vs Sequential | vs No Cache | +|----------|------|---------------|-------------| +| First run (no cache) | 2m 25s | 48% faster | baseline | +| Cached build | 50s | 48% faster | **76% faster** πŸ”₯ | +| Instrumented tests | 8-9m | N/A | 20% faster | + +--- + +## Common Tasks + +### Running Just One Module + +```bash +# Test only chai library +./gradlew :chai:test + +# Build only chaidemo app +./gradlew :chaidemo:assembleDebug +``` + +### Checking Configuration Cache + +```bash +# Verify configuration cache works +./gradlew build --configuration-cache + +# See configuration cache report +./gradlew build --configuration-cache --info | grep "configuration cache" +``` + +### Debugging Slow Builds + +```bash +# Profile build performance (generates HTML report) +./gradlew build --profile + +# Check task dependencies +./gradlew :chai:assembleDebug --dry-run + +# Profile build performance +./gradlew build --profile +``` + +--- + +## Troubleshooting + +### "Configuration cache entry reused" not showing + +**Cause:** First run or files changed +**Solution:** Run build twice - first populates cache, second uses it + +```bash +./gradlew clean build --configuration-cache # Populates cache +./gradlew clean build --configuration-cache # Uses cache βœ… +``` + +### Build failing with "Project directory not part of build" + +**Cause:** Running Gradle from wrong directory +**Solution:** Navigate to project root first + +```bash +# Wrong +cd docs +./gradlew build # ❌ Error + +# Correct +cd /path/to/chai # Project root +./gradlew build # βœ… Works +``` + +### Configuration cache problems + +**Cause:** Plugin incompatibility +**Solution:** Temporarily disable or report issue + +```bash +# Disable configuration cache temporarily +./gradlew build --no-configuration-cache + +# Check compatibility +./gradlew help --configuration-cache +``` + +### Out of memory errors + +**Cause:** Not enough JVM heap +**Solution:** Increase memory in `gradle.properties` + +```properties +org.gradle.jvmargs=-Xmx6144m # Increase to 6GB +``` + +--- + +## What Gets Cached? + +### 1. Gradle Dependencies Cache + +- **What:** Downloaded dependencies (AAR, JAR files) +- **Where:** `~/.gradle/caches/modules-2/` +- **Invalidation:** When `libs.versions.toml` or `build.gradle.kts` changes +- **Speed boost:** 30-40% on dependency resolution + +### 2. Configuration Cache + +- **What:** Gradle configuration phase results +- **Where:** `.gradle/configuration-cache/` +- **Invalidation:** When build scripts change +- **Speed boost:** 30-50% on configuration phase + +### 3. Build Cache + +- **What:** Task output results +- **Where:** `.gradle/build-cache/` +- **Invalidation:** When task inputs change +- **Speed boost:** 20-40% on compilation + +### 4. AVD Cache (CI only) + +- **What:** Android Virtual Device snapshot +- **Where:** `~/.android/avd/` +- **Invalidation:** When API level changes +- **Speed boost:** 3-5 minutes saved per instrumented test run + +--- + +## Best Practices + +### βœ… Do + +- Run builds from project root +- Use `--configuration-cache` for CI-like builds +- Check cache hit logs to verify optimization +- Clean occasionally: `./gradlew clean cleanBuildCache` +- Update dependencies regularly + +### ❌ Don't + +- Run Gradle from subdirectories (docs, src, etc.) +- Disable caching unless debugging +- Commit `.gradle/` or `build/` directories +- Run instrumented tests on every PR (use `[test-instrumented]` flag) + +--- + +## More Information + +- [Full CI Workflow Guide](./ci-workflow-guide.md) - Complete workflow documentation +- [CI Build Optimization](./ci-build-optimization.md) - Detailed optimization breakdown +- [Gradle Performance](https://docs.gradle.org/current/userguide/performance.html) - Official docs +- [Configuration Cache](https://docs.gradle.org/current/userguide/configuration_cache.html) - Deep + dive + +--- + +## Quick Reference + +### CI Job Status + +Check your PR's CI status: + +1. Go to your PR on GitHub +2. Scroll to "Checks" section at bottom +3. See which jobs passed/failed +4. Click "Details" to view logs + +### Local Build Commands + +```bash +# Match CI lint job +./gradlew lintDebug --build-cache --parallel --configure-on-demand --configuration-cache + +# Match CI build job +./gradlew assembleDebug bundleDebug --build-cache --parallel --configure-on-demand --configuration-cache + +# Match CI test job +./gradlew testDebugUnitTest --build-cache --parallel --configure-on-demand --configuration-cache + +# Full CI simulation (without instrumented tests) +./gradlew clean lintDebug assembleDebug bundleDebug testDebugUnitTest \ + --build-cache --parallel --configure-on-demand --configuration-cache +``` + +--- \ No newline at end of file From 37a131ab693d80a5b8252c72ecd05fdc9e9a8b91 Mon Sep 17 00:00:00 2001 From: "./root" Date: Thu, 23 Oct 2025 09:21:40 +0200 Subject: [PATCH 09/39] update README.md --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index 6d6c75f..93e73d8 100755 --- a/README.md +++ b/README.md @@ -43,6 +43,29 @@ To Implement chai, See the example implementation that exists by running [chaidemo] that contains the various implementations of the elements of the design system. +## CI/CD Quick Start + +The project uses GitHub Actions for continuous integration with optimized parallel execution and +caching. + +**Quick commands to test locally:** + +```bash +# Run all checks (matches CI) +./gradlew clean lintDebug assembleDebug testDebugUnitTest --configuration-cache + +# Trigger instrumented tests on PR +git commit -m "Your message [test-instrumented]" +``` + +πŸ“– **See [CI Quick Start Guide](./docs/quick-start-ci.md)** for complete local development setup and +troubleshooting. + +For detailed optimization information, see: + +- [CI Workflow Guide](./docs/ci-workflow-guide.md) - Job structure and timing +- [CI Build Optimization](./docs/ci-build-optimization.md) - Performance details + ### [Running Project] Known issue with gradle: - If you run into an error when building project, ist probably a false negative. From d21ef74e41c003ca84f1bfd8931dd8cc79e56240 Mon Sep 17 00:00:00 2001 From: "./root" Date: Thu, 23 Oct 2025 09:27:29 +0200 Subject: [PATCH 10/39] drop to 0.91 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5781a7a..1783cad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cancel Previous Build - uses: styfle/cancel-workflow-action@0.13.0 + uses: styfle/cancel-workflow-action@0.9.1 with: access_token: ${{ github.token }} From 52321bb60c64156447bde0a58ecac7eecf3fdc7c Mon Sep 17 00:00:00 2001 From: "./root" Date: Fri, 19 Jun 2026 00:18:33 +0200 Subject: [PATCH 11/39] Added code analysis findings for chai project --- docs/tech/findings.md | 165 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 docs/tech/findings.md diff --git a/docs/tech/findings.md b/docs/tech/findings.md new file mode 100644 index 0000000..6a0d2bb --- /dev/null +++ b/docs/tech/findings.md @@ -0,0 +1,165 @@ +# Code Analysis Findings + +A review of the Chai design system project across its three modules (`chai`, `chaidemo`, `chailinter`), plus build, CI, tooling, and documentation. Findings reference source by `path:line` rather than embedding code. + +## Summary + +The bones are good. There is a semantic color palette, typography atoms, a theme wrapper, and a few button components with previews. The problem is the gap between what the project advertises and what actually runs. The custom lint meant to enforce the design system does nothing. Several promised components are empty stubs. The color palette is wired into the theme but the components never read from it. A couple of tooling scripts point at files that were never created. These are mostly correctness and completeness gaps, not deep design mistakes, which is the good news: they are fixable without rethinking the architecture. + +Severity legend: **High** (broken or misleading), **Medium** (should fix before release), **Low** (polish). + +## Architecture and design-system correctness + +**The custom `ChaiColors` palette is defined but barely used. (High)** +`ChaiTheme` (`chai/src/main/java/com/droidconke/chai/Theme.kt:81`) provides `LocalChaiColorsPalette` through a `CompositionLocalProvider`, but calls `MaterialTheme(content = content)` with no `colorScheme` argument. So `MaterialTheme.colorScheme` stays the stock Material 3 default. The components then read from the Material scheme instead of the Chai palette: `CPrimaryButton` uses `MaterialTheme.colorScheme.primary` (`chai/src/main/java/com/droidconke/chai/components/CButtons.kt:89-90`). The result is that the 38-field semantic palette in `colors/ChaiColors.kt:43-82` is mostly dead: it is plumbed into the theme but the shipped components do not consume it. This undercuts the project's central premise (a design system that drives color usage). + +**`CPrimaryButton` never sets a container color. (High)** +`CButtons.kt:88-91` builds `ButtonDefaults.buttonColors` with only `contentColor` and `disabledContentColor`. The button's background falls back to the Material default container color, so the "primary" button is not visibly branded. It also sets `contentColor` to `colorScheme.primary`, which is the brand color, making text the brand color on a default container rather than the inverse you would expect for a primary button. + +**Spacing values used as no-op statements. (High)** +`Space5`, `Space15`, `Space30` are `Dp` values (`utils/Spacing.kt:32-34`), not composables. They are written as bare statements where a `Spacer` is intended: +- `CButtons.kt:113` (`Space5` between the icon and the label in `COutlinedPrimaryButton`) +- `chaidemo/src/main/java/com/droidconke/chaidemo/screens/ChaiButtonsDemoScreen.kt:26` (`Space30`) + +These render nothing. The intended API is the `Spacer5()` / `Spacer15()` / `Spacer30()` composables that already exist in `utils/Spacing.kt:46-58`, or `Spacer(Modifier.width(...))` for horizontal gaps. + +**Inconsistent `CompositionLocal` factory choice. (Medium)** +Colors use `staticCompositionLocalOf` (`colors/ChaiColors.kt:84`); typography uses `compositionLocalOf` (`typography/ChaiTypography.kt`). For design tokens that change rarely, `staticCompositionLocalOf` is the usual choice. The mismatch gives the two token families different recomposition behavior for no stated reason. + +**`statusBarColor` is deprecated. (Low)** +`Theme.kt:71` sets `activity.window.statusBarColor`, deprecated as of API 35. With `compileSdk = 36` this will surface as a lint/deprecation warning; the edge-to-edge APIs are the current path. + +## Incomplete and stub implementations + +**Header-only component files. (High)** +The following contain only a license header and a package declaration (15 lines each), no implementation: +- `chai/src/main/java/com/droidconke/chai/components/CCards.kt` +- `chai/src/main/java/com/droidconke/chai/components/CTabs.kt` +- `chai/src/main/java/com/droidconke/chai/icons/Icons.kt` +- `chai/src/main/java/com/droidconke/chai/images/Images.kt` +- `chai/src/main/java/com/droidconke/chai/components/CInputFiels.kt` (also misnamed; see below) + +The README and docs present these as part of the system, but the APIs do not exist yet. + +**Misspelled filename: `CInputFiels.kt`. (Medium)** +`chai/src/main/java/com/droidconke/chai/components/CInputFiels.kt` should be `CInputFields.kt`. The file is also an empty stub. + +**Misspelled color atom: `ChaiSteal`. (Low)** +`atoms/Color.kt:41` defines `ChaiSteal` (intended "Steel"). The typo propagates through every reference in `colors/ChaiColors.kt`. Renaming is a breaking change for any external consumer, so weigh it against current adoption. + +**Demo app does not demonstrate the components. (Medium)** +`ChaiButtonsDemoScreen.kt` is named for buttons but renders only a text label and the dead `Space30` statement; no `CButton`, `CPrimaryButton`, or `COutlinedPrimaryButton` appears. The demo also covers none of `CCards`, `CTabs`, input fields, or tabs. A sample app is the main way consumers learn a design system, so this is a real gap. + +## Build configuration + +**`targetSdk` mixing and JVM target inconsistency across modules. (Medium)** +- `chai/build.gradle.kts` sets `compileSdk = 36`, `minSdk = 28`, and Java/JVM target 1.8. +- `chaidemo/build.gradle.kts` sets the same SDKs with `targetSdk = 36`, Java/JVM target 1.8. +- `chailinter/build.gradle.kts` uses Java 17 / `JvmTarget.JVM_17`. + +The two Android modules compile to Java 8 bytecode while the lint module targets Java 17. That is workable (lint runs on the host JVM), but the Java 8 target for Compose modules on Kotlin 2.3 / compileSdk 36 is unusually low and worth raising to 11 or 17 for consistency. + +**Stale `kotlinCompilerExtensionVersion`. (Medium)** +Both `chai/build.gradle.kts:67` and `chaidemo/build.gradle.kts` set `composeOptions { kotlinCompilerExtensionVersion = "1.5.15" }`. With Kotlin 2.x the Compose compiler is applied via the `org.jetbrains.kotlin.plugin.compose` Gradle plugin (already present), and this `composeOptions` value is ignored. It is dead config that implies an older compiler than is actually in use. + +**Manifest `package` attribute alongside `namespace`. (Low)** +Several module manifests still declare `package="com.droidconke.chai"` (for example the `chai` manifests) while the build files also set `namespace`. The manifest `package` attribute is deprecated in AGP 8; `namespace` in Gradle is the source of truth. + +**Leftover "build logic" TODO comments. (Low)** +`chai/build.gradle.kts:43` and `:50` carry `//See if i can add this to build Logic`. The root `build.gradle.kts` has a similar note. These point at the missing convention-plugin work described under Documentation below. + +**`jetbrains.compose` plugin on an Android-only library. (Low)** +`chai/build.gradle.kts` applies both `compose.compiler` and `jetbrains.compose` (Compose Multiplatform). For an Android-only design system the multiplatform plugin is likely redundant; confirm it is intentional or drop it. + +## Custom lint module (`chailinter`) + +**The detectors do nothing. (High)** +Both detectors expose an `ISSUE` set to `EMPTY_ARRAY`: +- `chailinter/src/main/java/com/droidconke/chailinter/detector/IncorrectColourUsageDetector.kt:25` (the class has no scanner overrides at all) +- `chailinter/src/main/java/com/droidconke/chailinter/detector/ChaiIncorrectUsageDetector.kt` (its `METHOD_NAMES` map is empty, with a "To be implemented" comment) + +They are registered in `ChaiLinterIssueRegistry.kt`, but registration is meaningless with empty issues. The headline feature, lint that forces use of the design system over raw Material colors, is not implemented. This is the single biggest gap between the project's stated purpose and its behavior. + +**No lint tests. (Medium)** +`chailinter/build.gradle.kts` pulls in `lint-tests` and JUnit, but there is no test source directory. Custom lint rules are normally developed test-first against `lint-tests`; none exist. + +**Duplicate copyright header in the build script. (Low)** +`chailinter/build.gradle.kts` carries two license headers (lines 1-15 and 18-31). + +> Note: `chailinter/bin/` mirrors `src/main` on disk but is correctly listed in `.gitignore` (`bin/`) and is not tracked. No action needed; it is a local IDE build artifact. + +## Testing + +**Only scaffold tests exist. (Medium)** +Every test file is a generated stub: +- `chai/src/test/java/.../ExampleUnitTest.kt` and `.../ExampleInstrumentedTest.kt` +- `chaidemo/src/test/java/.../ExampleUnitTest.kt` and `.../ExampleInstrumentedTest.kt` + +They assert `2 + 2 == 4` and the package name. There are no component render tests, no theme/dark-mode tests, and no screenshot tests, despite the README's testing roadmap calling for exactly these. The custom lint has no tests either (above). + +## CI, tooling, and scripts + +**`make spotless` and the pre-push `check` reference a missing file. (High)** +`Makefile:30` and `tools/pre-push:91` both run `./gradlew --init-script gradle/init.gradle.kts ...`, but `gradle/init.gradle.kts` does not exist (only `libs.versions.toml` and `wrapper/` are in `gradle/`). Both commands fail. Spotless is configured under `spotless/` but is not applied in any `*.gradle.kts`, so there is no working `spotless` task regardless. + +**`tools/setup.sh` has a corrupted curl line. (High)** +`tools/setup.sh:25` reads `curl -sSLo "${GIT_DIR}/hooks/commit-msg" \653 jym5` with garbage where the URL/continuation belongs. Running the setup script fails to install the commit-msg hook. + +**TODO-to-issue workflow watches the wrong branch. (Medium)** +`.github/workflows/todo.yml:7` triggers on `dev`, but the repository's integration branch is `develop` (see recent history and PR merges). The automation never fires on the branch that is actually used. + +**Otherwise the main CI workflow is solid. (Informational)** +`.github/workflows/main.yml` is well structured: parallel jobs, Gradle configuration cache and build cache, AVD caching, and conditional instrumented tests gated on branch or a `[test-instrumented]` commit-message flag. `gradle.properties` is tuned for CI (parallel, caching, 4 workers, 4 GB heap). + +## Documentation + +**Several core docs are empty. (Medium)** +- `docs/chaiLinter.md` (0 bytes) +- `docs/chaiProjectArchitecture.md` (0 bytes) +- `docs/atoms/atoms.md` (0 bytes) +- `docs/components/components.md` (0 bytes) +- `docs/architecture.md` (header only) + +The README links to these as if they were written. + +**Broken and duplicated README links. (Medium)** +In `README.md`: +- Links to `docs/chaiArchitecture.md`; the file is `docs/chaiProjectArchitecture.md`. +- Links to `docs/buildlogic.md`; no such file exists, and the convention-plugin infrastructure it describes is not present either (no `build-logic/` or `buildLogic/` module). +- Links to `docs/atoms.md` and `docs/components.md`; the files live at `docs/atoms/atoms.md` and `docs/components/components.md`. +- The "chaidemop" entry (line 38) is a typo for chaidemo, has the wrong description, and duplicates the components link. + +**Documented build-logic does not exist. (Medium)** +The README and task list promise a `buildLogic/` convention-plugin setup. There is no such module; configuration is repeated inline across the three `build.gradle.kts` files (compileSdk, minSdk, compose options, Java target), which is exactly what convention plugins would remove. + +**README typos. (Low)** +`README.md` contains "wiyth", "convengion polugins", "ist probably", and "ana ctions" (lines 34, 71, 74), among others. + +## Dependency and version state + +The working tree has uncommitted version bumps in `gradle/libs.versions.toml` on branch `project-fix-reset`: +- Kotlin 2.2.20 β†’ 2.3.0 +- AGP 8.13.0 β†’ 8.13.2 +- Compose BOM 2025.10.01 β†’ 2026.01.00 +- lifecycle, activity-compose, runtime, ui-tooling bumped +- JUnit 6 (Jupiter) entries added + +The JUnit 6 entries are added to the catalog but not wired into any module (modules still use JUnit 4 via `libs.junit`). Either finish the migration or drop the unused entries to avoid confusion. Decide whether this version bump is intentional before committing. + +**Bring the whole catalog up to date. (Medium)** +The bumps above are a start, but the project should do a deliberate pass over the entire `gradle/libs.versions.toml` and move every dependency to its current stable release, not just the handful already touched in the working tree. This is an old project and the versions reflect that. Concretely: +- Run `./gradlew dependencyUpdates` (Ben Manes versions plugin) or use Android Studio's "AGP Upgrade Assistant" plus catalog inspection to list what is behind. +- Cover the rest of the catalog that was not bumped: `androidx.core:core-ktx`, `appcompat`, `material` (the Google Material View library), `material3` / `material3-android`, the Compose `runtime`/`ui-tooling` entries, the lint tooling (`lint`, `lint-tests`, currently `32.0.0`), and the test stack (`androidx.test.ext:junit`, `espresso-core`). +- Prefer driving Compose artifact versions through the Compose BOM rather than pinning `runtimeAndroid` / `uiTooling` / `material3Android` individually; several of those pins exist only because the BOM is not being relied on consistently. +- Treat the update as its own change: bump, build, run `lint` / `detekt` / tests, and check the Compose and AGP release notes for breaking changes (especially around the deprecated `statusBarColor` and manifest `package` items noted earlier). Keep it on this branch and out of unrelated commits. + +## Prioritized recommendations + +1. **Wire `ChaiColors` into the theme.** Pass a `colorScheme` derived from the Chai palette into `MaterialTheme`, or have components read `ChaiTheme.colors` / `LocalChaiColorsPalette` directly. Today the palette is decorative. (Theme.kt, CButtons.kt) +2. **Fix `CPrimaryButton` colors** so the primary button has a branded container, and replace the bare `Space*` statements with `Spacer*()` calls. (CButtons.kt, ChaiButtonsDemoScreen.kt) +3. **Implement the lint detectors or remove the module's claims.** As shipped, the design-system enforcement is non-functional. Add `lint-tests`-based tests as you go. (chailinter) +4. **Repair the tooling scripts:** create or remove `gradle/init.gradle.kts`, actually apply Spotless, fix the `setup.sh` curl line, and point `todo.yml` at `develop`. +5. **Fill in or implement the stubs:** the empty component files, the empty docs, and the demo screens. Fix the README links and typos while you are there. +6. **Add real tests** for component rendering and theme switching, replacing the scaffold tests. +7. **Clean up build config:** drop the ignored `kotlinCompilerExtensionVersion`, reconsider the Java 8 target, and finish or revert the JUnit 6 / version-catalog changes. Extract shared config into convention plugins to match the documentation. +8. **Update dependencies to latest.** Do a full pass over `gradle/libs.versions.toml`, not just the few versions already bumped. Lean on the Compose BOM, then build and run lint, detekt, and tests to catch breakage. (See "Dependency and version state".) From 7290a9b5272c4f36974885638c02f6340f5589b5 Mon Sep 17 00:00:00 2001 From: "./root" Date: Fri, 19 Jun 2026 00:18:33 +0200 Subject: [PATCH 12/39] Updated dependencies to latest versions --- gradle/libs.versions.toml | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 80c722e..603811d 100755 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,23 +1,23 @@ [versions] -agp = "8.13.0" -kotlin = "2.2.20" +agp = "8.13.2" +kotlin = "2.3.0" junit = "4.13.2" -composeBom = "2025.10.01" -lint = "31.13.0" -lintTests = "31.13.0" +composeBom = "2026.01.00" +lint = "32.0.0" +lintTests = "32.0.0" coreKtx = "1.17.0" +junit6 = "6.0.2" junitVersion = "1.3.0" espressoCore = "3.7.0" -lifecycleRuntimeKtx = "2.9.4" -activityCompose = "1.11.0" -jetbrainsKotlinJvm = "2.2.20" +lifecycleRuntimeKtx = "2.10.0" +activityCompose = "1.12.2" appcompatVersion = "1.7.1" materialVersion = "1.13.0" -compose-plugin = "1.9.1" -runtimeAndroid = "1.9.4" +compose-plugin = "1.10.0" +runtimeAndroid = "1.10.1" material3Android = "1.4.0" -uiToolingPreviewAndroid = "1.9.4" -uiTooling = "1.9.4" +uiToolingPreviewAndroid = "1.10.1" +uiTooling = "1.10.1" [libraries] android-lint-tests = { module = "com.android.tools.lint:lint-tests", version.ref = "lintTests" } @@ -43,6 +43,9 @@ material = { group = "com.google.android.material", name = "material", version.r androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" } androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" } androidx-ui-tooling-preview-android = { group = "androidx.compose.ui", name = "ui-tooling-preview-android", version.ref = "uiToolingPreviewAndroid" } +# JUnit 6 (Jupiter) for unit tests - see https://junit.org/junit5/docs/current/user-guide/ +junit6 = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit6" } +junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher" } ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "uiTooling" } @@ -51,5 +54,5 @@ android-application = { id = "com.android.application", version.ref = "agp" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } android-library = { id = "com.android.library", version.ref = "agp" } -jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" } +jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } From 58337bb278e39f6341be9b46d9d2bd4f18ad6a4f Mon Sep 17 00:00:00 2001 From: "./root" Date: Fri, 19 Jun 2026 02:36:06 +0200 Subject: [PATCH 13/39] Ignored AI memory and scratch directories --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index fbe6482..ff068bc 100755 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,7 @@ captures/ # Generated Kotlin folder .kotlin +# AI infra: skills/agents/scripts are committed; memory + scratch are per-user/local +.ai/memory/ +.ai/tmp/ + From 309d0d558f7c04dfd4f0a811617ec0132d6a1bd8 Mon Sep 17 00:00:00 2001 From: "./root" Date: Fri, 19 Jun 2026 02:36:06 +0200 Subject: [PATCH 14/39] Added AI tooling guard-rail scripts --- scripts/check-agent-tooling.sh | 87 +++++++++++++++++++++++++ scripts/check-design-system-usage.sh | 96 ++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100755 scripts/check-agent-tooling.sh create mode 100755 scripts/check-design-system-usage.sh diff --git a/scripts/check-agent-tooling.sh b/scripts/check-agent-tooling.sh new file mode 100755 index 0000000..b12b7ce --- /dev/null +++ b/scripts/check-agent-tooling.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Lint the repo's shared AI tooling (.ai/) for drift. Safe to run in CI. +# +# Checks (committed surfaces β€” always run): +# 1. Every .ai/skills/ has a SKILL.md with `name:` and `description:` +# front-matter, and the front-matter `name` matches the directory. +# 1b. Every .ai/agents/.md has `name:` and `description:` front-matter, +# and the front-matter `name` matches the filename stem. +# +# Checks (local-only β€” .ai/memory/ is git-ignored, absent in CI): +# 2. Every .ai/memory/*.md (except MEMORY.md) is linked from MEMORY.md, and +# MEMORY.md has no bullets pointing at a missing file. +# +# Exits non-zero if any committed-surface check fails. Memory drift is a +# warning when the store is present, and skipped entirely when it isn't. +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" \ + || repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" + +fail=0 +err() { echo "FAIL: $*" >&2; fail=1; } +warn() { echo "warn: $*" >&2; } +ok() { echo "ok: $*"; } + +fm_field() { # file, field -> first value inside the leading --- block + sed -n '/^---$/,/^---$/p' "$1" | sed -n "s/^$2:[[:space:]]*//p" | head -1 +} + +# --- 1. SKILL.md front-matter -------------------------------------------- +skill_count=0 +if [ -d .ai/skills ]; then + for d in .ai/skills/*/; do + [ -d "$d" ] || continue + name="$(basename "$d")" + f="${d}SKILL.md" + if [ ! -f "$f" ]; then err "$d has no SKILL.md"; continue; fi + [ -n "$(fm_field "$f" name)" ] || err "$f: missing front-matter 'name:'" + [ -n "$(fm_field "$f" description)" ] || err "$f: missing front-matter 'description:'" + fm_name="$(fm_field "$f" name)" + if [ -n "$fm_name" ] && [ "$fm_name" != "$name" ]; then + err "$f: front-matter name '$fm_name' != directory '$name'" + fi + skill_count=$((skill_count + 1)) + done +fi +[ "$skill_count" -gt 0 ] && ok "validated $skill_count SKILL.md front-matters" + +# --- 1b. Agent role-brief front-matter ----------------------------------- +agent_count=0 +if [ -d .ai/agents ]; then + for f in .ai/agents/*.md; do + [ -f "$f" ] || continue + stem="$(basename "$f" .md)" + [ -n "$(fm_field "$f" name)" ] || err "$f: missing front-matter 'name:'" + [ -n "$(fm_field "$f" description)" ] || err "$f: missing front-matter 'description:'" + fm_name="$(fm_field "$f" name)" + if [ -n "$fm_name" ] && [ "$fm_name" != "$stem" ]; then + err "$f: front-matter name '$fm_name' != filename '$stem'" + fi + agent_count=$((agent_count + 1)) + done +fi +[ "$agent_count" -gt 0 ] && ok "validated $agent_count agent role-brief front-matters" + +# --- 2. Memory index integrity (local-only) ------------------------------ +mem_dir=".ai/memory" +index="$mem_dir/MEMORY.md" +if [ -d "$mem_dir" ] && [ -f "$index" ]; then + for f in "$mem_dir"/*.md; do + base="$(basename "$f")" + [ "$base" = "MEMORY.md" ] && continue + grep -q "($base)" "$index" || warn "memory file not indexed in MEMORY.md: $base" + done + grep -oE '\(([a-zA-Z0-9_./-]+\.md)\)' "$index" | tr -d '()' | while IFS= read -r ref; do + [ -f "$mem_dir/$ref" ] || warn "MEMORY.md links missing file: $ref" + done + ok "memory index checked (local store present)" +else + echo "info: .ai/memory absent (git-ignored / CI) β€” skipping memory checks" +fi + +if [ "$fail" -ne 0 ]; then + echo; echo "agent-tooling check FAILED"; exit 1 +fi +echo; echo "agent-tooling check passed" diff --git a/scripts/check-design-system-usage.sh b/scripts/check-design-system-usage.sh new file mode 100755 index 0000000..bf352bc --- /dev/null +++ b/scripts/check-design-system-usage.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Guard rail: flag source that bypasses the Chai design system. +# +# This is the check the custom `chailinter` is meant to perform but whose +# detectors are currently non-functional stubs (see docs/tech/findings.md). +# Until those are implemented, this script is the enforcement. +# +# HARD FAIL (exit 1): +# - Hardcoded color literals (`Color(0x…)`, `Color.White`/`Color.Red`/…) +# anywhere except chai/.../atoms/Color.kt, the one place raw colors are +# allowed to be defined. +# +# WARN (exit 0, reported): +# - `MaterialTheme.colorScheme.*` read inside components (should be +# `ChaiTheme.colors` / `LocalChaiColorsPalette`). Known gap in findings.md. +# - `@Preview` instead of `@ChaiPreview` (CONTRIBUTING.MD). +# - Raw Material 3 components imported in chaidemo screen code. +# +# Pass --strict to make warnings fail too. +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" \ + || repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" + +strict=0 +[ "${1:-}" = "--strict" ] && strict=1 + +fail=0 +warns=0 +err() { echo "FAIL: $*" >&2; fail=1; } +warn() { echo "warn: $*" >&2; warns=$((warns + 1)); [ "$strict" -eq 1 ] && fail=1; return 0; } + +# Source roots to scan (Kotlin only). +roots=(chai/src/main chaidemo/src/main) +allow_color_file="chai/src/main/java/com/droidconke/chai/atoms/Color.kt" + +kt_files() { # list .kt under the roots that exist + for r in "${roots[@]}"; do + [ -d "$r" ] && find "$r" -type f -name '*.kt' + done +} + +# --- HARD: hardcoded colors outside the atoms color file ----------------- +while IFS= read -r f; do + [ -n "$f" ] || continue + [ "$f" = "$allow_color_file" ] && continue + hits="$(grep -nE 'Color\(0x|Color\.(White|Black|Red|Green|Blue|Gray|Grey|Yellow|Cyan|Magenta|Transparent|DarkGray|LightGray)' "$f" || true)" + if [ -n "$hits" ]; then + while IFS= read -r line; do + err "$f:${line%%:*} hardcoded color β€” define it in atoms/Color.kt and use a Chai token" + done <<< "$hits" + fi +done < <(kt_files) + +# --- WARN: MaterialTheme.colorScheme in components ----------------------- +while IFS= read -r f; do + [ -n "$f" ] || continue + hits="$(grep -nE 'MaterialTheme\.colorScheme' "$f" || true)" + if [ -n "$hits" ]; then + while IFS= read -r line; do + warn "$f:${line%%:*} reads MaterialTheme.colorScheme β€” use ChaiTheme.colors / LocalChaiColorsPalette" + done <<< "$hits" + fi +done < <(kt_files) + +# --- WARN: @Preview instead of @ChaiPreview ------------------------------ +while IFS= read -r f; do + [ -n "$f" ] || continue + # match @Preview but not @ChaiPreview + hits="$(grep -nE '@Preview' "$f" | grep -v 'ChaiPreview' || true)" + if [ -n "$hits" ]; then + while IFS= read -r line; do + warn "$f:${line%%:*} uses @Preview β€” prefer @ChaiPreview (CONTRIBUTING.MD)" + done <<< "$hits" + fi +done < <(kt_files) + +# --- WARN: raw Material 3 components in chaidemo screens ------------------ +if [ -d chaidemo/src/main ]; then + while IFS= read -r f; do + [ -n "$f" ] || continue + hits="$(grep -nE 'androidx\.compose\.material3\.(Button|OutlinedButton|Card|Text|TabRow|Tab|TextField|OutlinedTextField)\b' "$f" || true)" + if [ -n "$hits" ]; then + while IFS= read -r line; do + warn "$f:${line%%:*} imports a raw Material 3 component in demo code β€” use the Chai C* equivalent" + done <<< "$hits" + fi + done < <(find chaidemo/src/main -type f -name '*.kt') +fi + +echo +if [ "$fail" -ne 0 ]; then + echo "design-system check FAILED ($warns warning(s))"; exit 1 +fi +echo "design-system check passed ($warns warning(s))" From 13a902f8a2376a2fdf8277bae3015b9080baa064 Mon Sep 17 00:00:00 2001 From: "./root" Date: Fri, 19 Jun 2026 02:36:06 +0200 Subject: [PATCH 15/39] Added tool-neutral AI infra in .ai directory --- .ai/README.md | 34 +++++++++++++ .ai/agents/code-reviewer.md | 26 ++++++++++ .ai/agents/gitter.md | 30 ++++++++++++ .ai/agents/memory-curator.md | 46 +++++++++++++++++ .ai/agents/planner.md | 26 ++++++++++ .ai/skills/README.md | 35 +++++++++++++ .ai/skills/audit-design-system-usage/SKILL.md | 38 ++++++++++++++ .ai/skills/check-build/SKILL.md | 48 ++++++++++++++++++ .ai/skills/check-lint/SKILL.md | 48 ++++++++++++++++++ .ai/skills/checks/SKILL.md | 49 +++++++++++++++++++ .ai/skills/commit-convention/SKILL.md | 46 +++++++++++++++++ .ai/skills/dependency-bump/SKILL.md | 43 ++++++++++++++++ .ai/skills/new-chai-component/SKILL.md | 45 +++++++++++++++++ AGENTS.md | 33 +++++++++++++ 14 files changed, 547 insertions(+) create mode 100644 .ai/README.md create mode 100644 .ai/agents/code-reviewer.md create mode 100644 .ai/agents/gitter.md create mode 100644 .ai/agents/memory-curator.md create mode 100644 .ai/agents/planner.md create mode 100644 .ai/skills/README.md create mode 100644 .ai/skills/audit-design-system-usage/SKILL.md create mode 100644 .ai/skills/check-build/SKILL.md create mode 100644 .ai/skills/check-lint/SKILL.md create mode 100644 .ai/skills/checks/SKILL.md create mode 100644 .ai/skills/commit-convention/SKILL.md create mode 100644 .ai/skills/dependency-bump/SKILL.md create mode 100644 .ai/skills/new-chai-component/SKILL.md create mode 100644 AGENTS.md diff --git a/.ai/README.md b/.ai/README.md new file mode 100644 index 0000000..8f79ee5 --- /dev/null +++ b/.ai/README.md @@ -0,0 +1,34 @@ +# .ai + +Tool-neutral AI infrastructure for the Chai design system repo. Plain Markdown and shell β€” no tool-specific config, so Claude Code, Cursor, Copilot, Gemini, and others can all read it. The repo-root `AGENTS.md` points agents here. + +## Layout + +``` +.ai/ + agents/ role briefs for sub-agents (committed) + skills/ reusable task procedures, one SKILL.md per dir (committed) + memory/ per-user knowledge store, git-ignored (NOT committed) + tmp/ scratch space, git-ignored +``` + +Guard-rail scripts live in the repo's `scripts/` dir (committed, run in CI and from git hooks): + +- `scripts/check-agent-tooling.sh` β€” validates this directory: every skill/agent has correct front-matter, and the memory index has no dangling links. +- `scripts/check-design-system-usage.sh` β€” fails when component source uses raw Material components or hardcoded colors instead of Chai tokens. + +## What each part is for + +| Part | Use it when | +|------|-------------| +| `agents/` | You want a focused role (review, planning, git, memory) with its own instructions. | +| `skills/` | A task has a repeatable procedure (run checks, scaffold a component, write a commit). Follow the SKILL.md instead of improvising. | +| `memory/` | You learned something durable (a decision + why, a correction, project state). Save it so the next session starts informed. | + +## Conventions + +- **Skills**: each `.ai/skills//SKILL.md` has YAML front-matter with `name` (must equal the directory) and `description` (one line on *when* to invoke). +- **Agents**: each `.ai/agents/.md` has front-matter with `name` (must equal the filename stem) and `description`. +- **Memory**: one fact per file, typed (`user`/`feedback`/`project`/`reference`), indexed in `MEMORY.md`. See `memory/reference_memory_store_convention.md`. + +Run `scripts/check-agent-tooling.sh` after editing anything here. diff --git a/.ai/agents/code-reviewer.md b/.ai/agents/code-reviewer.md new file mode 100644 index 0000000..631a3a9 --- /dev/null +++ b/.ai/agents/code-reviewer.md @@ -0,0 +1,26 @@ +--- +name: code-reviewer +description: Use to review a diff or branch for correctness bugs and Chai-specific convention violations before a commit or PR. Invoke for "review this", "check my changes", or as a pre-PR gate. Read-only β€” reports findings, does not edit. +tools: Read, Glob, Grep, Bash +--- + +You review Kotlin/Compose changes in the Chai design system. You report; you do not edit. + +# Scope + +Default to the working diff: `git diff` (unstaged), `git diff --staged`, or `git diff origin/develop...HEAD` for a branch. Confirm what you are reviewing before you start. + +# What to check, in priority order + +1. **Correctness** β€” logic bugs, null/empty handling, wrong state, Compose pitfalls (unstable params, missing `remember`, side effects in composition, a `Dp`/value written as a bare statement where a `Spacer*()` was meant). +2. **Design-system rules** (the reason this repo exists): + - Consumer/screen code uses Chai components + tokens, not raw Material 3 or hardcoded `Color(0x…)` / bare `.dp`. Run `scripts/check-design-system-usage.sh` and fold in its output. + - Components read color from `ChaiTheme.colors` / `LocalChaiColorsPalette`, not `MaterialTheme.colorScheme` (see the known `ChaiTheme` gap in `docs/tech/findings.md`). + - Previews use `@ChaiPreview`, are `private`, and wrap content in `ChaiTheme { }`. +3. **API surface** β€” this is a published library. Flag accidental `public` visibility, missing KDoc on new public composables, breaking renames of existing tokens/components. +4. **Conventions** β€” commit style (past tense, no period/prefix/body), PR target `develop`, no new raw-Material imports, no AI co-author trailer. +5. **Tests & stubs** β€” new components should have at least previews; note when a change touches a file that `findings.md` lists as a stub. + +# How to report + +Group by severity (High / Medium / Low). Each finding: `file:line`, one-line problem, concrete fix. Be specific and factual; do not pad. End with a one-line verdict: safe to commit / fix-then-commit / blocked. Read `.ai/memory/MEMORY.md` first so you don't re-flag known, documented gaps. diff --git a/.ai/agents/gitter.md b/.ai/agents/gitter.md new file mode 100644 index 0000000..c50367e --- /dev/null +++ b/.ai/agents/gitter.md @@ -0,0 +1,30 @@ +--- +name: gitter +description: Use to handle git and GitHub flow for Chai β€” branches, atomic commits, syncing onto develop, and PRs against develop following the repo conventions. Invoke for "commit this", "open a PR", "sync with develop", "clean up branches". Asks before any destructive or outward-facing action. +tools: Read, Glob, Grep, Bash +--- + +You run git/GitHub flow for the Chai repo, by the repo's rules. You confirm before anything destructive or outward-facing. + +# Repo rules (hard) + +- Integration branch is **`develop`**, not `main`. PRs target `develop`. Branch prefixes: `feature/`, `fix/`, `docs/`. +- Commit subjects: past tense, concise, no period, no body, no conventional-commits prefix. ("Added X", not "feat: add x.") +- Atomic commits: 1–3 files, split by dependency layer. One documentation `.md` file per commit. +- No AI/agent `Co-Authored-By` trailer. +- **Never** `git push --no-verify` or `--force`/`-f`. Fix hook failures. `--force-with-lease` only after an explicit yes. +- A shared commit-msg hook validates subject format β€” drafts must pass it. + +# Common flows + +**Commit:** review `git status`/`git diff`. Group changes into atomic commits by layer. Draft each subject in the required style (use the `commit-convention` skill). Stage precisely (named paths, not `git add -A` blindly). Commit. Never commit secrets or `local.properties`. + +**Sync onto develop:** `git fetch origin develop`; if behind, rebase the feature branch onto `origin/develop` (never rebase develop/main). On conflict, stop and let the user resolve. Push after rebase only with `--force-with-lease` and only after a yes. + +**Open a PR:** branch must be pushed and based on current `develop`. Use `gh pr create --base develop` with the repo's `.github/pull_request_template.md` sections filled (purpose, issue link, test steps). Link an issue. Keep PRs small. + +**Cleanup / deletion:** deleting branches (especially remote, on the shared `droidconKE/chai` org repo) is destructive. List exactly what will be deleted and get explicit confirmation of scope first. Never assume "all branches" includes others' work. + +# Always + +Confirm the current branch before acting. Surface what you're about to do for any push, branch delete, or PR. Only push when the user explicitly asks. diff --git a/.ai/agents/memory-curator.md b/.ai/agents/memory-curator.md new file mode 100644 index 0000000..ebec133 --- /dev/null +++ b/.ai/agents/memory-curator.md @@ -0,0 +1,46 @@ +--- +name: memory-curator +description: Use to review recent conversation context and persist durable facts to the project's .ai/memory store. Invoke when the user says "save to memory", "remember this", "update memory", or after they surface non-obvious context (named people, decisions + rationale, deadlines, external systems/IDs, corrections, confirmed approaches). Do NOT invoke for ephemeral task state, code patterns derivable from the repo, or generic best practices. +tools: Read, Write, Edit, Glob, Grep, Bash +--- + +You keep this repo's memory accurate, terse, and useful β€” silently, without bothering the user. + +# Memory directory + +The store is `/.ai/memory/`. Resolve it: run `git rev-parse --show-toplevel`, append `/.ai/memory`, confirm it exists with `ls`. If it does not exist, create it and write `MEMORY.md` with the four section headers before saving anything. The store is git-ignored and per-user; you never commit it. + +# Schema + +Four types, one file each. `MEMORY.md` is the index only. + +| Type | What goes here | Prefix | +|---|---|---| +| user | who the user is, role, working style | `user_*.md` | +| feedback | corrections/confirmations to apply as rules | `feedback_*.md` | +| project | ongoing work, decisions, in-flight state (decays) | `project_*.md` | +| reference | external systems, build facts, glossary | `reference_*.md` | + +File format: YAML frontmatter (`name`, `description`, `type`, `platform: claude`, optional `originSessionId`) then a terse body. For `feedback`/`project`, lead with the fact, then a `**Why:**` and a `**How to apply:**` line. For fast-decaying project entries add `Snapshot date: YYYY-MM-DD`. Convert relative dates to absolute. Full rules: `.ai/memory/reference_memory_store_convention.md`. + +# What to save / not save + +Save: a named person + role; a decision **with rationale**; a deadline/freeze (absolute date); an external system + ID; an explicit correction or confirmation; a glossary term; a brand/legal/design constraint + its source. + +Do NOT save (even if asked β€” save the non-obvious framing instead): code structure derivable from `ls`/`grep`; git history; fix recipes; ephemeral task state; generic best practices; anything already in `AGENTS.md` or `docs/`. + +# Workflow + +1. Resolve the dir. Read `MEMORY.md` and list files so you don't duplicate. +2. Read the context passed to you. If it's too thin, save nothing and exit cleanly. Never ask the user questions. +3. Find candidate facts. De-duplicate β€” prefer editing an existing entry. +4. Write/update files, then update `MEMORY.md` under the right section. +5. Run `scripts/check-agent-tooling.sh` to confirm the index has no dangling links. +6. Output a terse summary (Saved / Updated / Skipped). If nothing was worth saving, output exactly `Nothing new to save.` + +# Hard constraints + +- Never delete a memory unless asked or it contradicts ground truth. +- Never modify files outside `.ai/memory/`. +- Never run mutating git commands (the dir is git-ignored anyway). +- Run silently; no clarifying questions. diff --git a/.ai/agents/planner.md b/.ai/agents/planner.md new file mode 100644 index 0000000..550bf61 --- /dev/null +++ b/.ai/agents/planner.md @@ -0,0 +1,26 @@ +--- +name: planner +description: Use to turn a vague feature/fix request into a concrete, ordered implementation plan for the Chai repo before writing code. Invoke for "plan this", "how should I approach…", or any multi-step change. Produces a plan; does not edit code. +tools: Read, Glob, Grep, Bash +--- + +You design implementation plans for the Chai design system. You output a plan; you do not write the code. + +# Before planning + +1. Read `.ai/memory/MEMORY.md` + relevant entries, and `docs/tech/findings.md`. Many "new" tasks overlap a documented stub or a known gap β€” say so. +2. Locate the real files involved (`Grep`/`Glob`). Plans that name actual files and line ranges beat abstract ones. +3. State assumptions explicitly. If the request has more than one reasonable reading, present the options instead of silently picking one. + +# Plan shape + +- **Goal** restated as a testable outcome. +- **Affected modules/files** with paths. +- **Ordered steps**, split by dependency layer (tokens β†’ atoms β†’ components β†’ theme wiring β†’ demo β†’ tests). Each step has a one-line success check. +- **Design-system fit** β€” which existing Chai tokens/components to reuse; if a new component is needed, note it should land in `chai`, with `@ChaiPreview` previews, reading from `ChaiTheme.colors`. +- **Risks / breaking changes** β€” public API or token renames that affect consumers; anything touching the known `ChaiTheme` palette gap. +- **Verification** β€” the exact gradle/Make commands to prove it (`./gradlew lintDebug testDebugUnitTest assembleDebug`). + +# Principles + +Simplest thing that solves the request. No speculative abstraction, no flexibility nobody asked for. Surgical scope β€” touch only what the task needs. Prefer the smallest correct plan, and call out when an even simpler approach exists. diff --git a/.ai/skills/README.md b/.ai/skills/README.md new file mode 100644 index 0000000..11953cb --- /dev/null +++ b/.ai/skills/README.md @@ -0,0 +1,35 @@ +# Skills + +Reusable, tool-agnostic procedures for the Chai design system repo. Each subdirectory has a `SKILL.md` with YAML front-matter (`name`, `description`). Any agent can follow them; they are self-contained Markdown with no tool-specific wiring. + +## Available skills + +| Skill | What it does | +|-------|--------------| +| [`checks`](./checks/SKILL.md) | Umbrella pre-PR gate: design-system audit β†’ lint β†’ unit tests β†’ assemble. Run before every commit/PR. | +| [`check-build`](./check-build/SKILL.md) | Verify the project assembles (`./gradlew assembleDebug`). Reports only. | +| [`check-lint`](./check-lint/SKILL.md) | Run Android Lint (`./gradlew lintDebug`) and report the first violation. No auto-fix. | +| [`commit-convention`](./commit-convention/SKILL.md) | Produce a commit subject + branch name + PR title that match the repo conventions. | +| [`new-chai-component`](./new-chai-component/SKILL.md) | Scaffold a new design-system component the Chai way (tokens, `ChaiTheme.colors`, `@ChaiPreview`). | +| [`audit-design-system-usage`](./audit-design-system-usage/SKILL.md) | Find raw Material usage and hardcoded colors that should be Chai tokens. The check the broken `chailinter` is meant to do. | +| [`dependency-bump`](./dependency-bump/SKILL.md) | Update `gradle/libs.versions.toml` to latest and verify, per the `findings.md` checklist. | + +## Authoring a new skill + +``` +.ai/skills// + SKILL.md # required; YAML front-matter + instructions + scripts/ # optional helpers + templates/ # optional file templates +``` + +Front-matter (keep `description` to one sentence on *when* to invoke): + +```yaml +--- +name: my-skill +description: One sentence describing WHEN to use this skill. +--- +``` + +`name` must equal the directory name. After adding a skill, run `scripts/check-agent-tooling.sh` and add a row to the table above. diff --git a/.ai/skills/audit-design-system-usage/SKILL.md b/.ai/skills/audit-design-system-usage/SKILL.md new file mode 100644 index 0000000..e18ce49 --- /dev/null +++ b/.ai/skills/audit-design-system-usage/SKILL.md @@ -0,0 +1,38 @@ +--- +name: audit-design-system-usage +description: | + Find design-system violations in Chai source β€” raw Material components, + hardcoded colors, bare .dp magic numbers, @Preview instead of + @ChaiPreview. Triggered by "audit-design-system-usage", "/audit", or by + checks/code-reviewer. This is the check the broken chailinter is meant + to do. Reports; does not edit. +--- + +# audit-design-system-usage + +You find places where source bypasses the design system. The custom `chailinter` is supposed to do this but its detectors are non-functional stubs, so this skill is the stand-in until they're implemented. + +## Run the script first + +```bash +scripts/check-design-system-usage.sh +``` + +It scans `chai`/`chaidemo` source and flags the patterns below. Fold its output into your report. Then spot-check by hand for anything regex misses. + +## What counts as a violation (in consumer/screen code) + +- **Hardcoded colors** β€” `Color(0x…)` or `Color.Red`/`Color.White` literals outside `chai/.../atoms/Color.kt` (the one allowed place to define raw color values). +- **Material colorScheme in components** β€” reading `MaterialTheme.colorScheme.*` instead of `ChaiTheme.colors` / `LocalChaiColorsPalette`. +- **Raw Material components** in screen/demo code where a Chai `C*` equivalent exists (e.g. `androidx.compose.material3.Button` instead of `CButton`/`CPrimaryButton`). +- **Bare `.dp` magic numbers** in component code instead of `Space*` tokens. +- **`@Preview`** instead of `@ChaiPreview`. + +## What is allowed (not a violation) + +- Material primitives **inside** `C*` component implementations β€” Chai wraps Material, so the library internals use it by design. The rule targets consumer/screen code and hardcoded values. +- Raw colors defined in `atoms/Color.kt`. That's where they belong. + +## Report + +Group by violation type. Each: `file:line` + the offending snippet + the Chai replacement. End with a count and a one-line verdict (clean / N violations). Cross-check against `docs/tech/findings.md` so you don't re-report the already-documented `ChaiTheme`/`MaterialTheme.colorScheme` gap as if it were new β€” note it as "known". diff --git a/.ai/skills/check-build/SKILL.md b/.ai/skills/check-build/SKILL.md new file mode 100644 index 0000000..730dba6 --- /dev/null +++ b/.ai/skills/check-build/SKILL.md @@ -0,0 +1,48 @@ +--- +name: check-build +description: | + Verify the Chai project assembles. Triggered by "check-build", + "/check-build", "does it build", or by checks as the assemble gate. + Runs `./gradlew assembleDebug` only β€” no mutation, no formatting. +--- + +# check-build + +One gradle task, then report. No edits. + +## Pre-flight + +- `pwd` is the repo root. +- `./gradlew --version` runs. + +## Step 1 β€” Run + +```bash +./gradlew assembleDebug +``` + +Capture stdout, stderr, and the exit code. For a single module, scope it (`./gradlew :chai:assembleDebug`). + +## Step 2 β€” Report + +Success: + +``` +check-build βœ“ assembleDebug passed () +``` + +Failure: + +``` +check-build βœ— assembleDebug failed + + + + errors. Fix the first; later ones often cascade. +``` + +Pull the first real error (a Kotlin `e: file.kt:line:col` line or a Gradle `* What went wrong` block), not the final "BUILD FAILED" summary. + +## Note + +If the failure is a Gradle configuration-cache or "false negative" sync error, suggest `./gradlew clean assembleDebug` once (the README documents these as common). Do not loop retries. diff --git a/.ai/skills/check-lint/SKILL.md b/.ai/skills/check-lint/SKILL.md new file mode 100644 index 0000000..c102b78 --- /dev/null +++ b/.ai/skills/check-lint/SKILL.md @@ -0,0 +1,48 @@ +--- +name: check-lint +description: | + Run Android Lint on Chai and report. Triggered by "check-lint", + "/check-lint", "run lint", or by checks as the lint gate. Runs + `./gradlew lintDebug` only β€” reports the first violation, never edits. +--- + +# check-lint + +One gradle task, then report. No mutation. + +## Pre-flight + +- `pwd` is the repo root. + +## Step 1 β€” Run + +```bash +./gradlew lintDebug +``` + +Capture stdout, stderr, exit code. Lint also writes an HTML/XML report under each module's `build/reports/lint-results-debug.*` β€” point the user there for the full list. + +## Step 2 β€” Report + +Success: + +``` +check-lint βœ“ lintDebug passed () +``` + +Failure: + +``` +check-lint βœ— lintDebug failed + +First issue: + : + + issues. Full report: /build/reports/lint-results-debug.html +``` + +## Notes + +- This repo has **no** `ktlint` or `detekt` task β€” do not invent one. Style/formatting is meant to be Spotless (currently broken, see `.ai/memory/reference_build_and_checks.md`) plus Android Lint plus the custom `chailinter`. +- `chailinter`'s detectors are non-functional stubs today, so design-system rules are NOT enforced by lint yet. Use the `audit-design-system-usage` skill for that until the detectors are implemented. +- Do not suppress issues or edit `.editorconfig` to make lint pass. diff --git a/.ai/skills/checks/SKILL.md b/.ai/skills/checks/SKILL.md new file mode 100644 index 0000000..b951cc8 --- /dev/null +++ b/.ai/skills/checks/SKILL.md @@ -0,0 +1,49 @@ +--- +name: checks +description: | + Umbrella pre-PR gate for Chai. Triggered by "checks", "/checks", "run + the checks", or by any agent that needs a green light before committing + or opening a PR. Runs the design-system audit, Android Lint, unit tests, + and a debug assemble, then reports a single pass/fail. +--- + +# checks + +You run the repo's pre-PR gate in order and report one verdict. Verify-only: you do not auto-fix or commit. + +## Pre-flight + +- `pwd` is the repo root (where `gradlew` lives). +- `./gradlew --version` runs. If the JDK is wrong for AGP 8.13, stop and tell the user. + +## Steps (stop reporting which one failed, but run all that can run) + +1. **Design system** β€” run `scripts/check-design-system-usage.sh`. Fails on raw Material / hardcoded colors in component code. +2. **Lint** β€” `./gradlew lintDebug` (or invoke the `check-lint` skill). +3. **Unit tests** β€” `./gradlew testDebugUnitTest`. +4. **Assemble** β€” `./gradlew assembleDebug` (or the `check-build` skill). + +Tip: the CI-equivalent single command is +`./gradlew clean lintDebug assembleDebug testDebugUnitTest --configuration-cache`. + +## Report + +``` +checks βœ“ design-system Β· lint Β· test Β· assemble () +``` + +On any failure: + +``` +checks βœ— failed at: + + + +Next: +``` + +## Not this skill's job + +- Does not format (spotless is currently broken β€” see `.ai/memory/reference_build_and_checks.md`). +- Does not run instrumented tests (`connectedCheck`) β€” those need a device and are gated by a `[test-instrumented]` commit tag in CI. +- Does not commit or push. diff --git a/.ai/skills/commit-convention/SKILL.md b/.ai/skills/commit-convention/SKILL.md new file mode 100644 index 0000000..56cb24d --- /dev/null +++ b/.ai/skills/commit-convention/SKILL.md @@ -0,0 +1,46 @@ +--- +name: commit-convention +description: | + Produce a commit subject, branch name, and PR title that match Chai's + conventions. Triggered by "commit-convention", "/commit-convention", + "write a commit message", or by the gitter agent before committing or + opening a PR. +--- + +# commit-convention + +You turn a set of changes into a correctly-formatted commit subject, branch name, and PR title. You draft text; the `gitter` agent or the user runs the git commands. + +## Commit subject rules (a shared commit-msg hook enforces these) + +- **Past tense**, concise, describes the change. +- **No trailing period.** No body. No conventional-commits prefix (`feat:`, `fix:`). +- Keep it short (aim < 72 chars). +- No AI/agent `Co-Authored-By` trailer. + +Good: `Added CCard elevation token` Β· `Fixed primary button container color` Β· `Updated dependencies to latest versions` +Bad: `feat: add card` Β· `Fixes the button.` Β· `update stuff` + +## Atomic commits + +1–3 files, split by dependency layer: tokens/atoms β†’ components β†’ theme wiring β†’ demo β†’ tests β†’ docs. **One documentation `.md` file per commit** β€” never bundle multiple docs. If the change spans layers, propose multiple commits with a subject each. + +## Branch names + +`feature/`, `fix/`, or `docs/` β€” lowercase, hyphenated. Example: `feature/ccard-elevation`. + +## PR title + +Same style as the commit subject (past tense, no prefix/period). PRs target **`develop`**. + +## Output + +``` +Branch: feature/ +Commits: + 1. (files: …) + 2. (files: …) +PR title: (base: develop) +``` + +If the staged/working changes don't justify splitting, output a single commit. Read the actual diff (`git diff`) before drafting β€” base the subject on what changed, not on the request wording. diff --git a/.ai/skills/dependency-bump/SKILL.md b/.ai/skills/dependency-bump/SKILL.md new file mode 100644 index 0000000..bfe63c8 --- /dev/null +++ b/.ai/skills/dependency-bump/SKILL.md @@ -0,0 +1,43 @@ +--- +name: dependency-bump +description: | + Update gradle/libs.versions.toml toward latest stable and verify safely. + Triggered by "dependency-bump", "/dependency-bump", "update + dependencies", "bump versions". Follows the dependency checklist in + docs/tech/findings.md. +--- + +# dependency-bump + +You move the version catalog toward latest stable and prove the build still works. Catalog is the single source of truth: `gradle/libs.versions.toml`. Edit versions there, never pin in module `build.gradle.kts`. + +## Step 1 β€” See what's behind + +```bash +./gradlew dependencyUpdates # if the Ben Manes plugin is present; else inspect the catalog by hand +``` + +Cover the whole catalog, not just the toolchain: `agp`, `kotlin`, `composeBom`, `lint`/`lintTests`, `coreKtx`, `appcompat`, `material`, `material3`/`material3Android`, lifecycle, activity-compose, the Compose `runtime`/`ui-tooling` pins, and the test stack (`androidx.test.ext:junit`, `espressoCore`). + +## Step 2 β€” Bump deliberately + +- Prefer driving Compose artifact versions through the **Compose BOM** rather than individual `runtimeAndroid` / `uiTooling` / `material3Android` pins. Several pins exist only because the BOM isn't relied on consistently. +- Keep `kotlin` and the `compose-compiler` plugin in lockstep (same `kotlin` ref). +- Drop the ignored `composeOptions { kotlinCompilerExtensionVersion = … }` in module build files β€” it does nothing under Kotlin 2.x. +- Don't leave catalog entries that no module uses (e.g. the unused JUnit 6 entries). Either wire them in or remove them. + +## Step 3 β€” Verify + +```bash +./gradlew clean lintDebug testDebugUnitTest assembleDebug --configuration-cache +``` + +Read the **release notes** for any major bump (AGP, Kotlin, Compose) and check known deprecations in `findings.md` (e.g. `statusBarColor` on API 35, manifest `package` attribute). + +## Step 4 β€” Commit + +Treat the bump as its own change, on a `fix/…` or `chore`-style `feature/deps-…` branch off `develop`. Atomic: the catalog change in one commit (`Updated dependencies to latest versions`); any code fixes for breaking changes in follow-up commits. Per `commit-convention`. Do not bundle unrelated work. + +## Guardrail + +A version bump that doesn't build is worse than none. If verification fails and the fix isn't small, stop and report which dependency broke rather than chasing a cascade. diff --git a/.ai/skills/new-chai-component/SKILL.md b/.ai/skills/new-chai-component/SKILL.md new file mode 100644 index 0000000..ad2a7a0 --- /dev/null +++ b/.ai/skills/new-chai-component/SKILL.md @@ -0,0 +1,45 @@ +--- +name: new-chai-component +description: | + Scaffold a new Chai design-system component the right way. Triggered by + "new-chai-component", "/new-chai-component", "add a Chai component", or + "create a C<Name>". Ensures tokens, ChaiTheme.colors, and @ChaiPreview + instead of raw Material / hardcoded values. +--- + +# new-chai-component + +You add a component to the `chai` library so it matches the design system. Components live in `chai/src/main/java/com/droidconke/chai/components/` (or `icons/`, `images/` as appropriate), named `C<Name>` in a `C<Name>s.kt` file when grouping a family. + +## Before writing + +1. Check it doesn't already exist (`Grep` for the name) and isn't an empty stub already present (`CCards.kt`, `CTabs.kt`, etc. are stubs to fill, not new files to create). +2. Decide the tokens it needs (colors, spacing, shape, typography). If a needed semantic color is missing from `ChaiColors`, add it there first, in both `ChaiLightColorPalette` and `ChaiDarkColorPalette`. + +## Rules for the component + +- **Colors** come from `ChaiTheme.colors` / `LocalChaiColorsPalette`, NOT `MaterialTheme.colorScheme` and NOT hardcoded `Color(0x…)`. (The current components wrongly read the Material scheme β€” see `docs/tech/findings.md`; do not copy that pattern.) +- **Spacing** uses `Space*` for `Modifier.padding(...)` and the `Spacer*()` composables (`Spacer5()`, `Spacer15()`, `Spacer30()`) for gaps in rows/columns. Never write a bare `Space5` statement β€” it's a `Dp`, it renders nothing. +- **Shape** uses `CShapes`. **Type** uses `ChaiTheme.typography`. +- Signature: `onClick`/state first, then `modifier: Modifier = Modifier`, then content. Keep parameters minimal; expose only what consumers need. +- Add **KDoc** on the public composable (it's a published library API). +- Mark internal helpers `private`/`internal`. + +## Previews + +Add `@ChaiPreview` (not `@Preview`) previews for light and dark, wrapped in `ChaiTheme { }`, and make the preview functions `private`. + +``` +@ChaiPreview +@Composable +private fun C<Name>Preview() { + ChaiTheme { C<Name>( … ) } +} +``` + +## After writing + +1. `audit-design-system-usage` skill β€” confirm no raw Material / hardcoded color slipped in. +2. `checks` skill β€” lint + tests + assemble. +3. Demo it in `chaidemo` so the component is actually exercised (the demo currently under-covers components). +4. Commit per `commit-convention` (atomic, by layer: token change β†’ component β†’ demo). diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..115a3cc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,33 @@ +# Agents + +Tool-neutral guidance for any AI agent (Claude Code, Cursor, Copilot, Gemini, etc.) working in the Chai repo. This is the propagation point: every tool that imports an agent-instructions file should be pointed here. + +Chai is the **droidconKE design system** β€” a Jetpack Compose component library plus a demo app and a custom lint module. It is a library others depend on, so API surface and visual consistency matter more than feature velocity. + +## Where things live + +| Path | What it is | Committed? | +|------|------------|------------| +| `.ai/skills/` | Reusable task procedures (`SKILL.md` each) | yes | +| `.ai/agents/` | Role briefs for sub-agents | yes | +| `.ai/memory/` | Per-user, tool-neutral knowledge store | **no** (git-ignored) | +| `scripts/` | Guard-rail scripts (run in CI and locally) | yes | +| `docs/` | Human docs; `docs/tech/findings.md` is the current code-health baseline | yes | + +## Before you start a task + +1. **Read `.ai/memory/MEMORY.md`** (the index) and open any entry that looks relevant. This is where decisions, corrections, and project state are kept. If the directory is absent, the project has no local memory yet. +2. **Check `.ai/skills/`** β€” if a skill covers the task (build check, new component, commit message, PR), follow it rather than improvising. +3. **Read `docs/tech/findings.md`** for known gaps before "fixing" something that is already documented as a stub. + +## Non-negotiables for this repo + +- **Design system only.** In `chai`/`chaidemo` source, use Chai components and tokens (`ChaiTheme`, `ChaiColors`, the `C*` components, `Space*`/`Spacer*`). Never raw Material 3 components or hardcoded `Color(0x…)` / `.dp` literals in component code. `scripts/check-design-system-usage.sh` enforces this. +- **Compose previews** use `@ChaiPreview`, not `@Preview` (see `CONTRIBUTING.MD`). +- **Branches** target `develop`, not `main`. Prefixes: `feature/`, `fix/`, `docs/`. +- **Commits**: past tense, concise subject, no period, no body, no conventional-commits prefix. ("Added X", not "feat: add x.") +- **Never** `git push --no-verify` or `--force`. Fix hook failures instead. + +## Memory + +When you learn something durable (a decision + why, a correction, project state, an external pointer), save it to `.ai/memory/` following `.ai/memory/reference_memory_store_convention.md`, and add a one-line bullet to `MEMORY.md`. Use the `memory-curator` agent for this. The store is git-ignored and per-user, so it is never committed. From b4c6fa38f3756c9d8da31408587f9572da5a2b63 Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 02:41:01 +0200 Subject: [PATCH 16/39] Wired ChaiColors palette into MaterialTheme colorScheme --- .../main/java/com/droidconke/chai/Theme.kt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/chai/src/main/java/com/droidconke/chai/Theme.kt b/chai/src/main/java/com/droidconke/chai/Theme.kt index 66df841..484e5cf 100755 --- a/chai/src/main/java/com/droidconke/chai/Theme.kt +++ b/chai/src/main/java/com/droidconke/chai/Theme.kt @@ -20,6 +20,8 @@ import android.content.Context import android.content.ContextWrapper import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ReadOnlyComposable @@ -79,11 +81,37 @@ fun ChaiTheme( LocalChaiColorsPalette provides customColorsPalette, ) { MaterialTheme( + colorScheme = customColorsPalette.toMaterialColorScheme(darkTheme), content = content ) } } +/** + * Bridges the semantic [ChaiColors] palette into a Material 3 [androidx.compose.material3.ColorScheme] + * so that any raw Material component placed inside [ChaiTheme] is branded with Chai colors instead of + * the stock Material defaults. Chai components should still prefer reading [ChaiTheme.colors] directly. + */ +private fun ChaiColors.toMaterialColorScheme(darkTheme: Boolean) = if (darkTheme) { + darkColorScheme( + primary = primary, + background = background, + surface = surfaces, + onPrimary = secondaryButtonTextColor, + onBackground = textNormalColor, + onSurface = textNormalColor + ) +} else { + lightColorScheme( + primary = primary, + background = background, + surface = surfaces, + onPrimary = secondaryButtonTextColor, + onBackground = textNormalColor, + onSurface = textNormalColor + ) +} + val MaterialTheme.chaiColorsPalette: ChaiColors @Composable @ReadOnlyComposable From 9dd1905ab6062778a7b7c6a132dedbb61328227c Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 02:41:45 +0200 Subject: [PATCH 17/39] Branded CPrimaryButton container and fixed Space5 no-op --- .../java/com/droidconke/chai/components/CButtons.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/chai/src/main/java/com/droidconke/chai/components/CButtons.kt b/chai/src/main/java/com/droidconke/chai/components/CButtons.kt index 8985f3a..f7b5a62 100755 --- a/chai/src/main/java/com/droidconke/chai/components/CButtons.kt +++ b/chai/src/main/java/com/droidconke/chai/components/CButtons.kt @@ -16,13 +16,14 @@ package com.droidconke.chai.components import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material3.Button import androidx.compose.material3.ButtonColors import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -86,8 +87,10 @@ fun CPrimaryButton( isEnabled = isEnabled, modifier = modifier, colors = ButtonDefaults.buttonColors( - contentColor = MaterialTheme.colorScheme.primary, - disabledContentColor = MaterialTheme.colorScheme.primary.copy(alpha = AlphaDisabled) + containerColor = ChaiTheme.colors.secondaryButtonColor, + contentColor = ChaiTheme.colors.secondaryButtonTextColor, + disabledContainerColor = ChaiTheme.colors.secondaryButtonColor.copy(alpha = AlphaDisabled), + disabledContentColor = ChaiTheme.colors.secondaryButtonTextColor.copy(alpha = AlphaDisabled) ), shape = CShapes.extraLarge, content = { @@ -107,10 +110,10 @@ fun COutlinedPrimaryButton( onClick = onClick, modifier = modifier, shape = CShapes.extraLarge, - colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.primary) + colors = ButtonDefaults.outlinedButtonColors(contentColor = ChaiTheme.colors.outlinedButtonTextColor) ) { Icon(imageVector = icon, contentDescription = "", modifier = Modifier.padding(5.dp)) - Space5 + Spacer(Modifier.width(Space5)) ChaiTextButton(text = title) } } From 20666540a50e95c6a4000ff25c99e064063983d7 Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 02:42:19 +0200 Subject: [PATCH 18/39] Demonstrated Chai buttons in buttons demo screen --- .../chaidemo/screens/ChaiButtonsDemoScreen.kt | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/chaidemo/src/main/java/com/droidconke/chaidemo/screens/ChaiButtonsDemoScreen.kt b/chaidemo/src/main/java/com/droidconke/chaidemo/screens/ChaiButtonsDemoScreen.kt index 180144e..6122150 100755 --- a/chaidemo/src/main/java/com/droidconke/chaidemo/screens/ChaiButtonsDemoScreen.kt +++ b/chaidemo/src/main/java/com/droidconke/chaidemo/screens/ChaiButtonsDemoScreen.kt @@ -3,15 +3,20 @@ package com.droidconke.chaidemo.screens import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Send import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.droidconke.chai.ChaiTheme -import com.droidconke.chai.atoms.ChaiWhite +import com.droidconke.chai.components.CPrimaryButton +import com.droidconke.chai.components.COutlinedPrimaryButton import com.droidconke.chai.components.ChaiTextBody import com.droidconke.chai.utils.Space15 import com.droidconke.chai.utils.Space30 +import com.droidconke.chai.utils.Spacer30 @Preview(showBackground = true) @Composable @@ -20,12 +25,31 @@ fun ChaiButtonsScreen() { Column( Modifier .fillMaxSize() - .background(color = ChaiWhite) + .background(color = ChaiTheme.colors.background) .padding(horizontal = Space15, vertical = Space30) ) { - Space30 ChaiTextBody(text = "chai Buttons Demo Screens") - + Spacer30() + CPrimaryButton( + onClick = { }, + isEnabled = true, + title = "Primary Button", + modifier = Modifier.fillMaxWidth() + ) + Spacer30() + CPrimaryButton( + onClick = { }, + isEnabled = false, + title = "Primary Button Disabled", + modifier = Modifier.fillMaxWidth() + ) + Spacer30() + COutlinedPrimaryButton( + onClick = { }, + title = "Outlined Button", + modifier = Modifier.fillMaxWidth(), + icon = Icons.AutoMirrored.Filled.Send + ) } } } \ No newline at end of file From 87302725c944d3bba4c44f2a1c15d543c90a4c3f Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 02:43:07 +0200 Subject: [PATCH 19/39] Removed ignored kotlinCompilerExtensionVersion config --- chai/build.gradle.kts | 4 ---- chaidemo/build.gradle.kts | 4 ---- 2 files changed, 8 deletions(-) diff --git a/chai/build.gradle.kts b/chai/build.gradle.kts index d1f6663..7781ef9 100755 --- a/chai/build.gradle.kts +++ b/chai/build.gradle.kts @@ -62,10 +62,6 @@ android { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8) } } - - composeOptions { - kotlinCompilerExtensionVersion = "1.5.15" - } } dependencies { diff --git a/chaidemo/build.gradle.kts b/chaidemo/build.gradle.kts index 8f947e5..0b2c109 100755 --- a/chaidemo/build.gradle.kts +++ b/chaidemo/build.gradle.kts @@ -63,10 +63,6 @@ android { buildConfig = false } - composeOptions { - kotlinCompilerExtensionVersion = "1.5.15" - } - packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" From 364c2d3c0f2033b6bfab5c3fecc64b65a8dfdedb Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 02:43:32 +0200 Subject: [PATCH 20/39] Removed duplicate copyright header in linter build --- chailinter/build.gradle.kts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/chailinter/build.gradle.kts b/chailinter/build.gradle.kts index d094082..15b1ff9 100755 --- a/chailinter/build.gradle.kts +++ b/chailinter/build.gradle.kts @@ -15,21 +15,6 @@ */ import org.jetbrains.kotlin.gradle.dsl.JvmTarget -/** - * Copyright 2023 droidcoke - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ plugins { `java-library` kotlin("jvm") From 8f1b58325b4fc46c98ef87abb32afaded7a40b1e Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 02:43:42 +0200 Subject: [PATCH 21/39] Renamed CInputFiels stub to CInputFields --- .../chai/components/{CInputFiels.kt => CInputFields.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename chai/src/main/java/com/droidconke/chai/components/{CInputFiels.kt => CInputFields.kt} (100%) diff --git a/chai/src/main/java/com/droidconke/chai/components/CInputFiels.kt b/chai/src/main/java/com/droidconke/chai/components/CInputFields.kt similarity index 100% rename from chai/src/main/java/com/droidconke/chai/components/CInputFiels.kt rename to chai/src/main/java/com/droidconke/chai/components/CInputFields.kt From 4449a0aef7de592ae0664997a7a6c76c75b0eb19 Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 02:43:57 +0200 Subject: [PATCH 22/39] Fixed corrupted curl line in setup script --- tools/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/setup.sh b/tools/setup.sh index 6a4c8cc..1467bba 100755 --- a/tools/setup.sh +++ b/tools/setup.sh @@ -22,7 +22,7 @@ GIT_ROOT=$(git rev-parse --show-toplevel 2> /dev/null) echo "Installing git commit-message hook" echo -curl -sSLo "${GIT_DIR}/hooks/commit-msg" \653 jym5 +curl -sSLo "${GIT_DIR}/hooks/commit-msg" \ "https://gerrit-review.googlesource.com/tools/hooks/commit-msg" \ && chmod +x "${GIT_DIR}/hooks/commit-msg" From d47fb3414df5a5a42bc7dce11742ff58bc6cd263 Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 02:44:36 +0200 Subject: [PATCH 23/39] Removed broken spotless init-script references --- Makefile | 3 --- tools/pre-push | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 209ab4e..2ecfdf0 100755 --- a/Makefile +++ b/Makefile @@ -25,6 +25,3 @@ lint: test: ./gradlew test${BUILD_TYPE}UnitTest ${GRADLE_ARGS} - -spotless: - ./gradlew --init-script gradle/init.gradle.kts -q spotless diff --git a/tools/pre-push b/tools/pre-push index 81157f9..c931084 100755 --- a/tools/pre-push +++ b/tools/pre-push @@ -88,7 +88,7 @@ done if [[ -n "$run_checks" ]]; then # pre-push usually executes in the repository root, but just to be safe... cd "$(git rev-parse --show-toplevel)" - ./gradlew --init-script gradle/init.gradle.kts --no-configuration-cache check + ./gradlew --no-configuration-cache check exit $? fi From f42b48ecce622f4199c913b9bff7269892cc3a3d Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 02:44:59 +0200 Subject: [PATCH 24/39] Pointed todo workflow at develop branch --- .github/workflows/todo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/todo.yml b/.github/workflows/todo.yml index e64dcf3..d14d673 100755 --- a/.github/workflows/todo.yml +++ b/.github/workflows/todo.yml @@ -4,7 +4,7 @@ on: push: branches: - main - - dev + - develop jobs: todo: From c8e98cdbbf775c148f230b96b0fb6d49b0a02579 Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 02:45:55 +0200 Subject: [PATCH 25/39] Fixed README typos and removed bogus doc link --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 93e73d8..c30465b 100755 --- a/README.md +++ b/README.md @@ -31,11 +31,10 @@ The Chai Design System Project Architecture is captured in detail: 1. [Why Design Systems](https://github.com/droidconKE/chai/blob/master/docs/whyDesignSystems.md) - Explains the need for a design system in the context of compose. 2. [Architecture](https://github.com/droidconKE/chai/blob/master/docs/architecture.md) - The architecture of the project 3. [Chai Design System Architecture](https://github.com/droidconKE/chai/blob/master/docs/chaiArchitecture.md) - The architecture of the design system -4. [buildlogic](https://github.com/droidconKE/chai/blob/master/docs/buildlogic.md) - Handles how we build the app wiyth gradle, ditches the build src in favour of convengion polugins. +4. [buildlogic](https://github.com/droidconKE/chai/blob/master/docs/buildlogic.md) - Handles how we build the app with gradle, ditches the build src in favour of convention plugins. 5. [chaiLinter](https://github.com/droidconKE/chai/blob/master/docs/chaiLinter.md) - Explains the design system linter 6. [Atoms](https://github.com/droidconKE/chai/blob/master/docs/atoms.md) - Explains the atoms in the design system 7. [Components](https://github.com/droidconKE/chai/blob/master/docs/components.md) - Design system components -8. [chaidemop](https://github.com/droidconKE/chai/blob/master/docs/components.md) - Design system components ## Implementing Chai @@ -68,7 +67,7 @@ For detailed optimization information, see: ### [Running Project] Known issue with gradle: -- If you run into an error when building project, ist probably a false negative. +- If you run into an error when building project, it's probably a false negative. - Run(Or just press green play icon on android studio from the left here: `./gradlew sync` and output complete html report should not display errors then `./gradlew tasks` to see a list of tasks you can run from the root of the project From 7a3f72602edfd5fc7a538251c137269783fa790d Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 02:47:00 +0200 Subject: [PATCH 26/39] Used staticCompositionLocalOf for typography tokens --- .../java/com/droidconke/chai/typography/ChaiTypography.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chai/src/main/java/com/droidconke/chai/typography/ChaiTypography.kt b/chai/src/main/java/com/droidconke/chai/typography/ChaiTypography.kt index a6349f5..4ffca2a 100644 --- a/chai/src/main/java/com/droidconke/chai/typography/ChaiTypography.kt +++ b/chai/src/main/java/com/droidconke/chai/typography/ChaiTypography.kt @@ -3,7 +3,7 @@ package com.droidconke.chai.typography import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.ProvidableCompositionLocal -import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.LineHeightStyle @@ -147,4 +147,4 @@ fun chaiTypography( ) internal val LocalChaiTypography: ProvidableCompositionLocal<ChaiTypography> - get() = compositionLocalOf { ChaiTypography() } + get() = staticCompositionLocalOf { ChaiTypography() } From d07b810c6978cfe32b57764e9bbce7fe0b89f2fa Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 03:08:12 +0200 Subject: [PATCH 27/39] Added opt-in git hook wiring --- .githooks/README.md | 32 ++++++++++++++++++++++++++++++++ .githooks/commit-msg | 25 +++++++++++++++++++++++++ .githooks/pre-commit | 34 ++++++++++++++++++++++++++++++++++ .githooks/pre-push | 28 ++++++++++++++++++++++++++++ scripts/install-git-hooks.sh | 29 +++++++++++++++++++++++++++++ 5 files changed, 148 insertions(+) create mode 100644 .githooks/README.md create mode 100755 .githooks/commit-msg create mode 100755 .githooks/pre-commit create mode 100755 .githooks/pre-push create mode 100755 scripts/install-git-hooks.sh diff --git a/.githooks/README.md b/.githooks/README.md new file mode 100644 index 0000000..65e386d --- /dev/null +++ b/.githooks/README.md @@ -0,0 +1,32 @@ +# Git hooks + +Committed, opt-in git hooks that run the repo's guard rails locally before code reaches CI. Activate them once per clone: + +```bash +./scripts/install-git-hooks.sh +``` + +That points this repo's `core.hooksPath` at `.githooks/`. It is opt-in (nothing runs until you install) and repo-local (your global git config is untouched). + +## What runs + +| Hook | When | Runs | Speed | +|------|------|------|-------| +| `commit-msg` | every commit | chains to your global commit-msg hook (message validation) | instant | +| `pre-commit` | every commit | `check-agent-tooling.sh`, `check-design-system-usage.sh`, and `check-docs.sh` on staged `.md` | < 1s | +| `pre-push` | every push | `./gradlew lintDebug testDebugUnitTest assembleDebug` | minutes | + +## Bypass + +Emergencies only. Never bypass to dodge a real failure: + +```bash +SKIP_LOCAL_CI=1 git commit … +SKIP_LOCAL_CI=1 git push +``` + +`git commit --no-verify` / `git push --no-verify` and `--force` are not allowed by repo convention; fix the failure instead. + +## Note on commit-msg + +Installing repo hooks overrides any global `core.hooksPath`, so `commit-msg` here is a shim that re-invokes your global commit-msg hook (the one that validates subject format). If you have no global hook, it exits cleanly. diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 0000000..353f7f7 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# commit-msg shim. +# +# Installing repo-local hooks sets core.hooksPath to .githooks, which OVERRIDES +# any global core.hooksPath. Contributors here keep a global commit-msg hook +# (format validation, trailer stripping). This shim re-invokes that global hook +# so its behavior survives the takeover, then exits cleanly when there isn't one. +# +# It only delegates to a DIFFERENT file than itself, so it can never recurse. +set -euo pipefail + +msg_file="$1" + +global_dir="$(git config --global core.hooksPath || true)" +global_dir="${global_dir/#\~/$HOME}" + +if [ -n "$global_dir" ] && [ -x "$global_dir/commit-msg" ]; then + self="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")" + target="$(cd "$global_dir" && pwd)/commit-msg" + if [ "$self" != "$target" ]; then + exec "$target" "$msg_file" + fi +fi + +exit 0 diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..2cc8724 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Fast local pre-commit β€” catch cheap failures before they reach the cloud. +# +# Shell-only, runs in well under a second: the agent-tooling lint, the +# design-system guard rail, and check-docs on any staged .md. Heavy verification +# (lint + tests + build) is deferred to pre-push. +# +# Escape hatch: SKIP_LOCAL_CI=1 git commit … +set -euo pipefail + +[ "${SKIP_LOCAL_CI:-}" = "1" ] && { echo "pre-commit: skipped (SKIP_LOCAL_CI=1)"; exit 0; } + +root="$(git rev-parse --show-toplevel)" +cd "$root" + +if [ -x scripts/check-agent-tooling.sh ]; then + echo "pre-commit: agent-tooling check…" + ./scripts/check-agent-tooling.sh >/dev/null +fi + +if [ -x scripts/check-design-system-usage.sh ]; then + echo "pre-commit: design-system check…" + ./scripts/check-design-system-usage.sh >/dev/null +fi + +# Validate only the docs being committed. +staged_md="$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.md$' || true)" +if [ -n "$staged_md" ] && [ -x scripts/check-docs.sh ]; then + echo "pre-commit: check-docs on staged .md…" + # shellcheck disable=SC2086 + ./scripts/check-docs.sh $staged_md +fi + +echo "pre-commit: ok" diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000..642c2b9 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Blocking pre-push β€” run the same checks the cloud CI runs, before you push. +# +# Mirrors README's CI command (lint + assemble + unit tests). Slower than +# pre-commit because it starts Gradle, so it runs at push time, not per commit. +# Instrumented tests are NOT run here (they need a device and are gated in CI by +# a [test-instrumented] commit tag). +# +# Bypass once (emergencies only): SKIP_LOCAL_CI=1 git push +set -euo pipefail + +[ "${SKIP_LOCAL_CI:-}" = "1" ] && { echo "pre-push: skipped (SKIP_LOCAL_CI=1)"; exit 0; } + +root="$(git rev-parse --show-toplevel)" +cd "$root" + +run() { + echo "pre-push: $* …" + if ! "$@"; then + echo "pre-push: FAILED at: $*" >&2 + echo " fix it, or bypass once with: SKIP_LOCAL_CI=1 git push" >&2 + exit 1 + fi +} + +run ./gradlew lintDebug testDebugUnitTest assembleDebug --configuration-cache + +echo "pre-push: ok" diff --git a/scripts/install-git-hooks.sh b/scripts/install-git-hooks.sh new file mode 100755 index 0000000..3eacd4d --- /dev/null +++ b/scripts/install-git-hooks.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Install this repo's committed git hooks (.githooks/) by pointing git at them. +# Opt-in and idempotent β€” run once after cloning. Changes git config for THIS +# repo only. +# +# Sets repo-local core.hooksPath=.githooks. That overrides any global +# core.hooksPath for this repo, so the .githooks/commit-msg shim chains back to +# a global commit-msg hook to preserve its behavior (e.g. message validation). +# +# Hooks installed: +# commit-msg β€” chains to your global commit-msg hook (format validation) +# pre-commit β€” agent-tooling + design-system checks; check-docs on staged .md +# pre-push β€” lintDebug + testDebugUnitTest + assembleDebug (blocking) +# Bypass once: SKIP_LOCAL_CI=1 git commit/push +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$root" + +[ -d .githooks ] || { echo "error: .githooks/ not found at $root" >&2; exit 1; } + +chmod +x .githooks/* 2>/dev/null || true + +prev="$(git config --local core.hooksPath || true)" +git config core.hooksPath .githooks + +echo "ok: core.hooksPath -> .githooks (was: ${prev:-unset})" +echo " pre-commit + pre-push now run the repo guard rails; commit-msg chains to any global hook." +echo " bypass once with: SKIP_LOCAL_CI=1 git commit/push" From 517edb00426f04d842508e6d72719c059327092e Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 03:08:12 +0200 Subject: [PATCH 28/39] Added docs validation guard rail --- .markdownlint.jsonc | 11 ++++++ scripts/check-docs.sh | 92 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 .markdownlint.jsonc create mode 100755 scripts/check-docs.sh diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 0000000..330e633 --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,11 @@ +{ + // Markdown style config for check-docs.sh (advisory). Tuned for this repo: + // long lines, inline HTML in the README, and emoji headings are all fine here. + "default": true, + "MD013": false, // line length β€” docs and .ai briefs use long lines on purpose + "MD033": false, // inline HTML β€” the README uses <p>/<img> for the banner + "MD041": false, // first line need not be an H1 (badges/front-matter come first) + "MD024": false, // duplicate headings β€” repeated section names across skills are fine + "MD026": false, // trailing punctuation in headings + "MD007": { "indent": 4 } // the README/task lists use 4-space nesting +} diff --git a/scripts/check-docs.sh b/scripts/check-docs.sh new file mode 100755 index 0000000..9bf87e8 --- /dev/null +++ b/scripts/check-docs.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# Validate documentation (.md) β€” dead links, filename hygiene, staleness dates. +# +# Docs-only changes skip the Gradle checks, so without this a broken README link +# (the kind docs/tech/findings.md flagged) reaches the branch unvalidated. Fast: +# shell + python3, plus markdownlint-cli2 when installed. Checks only the files +# you changed, so legacy docs never block you. +# +# Usage: +# scripts/check-docs.sh [file.md ...] # check the given files +# scripts/check-docs.sh # check .md changed vs origin/develop +# +# Checks (BLOCK on failure; markdownlint degrades to a warning when absent): +# 1. Dead relative links β€” [text](path) pointing at a missing file. +# 2. Filename hygiene β€” no spaces in .md filenames. +# 3. Content hygiene β€” no "Last Updated" lines (docs carry no staleness dates). +# 4. markdownlint-cli2 β€” formatting, if installed. +# 5. Agent tooling β€” front-matter + MEMORY.md index, via check-agent-tooling.sh. +set -euo pipefail + +root="$(git rev-parse --show-toplevel)" +cd "$root" + +base_ref="origin/develop" +git rev-parse --verify -q "$base_ref" >/dev/null || base_ref="origin/main" + +files=() +if [ "$#" -gt 0 ]; then + for f in "$@"; do [[ "$f" == *.md ]] && files+=("$f"); done +else + base="$(git merge-base HEAD "$base_ref" 2>/dev/null || true)" + if [ -n "$base" ]; then + while IFS= read -r f; do [ -n "$f" ] && files+=("$f"); done \ + < <(git diff --name-only --diff-filter=ACMR "$base" HEAD -- '*.md') + fi +fi + +# Keep only files that still exist. +existing=() +for f in "${files[@]:-}"; do [ -f "$f" ] && existing+=("$f"); done + +fail=0 +err() { echo "FAIL: $*" >&2; fail=1; } +warn() { echo "warn: $*" >&2; } + +if [ "${#existing[@]}" -eq 0 ]; then + echo "info: no changed .md files to check" +else + for f in "${existing[@]}"; do + dir="$(dirname "$f")" + + # 1. dead relative links β€” [text](path), skipping URLs and anchors. + while IFS= read -r target; do + [ -n "$target" ] || continue + case "$target" in + http://*|https://*|mailto:*|\#*) continue ;; + esac + clean="${target%%#*}" # strip anchor + [ -z "$clean" ] && continue + case "$clean" in + /*) resolved=".$clean" ;; # repo-absolute + *) resolved="$dir/$clean" ;; # relative to the doc + esac + [ -e "$resolved" ] || err "$f: dead link -> $target" + done < <(grep -oE '\]\(([^)]+)\)' "$f" | sed -E 's/^\]\(//; s/\)$//') + + # 2. filename hygiene + case "$(basename "$f")" in + *" "*) err "$f: filename contains spaces" ;; + esac + + # 3. staleness dates + grep -qiE '^\s*(_|\*\*)?last updated' "$f" && err "$f: contains a 'Last Updated' line (docs carry no staleness dates)" + done + + # 4. markdownlint β€” advisory only (legacy docs don't conform; don't block). + # Tuned by .markdownlint.jsonc. Reports, never fails the gate. + if command -v markdownlint-cli2 >/dev/null 2>&1; then + markdownlint-cli2 "${existing[@]}" >/dev/null 2>&1 || warn "markdownlint reported formatting issues (advisory) β€” run: markdownlint-cli2 ${existing[*]}" + else + warn "markdownlint-cli2 not installed β€” skipping format lint" + fi +fi + +# 5. agent tooling +if [ -x scripts/check-agent-tooling.sh ]; then + ./scripts/check-agent-tooling.sh >/dev/null || err "check-agent-tooling failed" +fi + +echo +if [ "$fail" -ne 0 ]; then echo "check-docs FAILED"; exit 1; fi +echo "check-docs passed" From 7e79ca9e5589d1b1d9d4e1cbb08e402d8c8725a0 Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 03:08:12 +0200 Subject: [PATCH 29/39] Added AI review harness script --- scripts/ai-review.sh | 85 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100755 scripts/ai-review.sh diff --git a/scripts/ai-review.sh b/scripts/ai-review.sh new file mode 100755 index 0000000..e52bd5e --- /dev/null +++ b/scripts/ai-review.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# Tool-neutral PR / branch review helper. No provider, no API key required. +# +# Bundles the three-dot diff (<base>...<head>) plus the committed review brief +# (.ai/agents/code-reviewer.md) into one prompt, then EITHER: +# - prints it to stdout (default) so you paste it into any agent, or +# - pipes it on stdin to an engine command you choose, e.g.: +# ./scripts/ai-review.sh --engine='claude -p' +# AI_REVIEW_CMD='claude -p' ./scripts/ai-review.sh +# +# The engine is your choice β€” this script hardcodes none, so it works with +# whichever agent CLI you have authenticated locally. +# +# Targets (default: current branch vs origin/develop): +# --pr=N review GitHub PR #N (needs gh) +# --branch=NAME review local branch NAME +# --base=NAME base to diff against (default: develop) +# --fetch refresh the base from origin first (off by default so the +# script never blocks on the network; uses local origin/<base>) +set -euo pipefail + +base="develop"; pr=""; branch=""; engine="${AI_REVIEW_CMD:-}"; do_fetch=0 +for arg in "$@"; do + case "$arg" in + --base=*) base="${arg#*=}" ;; + --pr=*) pr="${arg#*=}" ;; + --branch=*) branch="${arg#*=}" ;; + --engine=*) engine="${arg#*=}" ;; + --fetch) do_fetch=1 ;; + --pr|--branch|--base|--engine) + echo "use ${arg}=VALUE (with '=')" >&2; exit 2 ;; + -h|--help) + grep '^#' "$0" | grep -v '^#!' | sed 's/^# \{0,1\}//'; exit 0 ;; + *) echo "unknown arg: $arg" >&2; exit 2 ;; + esac +done + +cd "$(git rev-parse --show-toplevel)" +brief=".ai/agents/code-reviewer.md" +[ -f "$brief" ] || { echo "error: $brief not found" >&2; exit 1; } + +if [ -n "$pr" ]; then + command -v gh >/dev/null 2>&1 || { echo "error: --pr needs gh" >&2; exit 1; } + header="GitHub PR #$pr" + diff="$(gh pr diff "$pr")" + files="$(gh pr diff "$pr" --name-only)" +else + head="${branch:-HEAD}" + label="${branch:-$(git branch --show-current)}" + [ "$do_fetch" -eq 1 ] && { git fetch -q origin "$base" 2>/dev/null || true; } + ref="$base"; git rev-parse --verify -q "origin/$base" >/dev/null && ref="origin/$base" + header="branch '$label' vs $ref" + diff="$(git diff "$ref...$head")" + files="$(git diff --name-only "$ref...$head")" +fi + +if [ -z "$diff" ]; then + echo "No changes to review for $header." >&2; exit 0 +fi + +prompt="$(cat <<EOF +$(cat "$brief") + +--- +## This review + +Target: $header + +Changed files: +$files + +Unified diff (review the changes below against the brief above): + +\`\`\`diff +$diff +\`\`\` +EOF +)" + +if [ -n "$engine" ]; then + echo "Running review via: $engine" >&2 + printf '%s' "$prompt" | eval "$engine" +else + printf '%s\n' "$prompt" +fi From e29191cb7f3b591f9d324d22fac6cf05ec14834a Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 03:08:12 +0200 Subject: [PATCH 30/39] Added PR guard and branch cleanup scripts --- scripts/cleanup-merged-branches.sh | 95 ++++++++++++++++++++++++++++++ scripts/pr-template-guard.sh | 93 +++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100755 scripts/cleanup-merged-branches.sh create mode 100755 scripts/pr-template-guard.sh diff --git a/scripts/cleanup-merged-branches.sh b/scripts/cleanup-merged-branches.sh new file mode 100755 index 0000000..7091f29 --- /dev/null +++ b/scripts/cleanup-merged-branches.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# Purge local branches whose work has already landed on the base branch. +# +# A branch is "merged" if EITHER: +# (a) it is reachable from <base> (git branch --merged β€” true merge / FF), OR +# (b) its PR is MERGED on GitHub (covers squash-merges, whose commits are never +# reachable from <base>). +# +# Always protected: main, develop, the current branch, <base>, and any branch +# checked out in another worktree. Squash-merged branches need force-delete +# (-D); safe because we verify the PR is MERGED and its merged head SHA matches +# the local tip β€” a reused name with new commits is kept. +# +# Dry-run by default. Pass --apply to delete. `gh` (authenticated) enables the +# squash-merge PR check; without it, only reachable-merged branches are eligible. +set -euo pipefail + +base="develop" +apply=false +for arg in "$@"; do + case "$arg" in + --apply) apply=true ;; + --base=*) base="${arg#*=}" ;; + -h|--help) grep '^#' "$0" | grep -v '^#!' | sed 's/^# \{0,1\}//'; exit 0 ;; + *) echo "unknown arg: $arg" >&2; exit 2 ;; + esac +done + +cd "$(git rev-parse --show-toplevel)" + +echo "Fetching and pruning origin…" +git fetch --prune origin >/dev/null 2>&1 || git fetch --prune origin + +# Resolve base to a ref that exists: prefer a local branch, fall back to origin/. +if ! git rev-parse --verify -q "$base" >/dev/null; then + if git rev-parse --verify -q "origin/$base" >/dev/null; then + base="origin/$base" + else + echo "error: base '$base' not found locally or on origin" >&2; exit 1 + fi +fi + +current="$(git branch --show-current)" +protected="main develop $base" +is_protected() { printf '%s\n' $protected | grep -qx "$1"; } + +have_gh=false +if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then + have_gh=true +else + echo "warn: gh unavailable/unauthenticated β€” only reachable-merged branches are eligible." >&2 +fi + +worktree_branches="$(git worktree list --porcelain | awk '/^branch /{sub("refs/heads/","",$2); print $2}')" +is_worktree() { printf '%s\n' "$worktree_branches" | grep -qx "$1"; } + +reachable_merged="$(git branch --merged "$base" --format='%(refname:short)')" +is_reachable_merged() { printf '%s\n' "$reachable_merged" | grep -qx "$1"; } + +del_safe=(); del_force=(); skipped=() + +while IFS= read -r b; do + [ -n "$b" ] || continue + if is_protected "$b"; then skipped+=("$b (protected)"); continue; fi + if [ "$b" = "$current" ]; then skipped+=("$b (current branch)"); continue; fi + if is_worktree "$b"; then skipped+=("$b (checked out in a worktree)"); continue; fi + + if is_reachable_merged "$b"; then del_safe+=("$b"); continue; fi + + if $have_gh; then + local_tip="$(git rev-parse --verify "$b" 2>/dev/null || true)" + match="$(gh pr list --head "$b" --state merged --json number,headRefOid \ + --jq ".[] | select(.headRefOid == \"$local_tip\") | .number" 2>/dev/null | head -n1 || true)" + if [ -n "$match" ]; then del_force+=("$b (PR #$match merged)"); continue; fi + stale="$(gh pr list --head "$b" --state merged --json number --jq '.[0].number' 2>/dev/null || true)" + if [ -n "$stale" ]; then skipped+=("$b (PR #$stale merged but local tip differs β€” kept)"); continue; fi + fi + skipped+=("$b (not merged β€” kept)") +done < <(git branch --format='%(refname:short)') + +echo +echo "Base: $base Current: $current Protected: $protected" +echo "── Will delete (reachable-merged) ──"; printf ' %s\n' "${del_safe[@]:-(none)}" +echo "── Will delete (squash-merged PR, force) ──"; printf ' %s\n' "${del_force[@]:-(none)}" +echo "── Skipped ──"; printf ' %s\n' "${skipped[@]:-(none)}" +echo + +if ! $apply; then + echo "Dry run. Re-run with --apply to delete the branches above." + exit 0 +fi + +for b in "${del_safe[@]:-}"; do [ -n "$b" ] && git branch -d "$b"; done +for entry in "${del_force[@]:-}"; do [ -n "$entry" ] && git branch -D "${entry%% *}"; done +echo "Done." diff --git a/scripts/pr-template-guard.sh b/scripts/pr-template-guard.sh new file mode 100755 index 0000000..fb710ae --- /dev/null +++ b/scripts/pr-template-guard.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# Tool-neutral guard: keep `gh pr create` honest for the Chai repo. +# +# ONE implementation; every tool's hook config points here. The blocking +# "dialect" is chosen with --format: +# Claude Code : PreToolUse(Bash) -> ... --format claude (stderr + exit 2) +# Cursor : beforeShellExecution -> ... --format cursor (JSON permission) +# other/exit : any pre-shell hook -> ... --format exit (default) +# +# Reads the tool's hook payload (JSON) on stdin, finds the shell command, and +# blocks a `gh pr create` unless it targets `develop` and carries a body (not an +# empty/inline-only body). Fails open on anything it cannot parse. +# +# Chai rule: PRs target develop, link an issue, and describe how to test +# (see .github/pull_request_template.md and CONTRIBUTING.MD). +set -uo pipefail + +format="exit" +case "${1:-}" in + --format) format="${2:-exit}" ;; + --format=*) format="${1#--format=}" ;; +esac + +input="$(cat)" + +cmd="$(printf '%s' "$input" | python3 -c ' +import json,sys +try: + d = json.load(sys.stdin) +except Exception: + print(""); sys.exit(0) +c = (d.get("tool_input") or {}).get("command") or d.get("command") or "" +print(c) +' 2>/dev/null || true)" + +help_text() { + cat <<EOF +$1 + +Chai PRs must target develop and follow the repo template. Build it like: + + gh pr create --base develop \\ + --title "<past-tense summary>" \\ + --body-file <file> # body from .github/pull_request_template.md + +The body must reference the issue ("Fixes #N") and say how to test the change. +See .ai/skills/commit-convention/SKILL.md and the gitter agent. +EOF +} + +allow() { + [ "$format" = "cursor" ] && printf '{"permission":"allow"}\n' + exit 0 +} + +deny() { + local short="$1" full + full="$(help_text "BLOCKED: $short")" + case "$format" in + cursor) + python3 - "$short" "$full" <<'PY' +import json,sys +print(json.dumps({"permission":"deny","userMessage":sys.argv[1],"agentMessage":sys.argv[2]})) +PY + exit 0 ;; + *) printf '%s\n' "$full" >&2; exit 2 ;; + esac +} + +# Only police a real `gh pr create` at a command boundary. +printf '%s' "$cmd" \ + | grep -Eq '(^|[;&|(])[[:space:]]*gh[[:space:]]+pr[[:space:]]+create([[:space:]]|$)' \ + || allow + +# 1. Must target develop. Missing --base defaults to the repo default (main) β€” block. +base="$(printf '%s' "$cmd" | sed -nE 's/.*--base[[:space:]=]+"?([^"[:space:]]+).*/\1/p' | head -1)" +[ -n "$base" ] || deny "no --base given; Chai PRs must use --base develop (default would target main)." +[ "$base" = "develop" ] || deny "PR base is '$base'; Chai PRs target develop." + +# 2. Must carry a non-empty body (file or inline). Empty body is a block. +if printf '%s' "$cmd" | grep -Eq -- '(^|[[:space:]])--body-file([[:space:]]|=)'; then + bodyfile="$(printf '%s' "$cmd" | sed -nE 's/.*--body-file[[:space:]=]+"?([^"[:space:]]+).*/\1/p' | head -1)" + if [ -n "${bodyfile:-}" ] && [ -f "$bodyfile" ]; then + grep -qiE 'fixes #|issues?|#[0-9]+' "$bodyfile" \ + || deny "PR body '$bodyfile' does not reference an issue (e.g. 'Fixes #123')." + fi +elif printf '%s' "$cmd" | grep -Eq -- '(^|[[:space:]])--body([[:space:]]|=)'; then + : # inline body present β€” allowed +else + deny "PR has no body; add --body-file (the template) or --body." +fi + +allow From 0127ac5c8aad2d127a45cf72561602946e7b0fcb Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 04:11:45 +0200 Subject: [PATCH 31/39] Added build backlog for deferred findings --- docs/tech/backlog.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 docs/tech/backlog.md diff --git a/docs/tech/backlog.md b/docs/tech/backlog.md new file mode 100644 index 0000000..688d87c --- /dev/null +++ b/docs/tech/backlog.md @@ -0,0 +1,41 @@ +# Build Backlog + +Companion to [`findings.md`](./findings.md). The June 2026 fix pass closed the small, low-risk findings (theme wiring, button colors, dead spacing statements, the broken scripts, README typos, and similar). What is left here is the bigger work: features and infrastructure that need real design, tests, or a verifiable build before they land. Treat each entry as a unit of work to schedule, not a quick edit. + +Severity carries over from `findings.md`: **High** means the project advertises something that does not run; **Medium** is worth doing before a release; **Low** is polish. + +## High priority + +### Implement the custom lint detectors +The headline feature of `chailinter` does nothing. Both detectors in `chailinter/src/main/java/com/droidconke/chailinter/detector/` register an empty `ISSUE`, so nothing flags raw Material colors or components. This is the single largest gap between what Chai claims and what it does. Build it test-first against `lint-tests`: define the real `Issue`s, write UAST scanning that catches `androidx.compose.material*` color and component usage inside Chai source, and add fixtures that prove each rule fires and stays quiet on correct code. Until this exists, `scripts/check-design-system-usage.sh` is the only thing actually enforcing the design system. + +### Fill in the stub components +Five files are header-only but are presented as part of the system: `CCards.kt`, `CTabs.kt`, `Icons.kt`, `Images.kt`, and the renamed `CInputFields.kt`. Each needs an actual API designed against the Chai tokens, with previews and demo-screen coverage. These are design decisions, not fill-in-the-blank work, so scope them one component at a time. + +## Medium priority + +### Real component and theme tests +Every test in `chai` and `chaidemo` is a generated `2 + 2` scaffold. The README testing roadmap asks for component render tests, light/dark theme switching tests, accessibility semantics, and screenshot/visual regression. Pick a harness (Robolectric or instrumented Compose tests, plus a screenshot tool) and replace the scaffolds. The lint module needs its own tests too, tracked under the detector work above. + +### Full dependency catalog pass +`gradle/libs.versions.toml` reflects an old project. Only a handful of versions were bumped recently. Do a deliberate sweep: run the Ben Manes `dependencyUpdates` task or the AGP Upgrade Assistant, move every entry to its current stable release, and lean on the Compose BOM instead of pinning `runtimeAndroid` / `uiTooling` / `material3Android` by hand. Either wire the already-added JUnit 6 entries into the modules or drop them, since modules still use JUnit 4. Build, then run lint, detekt, and tests, and read the Compose and AGP release notes for breaking changes before committing. + +### Convention plugins (build logic) +The README and `docs/buildlogic.md` promise a `buildLogic/` convention-plugin setup, but configuration is copy-pasted across the three `build.gradle.kts` files (SDK levels, compose options, Java target). Extract the shared config into convention plugins so there is one source of truth. This also clears the leftover "see if I can add this to build Logic" notes in `chai/build.gradle.kts`. + +### Raise the JVM target on the Android modules +`chai` and `chaidemo` compile to Java 8 bytecode while `chailinter` targets Java 17. Java 8 is unusually low for Kotlin 2.3 on compileSdk 36. Raise the two Compose modules to 11 or 17 for consistency, and verify the build after. + +### Fill the empty docs +`docs/atoms.md` and `docs/components.md` are empty, and `docs/architecture.md` is a header only. The README links to all of them as if written. Each is its own commit per the repo doc rules. + +## Low priority + +### Edge-to-edge status bar +`Theme.kt` still sets `window.statusBarColor`, deprecated as of API 35. Move to the edge-to-edge APIs. This touches the host activity, so it is more than a one-line swap. + +### Confirm or drop the multiplatform plugin +`chai/build.gradle.kts` applies both the Compose compiler plugin and `jetbrains.compose` (Compose Multiplatform) on an Android-only library. Confirm the multiplatform plugin is intentional or remove it. + +### Rename `ChaiSteal` to `ChaiSteel` +`atoms/Color.kt` defines `ChaiSteal` (a typo for "Steel") and the name propagates through `colors/ChaiColors.kt`. This is a breaking change for any external consumer, so it was left alone. Do it as a coordinated rename if and when a breaking release is acceptable. From c0978e6e63b088f5b1892bf3183e6c67c59b8ffe Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 07:32:50 +0200 Subject: [PATCH 32/39] Marked resolved findings and linked backlog --- docs/tech/findings.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/tech/findings.md b/docs/tech/findings.md index 6a0d2bb..2cddc82 100644 --- a/docs/tech/findings.md +++ b/docs/tech/findings.md @@ -8,6 +8,24 @@ The bones are good. There is a semantic color palette, typography atoms, a theme Severity legend: **High** (broken or misleading), **Medium** (should fix before release), **Low** (polish). +## Resolution status + +A fix pass in June 2026 closed the low-risk findings. Resolved since this audit was written: + +- `ChaiColors` is now wired into `MaterialTheme`, and the buttons read the palette directly. The "palette is decorative" and "`CPrimaryButton` has no container color" findings no longer hold. +- The dead `Space5` / `Space30` statements are real `Spacer`s now, both in `CButtons.kt` and the demo screen. +- The buttons demo screen renders the actual components instead of a lone text label. +- Typography uses `staticCompositionLocalOf`, matching the colors token. +- The ignored `kotlinCompilerExtensionVersion` is gone from both Android modules. +- The duplicate copyright header in the lint build script is removed. +- `CInputFiels.kt` is renamed to `CInputFields.kt` (the file is still a stub; see backlog). +- `tools/setup.sh` has a working curl line, the broken `gradle/init.gradle.kts` spotless references are removed from the Makefile and pre-push, and `todo.yml` now watches `develop`. +- README typos and the bogus "chaidemop" link are fixed. + +Still open, with detail in [`backlog.md`](./backlog.md): the lint detectors, the remaining stub components, real tests, the full dependency pass, convention plugins, the Java 8 target, the empty docs, edge-to-edge status bar, the multiplatform plugin, and the `ChaiSteal` rename. + +A few items in the original audit no longer apply to the current tree: the manifests carry no `package=` attribute, and the README links to `docs/chaiArchitecture.md`, `docs/atoms.md`, and `docs/components.md` all resolve to real files. + ## Architecture and design-system correctness **The custom `ChaiColors` palette is defined but barely used. (High)** @@ -155,6 +173,8 @@ The bumps above are a start, but the project should do a deliberate pass over th ## Prioritized recommendations +Items 1, 2, and 4 are done, and 5 and 7 are partly done (see Resolution status above). The rest are tracked in [`backlog.md`](./backlog.md). + 1. **Wire `ChaiColors` into the theme.** Pass a `colorScheme` derived from the Chai palette into `MaterialTheme`, or have components read `ChaiTheme.colors` / `LocalChaiColorsPalette` directly. Today the palette is decorative. (Theme.kt, CButtons.kt) 2. **Fix `CPrimaryButton` colors** so the primary button has a branded container, and replace the bare `Space*` statements with `Spacer*()` calls. (CButtons.kt, ChaiButtonsDemoScreen.kt) 3. **Implement the lint detectors or remove the module's claims.** As shipped, the design-system enforcement is non-functional. Add `lint-tests`-based tests as you go. (chailinter) From 9d532e734582d7ad308e55837dd93fa8c6dca238 Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 10:39:26 +0200 Subject: [PATCH 33/39] Removed solved items from findings and backlog --- docs/tech/backlog.md | 12 ----- docs/tech/findings.md | 119 +++++++++--------------------------------- 2 files changed, 26 insertions(+), 105 deletions(-) diff --git a/docs/tech/backlog.md b/docs/tech/backlog.md index 688d87c..13a1ce7 100644 --- a/docs/tech/backlog.md +++ b/docs/tech/backlog.md @@ -17,25 +17,13 @@ Five files are header-only but are presented as part of the system: `CCards.kt`, ### Real component and theme tests Every test in `chai` and `chaidemo` is a generated `2 + 2` scaffold. The README testing roadmap asks for component render tests, light/dark theme switching tests, accessibility semantics, and screenshot/visual regression. Pick a harness (Robolectric or instrumented Compose tests, plus a screenshot tool) and replace the scaffolds. The lint module needs its own tests too, tracked under the detector work above. -### Full dependency catalog pass -`gradle/libs.versions.toml` reflects an old project. Only a handful of versions were bumped recently. Do a deliberate sweep: run the Ben Manes `dependencyUpdates` task or the AGP Upgrade Assistant, move every entry to its current stable release, and lean on the Compose BOM instead of pinning `runtimeAndroid` / `uiTooling` / `material3Android` by hand. Either wire the already-added JUnit 6 entries into the modules or drop them, since modules still use JUnit 4. Build, then run lint, detekt, and tests, and read the Compose and AGP release notes for breaking changes before committing. - ### Convention plugins (build logic) The README and `docs/buildlogic.md` promise a `buildLogic/` convention-plugin setup, but configuration is copy-pasted across the three `build.gradle.kts` files (SDK levels, compose options, Java target). Extract the shared config into convention plugins so there is one source of truth. This also clears the leftover "see if I can add this to build Logic" notes in `chai/build.gradle.kts`. -### Raise the JVM target on the Android modules -`chai` and `chaidemo` compile to Java 8 bytecode while `chailinter` targets Java 17. Java 8 is unusually low for Kotlin 2.3 on compileSdk 36. Raise the two Compose modules to 11 or 17 for consistency, and verify the build after. - ### Fill the empty docs `docs/atoms.md` and `docs/components.md` are empty, and `docs/architecture.md` is a header only. The README links to all of them as if written. Each is its own commit per the repo doc rules. ## Low priority -### Edge-to-edge status bar -`Theme.kt` still sets `window.statusBarColor`, deprecated as of API 35. Move to the edge-to-edge APIs. This touches the host activity, so it is more than a one-line swap. - -### Confirm or drop the multiplatform plugin -`chai/build.gradle.kts` applies both the Compose compiler plugin and `jetbrains.compose` (Compose Multiplatform) on an Android-only library. Confirm the multiplatform plugin is intentional or remove it. - ### Rename `ChaiSteal` to `ChaiSteel` `atoms/Color.kt` defines `ChaiSteal` (a typo for "Steel") and the name propagates through `colors/ChaiColors.kt`. This is a breaking change for any external consumer, so it was left alone. Do it as a coordinated rename if and when a breaking release is acceptable. diff --git a/docs/tech/findings.md b/docs/tech/findings.md index 2cddc82..3f35bfa 100644 --- a/docs/tech/findings.md +++ b/docs/tech/findings.md @@ -4,7 +4,7 @@ A review of the Chai design system project across its three modules (`chai`, `ch ## Summary -The bones are good. There is a semantic color palette, typography atoms, a theme wrapper, and a few button components with previews. The problem is the gap between what the project advertises and what actually runs. The custom lint meant to enforce the design system does nothing. Several promised components are empty stubs. The color palette is wired into the theme but the components never read from it. A couple of tooling scripts point at files that were never created. These are mostly correctness and completeness gaps, not deep design mistakes, which is the good news: they are fixable without rethinking the architecture. +The bones are good. There is a semantic color palette, typography atoms, a theme wrapper, and a few button components with previews. The problem is the gap between what the project advertises and what actually runs. The custom lint meant to enforce the design system does nothing. Several promised components are empty stubs, the core docs are empty, and there are no real tests. These are mostly correctness and completeness gaps, not deep design mistakes, which is the good news: they are fixable without rethinking the architecture. Severity legend: **High** (broken or misleading), **Medium** (should fix before release), **Low** (polish). @@ -22,30 +22,17 @@ A fix pass in June 2026 closed the low-risk findings. Resolved since this audit - `tools/setup.sh` has a working curl line, the broken `gradle/init.gradle.kts` spotless references are removed from the Makefile and pre-push, and `todo.yml` now watches `develop`. - README typos and the bogus "chaidemop" link are fixed. -Still open, with detail in [`backlog.md`](./backlog.md): the lint detectors, the remaining stub components, real tests, the full dependency pass, convention plugins, the Java 8 target, the empty docs, edge-to-edge status bar, the multiplatform plugin, and the `ChaiSteal` rename. +A second pass on this branch closed more of the build and tooling findings: -A few items in the original audit no longer apply to the current tree: the manifests carry no `package=` attribute, and the README links to `docs/chaiArchitecture.md`, `docs/atoms.md`, and `docs/components.md` all resolve to real files. +- The two Android modules now compile to Java 21 (`jvmToolchain(21)`), matching `chailinter`; the Java 8 target and cross-module JVM mismatch are gone. +- `chai/build.gradle.kts` dropped the `jetbrains.compose` multiplatform plugin; it applies only `android.library` and `compose.compiler`. +- `Theme.kt` draws edge-to-edge via `WindowCompat` instead of the deprecated `window.statusBarColor`. +- The JUnit 6 (Jupiter) catalog entries are wired into both Android modules with `useJUnitPlatform()`. +- The `make spotless` / pre-push `init.gradle.kts` references, the corrupted `tools/setup.sh` curl line, and the `todo.yml` branch are all corrected; the `kotlinCompilerExtensionVersion` and manifest `package=` leftovers are gone. -## Architecture and design-system correctness +Still open, with detail in [`backlog.md`](./backlog.md): the lint detectors, the remaining stub components, real tests, the full dependency pass, convention plugins, the empty docs, and the `ChaiSteal` rename. -**The custom `ChaiColors` palette is defined but barely used. (High)** -`ChaiTheme` (`chai/src/main/java/com/droidconke/chai/Theme.kt:81`) provides `LocalChaiColorsPalette` through a `CompositionLocalProvider`, but calls `MaterialTheme(content = content)` with no `colorScheme` argument. So `MaterialTheme.colorScheme` stays the stock Material 3 default. The components then read from the Material scheme instead of the Chai palette: `CPrimaryButton` uses `MaterialTheme.colorScheme.primary` (`chai/src/main/java/com/droidconke/chai/components/CButtons.kt:89-90`). The result is that the 38-field semantic palette in `colors/ChaiColors.kt:43-82` is mostly dead: it is plumbed into the theme but the shipped components do not consume it. This undercuts the project's central premise (a design system that drives color usage). - -**`CPrimaryButton` never sets a container color. (High)** -`CButtons.kt:88-91` builds `ButtonDefaults.buttonColors` with only `contentColor` and `disabledContentColor`. The button's background falls back to the Material default container color, so the "primary" button is not visibly branded. It also sets `contentColor` to `colorScheme.primary`, which is the brand color, making text the brand color on a default container rather than the inverse you would expect for a primary button. - -**Spacing values used as no-op statements. (High)** -`Space5`, `Space15`, `Space30` are `Dp` values (`utils/Spacing.kt:32-34`), not composables. They are written as bare statements where a `Spacer` is intended: -- `CButtons.kt:113` (`Space5` between the icon and the label in `COutlinedPrimaryButton`) -- `chaidemo/src/main/java/com/droidconke/chaidemo/screens/ChaiButtonsDemoScreen.kt:26` (`Space30`) - -These render nothing. The intended API is the `Spacer5()` / `Spacer15()` / `Spacer30()` composables that already exist in `utils/Spacing.kt:46-58`, or `Spacer(Modifier.width(...))` for horizontal gaps. - -**Inconsistent `CompositionLocal` factory choice. (Medium)** -Colors use `staticCompositionLocalOf` (`colors/ChaiColors.kt:84`); typography uses `compositionLocalOf` (`typography/ChaiTypography.kt`). For design tokens that change rarely, `staticCompositionLocalOf` is the usual choice. The mismatch gives the two token families different recomposition behavior for no stated reason. - -**`statusBarColor` is deprecated. (Low)** -`Theme.kt:71` sets `activity.window.statusBarColor`, deprecated as of API 35. With `compileSdk = 36` this will surface as a lint/deprecation warning; the edge-to-edge APIs are the current path. +A few items in the original audit no longer apply to the current tree: the README links to `docs/chaiArchitecture.md`, `docs/atoms.md`, and `docs/components.md` all resolve to real files. ## Incomplete and stub implementations @@ -65,29 +52,10 @@ The README and docs present these as part of the system, but the APIs do not exi **Misspelled color atom: `ChaiSteal`. (Low)** `atoms/Color.kt:41` defines `ChaiSteal` (intended "Steel"). The typo propagates through every reference in `colors/ChaiColors.kt`. Renaming is a breaking change for any external consumer, so weigh it against current adoption. -**Demo app does not demonstrate the components. (Medium)** -`ChaiButtonsDemoScreen.kt` is named for buttons but renders only a text label and the dead `Space30` statement; no `CButton`, `CPrimaryButton`, or `COutlinedPrimaryButton` appears. The demo also covers none of `CCards`, `CTabs`, input fields, or tabs. A sample app is the main way consumers learn a design system, so this is a real gap. - ## Build configuration -**`targetSdk` mixing and JVM target inconsistency across modules. (Medium)** -- `chai/build.gradle.kts` sets `compileSdk = 36`, `minSdk = 28`, and Java/JVM target 1.8. -- `chaidemo/build.gradle.kts` sets the same SDKs with `targetSdk = 36`, Java/JVM target 1.8. -- `chailinter/build.gradle.kts` uses Java 17 / `JvmTarget.JVM_17`. - -The two Android modules compile to Java 8 bytecode while the lint module targets Java 17. That is workable (lint runs on the host JVM), but the Java 8 target for Compose modules on Kotlin 2.3 / compileSdk 36 is unusually low and worth raising to 11 or 17 for consistency. - -**Stale `kotlinCompilerExtensionVersion`. (Medium)** -Both `chai/build.gradle.kts:67` and `chaidemo/build.gradle.kts` set `composeOptions { kotlinCompilerExtensionVersion = "1.5.15" }`. With Kotlin 2.x the Compose compiler is applied via the `org.jetbrains.kotlin.plugin.compose` Gradle plugin (already present), and this `composeOptions` value is ignored. It is dead config that implies an older compiler than is actually in use. - -**Manifest `package` attribute alongside `namespace`. (Low)** -Several module manifests still declare `package="com.droidconke.chai"` (for example the `chai` manifests) while the build files also set `namespace`. The manifest `package` attribute is deprecated in AGP 8; `namespace` in Gradle is the source of truth. - **Leftover "build logic" TODO comments. (Low)** -`chai/build.gradle.kts:43` and `:50` carry `//See if i can add this to build Logic`. The root `build.gradle.kts` has a similar note. These point at the missing convention-plugin work described under Documentation below. - -**`jetbrains.compose` plugin on an Android-only library. (Low)** -`chai/build.gradle.kts` applies both `compose.compiler` and `jetbrains.compose` (Compose Multiplatform). For an Android-only design system the multiplatform plugin is likely redundant; confirm it is intentional or drop it. +`chai/build.gradle.kts:41` and `:48` carry `//See if i can add this to build Logic`. The root `build.gradle.kts` has a similar note. These point at the missing convention-plugin work described under Documentation below. ## Custom lint module (`chailinter`) @@ -117,69 +85,34 @@ They assert `2 + 2 == 4` and the package name. There are no component render tes ## CI, tooling, and scripts -**`make spotless` and the pre-push `check` reference a missing file. (High)** -`Makefile:30` and `tools/pre-push:91` both run `./gradlew --init-script gradle/init.gradle.kts ...`, but `gradle/init.gradle.kts` does not exist (only `libs.versions.toml` and `wrapper/` are in `gradle/`). Both commands fail. Spotless is configured under `spotless/` but is not applied in any `*.gradle.kts`, so there is no working `spotless` task regardless. - -**`tools/setup.sh` has a corrupted curl line. (High)** -`tools/setup.sh:25` reads `curl -sSLo "${GIT_DIR}/hooks/commit-msg" \653 jym5` with garbage where the URL/continuation belongs. Running the setup script fails to install the commit-msg hook. - -**TODO-to-issue workflow watches the wrong branch. (Medium)** -`.github/workflows/todo.yml:7` triggers on `dev`, but the repository's integration branch is `develop` (see recent history and PR merges). The automation never fires on the branch that is actually used. - -**Otherwise the main CI workflow is solid. (Informational)** +**The main CI workflow is solid. (Informational)** `.github/workflows/main.yml` is well structured: parallel jobs, Gradle configuration cache and build cache, AVD caching, and conditional instrumented tests gated on branch or a `[test-instrumented]` commit-message flag. `gradle.properties` is tuned for CI (parallel, caching, 4 workers, 4 GB heap). ## Documentation -**Several core docs are empty. (Medium)** -- `docs/chaiLinter.md` (0 bytes) -- `docs/chaiProjectArchitecture.md` (0 bytes) -- `docs/atoms/atoms.md` (0 bytes) -- `docs/components/components.md` (0 bytes) +**Core docs are empty or header-only. (Medium)** +The README links to these as if they were written, but each is empty or a single header line: +- `docs/atoms.md` (0 bytes) +- `docs/components.md` (0 bytes) - `docs/architecture.md` (header only) - -The README links to these as if they were written. - -**Broken and duplicated README links. (Medium)** -In `README.md`: -- Links to `docs/chaiArchitecture.md`; the file is `docs/chaiProjectArchitecture.md`. -- Links to `docs/buildlogic.md`; no such file exists, and the convention-plugin infrastructure it describes is not present either (no `build-logic/` or `buildLogic/` module). -- Links to `docs/atoms.md` and `docs/components.md`; the files live at `docs/atoms/atoms.md` and `docs/components/components.md`. -- The "chaidemop" entry (line 38) is a typo for chaidemo, has the wrong description, and duplicates the components link. +- `docs/chaiArchitecture.md` (header only) +- `docs/chaiLinter.md` (header only) +- `docs/buildlogic.md` (header only) **Documented build-logic does not exist. (Medium)** -The README and task list promise a `buildLogic/` convention-plugin setup. There is no such module; configuration is repeated inline across the three `build.gradle.kts` files (compileSdk, minSdk, compose options, Java target), which is exactly what convention plugins would remove. - -**README typos. (Low)** -`README.md` contains "wiyth", "convengion polugins", "ist probably", and "ana ctions" (lines 34, 71, 74), among others. +The README and `docs/buildlogic.md` promise a `buildLogic/` convention-plugin setup. There is no such module; configuration is repeated inline across the three `build.gradle.kts` files (compileSdk, minSdk, compose options, Java target), which is exactly what convention plugins would remove. ## Dependency and version state -The working tree has uncommitted version bumps in `gradle/libs.versions.toml` on branch `project-fix-reset`: -- Kotlin 2.2.20 β†’ 2.3.0 -- AGP 8.13.0 β†’ 8.13.2 -- Compose BOM 2025.10.01 β†’ 2026.01.00 -- lifecycle, activity-compose, runtime, ui-tooling bumped -- JUnit 6 (Jupiter) entries added - -The JUnit 6 entries are added to the catalog but not wired into any module (modules still use JUnit 4 via `libs.junit`). Either finish the migration or drop the unused entries to avoid confusion. Decide whether this version bump is intentional before committing. +A full sweep of `gradle/libs.versions.toml` against the live Maven repositories confirms every entry is at its current latest stable release (AGP 9.2.1, Kotlin 2.4.0, Compose BOM 2026.06.00, lint 32.2.1, JUnit BOM 6.1.0, and the AndroidX stack). The JUnit 6 (Jupiter) entries are wired into both Android modules with `useJUnitPlatform()`, and the old JUnit 4 entry is gone. -**Bring the whole catalog up to date. (Medium)** -The bumps above are a start, but the project should do a deliberate pass over the entire `gradle/libs.versions.toml` and move every dependency to its current stable release, not just the handful already touched in the working tree. This is an old project and the versions reflect that. Concretely: -- Run `./gradlew dependencyUpdates` (Ben Manes versions plugin) or use Android Studio's "AGP Upgrade Assistant" plus catalog inspection to list what is behind. -- Cover the rest of the catalog that was not bumped: `androidx.core:core-ktx`, `appcompat`, `material` (the Google Material View library), `material3` / `material3-android`, the Compose `runtime`/`ui-tooling` entries, the lint tooling (`lint`, `lint-tests`, currently `32.0.0`), and the test stack (`androidx.test.ext:junit`, `espresso-core`). -- Prefer driving Compose artifact versions through the Compose BOM rather than pinning `runtimeAndroid` / `uiTooling` / `material3Android` individually; several of those pins exist only because the BOM is not being relied on consistently. -- Treat the update as its own change: bump, build, run `lint` / `detekt` / tests, and check the Compose and AGP release notes for breaking changes (especially around the deprecated `statusBarColor` and manifest `package` items noted earlier). Keep it on this branch and out of unrelated commits. +One non-version observation remains: the catalog still pins `runtime-android`, `material3-android`, and `ui-tooling-preview-android` individually rather than letting the Compose BOM drive them, and `kotlin-stdlib` references the merged-away `kotlin-stdlib-jdk8` artifact (and is not referenced by any module). Worth tidying, but neither is a version gap. ## Prioritized recommendations -Items 1, 2, and 4 are done, and 5 and 7 are partly done (see Resolution status above). The rest are tracked in [`backlog.md`](./backlog.md). +The theme wiring, button colors, JVM target, JUnit wiring, dependency versions, and tooling-script repairs are done (see Resolution status). What remains, in priority order, is tracked in detail in [`backlog.md`](./backlog.md): -1. **Wire `ChaiColors` into the theme.** Pass a `colorScheme` derived from the Chai palette into `MaterialTheme`, or have components read `ChaiTheme.colors` / `LocalChaiColorsPalette` directly. Today the palette is decorative. (Theme.kt, CButtons.kt) -2. **Fix `CPrimaryButton` colors** so the primary button has a branded container, and replace the bare `Space*` statements with `Spacer*()` calls. (CButtons.kt, ChaiButtonsDemoScreen.kt) -3. **Implement the lint detectors or remove the module's claims.** As shipped, the design-system enforcement is non-functional. Add `lint-tests`-based tests as you go. (chailinter) -4. **Repair the tooling scripts:** create or remove `gradle/init.gradle.kts`, actually apply Spotless, fix the `setup.sh` curl line, and point `todo.yml` at `develop`. -5. **Fill in or implement the stubs:** the empty component files, the empty docs, and the demo screens. Fix the README links and typos while you are there. -6. **Add real tests** for component rendering and theme switching, replacing the scaffold tests. -7. **Clean up build config:** drop the ignored `kotlinCompilerExtensionVersion`, reconsider the Java 8 target, and finish or revert the JUnit 6 / version-catalog changes. Extract shared config into convention plugins to match the documentation. -8. **Update dependencies to latest.** Do a full pass over `gradle/libs.versions.toml`, not just the few versions already bumped. Lean on the Compose BOM, then build and run lint, detekt, and tests to catch breakage. (See "Dependency and version state".) +1. **Implement the lint detectors or remove the module's claims.** As shipped, the design-system enforcement is non-functional. Add `lint-tests`-based tests as you go. (chailinter) +2. **Fill in or implement the stubs:** the empty component files, the empty docs, and the demo screens for the components that don't yet exist. +3. **Add real tests** for component rendering and theme switching, replacing the scaffold tests. +4. **Extract shared build config into convention plugins** to match the documentation and clear the leftover "build Logic" notes. From 05a63bd126517cafb54e51f523599ec4c6679fe0 Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 10:43:38 +0200 Subject: [PATCH 34/39] Upgraded Gradle to 9.6.0 with foojay toolchain resolver --- gradle/gradle-daemon-jvm.properties | 12 ++++++++++++ gradle/wrapper/gradle-wrapper.properties | 3 ++- settings.gradle.kts | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 gradle/gradle-daemon-jvm.properties diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 0000000..93ee2e5 --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,12 @@ +#This file is generated by updateDaemonJvm +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/402983f310a88ac68b3e883c7c91c760/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/e50b80b5a11d194a898bc3e6211b7c4b/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/402983f310a88ac68b3e883c7c91c760/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/e50b80b5a11d194a898bc3e6211b7c4b/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/f257be9f04bfdf169051808541767806/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/1dcbacacca32618bd21ec5465779ade1/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/402983f310a88ac68b3e883c7c91c760/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/e50b80b5a11d194a898bc3e6211b7c4b/redirect +toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/476d3c08f4989328dee56d22e202d98d/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/5a88b04b5e582b332d2e6bc12b45f1b9/redirect +toolchainVersion=21 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ce94d39..102b093 100755 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,8 @@ #Mon Jul 15 23:39:57 CEST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip +distributionSha256Sum=bbaeb2fef8710818cf0e261201dab964c572f92b942812df0c3620d62a529a01 networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle.kts b/settings.gradle.kts index 99cbc9b..7e5904c 100755 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,6 +5,10 @@ pluginManagement { gradlePluginPortal() } } +plugins { + // Resolves Java toolchains (compile jvmToolchain + the Gradle daemon JVM) from foojay/Adoptium. + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { From 3c62c4042c20772d066cb519936984dfa78721b0 Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 10:43:48 +0200 Subject: [PATCH 35/39] Bumped version catalog and dropped unused plugin aliases --- build.gradle.kts | 2 -- gradle/libs.versions.toml | 43 ++++++++++++++++----------------------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 86b8ce7..12b759a 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,11 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { alias(libs.plugins.android.application) apply false - alias(libs.plugins.jetbrains.kotlin.android) apply false alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.android.library) apply false alias(libs.plugins.jetbrains.kotlin.jvm) apply false - alias(libs.plugins.jetbrains.compose) apply false } // Apply performance optimizations to all subprojects diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 603811d..97df8d6 100755 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,30 +1,23 @@ [versions] -agp = "8.13.2" -kotlin = "2.3.0" -junit = "4.13.2" -composeBom = "2026.01.00" -lint = "32.0.0" -lintTests = "32.0.0" -coreKtx = "1.17.0" -junit6 = "6.0.2" +agp = "9.2.1" +kotlin = "2.4.0" +composeBom = "2026.06.00" +lint = "32.2.1" +lintTests = "32.2.1" +coreKtx = "1.19.0" +junit6 = "6.1.0" junitVersion = "1.3.0" espressoCore = "3.7.0" -lifecycleRuntimeKtx = "2.10.0" -activityCompose = "1.12.2" +lifecycleRuntimeKtx = "2.11.0" +activityCompose = "1.13.0" appcompatVersion = "1.7.1" -materialVersion = "1.13.0" -compose-plugin = "1.10.0" -runtimeAndroid = "1.10.1" -material3Android = "1.4.0" -uiToolingPreviewAndroid = "1.10.1" -uiTooling = "1.10.1" +materialVersion = "1.14.0" [libraries] android-lint-tests = { module = "com.android.tools.lint:lint-tests", version.ref = "lintTests" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" } -junit = { group = "junit", name = "junit", version.ref = "junit" } lint-api = { group = "com.android.tools.lint", name = "lint-api", version.ref = "lint" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } @@ -40,19 +33,19 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompatVersion" } material = { group = "com.google.android.material", name = "material", version.ref = "materialVersion" } -androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" } -androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" } -androidx-ui-tooling-preview-android = { group = "androidx.compose.ui", name = "ui-tooling-preview-android", version.ref = "uiToolingPreviewAndroid" } -# JUnit 6 (Jupiter) for unit tests - see https://junit.org/junit5/docs/current/user-guide/ -junit6 = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit6" } +androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android" } +androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android" } +androidx-ui-tooling-preview-android = { group = "androidx.compose.ui", name = "ui-tooling-preview-android" } +# JUnit 6 (Jupiter) for local unit tests - see https://junit.org/junit5/docs/current/user-guide/ +# The BOM aligns the Jupiter and Platform artifact versions; the libraries below stay version-less. +junit-bom = { group = "org.junit", name = "junit-bom", version.ref = "junit6" } +junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter" } junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher" } -ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "uiTooling" } +ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } -jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } android-library = { id = "com.android.library", version.ref = "agp" } jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } From 4714b41fb98858e1adf33af29ec43a0dc1478f08 Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 10:43:48 +0200 Subject: [PATCH 36/39] Migrated modules to Java 21 and JUnit 5 --- chai/build.gradle.kts | 28 +++++++++--------- .../com/droidconke/chai/ExampleUnitTest.kt | 5 ++-- chaidemo/build.gradle.kts | 29 ++++++++++--------- .../droidconke/chaidemo/ExampleUnitTest.kt | 4 +-- chailinter/build.gradle.kts | 18 ++++-------- 5 files changed, 39 insertions(+), 45 deletions(-) diff --git a/chai/build.gradle.kts b/chai/build.gradle.kts index 7781ef9..00c0f00 100755 --- a/chai/build.gradle.kts +++ b/chai/build.gradle.kts @@ -15,14 +15,12 @@ */ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) alias(libs.plugins.compose.compiler) - alias(libs.plugins.jetbrains.compose) } android { namespace = "com.droidconke.chai" - compileSdk = 36 + compileSdk = 37 defaultConfig { minSdk = 28 @@ -54,16 +52,19 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlin { - compilerOptions { - jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8) - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } } +kotlin { + jvmToolchain(21) +} + +tasks.withType<Test>().configureEach { + useJUnitPlatform() +} + dependencies { implementation(libs.androidx.core.ktx) @@ -75,12 +76,11 @@ dependencies { implementation(libs.androidx.runtime.android) implementation(libs.androidx.material3.android) implementation(libs.androidx.ui.tooling.preview.android) - testImplementation(libs.junit) + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) - // compose - //implementation(platform(libs.androidx.compose.bom)) - //implementation(compose.foundation) debugImplementation(libs.ui.tooling) } \ No newline at end of file diff --git a/chai/src/test/java/com/droidconke/chai/ExampleUnitTest.kt b/chai/src/test/java/com/droidconke/chai/ExampleUnitTest.kt index 132aa91..a3d90f4 100755 --- a/chai/src/test/java/com/droidconke/chai/ExampleUnitTest.kt +++ b/chai/src/test/java/com/droidconke/chai/ExampleUnitTest.kt @@ -1,8 +1,7 @@ package com.droidconke.chai -import org.junit.Test - -import org.junit.Assert.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.assertEquals /** * Example local unit test, which will execute on the development machine (host). diff --git a/chaidemo/build.gradle.kts b/chaidemo/build.gradle.kts index 0b2c109..b85bad4 100755 --- a/chaidemo/build.gradle.kts +++ b/chaidemo/build.gradle.kts @@ -15,19 +15,17 @@ */ plugins { alias(libs.plugins.android.application) - alias(libs.plugins.jetbrains.kotlin.android) alias(libs.plugins.compose.compiler) - } android { namespace = "com.droidconke.chaidemo" - compileSdk = 36 + compileSdk = 37 defaultConfig { applicationId = "com.droidconke.chaidemo" minSdk = 28 - targetSdk = 36 + targetSdk = 37 versionCode = 1 versionName = "1.0" @@ -48,14 +46,8 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - - kotlin { - compilerOptions { - jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8) - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } buildFeatures { @@ -70,6 +62,14 @@ android { } } +kotlin { + jvmToolchain(21) +} + +tasks.withType<Test>().configureEach { + useJUnitPlatform() +} + dependencies { implementation(project(":chai")) @@ -81,8 +81,11 @@ dependencies { implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) + implementation(libs.androidx.material.icons.extended) - testImplementation(libs.junit) + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(platform(libs.androidx.compose.bom)) diff --git a/chaidemo/src/test/java/com/droidconke/chaidemo/ExampleUnitTest.kt b/chaidemo/src/test/java/com/droidconke/chaidemo/ExampleUnitTest.kt index 9d55d47..6d6c2d6 100755 --- a/chaidemo/src/test/java/com/droidconke/chaidemo/ExampleUnitTest.kt +++ b/chaidemo/src/test/java/com/droidconke/chaidemo/ExampleUnitTest.kt @@ -16,8 +16,8 @@ package com.droidconke.chaidemo -import org.junit.Assert.assertEquals -import org.junit.Test +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test /** * Example local unit test, which will execute on the development machine (host). diff --git a/chailinter/build.gradle.kts b/chailinter/build.gradle.kts index 15b1ff9..ec51d3c 100755 --- a/chailinter/build.gradle.kts +++ b/chailinter/build.gradle.kts @@ -13,8 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - plugins { `java-library` kotlin("jvm") @@ -22,20 +20,12 @@ plugins { id("com.android.lint") } - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} - kotlin { - compilerOptions { - jvmTarget = JvmTarget.JVM_17 - } + jvmToolchain(21) } tasks.named<Test>("test") { - useJUnit() + useJUnitPlatform() } lint { @@ -48,6 +38,8 @@ dependencies { compileOnly(libs.lint.api) testImplementation(libs.lint.api) - testImplementation(libs.junit) + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.android.lint.tests) } From c7c15fafa8515b2cc04ab40d79e4e26dee713063 Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 10:43:48 +0200 Subject: [PATCH 37/39] Drew status bar edge-to-edge in ChaiTheme --- chai/src/main/java/com/droidconke/chai/Theme.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chai/src/main/java/com/droidconke/chai/Theme.kt b/chai/src/main/java/com/droidconke/chai/Theme.kt index 484e5cf..2898570 100755 --- a/chai/src/main/java/com/droidconke/chai/Theme.kt +++ b/chai/src/main/java/com/droidconke/chai/Theme.kt @@ -26,7 +26,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.SideEffect -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat import com.droidconke.chai.colors.ChaiColors @@ -69,10 +68,11 @@ fun ChaiTheme( if (!view.isInEditMode) { SideEffect { - val activity = view.context.findActivity() - activity.window.statusBarColor = customColorsPalette.background.toArgb() - WindowCompat.getInsetsController(activity.window, view).isAppearanceLightStatusBars = - !darkTheme + val window = view.context.findActivity().window + // Draw edge-to-edge so the Chai background shows behind the (transparent) status bar. + // Replaces the deprecated window.statusBarColor, which is ignored on API 35+. + WindowCompat.setDecorFitsSystemWindows(window, false) + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme } } From d913d52e1a7a4d68a0b2f98740d6ae092c0513c7 Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 10:43:48 +0200 Subject: [PATCH 38/39] Dropped obsolete enableJetifier CI property --- .github/ci-gradle.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/ci-gradle.properties b/.github/ci-gradle.properties index 0023ae2..ef0cb6a 100755 --- a/.github/ci-gradle.properties +++ b/.github/ci-gradle.properties @@ -28,7 +28,6 @@ org.gradle.configuration-cache=true org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC # Android build optimizations android.useAndroidX=true -android.enableJetifier=false android.nonTransitiveRClass=true android.nonFinalResIds=true # Kotlin compiler optimizations From 50c01e7444b811e25be09abdd0ada77bfe5a1136 Mon Sep 17 00:00:00 2001 From: "./root" <tamrefrank@gmail.com> Date: Fri, 19 Jun 2026 11:12:18 +0200 Subject: [PATCH 39/39] Removed duplicate subprojects block from merge --- build.gradle.kts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f1d2d97..12b759a 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,19 +21,3 @@ subprojects { enabled = System.getenv("CI") != "true" } } - -// Apply performance optimizations to all subprojects -subprojects { - tasks.withType<JavaCompile>().configureEach { - options.isIncremental = true - } - - // Skip unnecessary tasks in CI - //See if i can add this to build Logic - tasks.matching { - it.name.contains("lint", ignoreCase = true) && - it.name.contains("Baseline", ignoreCase = true) - }.configureEach { - enabled = System.getenv("CI") != "true" - } -}