This file is the single source of project context for AI agents. See also
docs/domain/ for product-specific behavior rules and common/docs/ for
business logic (account types, subscriptions, feature access).
Blokada 6 is a cross-platform mobile ad blocker and privacy app with cloud-based blocking and WireGuard VPN integration. The codebase builds two distinct apps:
- Blokada 6 – Full VPN and cloud-based blocking for individual users.
- Blokada Family – Simplified family/parental protection.
Both share a Flutter module (common/) embedded into native Android (Kotlin)
and iOS (Swift) shells. Platform channels are generated by Pigeon; state
management uses MobX; payments use Adapty.
common/– Shared Flutter module (UI + business logic)lib/common.dart– Public package surface (entrypoints and shared types)lib/src/core/– Core utilities, dependency injection, persistencelib/src/features/– Feature-first modules (<feature>/{domain,ui}), including tiered features likepluslib/src/app_variants/– Variant-specific overrides (v6,family)lib/src/shared/– Shared UI, navigation, automation idslib/src/platform/– Platform channel interfaces (generated by Pigeon)lib/src/util/– Shared utilities
android/– Gradle project (flavors:family,six; build types:debug,release)ios/– Xcode project (schemes:FamilyProd,FamilyDev,Prod,Dev,Mocked)scripts/– Build and utility scriptsmetadata/– App store metadata for all platforms
Iterative process:
- Make code changes
- Regenerate interfaces (if modified
libgen/*.dart):make regen-ios(iOS only) ormake regen(all platforms) - Check linting:
fvm flutter analyze– fix issues immediately - Ensure tests pass:
make test– iterate 1-4 until all tests pass - Test on device: Manual verification on physical device
git submodule update --init --recursiveIn a fresh worktree, the command above re-fetches every submodule from
origin (~20 s and ~340 MB for this repo, dominated by landing-github-pages).
Faster alternative: copy the already-populated submodule directories from
your main checkout. On APFS (macOS), cp -cR is near-instant and uses
copy-on-write to avoid duplicating disk:
cp -cR /path/to/main-checkout/deps/* deps/Use plain cp -R on non-APFS filesystems. Either path leaves the worktree
buildable; pick whichever fits your workflow.
# Android
make build-android-family-debug
make build-android-six-debug
make build-android-family-debug-quick # if common lib unchanged
make build-android-six-debug-quick
# iOS
make build-ios-six-debug# Android
make build-android # all
make build-android-family
make build-android-six
# iOS
make build-ios # all
make build-ios-family
make build-ios-sixmake install-family # release
make install-family-debug # debug
make install-six # release
make install-six-debug # debug
make uninstall # remove all# iOS: build, install and run with console output (BLOCKING – run as background task)
make -C ios run-six [DEVICE_NAME="device name"] [CONFIG="Release|Debug"]
make -C ios run-family [DEVICE_NAME="device name"] [CONFIG="Release|Debug"]
# Default: CONFIG=Release (required for iOS 14+ to launch without Xcode/Flutter tooling)
# Android
make install-family-debug # or make install-six-debug# iOS Mocked scheme on an auto-cloned per-worktree simulator (NetxServiceMock
# substitutes the real NetworkExtension — no DNS/VPN entitlement needed).
# Each worktree gets its own sim, so parallel checkouts don't contend.
make -C ios run-six-mocked # or run-family-mocked
make -C ios appium-install-six-mocked # install only, prints UDID for Appium
# Pre-warm onboarding/login state once, reuse across clones:
make -C ios run-six-mocked SIM_TEMPLATE="warm template"
# Status / cleanup
make -C ios sim-status sim-clean sim-gcUse for UX/notifications/state work; use run-six/run-family on a real
device when you need actual DNS/VPN behaviour. See ios/SIMULATOR.md for
overrides, warm-template setup, and caveats.
make test # safe default: refreshes pub, Pigeon Dart outputs via gen-android,
# and build_runner before running the Flutter test suite
make test-local # fast local path: fvm flutter test --no-pub (assumes deps available)
fvm flutter analyze # lintingmake regen # generate all platform interfaces and mocks
make gen # generate just platform interfaces
make regen-android # clean and regenerate for Android
make regen-ios # clean and regenerate for iOSVersions are YY.M.BUILD and are computed by CI, not chosen by hand. See
docs/release-pipeline.md.
# CODE is the raw build number; VERSION_CODE_OFFSET (669000000) is added for you
make version NAME=26.7.1042 CODE=1042
# Or via env vars: BLOKADA_VERSION_NAME / BLOKADA_VERSION_CODENever pass a CODE far above the current build counter: store codes must increase monotonically, so an inflated value permanently strands every later release below it.
If platform bindings changed and a build or install step starts failing, rerun the matching regeneration target before retrying:
make regen-iosfor iOS build flowsmake regen-androidfor Android build flows
This commonly fixes generated-signature mismatches after API changes.
- Fastlane for automation and deployment. Native dependencies use Swift Package
Manager (Firebase, Factory, CodeScanner); the Flutter module is consumed as a
local Swift package (
common/build/ios-spm/FlutterNativeIntegration) frommake -C common build-ios. App schemes flip Debug/Release via aflutter_integration.sh prebuildpre-action. The host has no CocoaPods; the module build still runs a vestigial pods pass (flutter/flutter#184590) served by the stub podspec incommon/vendor/adapty_flutter. - iOS 14+ Flutter Debug Limitation: Flutter debug mode apps cannot launch
directly on device without Xcode/Flutter tooling. The
make -C ios runtarget defaults to Release mode to bypass this.
If you get provisioning profile errors when selecting Mac as target:
- Register Mac device (requires OTP – must be run manually):
cd ios fastlane run register_device name:"Mac Name" udid:"$(system_profiler SPHardwareDataType | grep 'Provisioning UDID' | awk '{print $3}')" team_id:"HQH5AFGB68"
- Regenerate profiles:
make fastlane-match
English source strings live in the translate submodule (deps/translate), keyed by the English phrase itself; new strings are authored there and translated via Crowdin, never extracted from the app. Everything under common/assets/translations/ (and the iOS/Android equivalents) is generated by make translate, so never hand-edit it.
GitHub Actions for CI. See make ci-* targets for CI-specific builds.
Consult docs/domain/ whenever a domain topic needs better understanding —
exploring, planning, reviewing, or changing onboarding, startup, protection,
subscriptions, home-state, browser-extension flows, or flavor-specific behavior.
Start with:
docs/domain/domain-knowledge.md
Then open only the area relevant to the task:
docs/domain/shared/for behavior shared across flavorsdocs/domain/v6/for Blokada 6-specific behaviordocs/domain/family/for Family-specific behavior
Keep AGENTS.md as a pointer only. Durable product rules belong in
docs/domain/, not duplicated here.
All code components should carry documentation that explains why the component exists, not only what it does. Apply the same rule to any function or method whose purpose is not obvious from its name and local context.
When adding new code:
- add or update the relevant doc comment so it captures the component's reason for existing
- include "why" documentation for non-obvious functions and methods
When modifying existing code:
- bring the touched code up to this standard as part of the same change
- update stale comments so they still explain why the code has to exist
Treat this as a required maintenance rule for all newly added code and all code that is modified.
- The WebdriverIO + Appium harness lives under
automation/appium/wdio/. - Device/WebDriverAgent setup steps are documented in
automation/appium/README.md(UI testing toggle, installing Appium + xcuitest driver, opening WDA in Xcode, setting the development team, and running the initial install). - Run the launch smoke test with
make appium-test. Optional overrides:IOS_DEVICE_NAME=<name>to select a connected device by name.IOS_UDID=<udid>to target a specific device directly.
- The test spawns the global Appium CLI; when executed from automations or assistants, be prepared to request elevated permission so the process can access the device and CoreSimulator services.
- The harness takes roughly a minute end-to-end. If
make appium-testis launched through the CLI tools, bump the command timeout (e.g.timeout_ms: 180000) so the wrapper does not abort at the default 90 s. - Test artifacts (screenshots and UI XML dumps) are written to
automation/appium/output/after each run:wdio-launch-foreground.png,wdio-after-power.png, andwdio-launch-foreground.xml. - The reusable flows live under
automation/appium/wdio/src/flows/and the primary smoke spec isautomation/appium/wdio/src/specs/smoke/dns-onboarding.spec.ts. Assistants can invoke these specs to validate behaviour on a real device (notification handling, DNS provisioning, screenshots, etc.) before or after making code changes.