The skill's detector scored the README, PARITY.md and the extracted
comment prose. Fixed what it flagged: hollow intensifiers, "in order
to", "additionally", "the whole point", bold sentences in docs, and
bold that had wrapped across comment lines. Comment prose scores a
maximum of 4 out of 100 across the repo.
One sweep rule mangled four comments ("which is the entire point of X"
lost its clause); repaired, and the rule now keeps the clause.
Comments and strings only. 1208 tests pass, counted from the XML.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3.1 KiB
Contributing
The Android client for Flamenet Messenger, in Kotlin and Compose. Three modules:
engine (the encryption primitives), client (protocol and transport), app
(UI). Feature parity with iOS is tracked in PARITY.md, read it
before assuming something is missing by accident.
Security bugs do not go in an issue
Email security@flamenetmessenger.com. See SECURITY.md.
Building
JDK 21 and the Android SDK. On a machine with no default Java, which is the usual state of a Mac. Gradle reports "Unable to locate a Java Runtime" rather than anything about JDKs, so set it explicitly:
export JAVA_HOME=/opt/homebrew/opt/openjdk@21
export ANDROID_HOME=$HOME/Library/Android/sdk
./gradlew :engine:test :client:test :app:testDebugUnitTest
Those three suites are the merge gate.
JVM crypto tests prove nothing about Android. The JVM's JCA and Android's
Conscrypt are different providers with different opinions. X25519/X448 need DER
key specs and API 33 on device, and code that passes :engine:test on the JVM
can throw on a handset. Anything touching key agreement or signatures needs an
instrumented test, which is why interop/ exists.
Instrumented tests are not in the gate: they need an emulator and a local relay.
What a change needs
- Touching the wire? iOS and the relay move with you. A field this client required and the relay never sent broke groups completely while every unit test stayed green. Diff the wire types first.
- Touching state that arrives incrementally: avatars, rosters, device lists, merge, never replace. Absent from a fetch means "not loaded yet", not "deleted".
- Touching WebRTC callbacks? Never let an exception escape one. It does not
surface as a caught error, it
SIGABRTs the process. Calls have crashed this app twice that way. - Adding a permission? Declare it and verify on device. A missing
ACCESS_NETWORK_STATEpresented as a dead button, not as a permission error. - Writing KDoc that mentions a path like
/e2e/*? Kotlin does not nest block comments, so*/inside one closes it and the compiler reports "Unclosed comment" at the end of the file, hundreds of lines from the cause.
Releases
The signed release APK is built with a keystore that is not in this repository and never will be, it is public. Signing keys are permanent for the life of an installed app: a key change means every existing user must uninstall and lose their local history, so the certificate identity is not something to fix later.
House rules
- Fail closed. Unverifiable signatures, unknown versions and unpinned identities are refusals, and the user is told which.
- Never log message contents, keys, or tokens.
- No secrets, keystores, or passphrases in the repository.
- Match the surrounding style. Comments explain why a line exists, usually, which attack or which platform quirk it accounts for.
Comments and prose
Plain text. No emoji or symbol markers, no markdown bold inside code comments, no em-dashes. Say what the code does and why, briefly; the history of how it got that way belongs in the commit message, not in the file.