This file records stable project decisions and collaboration conventions for future coding sessions. Keep it concise and update it when a decision changes; do not add transient CI run IDs, temporary branch names, credentials, signing material, or other secrets.
-
JDK 21 is the project toolchain. CI and F-Droid builds must use JDK 21. Local configuration must discover a compatible JDK without hard-coding a Homebrew, macOS architecture, or user-specific installation path.
-
Fastlane is the primary supported entry point for builds, checks, tests, and release artifacts. Run it through Bundler (
bundle exec fastlane ...) soGemfile.lockcontrols dependency versions. Keep the English and Russian command reference indocs/en/fastlane.mdanddocs/ru/fastlane.mdsynchronized with the lanes infastlane/Fastfile. -
Direct scripts and Gradle tasks may remain implementation details behind Fastlane lanes, but documentation and CI should normally expose the Fastlane commands.
-
Python runtime scripts use the standard library and native
ghfor GitHub access. Format with pinned Black/isort throughpython_format;python_testsandpython_checksusePYTHON.
- Pull requests must run native tests and Android JVM unit/lint/build checks. Do not require an Android emulator in GitHub Actions: hosted-runner KVM availability proved too unreliable for a trustworthy required check.
- Compare each suite against its last successful ancestor check in the same PR and base.
Failed/skipped/cancelled jobs do not advance coverage. Fall back to the full PR diff when
history is unavailable; unknown paths and shared build/CI inputs enable all suites. Require
Change scopeandPython tests and stylealongside native/Android checks when this workflow is adopted. - Every push to main runs all suites without diff/history filtering. The README CI badge is pinned to main/push; selective checks apply to initial PR runs.
- A full CI rerun disables change filtering when Change scope executes on run attempt > 1, so previously skipped suites run too. Failed-only reruns reuse scope unless that job also reruns.
- PR builds may publish debug and unsigned APK artifacts. They must never have access to release signing material and must never produce or publish a signed release APK.
- Surface downloadable APK artifacts in the GitHub Actions job summary in addition to uploading
them through
actions/upload-artifact. - Prefer extracting UI-facing decisions into small production contracts and testing those with deterministic JVM unit tests. Resource parity, navigation destination wiring, preference serialization/defaults, formatting, and state transitions should not require a device.
- Compose interaction tests may run in
app/src/testusing Robolectric with a pinned SDK and plain test Application. Inject platform operations; do not load Go JNI or real Keystore in those tests. They run through the existing Fastlane Android checks without an emulator. - Exercise real screens and ConfigStore with a test-only Keystore provider. Before asserting
service commands or their absence, drain ordered configuration writes and check both completion
and failure state;
pending == 0alone does not prove success. Recorded service intents do not establish VPN lifecycle/JNI coverage. Seedocs/reviews/test-quality.mdfor coverage gaps. - Keep device-only tests out of required GitHub CI unless the project later adopts a dependable device farm or controlled self-hosted runner. Do not reintroduce a software-emulated Android fallback.
- Release builds and signing are separate from PR CI. Release artifacts are created only through the dedicated release workflow/Fastlane lane.
- Google Play uploads use
SUPPLY_JSON_KEY_DATAwith the complete service-account JSON in both local env and GitHub Actions secrets. The tag release workflow uploads an internal draft after publishing GitHub artifacts; PR workflows must never receive this secret. - After creating and verifying a release, update the corresponding F-Droid submission/build recipe when required. The repository's F-Droid-related files are for reproducible verification, not an excuse to maintain a duplicate unused build path.
- Store F-Droid listing metadata and current store artwork under
fastlane/metadata/android. Obsolete artwork does not need an archive copy in the working tree because Git preserves history.
-
All user-visible UI text must use Android string resources and be supplied in both the default English resources and Russian resources. The app language selects UI words; number, date and other data formatting follows the system locale. Do not localize standardized data-unit symbols.
-
Traffic totals and rates share the selected unit system. IEC is the default and uses powers of 1024 with Latin symbols (
KiB,MiB,GiB,TiB,PiB); users can select SI powers of 1000 with Latin symbols (KB,MB,GB,TB,PB). -
Traffic-limit inputs remain in
MiBand must be labelled exactly with the LatinMiBsymbol. -
In Russian UI, translate “samples” as “сэмплы”, not “попытки”. Relative latency timestamps belong on a separate line in smaller text.
-
Connection diagnostics include exit IP and country, with fallback providers so one unavailable external service does not make the whole check fail. Keep presentation inputs and formatting covered by JVM tests.
-
Keep screens usable on narrow windows and with enlarged system fonts. Let actions and status rows wrap or stack; constrain app-bar titles and field labels, and make long dialog content scrollable. Verify visual changes locally without adding emulator requirements to GitHub CI.
-
Configuration writes must outlive individual screens and expose pending/failure state. Keep transfer operations across configuration changes; never put credentials or export payloads into Android saved-state bundles, and reject a lost export before opening the output stream.
PRIVACY.mdis the public privacy policy. Keep it consistent with actual app behavior and store metadata; put detailed diagnostic endpoint inventories and encryption implementation details in README instead of duplicating them in the policy.- Support emails, including sender addresses, messages and attachments, are retained until the reported problem is fixed, then deleted. This is the developer's operational practice, not an app-enforced retention timer. Uninstalling the app does not remove email or exported/shared copies.
- Keep the privacy-policy link at the bottom of Settings, targeting the public policy on
main, with localized labels and an error message when no browser can open it. - Update current documentation while preserving historical changelogs. Review reports are dated
snapshots, not proof of current coverage or external store compliance; see
docs/reviews/privacy-policy.mdfor privacy follow-ups.
ProxyVpnServiceextends Android's standardandroid.net.VpnService. It owns VPN lifecycle, creates the TUN interface, coordinates profiles/reconnects/status, and hands the TUN file descriptor to the native networking layer. Native code performs the actual proxy forwarding.- JNI calls use the generated gomobile types as a compile-time dependency. Native
Startborrows the JVM TUN descriptor only for the call, duplicates it internally, and receives the Android MTU explicitly. Keep callback exceptions inside the JVM boundary and reject stale-session callbacks. - Navigation uses a single activity/back stack. Keep route and settings-destination definitions in shared production contracts whose completeness and uniqueness can be checked by JVM tests.
- Put each new change on a branch based on the current
mainand normally deliver it as one focused GitHub pull request. After a PR is merged, start subsequent work from the updatedmaininstead of continuing on the merged branch. - Keep required CI deterministic. If a check depends on unreliable hosted-runner capabilities, replace it with JVM coverage where practical or move it to purpose-built infrastructure rather than normalizing repeated reruns.