docs(contributing): align contributor docs with real workflow + fix ktlint command (#960)
- Add "search for dupes first" rule (open/closed PRs + issues) before starting. - Fix ktlint instructions: the authoritative command is ./gradlew ktlintCheck / ktlintFormat, not the standalone ./ktlint binary (hand-fixes fail CI). - Document rebase-onto-main before squash-merge (stale-branch overwrite trap). - Add test-storage hygiene rule (no real on-device storage; TemporaryFolder / in-memory Room) — generic, not tied to any specific test harness or box. - Add dependency-pinning note (AGP/Kotlin/SDK "SDK not writable" trap). - Tighten PR template: type-of-change, rebased, ktlintCheck, navigateTo().
This commit is contained in:
2 files changed
+68
-19
No files matched your search
@@ -1,19 +1,35 @@
|
|||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
<!-- One sentence. -->
|
<!-- One sentence: what + why. -->
|
||||||
|
|
||||||
Fixes #
|
Fixes #
|
||||||
|
|
||||||
|
## Type of Change
|
||||||
|
|
||||||
|
- [ ] 🐛 Bug fix
|
||||||
|
- [ ] ✨ Feature
|
||||||
|
- [ ] ♻️ Refactor (no behavior change)
|
||||||
|
- [ ] 📝 Docs
|
||||||
|
- [ ] ✅ Tests
|
||||||
|
- [ ] 🔧 CI / chore
|
||||||
|
|
||||||
## What changed
|
## What changed
|
||||||
|
|
||||||
<!-- What was wrong and what this PR does about it. -->
|
<!-- What was wrong and what this PR does about it. Link the root cause if known. -->
|
||||||
|
|
||||||
## How to test
|
## How to test
|
||||||
|
|
||||||
<!-- Steps to verify. -->
|
<!-- Steps to verify. For UI: which screen + which emulator/device API level. For WS: which RPC. -->
|
||||||
|
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
- [ ] ktlint and CI pass
|
- [ ] Branch rebased onto `main` (`git rebase origin/main`)
|
||||||
- [ ] Tested on a device or CI APK
|
- [ ] `./gradlew ktlintCheck` passes (ran `ktlintFormat` first)
|
||||||
- [ ] Navigation goes through `NavigationController.navigateTo()`
|
- [ ] `./gradlew testDebugUnitTest` green (or CI unit-tests job)
|
||||||
|
- [ ] `checkColorLiterals` passes (no hardcoded Color outside theme/)
|
||||||
|
- [ ] Navigation goes through `NavigationController.navigateTo()` (not `backStack.add`)
|
||||||
|
- [ ] Tested on device/emulator or verified via CI APK
|
||||||
+46
-13
@@ -4,18 +4,33 @@ Thank you for your interest in contributing to Hermes Mobile! To maintain high c
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Before You Start: Search First
|
||||||
|
|
||||||
|
A quick duplicate check saves your time and keeps the PR queue clean:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh search prs --repo Hy4ri/hermes-mobile --state open "<your feature>"
|
||||||
|
gh search prs --repo Hy4ri/hermes-mobile --state closed "<your feature>"
|
||||||
|
gh search issues --repo Hy4ri/hermes-mobile --state open "<your feature>"
|
||||||
|
```
|
||||||
|
|
||||||
|
If an open PR already covers it, review or improve that one instead of opening a competing duplicate. For larger work, comment on the issue to signal you're on it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## PR Workflow
|
## PR Workflow
|
||||||
|
|
||||||
All code changes must go through a pull request (PR). Directly pushing to `main` is not allowed.
|
All code changes must go through a pull request (PR). Directly pushing to `main` is not allowed **except for trivial changes the maintainer explicitly okays** — when in doubt, open a PR.
|
||||||
|
|
||||||
1. **Pick or open an issue** to discuss the changes you want to make.
|
1. **Pick or open an issue** to discuss the changes you want to make.
|
||||||
2. **Create a branch** off `main` using the following naming convention:
|
2. **Create a branch** off `main` using the following naming convention:
|
||||||
- Features: `feat/issue-N-description`
|
- Features: `feat/short-description` (optionally `feat/issue-N-description`)
|
||||||
- Bug fixes: `fix/issue-N-description`
|
- Bug fixes: `fix/short-description` (optionally `fix/issue-N-description`)
|
||||||
3. **Implement your changes** and format them locally.
|
- CI/chore: `ci/...`, `chore/...`
|
||||||
4. **Submit a PR** targeting the `main` branch.
|
3. **Implement your changes** and format them locally (see Code Style).
|
||||||
5. **Ensure all CI checks pass** (formatting, linting, and unit tests).
|
4. **Submit a PR** targeting the `main` branch, filling the PR template.
|
||||||
6. **Maintainers will squash-merge** your PR into `main`.
|
5. **Ensure all CI checks pass** (ktlint, Android Lint, unit tests, build).
|
||||||
|
6. **Rebase onto `main` before merge.** Maintainers squash-merge; a stale branch's version of an unrelated file can silently overwrite recent fixes on `main` when squashed. `git fetch origin main && git rebase origin/main` first.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -57,34 +72,52 @@ If you use AI coding tools (including agents) to contribute:
|
|||||||
We enforce Kotlin coding conventions and Jetpack Compose best practices.
|
We enforce Kotlin coding conventions and Jetpack Compose best practices.
|
||||||
|
|
||||||
### Kotlin Formatting
|
### Kotlin Formatting
|
||||||
- Code formatting is checked and enforced by **ktlint 1.2.1**.
|
|
||||||
- Run formatting check locally before committing:
|
- Code formatting is checked and enforced by **ktlint 1.2.1** in CI. The authoritative command is the Gradle task (not the standalone binary):
|
||||||
```bash
|
```bash
|
||||||
./ktlint <file> # Check one file
|
./gradlew ktlintCheck # check (CI-equivalent)
|
||||||
./ktlint --format <file> # Auto-fix formatting issues
|
./gradlew ktlintFormat # auto-fix, then re-check with ktlintCheck
|
||||||
```
|
```
|
||||||
|
> ⚠️ Hand-patching indentation/import order routinely fails the real CI gate (multiline-expression-wrapping and ASCII import order are the usual casualties). Always run `ktlintFormat`, never fix formatting by hand.
|
||||||
- Import ordering is strictly ASCII-lexicographic (uppercase before lowercase: e.g., `LaunchedEffect` before `collectAsState`).
|
- Import ordering is strictly ASCII-lexicographic (uppercase before lowercase: e.g., `LaunchedEffect` before `collectAsState`).
|
||||||
|
|
||||||
### Compose Guidelines
|
### Compose Guidelines
|
||||||
|
|
||||||
- Standard screen structures must use `HermesScaffold` rather than raw Material3 `Scaffold`.
|
- Standard screen structures must use `HermesScaffold` rather than raw Material3 `Scaffold`.
|
||||||
- Composable parameters must follow the standard order: `modifier` first, then event callbacks, and finally children content.
|
- Composable parameters must follow the standard order: `modifier` first, then event callbacks, and finally children content.
|
||||||
- Do **not** apply `paddingValues` on inner content inside `HermesScaffold` — the scaffold already handles top bar padding. See `AGENTS.md` for the full breakdown of this recurring bug.
|
- Do **not** apply `paddingValues` on inner content inside `HermesScaffold` — the scaffold already handles top bar padding. See `AGENTS.md` for the full breakdown of this recurring bug.
|
||||||
- Every data screen must implement `LoadingState`, `ErrorState`, and `EmptyState` branches in its `when { }` block.
|
- Every data screen must implement `LoadingState`, `ErrorState`, and `EmptyState` branches in its `when { }` block.
|
||||||
|
|
||||||
### Color Usage
|
### Color Usage
|
||||||
|
|
||||||
- **No hardcoded `Color(0x...)` literals** outside `theme/`, `*Preview.kt`, and auth screens. A Gradle task (`checkColorLiterals`) enforces this in CI.
|
- **No hardcoded `Color(0x...)` literals** outside `theme/`, `*Preview.kt`, and auth screens. A Gradle task (`checkColorLiterals`) enforces this in CI.
|
||||||
- Use `MaterialTheme.colorScheme.<token>` or `LocalHermesStatusColors.current.<semantic>` instead.
|
- Use `MaterialTheme.colorScheme.<token>` or `LocalHermesStatusColors.current.<semantic>` instead.
|
||||||
- `Color.Transparent` and `Color.Unspecified` are allowed.
|
- `Color.Transparent` and `Color.Unspecified` are allowed.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- Unit tests: `./gradlew testDebugUnitTest` (MockK). Instrumented Compose UI tests run in CI on an emulator.
|
||||||
|
- **Tests must not touch real on-device storage.** Use `@get:Rule TemporaryFolder` (or Room `inMemoryDatabaseBuilder`) — never write to real app data or external storage. CI/emulator environments are ephemeral; leaking state breaks the next run.
|
||||||
|
- Mock time/dispatchers explicitly (`StandardTestDispatcher` + `Dispatchers.setMain`) — see `AGENTS.md` test references.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependency & Build Hygiene
|
||||||
|
|
||||||
|
- **Pin build tooling.** AGP/Kotlin/SDK versions are pinned in `gradle/libs.versions.toml` and `build.gradle.kts` on purpose. Unpinned versions silently break the build on the next dependabot/CI refresh (the "SDK not writable" AGP trap). Bump deliberately, not via wildcard ranges.
|
||||||
|
- Never add a dependency without updating the version catalog; CI validates the Gradle wrapper.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## PR Checklist
|
## PR Checklist
|
||||||
|
|
||||||
Before submitting your PR, please verify:
|
Before submitting your PR, please verify:
|
||||||
|
|
||||||
- [ ] Your branch is up-to-date with `main`.
|
- [ ] I searched open/closed PRs + issues for duplicates (see *Before You Start*).
|
||||||
- [ ] Local build and `ktlint` checks pass successfully.
|
- [ ] Branch is rebased onto current `main` (`git rebase origin/main`).
|
||||||
|
- [ ] `./gradlew ktlintCheck` passes (ran `ktlintFormat` first — no hand edits).
|
||||||
- [ ] `checkColorLiterals` passes (no hardcoded Color literals outside theme/).
|
- [ ] `checkColorLiterals` passes (no hardcoded Color literals outside theme/).
|
||||||
|
- [ ] `./gradlew testDebugUnitTest` passes locally (or CI unit-tests job is green).
|
||||||
- [ ] No unused imports, unused parameters, or dead code.
|
- [ ] No unused imports, unused parameters, or dead code.
|
||||||
- [ ] Every `Image` and `Icon` element has a descriptive `contentDescription` for accessibility.
|
- [ ] Every `Image` and `Icon` element has a descriptive `contentDescription` for accessibility.
|
||||||
- [ ] New screens use `HermesScaffold` and implement Loading/Error/Empty states.
|
- [ ] New screens use `HermesScaffold` and implement Loading/Error/Empty states.
|
||||||
|
|||||||
Reference in new issue
Block a user