-
-
Notifications
You must be signed in to change notification settings - Fork 499
feat: support multiple installed apps per repository (monorepo) #481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| # AGENTS.md | ||
|
|
||
| This file provides guidance to WARP (warp.dev) when working with code in this repository. | ||
|
|
||
| ## Project Overview | ||
|
|
||
| GitHub Store is a cross-platform app store for GitHub releases built with **Kotlin Multiplatform (KMP)** and **Compose Multiplatform**. It targets **Android** (min API 26, target 36) and **Desktop** (Windows, macOS, Linux via JVM). | ||
|
|
||
| Package: `zed.rainxch.githubstore` | ||
|
|
||
| ## Build & Run Commands | ||
|
|
||
| ```bash | ||
| # Android debug build | ||
| ./gradlew :composeApp:assembleDebug | ||
|
|
||
| # Desktop (run in dev mode) | ||
| ./gradlew :composeApp:run | ||
|
|
||
| # Full build check (both platforms) | ||
| ./gradlew build | ||
|
|
||
| # Lint (ktlint auto-formats on preBuild/compileKotlin* tasks automatically) | ||
| ./gradlew ktlintFormat # manual format all modules | ||
| ./gradlew ktlintCheck # check without fixing | ||
|
|
||
| # Desktop installers | ||
| ./gradlew :composeApp:packageDmg # macOS | ||
| ./gradlew :composeApp:packageExe # Windows | ||
| ./gradlew :composeApp:packageDeb # Linux | ||
| ``` | ||
|
|
||
| **Requirements:** JDK 21+ (Temurin recommended), Android SDK for Android builds. | ||
|
|
||
| **Setup:** Create a GitHub OAuth App and put `GITHUB_CLIENT_ID=<your_id>` in `local.properties` (root). Callback URL: `githubstore://callback`. | ||
|
|
||
| ## Architecture | ||
|
|
||
| **Clean Architecture + MVVM** with strict layer separation: | ||
|
|
||
| - **Domain** — Repository interfaces, models, use cases. No framework dependencies. | ||
| - **Data** — Repository implementations, Ktor API clients, Room DAOs, DTOs, mappers. Each feature's DI module lives in `data/di/SharedModule.kt`. | ||
| - **Presentation** — ViewModels with `StateFlow`/`Channel`, Compose screens. | ||
|
|
||
| ### State Management Pattern (every screen) | ||
|
|
||
| Every ViewModel follows the same State/Action/Event pattern: | ||
| - `State` — data class holding all UI state, exposed via `StateFlow` | ||
| - `Action` — sealed interface for user input (clicks, refreshes) | ||
| - `Event` — sealed interface for one-off effects (navigation, toasts), sent via `Channel.receiveAsFlow()` | ||
|
|
||
| ### Module Layout | ||
|
|
||
| ``` | ||
| composeApp/ # App entry points, navigation, DI wiring | ||
| src/commonMain/ # Shared UI & wiring | ||
| src/androidMain/ # Android entry (MainActivity) | ||
| src/jvmMain/ # Desktop entry (DesktopApp.kt) | ||
| core/ | ||
| domain/ # Shared interfaces, models, use cases | ||
| data/ # Networking (Ktor), database (Room), DI, platform impls | ||
| presentation/ # Material 3 theming, reusable UI components, localized strings (13 languages) | ||
| feature/<name>/ | ||
| domain/ # Feature-specific interfaces & models | ||
| data/ # Feature-specific implementations & Koin DI module | ||
| presentation/ # Feature ViewModel + Compose screens | ||
| build-logic/convention/ # Custom Gradle convention plugins | ||
| ``` | ||
|
|
||
| Some features (favourites, starred, recently-viewed, tweaks) are **presentation-only** — they use core repositories directly and register ViewModels in `composeApp/.../di/ViewModelsModule.kt` instead of having a `data/di/` layer. | ||
|
|
||
| ### Convention Plugins (build-logic) | ||
|
|
||
| | Plugin ID | Use For | | ||
| |-----------|---------| | ||
| | `convention.kmp.library` | KMP shared library modules (domain, data) | | ||
| | `convention.cmp.library` | Compose Multiplatform library modules | | ||
| | `convention.cmp.feature` | Feature presentation modules (auto-adds Compose + Koin + core:presentation) | | ||
| | `convention.cmp.application` | Main app module | | ||
| | `convention.room` | Room database modules | | ||
| | `convention.buildkonfig` | Build-time config (reads from local.properties) | | ||
|
|
||
| ### Navigation | ||
|
|
||
| Type-safe navigation using `@Serializable` sealed interface `GithubStoreGraph` in `composeApp/.../navigation/GithubStoreGraph.kt`. Routes are wired in `AppNavigation.kt`. Parameterized routes: `DetailsScreen(repositoryId, owner, repo, isComingFromUpdate)`, `DeveloperProfileScreen(username)`. | ||
|
|
||
| ### Dependency Injection | ||
|
|
||
| **Koin** — each feature's data layer defines a module in `data/di/SharedModule.kt`. All modules are registered in `composeApp/.../di/initKoin.kt`. ViewModels injected via `koinViewModel()`. `DetailsViewModel` and `MirrorPickerViewModel` use manual Koin `viewModel { }` with `parametersOf()` for constructor args; all others use `viewModelOf(::ClassName)`. | ||
|
|
||
| ### Key Cross-Cutting Concerns | ||
|
|
||
| - **Auth flow:** GitHub device-flow OAuth. Primary path goes through backend proxy (`/v1/auth/device/start`, `/v1/auth/device/poll`); falls back to direct GitHub only on infrastructure errors (5xx, timeouts). HTTP 4xx and GitHub's negative 200-bodies never trigger fallback. Backend rate limits (10 starts/hr, 200 polls/hr per IP) are hard — do not add retry loops. | ||
| - **`X-GitHub-Token` header:** Only sent on `/v1/search` and `/v1/search/explore`. Never on other endpoints, never logged. | ||
| - **Platform branching:** Source sets are `commonMain` (shared), `androidMain` (Android), `jvmMain` (Desktop). Some features (apps, installation, Shizuku) are Android-only. | ||
| - **Shizuku (Android):** Optional silent install via AIDL service. Falls back to standard installer on failure. | ||
|
|
||
| ## Coding Conventions | ||
|
|
||
| - Packages: `zed.rainxch.{module}.{layer}` (e.g. `zed.rainxch.home.data.repository`) | ||
| - Private state: underscore prefix `_state`, `_events` | ||
| - Sealed classes/interfaces for type-safe routes, actions, events | ||
| - Repository pattern: interface in `domain/`, implementation in `data/` | ||
| - Ktlint auto-runs on `preBuild`/`compileKotlin*` tasks; `ignoreFailures = true` | ||
| - Ktlint rules: wildcard imports allowed, filename rule disabled, `@Composable` functions exempt from function naming rule (see `.editorconfig`) | ||
|
|
||
| ## Adding a New Feature | ||
|
|
||
| 1. Create `feature/<name>/domain/`, `feature/<name>/data/`, `feature/<name>/presentation/` | ||
| 2. Add `build.gradle.kts` in each using the appropriate convention plugin | ||
| 3. Add `include` entries in `settings.gradle.kts` | ||
| 4. Define domain interfaces/models in `domain/` | ||
| 5. Implement repository + Koin DI module in `data/di/SharedModule.kt` | ||
| 6. Create ViewModel (State/Action/Event pattern) and Screen in `presentation/` | ||
| 7. Add navigation route to `GithubStoreGraph.kt` and wire in `AppNavigation.kt` | ||
| 8. Register the Koin module in `initKoin.kt` | ||
|
|
||
| ## Feature-Level Documentation | ||
|
|
||
| Each `feature/` directory contains its own `CLAUDE.md` with module structure, key interfaces, navigation routes, and implementation notes. Read those for feature-specific guidance. | ||
|
|
||
| ## Versions | ||
|
|
||
| All library versions managed in `gradle/libs.versions.toml`. Key versions: Kotlin 2.3.10, Compose Multiplatform 1.10.3, Ktor 3.4.0, Room 2.8.4, Koin 4.1.1. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a language identifier to the fenced block
The code fence at Line 54 is missing a language tag (
MD040). Use something like```textfor the module tree block to clear lint warnings.🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 54-54: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents