Thank you for your interest in contributing to Umbra! We welcome bug reports, feature requests, and code contributions.
Please read AUDIT.md — it contains mandatory security, privacy, and architecture rules. All code must follow these rules.
Key principles:
- Security first — No compromises on TOR routing, key management, or privacy
- Censorship-resistant — Content moderation (muting, NSFW hiding, feed filters) is always user-controlled and fully editable/removable; defaults exist, nothing is hardcoded or forced by the app
- Clean Architecture — UI → Domain → Data (no circular dependencies)
- Tests required — Every feature or fix must include unit tests
- English only — All comments, commits, and documentation in English
- JDK 17 (for development)
- Android SDK 37 (API level)
- Gradle 9.x (included via wrapper)
- Git
# Clone the repo
git clone https://github.com/deymosh/umbra.git
cd umbra
# Create local.properties with your SDK path
echo "sdk.dir=/path/to/Android/Sdk" > local.properties
# Build and install on device/emulator
./gradlew installDebug # Unix/macOS/Linux
.\gradlew.bat installDebug # WindowsLinux without a JDK/Android SDK yet: run scripts/install-toolchain.sh first — see the root README.md for the one-time setup.
./gradlew testDebugUnitTest # Unix/macOS/Linux — full unit suite
./gradlew compileDebugKotlin # fast type-check only
./gradlew assembleDebug # assemble APK, no installUse the .\gradlew.bat equivalents on Windows. All tests must pass before submitting a PR.
Open a GitHub issue with:
- What you did — Steps to reproduce
- What you expected — What should happen
- What happened — What actually occurred
- Device info — Android version, Umbra version
Example:
Feed shows hex pubkey instead of NIP-05 badge when viewing profile.
Expected: Badge shows "alice@example.com ✓"
Actual: Shows "a1b2c3d4..." hex
Device: Android 14, Umbra 0.1.0
Open a GitHub issue titled [Feature] Your idea with:
- Problem statement — What's the pain point?
- Proposed solution — How should it work?
- Impact on privacy — Does this leak data or require TOR bypass? (Usually answer is "no")
-
Fork and branch
git checkout -b fix/nip05-badge-display
-
Make changes
- Read the file before editing it
- One concern per commit (small, focused changes)
- Write tests for every new feature or bug fix
-
Run tests and lint
./gradlew testDebugUnitTest compileDebugKotlin # .\gradlew.bat on Windows -
Commit with clear messages
type(scope): short description - bullet point explaining what changed and why - another bullet point if neededValid types:
featfixrefactorperfsecuritytestdocschoreExample:
fix(feed): surface nip05 state from user profile - EventDao now joins p.nip05VerificationState in feed queries - EventCard displays badge when state is Verified - Added EventCardNip05StateTest to verify rendering -
Create a pull request
- Link to related issues
- Describe what you changed and why
- Confirm all tests pass
Before submitting, verify:
-
./gradlew testDebugUnitTestpasses (.\gradlew.baton Windows) -
./gradlew compileDebugKotlinsucceeds with no errors - All new code has unit tests (minimum: happy path + error case)
- No secrets in code (no API keys, no hardcoded URLs, no
nsecanywhere) - No absolute file paths (e.g.,
C:\Users\...) - No machine/username references
- All comments in English
- Commit message follows the format above
- Read AUDIT.md and followed the rules
The full directory-by-directory layer map lives in AUDIT.md Part 6 — read that instead of a second copy here, since a tree duplicated across two files drifts out of sync as the codebase evolves (this one already had). In short: data/, domain/, and ui/ follow Clean Architecture layering, with NIPs organized one package per protocol under domain/nipXX/ (see docs/nip-social-coverage.md for what each NIP covers).
The complete, current rule set is AUDIT.md — read it before writing code, not just this summary:
- ViewModels never import from
data.*— always go through domain (UseCase or Repository interface) - All UI state is
@Immutable, viaStateFlow<T>only — neverLiveData - All DAO calls on
Dispatchers.IO - TOR is mandatory — every network call checks
TorProxyConfig.isReadyfirst - Tests required — every UseCase, Repository method, mapper, and ViewModel needs tests
- Use cases are plain Kotlin — one
operator fun invoke()method, no Hilt
- UseCase logic — Happy path + error cases
- Repository methods — With fake/mock data sources
- Event builders — Verify tag structure matches Nostr spec
- Mappers — Test round-trip (Entity → Domain → Entity)
- Security-critical code — Verify crypto, Tor checks, key handling
Use BDD pattern:
@Test
fun `given_X_when_Y_then_Z`() {
// Arrange
val input = ...
val useCase = ...
// Act
val result = useCase(input)
// Assert
assertEquals(expected, result)
}- Unit tests:
app/src/test/java/com/umbra/app/ - Mirror package structure of class under test
- Use existing test infrastructure (FakeXxx, runTest, etc.)
Example:
app/src/test/java/com/umbra/app/
domain/usecase/
VerifyNip05UseCaseTest.kt
data/repository/
UserRepositoryImplTest.kt
Security-sensitive changes (network, crypto, key handling, logging, persistence, signing) require extra attention — see AUDIT.md Part 1 for the full rule set and its "what to flag" checklists per topic.
If you're unsure, ask before implementing. Better to discuss than to rework.
- Kotlin idioms — Prefer functional style where it improves clarity
- Naming — Use descriptive names; abbreviations only for well-known terms (e.g.,
pubkey,nip05) - Comments — Explain why, not what. Code should be self-documenting.
- Line length — Aim for ~120 characters
- Imports — Use short imports;
import Rfor resources
Do not open a public GitHub issue for security vulnerabilities.
See SECURITY.md for private disclosure instructions.
Open a GitHub discussion or issue. The maintainers are happy to help!
Thank you for contributing to Umbra. ❤️