From cbf78c823f878e87c9ee974b2a5e7abb3c7d9685 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 15 May 2026 13:54:02 +0800 Subject: [PATCH 1/5] Update CONTRIBUTING.md with project overview, module structure, tech stack, and development setup --- CONTRIBUTING.md | 174 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 158 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79cf6bb98c..4dbb635624 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,48 +2,190 @@ Thank you for contributing to this project. -### Before you start +## Project Overview + +**Tabby** is an Android GUI application for [Mihomo](https://github.com/MetaCubeX/mihomo) +(formerly Clash Meta), a rule-based proxy kernel. +The app is written in Kotlin with Jetpack Compose for the UI, and embeds a compiled Go binary +(`libclash.so`) built from the Mihomo submodule. + +- **Application ID**: `io.github.goooler.tabby` +- **Min SDK**: 28 | **Compile SDK**: 37 | **Target SDK**: 35 +- **Default branch**: `trunk` + +## Module Structure + +```text +Tabby/ +├── app/ # Application shell: MainActivity, MainApplication, AppModule (Koin), BroadcastReceivers, TileService +├── core/ # Mihomo bridge: Go/JNI bindings, data models, C++ CMake layer +│ # └── src/foss/golang/clash/ (git submodule → MetaCubeX/mihomo) +├── service/ # Background VPN service, Room database, IPC via kaidl, OkHttp profile fetching +├── common/ # Shared constants, store providers, utility extensions (no Android framework dependencies) +├── glue/ # Dependency-injection wiring via Koin; exposes api() of core, service, common +└── ui/ # Compose screens (multi-module) + ├── crash/ # Crash reporting screen + ├── home/ # Dashboard / tunnel toggle + ├── log/ # Real-time logcat viewer + ├── proxy/ # Proxy group selector + ├── profile/ # Profile management + └── settings/ # App settings +``` + +### Module dependency graph (simplified) + +```text +app → glue → core → common + └── service → core + └── common +app → ui/* +``` + +## Tech Stack + +| Layer | Technology | +|------------|--------------------------------------------------------------------------| +| Language | Kotlin 2.x, Go 1.26+ | +| UI | Jetpack Compose + Material 3, Navigation3 | +| DI | Koin 4 | +| IPC | kaidl (custom AIDL generator) | +| DB | Room | +| Network | OkHttp 5 | +| Core proxy | Mihomo (Go submodule via golang-gradle-plugin) | +| Code style | ktfmt (Google style) via Spotless | +| Build | Gradle 8+ with Kotlin DSL, Version Catalog (`gradle/libs.versions.toml`) | + +## Before You Start - Search existing issues and pull requests before opening a new one. - For bug reports, include steps to reproduce, expected behavior, and logs/screenshots when possible. - For feature requests, explain the use case and scope clearly. -### Development setup +## Development Setup 1. Fork this repository and create a branch from `trunk`. -2. Initialize submodules: - +2. Clone with submodules (or initialize them after clone): ```bash + git clone --recurse-submodules https://github.com/Goooler/Tabby.git + # or after a plain clone: git submodule update --init --recursive ``` - 3. Install required tools: - JDK 21 - - Android SDK - - CMake - - Go 1.26 or above - + - Android SDK (set `sdk.dir` in `local.properties`) + - NDK `29.0.14206865` (installed automatically by AGP) + - CMake 4.x + - Go 1.26+ 4. Create `local.properties` in the project root: - ```properties sdk.dir=/path/to/android-sdk + # Optional: skip downloading geo data files if they already exist + # skip.downloadGeoFiles=true ``` -### Validations - -Run checks before submitting a pull request: +## Build Commands ```bash -# For checking code style. +# Check code style (required before every PR) ./gradlew spotlessCheck -# For checking build. +# Auto-fix style violations +./gradlew spotlessApply + +# Build a debug APK (skips geo file download by default if present) +./gradlew app:assembleDebug + +# Build a release APK (full validation; requires signing config) ./gradlew app:assembleRelease + +# Run all checks +./gradlew check ``` -### Pull request guidelines +> **Note**: The first build downloads three geo database files from +> `MetaCubeX/meta-rules-dat` into `app/src/main/assets/`. Set +> `skip.downloadGeoFiles=true` in `local.properties` to skip this when the +> files already exist. + +## Code Style & Conventions + +- **Formatter**: ktfmt (Google style). Run `./gradlew spotlessApply` to fix. + All Kotlin files in `src/**/*.kt` and `*.gradle.kts` are covered. +- **Warnings as errors**: `allWarningsAsErrors = true` for all Kotlin + compilations. Fix every warning before merging. +- **JVM target**: Java 21. +- **Opt-ins** applied project-wide: + - `kotlin.uuid.ExperimentalUuidApi` + - `androidx.compose.foundation.ExperimentalFoundationApi` + - `androidx.compose.material3.ExperimentalMaterial3Api` +- **Compiler flags**: `-Xcontext-sensitive-resolution`, `-Xexplicit-backing-fields`. +- **Imports**: No wildcard imports (star import threshold is `Int.MAX_VALUE`). + `ktfmt` manages ordering automatically. +- **Trailing commas**: allowed in both declarations and call sites. +- **Indentation**: 2 spaces for Kotlin/Gradle, 4 spaces for C/CMake. + +## Namespace Convention + +Every Android module's namespace follows the pattern: + +```text +com.github.kr328.clash. +``` + +For example: + +- `:app` → `com.github.kr328.clash.app` +- `:glue` → `com.github.kr328.clash.glue` +- `:ui:home` → `com.github.kr328.clash.home` + +This is enforced automatically in the root `build.gradle.kts` via +`namespace = "com.github.kr328.clash.${project.name}"`. + +## Dependency Injection (Koin) + +- All DI modules are defined in the `glue` module. +- `app/AppModule.kt` provides app-level bindings and is started in + `MainApplication`. +- UI modules should consume injected dependencies via `koinInject()` / + `getKoin()` in Compose; avoid constructor injection in `Activity`. + +## IPC (kaidl) + +- The `service` module defines IPC interfaces using `kaidl` annotations. +- Generated code is produced by `ksp(libs.kaidl.compiler)` at build time. +- Do not hand-edit generated files. + +## Signing + +- Debug builds use the default debug keystore. +- Release builds read from `signing.properties` (not committed). Create it + locally if you need signed release APKs: + ```properties + keystore.password= + key.alias= + key.password= + ``` + +## CI / Automated Checks + +GitHub Actions runs on every push to `trunk` and on every pull request: + +| Job | Command | Description | +|----------------|---------------------------------|-------------------------------------------------------| +| `check-style` | `./gradlew spotlessCheck` | Fails if formatting issues exist | +| `build` | `./gradlew app:assembleRelease` | Full release build including Go cross-compilation | +| `final-status` | — | Required branch-protection status combining both jobs | + +A nightly pre-release is published automatically on pushes to `trunk`. + +## Pull Request Guidelines +- Branch from `trunk`. - Keep changes focused and small. - Include a clear description of what changed and why. +- Run `./gradlew spotlessCheck` and `./gradlew app:assembleRelease` locally + before opening a PR. - Link related issues when applicable. - Update docs when behavior or developer workflow changes. +- Do **not** commit `local.properties`, `signing.properties`, or + `release.keystore`. From 4671fa952bae6d936e2e40fb0a3914204b247cef Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 15 May 2026 13:58:46 +0800 Subject: [PATCH 2/5] Update AGENTS.md to reference CONTRIBUTING.md as the single source of truth --- AGENTS.md | 185 +----------------------------------------------------- 1 file changed, 3 insertions(+), 182 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6e3646334c..7f484d3a19 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,185 +1,6 @@ # AGENTS.md -This file provides guidance for AI coding agents working in this repository. +All development and contribution rules for this repository are defined in `CONTRIBUTING.md` as the +single source of truth. -## Project Overview - -**Tabby** is an Android GUI application for [Mihomo](https://github.com/MetaCubeX/mihomo) -(formerly Clash Meta), a rule-based proxy kernel. -The app is written in Kotlin with Jetpack Compose for the UI, and embeds a compiled Go binary -(`libclash.so`) built from the Mihomo submodule. - -- **Application ID**: `io.github.goooler.tabby` -- **Min SDK**: 28 | **Compile SDK**: 37 | **Target SDK**: 35 -- **Default branch**: `trunk` - -## Module Structure - -``` -Tabby/ -├── app/ # Application shell: MainActivity, MainApplication, AppModule (Koin), BroadcastReceivers, TileService -├── core/ # Mihomo bridge: Go/JNI bindings, data models, C++ CMake layer -│ # └── src/foss/golang/clash/ (git submodule → MetaCubeX/mihomo) -├── service/ # Background VPN service, Room database, IPC via kaidl, OkHttp profile fetching -├── common/ # Shared constants, store providers, utility extensions (no Android framework dependencies) -├── glue/ # Dependency-injection wiring via Koin; exposes api() of core, service, common -└── ui/ # Compose screens (multi-module) - ├── crash/ # Crash reporting screen - ├── home/ # Dashboard / tunnel toggle - ├── log/ # Real-time logcat viewer - ├── proxy/ # Proxy group selector - ├── profile/ # Profile management - └── settings/ # App settings -``` - -### Module dependency graph (simplified) - -``` -app → glue → core → common - └── service → core - └── common -app → ui/* -``` - -## Tech Stack - -| Layer | Technology | -|------------|--------------------------------------------------------------------------| -| Language | Kotlin 2.x, Go 1.26+ | -| UI | Jetpack Compose + Material 3, Navigation3 | -| DI | Koin 4 | -| IPC | kaidl (custom AIDL generator) | -| DB | Room | -| Network | OkHttp 5 | -| Core proxy | Mihomo (Go submodule via golang-gradle-plugin) | -| Code style | ktfmt (Google style) via Spotless | -| Build | Gradle 8+ with Kotlin DSL, Version Catalog (`gradle/libs.versions.toml`) | - -## Development Setup - -1. **Clone with submodules**: - ```bash - git clone --recurse-submodules https://github.com/Goooler/Tabby.git - # or after a plain clone: - git submodule update --init --recursive - ``` - -2. **Required toolchain**: - - JDK 21 - - Android SDK (set `sdk.dir` in `local.properties`) - - NDK `29.0.14206865` (installed automatically by AGP) - - CMake 4.x - - Go 1.26+ - -3. **`local.properties`** (create in project root): - ```properties - sdk.dir=/path/to/android-sdk - # Optional: skip downloading geo data files if they already exist - # skip.downloadGeoFiles=true - ``` - -## Build Commands - -```bash -# Check code style (required before every PR) -./gradlew spotlessCheck - -# Auto-fix style violations -./gradlew spotlessApply - -# Build a debug APK (skips geo file download by default if present) -./gradlew app:assembleDebug - -# Build a release APK (full validation; requires signing config) -./gradlew app:assembleRelease - -# Run all checks -./gradlew check -``` - -> **Note**: The first build downloads three geo database files from -> `MetaCubeX/meta-rules-dat` into `app/src/main/assets/`. Set -> `skip.downloadGeoFiles=true` in `local.properties` to skip this when the -> files already exist. - -## Code Style & Conventions - -- **Formatter**: ktfmt (Google style). Run `./gradlew spotlessApply` to fix. - All Kotlin files in `src/**/*.kt` and `*.gradle.kts` are covered. -- **Warnings as errors**: `allWarningsAsErrors = true` for all Kotlin - compilations. Fix every warning before merging. -- **JVM target**: Java 21. -- **Opt-ins** applied project-wide: - - `kotlin.uuid.ExperimentalUuidApi` - - `androidx.compose.foundation.ExperimentalFoundationApi` - - `androidx.compose.material3.ExperimentalMaterial3Api` -- **Compiler flags**: `-Xcontext-sensitive-resolution`, `-Xexplicit-backing-fields`. -- **Imports**: No wildcard imports (star import threshold is `Int.MAX_VALUE`). - `ktfmt` manages ordering automatically. -- **Trailing commas**: allowed in both declarations and call sites. -- **Indentation**: 2 spaces for Kotlin/Gradle, 4 spaces for C/CMake. - -## Namespace Convention - -Every Android module's namespace follows the pattern: - -``` -com.github.kr328.clash. -``` - -For example: - -- `:app` → `com.github.kr328.clash.app` -- `:glue` → `com.github.kr328.clash.glue` -- `:ui:home` → `com.github.kr328.clash.home` - -This is enforced automatically in the root `build.gradle.kts` via -`namespace = "com.github.kr328.clash.${project.name}"`. - -## Dependency Injection (Koin) - -- All DI modules are defined in the `glue` module. -- `app/AppModule.kt` provides app-level bindings and is started in - `MainApplication`. -- UI modules should consume injected dependencies via `koinInject()` / - `getKoin()` in Compose; avoid constructor injection in `Activity`. - -## IPC (kaidl) - -- The `service` module defines IPC interfaces using `kaidl` annotations. -- Generated code is produced by `ksp(libs.kaidl.compiler)` at build time. -- Do not hand-edit generated files. - -## Signing - -- Debug builds use the default debug keystore. -- Release builds read from `signing.properties` (not committed). Create it - locally if you need signed release APKs: - ```properties - keystore.password= - key.alias= - key.password= - ``` - -## CI / Automated Checks - -GitHub Actions runs on every push to `trunk` and on every pull request: - -| Job | Command | Description | -|----------------|---------------------------------|-------------------------------------------------------| -| `check-style` | `./gradlew spotlessCheck` | Fails if formatting issues exist | -| `build` | `./gradlew app:assembleRelease` | Full release build including Go cross-compilation | -| `final-status` | — | Required branch-protection status combining both jobs | - -A nightly pre-release is published automatically on pushes to `trunk`. - -## Pull Request Guidelines - -- Branch from `trunk`. -- Keep changes focused and small. -- Run `./gradlew spotlessCheck` and `./gradlew app:assembleRelease` locally - before opening a PR. -- Link related issues in the PR description. -- Update docs when behavior or developer workflow changes. -- Do **not** commit `local.properties`, `signing.properties`, or - `release.keystore`. +Agents MUST read and strictly follow all rules in `./CONTRIBUTING.md`. From 575d66908d2f9a9bd5aabff921188f7b631630bc Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 15 May 2026 14:02:43 +0800 Subject: [PATCH 3/5] Tweaks --- AGENTS.md | 5 +---- CONTRIBUTING.md | 27 ++++++++++++--------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7f484d3a19..2419f078aa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,3 @@ # AGENTS.md -All development and contribution rules for this repository are defined in `CONTRIBUTING.md` as the -single source of truth. - -Agents MUST read and strictly follow all rules in `./CONTRIBUTING.md`. +Agents MUST read and strictly follow all rules defined in [`CONTRIBUTING.md`](CONTRIBUTING.md). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4dbb635624..39467fd877 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,7 +58,8 @@ app → ui/* ## Before You Start - Search existing issues and pull requests before opening a new one. -- For bug reports, include steps to reproduce, expected behavior, and logs/screenshots when possible. +- For bug reports, include steps to reproduce, expected behavior, and logs/screenshots when + possible. - For feature requests, explain the use case and scope clearly. ## Development Setup @@ -71,11 +72,11 @@ app → ui/* git submodule update --init --recursive ``` 3. Install required tools: - - JDK 21 - - Android SDK (set `sdk.dir` in `local.properties`) - - NDK `29.0.14206865` (installed automatically by AGP) - - CMake 4.x - - Go 1.26+ + - JDK 21 + - Android SDK (set `sdk.dir` in `local.properties`) + - NDK `29.0.14206865` (installed automatically by AGP) + - CMake 4.x + - Go 1.26+ 4. Create `local.properties` in the project root: ```properties sdk.dir=/path/to/android-sdk @@ -86,16 +87,13 @@ app → ui/* ## Build Commands ```bash -# Check code style (required before every PR) -./gradlew spotlessCheck - -# Auto-fix style violations +# Check and fix code style (required before every commit) ./gradlew spotlessApply -# Build a debug APK (skips geo file download by default if present) +# Build a debug APK (required before every commit) ./gradlew app:assembleDebug -# Build a release APK (full validation; requires signing config) +# Build a release APK (don't have to run this in general developments) ./gradlew app:assembleRelease # Run all checks @@ -183,9 +181,8 @@ A nightly pre-release is published automatically on pushes to `trunk`. - Branch from `trunk`. - Keep changes focused and small. - Include a clear description of what changed and why. -- Run `./gradlew spotlessCheck` and `./gradlew app:assembleRelease` locally +- Run `./gradlew spotlessApply` and `./gradlew app:assembleDebug` locally before opening a PR. - Link related issues when applicable. - Update docs when behavior or developer workflow changes. -- Do **not** commit `local.properties`, `signing.properties`, or - `release.keystore`. +- Do **not** commit `local.properties`, `signing.properties`, or `release.keystore`. From 39fe2f96a8cc3676766af04c18581401bc32b1bd Mon Sep 17 00:00:00 2001 From: Zongle Wang Date: Fri, 15 May 2026 14:13:35 +0800 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- CONTRIBUTING.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39467fd877..f5c91d03ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ Tabby/ ├── core/ # Mihomo bridge: Go/JNI bindings, data models, C++ CMake layer │ # └── src/foss/golang/clash/ (git submodule → MetaCubeX/mihomo) ├── service/ # Background VPN service, Room database, IPC via kaidl, OkHttp profile fetching -├── common/ # Shared constants, store providers, utility extensions (no Android framework dependencies) +├── common/ # Shared constants, store providers, and utility extensions; includes Android-specific helpers ├── glue/ # Dependency-injection wiring via Koin; exposes api() of core, service, common └── ui/ # Compose screens (multi-module) ├── crash/ # Crash reporting screen @@ -72,11 +72,11 @@ app → ui/* git submodule update --init --recursive ``` 3. Install required tools: - - JDK 21 - - Android SDK (set `sdk.dir` in `local.properties`) - - NDK `29.0.14206865` (installed automatically by AGP) - - CMake 4.x - - Go 1.26+ + - JDK 21 + - Android SDK (set `sdk.dir` in `local.properties`) + - NDK `29.0.14206865` (installed automatically by AGP) + - CMake 4.x + - Go 1.26+ 4. Create `local.properties` in the project root: ```properties sdk.dir=/path/to/android-sdk @@ -141,9 +141,9 @@ This is enforced automatically in the root `build.gradle.kts` via ## Dependency Injection (Koin) -- All DI modules are defined in the `glue` module. -- `app/AppModule.kt` provides app-level bindings and is started in - `MainApplication`. +- The current Koin module definition lives in `app/AppModule.kt`. +- `MainApplication` starts Koin with `appModule`, so add or update DI + bindings there unless the application startup is changed. - UI modules should consume injected dependencies via `koinInject()` / `getKoin()` in Compose; avoid constructor injection in `Activity`. @@ -168,11 +168,12 @@ This is enforced automatically in the root `build.gradle.kts` via GitHub Actions runs on every push to `trunk` and on every pull request: -| Job | Command | Description | -|----------------|---------------------------------|-------------------------------------------------------| -| `check-style` | `./gradlew spotlessCheck` | Fails if formatting issues exist | -| `build` | `./gradlew app:assembleRelease` | Full release build including Go cross-compilation | -| `final-status` | — | Required branch-protection status combining both jobs | +| Job | Command | Description | +|----------------|---------------------------------|------------------------------------------------------------| +| `check-style` | `./gradlew spotlessCheck` | Fails if formatting issues exist | +| `lint` | `./gradlew lintDebug` | Runs Android lint checks for the debug variant | +| `build` | `./gradlew app:assembleRelease` | Full release build including Go cross-compilation | +| `final-status` | — | Required branch-protection status combining all CI checks | A nightly pre-release is published automatically on pushes to `trunk`. @@ -185,4 +186,5 @@ A nightly pre-release is published automatically on pushes to `trunk`. before opening a PR. - Link related issues when applicable. - Update docs when behavior or developer workflow changes. -- Do **not** commit `local.properties`, `signing.properties`, or `release.keystore`. +- Do **not** commit `local.properties` or `signing.properties`, and do not add + personal/local signing keys or extra keystore files to the repository. From 3132a040db431742d1ffdc68873faa88134b5054 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 15 May 2026 14:17:46 +0800 Subject: [PATCH 5/5] Polish --- CONTRIBUTING.md | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f5c91d03ec..cc72c17f4f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,7 +72,7 @@ app → ui/* git submodule update --init --recursive ``` 3. Install required tools: - - JDK 21 + - JDK 21 or above - Android SDK (set `sdk.dir` in `local.properties`) - NDK `29.0.14206865` (installed automatically by AGP) - CMake 4.x @@ -155,25 +155,23 @@ This is enforced automatically in the root `build.gradle.kts` via ## Signing -- Debug builds use the default debug keystore. -- Release builds read from `signing.properties` (not committed). Create it - locally if you need signed release APKs: - ```properties - keystore.password= - key.alias= - key.password= - ``` +- The repository ships `app/release.keystore` with its credentials already + configured in `app/build.gradle.kts`. +- **Both debug and release builds** are signed automatically with this keystore; + no extra files or manual configuration are required. +- Do **not** replace or remove `app/release.keystore`, and do not commit any + personal keystores or override signing credentials. ## CI / Automated Checks GitHub Actions runs on every push to `trunk` and on every pull request: -| Job | Command | Description | -|----------------|---------------------------------|------------------------------------------------------------| -| `check-style` | `./gradlew spotlessCheck` | Fails if formatting issues exist | -| `lint` | `./gradlew lintDebug` | Runs Android lint checks for the debug variant | -| `build` | `./gradlew app:assembleRelease` | Full release build including Go cross-compilation | -| `final-status` | — | Required branch-protection status combining all CI checks | +| Job | Command | Description | +|----------------|---------------------------------|-----------------------------------------------------------| +| `check-style` | `./gradlew spotlessCheck` | Fails if formatting issues exist | +| `lint` | `./gradlew lintDebug` | Runs Android lint checks for the debug variant | +| `build` | `./gradlew app:assembleRelease` | Full release build including Go cross-compilation | +| `final-status` | — | Required branch-protection status combining all CI checks | A nightly pre-release is published automatically on pushes to `trunk`. @@ -186,5 +184,3 @@ A nightly pre-release is published automatically on pushes to `trunk`. before opening a PR. - Link related issues when applicable. - Update docs when behavior or developer workflow changes. -- Do **not** commit `local.properties` or `signing.properties`, and do not add - personal/local signing keys or extra keystore files to the repository.