# AGENTS.md This file is for agentic coding assistants working in this repository. It captures build/test commands and code conventions observed in the codebase. ## Project Snapshot - Name: `Kanbn4Droid` - Platform: Android app (Kotlin, XML views, coroutines) - Build system: Gradle Kotlin DSL (`*.gradle.kts`) - Module layout: single app module `:app` - Namespace/application id: `space.hackenslacker.kanbn4droid.app` - Compile/target SDK: 35 - Min SDK: 29 - Java/Kotlin target: 17 ## Environment Prerequisites - Java 17 installed and available on PATH - Android SDK command-line tools installed - Android SDK packages: - `platforms;android-35` - `build-tools;35.0.0` - `platform-tools` - If SDK is not auto-detected, copy `local.properties.example` to `local.properties` and set `sdk.dir` ## Source of Truth for Commands - Prefer Gradle wrapper (`./gradlew`) over system Gradle - Most Android tasks should be run with explicit module prefix when possible (`:app:`) - Discover tasks with: `./gradlew tasks --all` ## Build / Lint / Test Commands ### Core Daily Commands - List tasks: `./gradlew tasks --all` - Clean: `./gradlew clean` - Build everything in app module: `./gradlew :app:build` - Build debug APK: `./gradlew :app:assembleDebug` - Install debug APK on connected device/emulator: `./gradlew :app:installDebug` ### Lint - Run default lint: `./gradlew :app:lint` - Run debug lint explicitly: `./gradlew :app:lintDebug` - Run release lint explicitly: `./gradlew :app:lintRelease` - Auto-fix safe lint issues: `./gradlew :app:lintFix` ### Unit Tests (JVM) - Run all unit tests (all variants): `./gradlew :app:test` - Run debug unit tests: `./gradlew :app:testDebugUnitTest` - Run release unit tests: `./gradlew :app:testReleaseUnitTest` ### Run a Single Unit Test (important) - Single test class: - `./gradlew :app:testDebugUnitTest --tests "space.hackenslacker.kanbn4droid.app.boards.BoardsViewModelTest"` - Single test method: - `./gradlew :app:testDebugUnitTest --tests "space.hackenslacker.kanbn4droid.app.boards.BoardsViewModelTest.createBoardSuccessEmitsNavigateEvent"` - Pattern match within class/package: - `./gradlew :app:testDebugUnitTest --tests "space.hackenslacker.kanbn4droid.app.boarddetail.*"` ### Instrumentation Tests (device/emulator required) - Run all connected debug instrumentation tests: - `./gradlew :app:connectedDebugAndroidTest` - Equivalent aggregate task: - `./gradlew :app:connectedAndroidTest` ### Run a Single Instrumentation Test (important) - Single instrumentation test class: - `./gradlew :app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=space.hackenslacker.kanbn4droid.app.BoardsFlowTest` - Single instrumentation test method: - `./gradlew :app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=space.hackenslacker.kanbn4droid.app.BoardsFlowTest#pullToRefreshWorks` ## Repository-Local Rules (Cursor/Copilot) - Checked for Cursor rules in `.cursor/rules/` and `.cursorrules`: none found - Checked for Copilot instructions in `.github/copilot-instructions.md`: none found - Therefore, no additional repo-local AI instruction files are currently enforced beyond this document - If those files are added later, treat them as higher-priority guidance and update this AGENTS.md ## Architecture and Code Organization - UI is Activity-driven (not Compose) - Feature folders under `app/src/main/java/space/hackenslacker/kanbn4droid/app/`: `auth/`, `boards/`, `boarddetail/`, `carddetail/` - Typical layering: - Activity handles view binding and user interaction wiring - ViewModel holds UI state and events (`StateFlow` + `SharedFlow`) - Repository/data source handles session and API calls - API client encapsulates HTTP + response parsing ## Kotlin Style Guidelines (observed conventions) ### Formatting - Follow Kotlin official code style (`gradle.properties: kotlin.code.style=official`) - Use 4-space indentation - Use trailing commas in multiline argument lists/constructors where already used - Keep functions focused; prefer extraction for repeated logic - Avoid unnecessary comments; use clear naming instead ### Imports - Do not use wildcard imports - Keep imports grouped (platform/library/project), matching existing file style - Remove unused imports - Prefer explicit imports for readability in large files ### Types and API design - Prefer immutable `val`; use `var` only when mutation is required - Use data classes for immutable UI/domain models - Use sealed interfaces/classes for event/result hierarchies (`BoardsUiEvent`, mutation results) - Keep nullability explicit; avoid nullable types unless required by API/domain - Return domain/result wrappers instead of throwing for expected failures ### Naming - Packages: lowercase, dot-separated (`space.hackenslacker...`) - Classes/interfaces/objects: PascalCase - Functions/properties/locals: camelCase - Constants: UPPER_SNAKE_CASE (`private const val ...`) - Test method names: descriptive camelCase phrases that state behavior ### Coroutines and threading - Use `viewModelScope.launch` for ViewModel async work - Use `withContext(Dispatchers.IO)` or injected IO dispatcher for blocking/network work - Keep dispatcher injection points testable (repositories already do this) - Avoid blocking calls on main thread ### State and events - Model screen state as immutable `data class` in ViewModel - Update state via copy semantics (`state.copy(...)`), often with `MutableStateFlow.update` - Use one-off events via `SharedFlow` rather than state flags for navigation/toasts/dialog alerts - Keep rendering deterministic from current state ### Error handling - Prefer explicit result types (`BoardsApiResult`, `AuthResult`) over exceptions for normal failure paths - Surface user-visible messages through UI events/state - Validate user input early (blank checks, URL normalization) before network calls ### Android/UI conventions - Keep user-facing text in `res/values/strings.xml` when practical - Use Material components already present in the project - Keep Activity responsibilities focused on binding and orchestration, not business logic - Respect existing navigation via explicit intents and extras constants ## Testing Conventions - Unit tests live in `app/src/test/...` and use JUnit4 + coroutines test utilities - Instrumentation tests live in `app/src/androidTest/...` and use Espresso/Intents - Prefer fakes/in-memory stores for API/session dependencies in tests - In coroutine tests: - set/reset main dispatcher in `@Before/@After` - use `runTest` and `advanceUntilIdle()` - Keep tests behavior-focused (arrange -> act -> assert)