Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 1 addition & 183 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,185 +1,3 @@
# AGENTS.md

This file provides guidance for AI coding agents working in this repository.

## 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.<module-name>
```

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=<password>
key.alias=<alias>
key.password=<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 defined in [`CONTRIBUTING.md`](CONTRIBUTING.md).
175 changes: 156 additions & 19 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,185 @@

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, 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
├── 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 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

- JDK 21 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.
./gradlew spotlessCheck
# Check and fix code style (required before every commit)
./gradlew spotlessApply

# Build a debug APK (required before every commit)
./gradlew app:assembleDebug

# For checking build.
# Build a release APK (don't have to run this in general developments)
./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:

```text
com.github.kr328.clash.<module-name>
```

### Pull request guidelines
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)

- 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`.

## 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

- 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 |

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 spotlessApply` and `./gradlew app:assembleDebug` locally
before opening a PR.
- Link related issues when applicable.
- Update docs when behavior or developer workflow changes.