# AGENTS.md — DuckDuckGo Android Browser DuckDuckGo Android is a privacy-focused browser built as a large multi-module Gradle project, providing built-in search, tracker blocking, HTTPS enforcement and other privacy features. **Versions** (SDK levels, Kotlin, Gradle, libraries) live in the build files — don't restate them here: `min_sdk` / `target_sdk` / `compile_sdk` in `build.gradle`, `version.kotlin` in `versions.properties`, and the Gradle version in `gradle/wrapper/gradle-wrapper.properties`. **Build:** AGP via refreshVersions. DI is **Metro** (KSP generators in `:codegen-ksp`; see `.claude/docs/architecture.md`). **Toolchain:** Kotlin JVM target 17; building requires **JDK 21** (Metro compiler plugin). Notable libraries: Room, Retrofit/OkHttp/Moshi, Coroutines, Jetpack Compose (selective), WorkManager, `logcat` (Square), KSP for annotation processing. Versions are in `versions.properties` and the module `build.gradle` files. --- ## Privacy invariants for anything leaving the device This is a privacy browser; these hold for every outbound value — pixels, wide events, logs, crash reports, debug output — not just telemetry you are deliberately designing. - **No PII.** Never emails, names, account IDs, usernames or phone numbers, in a name or a value. - **No URLs, domains or page titles.** Breakage reports are the one controlled exception, with explicit user consent. - **No correlation IDs.** No session IDs, GUIDs or exact timestamps — anything that links events to one user session. - **Bucket numeric values.** Exact durations, byte counts and item counts fingerprint users; send ranges. - **Bounded enums over free-form strings.** High-cardinality unbounded strings are both a privacy risk and unanalysable. Adding or changing a pixel or the pixel registry also requires privacy triage — see `.claude/docs/pixels.md`. --- ## Read these when the situation applies These files are not in context. Read the whole file before doing the work it covers — don't rely on what you remember of it. | Read | When | |---|---| | `.claude/docs/architecture.md` | **planning or implementing** any new code, refactoring, or module changes — `-api`/`-impl` structure, DI scopes, navigation, coroutine job patterns | | `.claude/docs/android-design-system.md` | **planning or implementing** any UI — which ADS component to use, colors, spacing; raw Android/Material widgets are lint-blocked | | `.claude/docs/contributions.md` | **planning** a change that touches a `-api` surface or spans multiple modules — it needs an approved API Proposal and/or Tech Design, and that has to be raised before implementation, not at PR time. Also read it when naming a branch, writing a commit, or opening a PR | | `.claude/docs/lateinit-hazards.md` | writing or reviewing any `lateinit var`, especially `@Inject lateinit var` in a View | | `.claude/docs/plugin-system.md` | declaring a plugin point or contributing a plugin (`PluginPoint` / `ActivePluginPoint`) | | `.claude/docs/native-input.md` | **changing** the native input (`NativeInputModeWidget`, its plugins and footers, `NativeInputManager`, the contextual sheet or the edit screen) — adding a control, state-store rules, host gotchas, and the refactor still in flight | | `.claude/docs/pixels.md` | adding or changing pixel telemetry — including whether a pixel or a wide event is the right instrument | | `.claude/docs/di-scopes.md` | an injection fails at runtime ("Could not find the injector"), or you're deciding which scope to pass to `@InjectWith` — has the component/subcomponent diagram and the `injectorFactoryMap` lookups | | `.claude/docs/navigation.md` | adding a screen or navigating to one — `ActivityParams`, `@ContributeToActivityStarter`, deeplinks, and which `GlobalActivityStarter` overload to use | | `.claude/docs/url-classification.md` | routing typed input to navigation vs search — use `QueryUrlPredictor`, not `UriString.isWebUrl()` | | `.claude/docs/icons.md` | the change needs an icon the project doesn't have yet — it must be fetched from the internal Icons repository, never invented | | `.claude/docs/translations.md` | adding or changing any user-facing string — `donottranslate.xml` vs `strings-.xml`, placeholder/`instruction` rules, how to change or remove already-translated copy | --- ## Build & Test Commands ```bash # Unit tests (all modules) ./gradlew jvm_tests # Unit tests for a single module ./gradlew :my-feature-impl:testDebugUnitTest # Code quality (spotless + lint + unit tests) ./gradlew jvm_checks # Lint only ./gradlew lint_check # Code formatting check / fix ./gradlew spotlessCheck ./gradlew spotlessApply # Install app ./gradlew installInternalRelease # internal build (more testing features) ./gradlew installPlayRelease # play store build ``` `jvm_tests` and `jvm_checks` resolve to `testPlayDebugUnitTest` in `:app` and `testDebugUnitTest` in library modules. To run a single test class, use `--tests`: ```bash ./gradlew :my-feature-impl:testDebugUnitTest --tests "com.duckduckgo.my.feature.RealFooTest" ``` ### Build Variants | Dimension | Flavors | |---|---| | store | `internal`, `fdroid`, `play` | | Build types | `debug`, `release`, `upload` | ### Proprietary Fonts The app uses a proprietary DuckSans font from a private GitHub Packages repository. The build conditionally swaps between the proprietary `ddg-proprietary-fonts` AAR and a local `:fonts` fallback module (empty `` stubs) based on credential availability. See `build.gradle` for the credential detection logic and `android-design-system/fonts/readme.md` for details. --- ## Code Formatting - **Spotless** with ktlint for Kotlin; Google Java Format in AOSP style for Java - Max line length: 150 characters - Ratchet from `origin/develop` — only changed code is enforced --- ## Code Comments Comments explain the **why** (intent, assumptions, non-obvious decisions), not the **what** the code already shows. Default to no comment: prefer self-documenting code (clear names, small functions), and add one only where code alone can't carry the reasoning, and only if it still answers *"why was this done this way?"* for someone reading it two years from now. `-api` module declarations may carry KDoc describing the contract — that's documentation for consumers, not narration. Never add: - **narration** of trivial code - **conversational / temporal residue** - **process / plan references** ```kotlin count++ // increment the counter -> narration of trivial code val user = repo.load() // fixes the bug from the previous task -> temporal residue val state = flow // changed from LiveData -> temporal residue cache.clear() // step 3 of the plan -> process / plan reference retryCount = 3 // per the Asana task -> process / plan reference ``` When reviewing a diff, flag any added comment that doesn't follow these rules.