Remove machine-writing tells from comments and docs

Emoji markers, markdown bold inside code comments, and em-dashes are
gone from source comments, string literals, scripts, Gradle files and
the markdown docs. Counted before and after:

  emoji markers   2680 -> 1 (the emoji picker's own list, which is the feature)
  em-dashes       2490 -> 9 (table placeholders and fenced code)

Two warning glyphs in user-facing strings in ChatScreens.kt are removed
as well; the sentences say the same thing without them. Two backtick
test names carried an em-dash and now carry a comma.

Nothing else changed. 1208 tests pass, counted from the result XML.
The three obfs4 interop tests are opted out with OBFS4_INTEROP_OPTOUT=1
for this run, as their own message instructs when no bridge is set up.

CONTRIBUTING gets a four-line rule so it does not grow back.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Paul Hitt
2026-09-08 18:52:12 -04:00
co-authored by Claude Fable 5.1
parent 04e06452b6
commit 4dd544083c
277 changed files with 4885 additions and 4883 deletions
+36 -36
View File
@@ -1,8 +1,8 @@
import java.io.File
import java.util.Properties
// The Android app shell. Everything below it — the cryptography and the wire
// layer — lives in :engine and :client, which are plain JVM libraries so they
// The Android app shell. Everything below it, the cryptography and the wire
// layer, lives in :engine and :client, which are plain JVM libraries so they
// can be tested without any of this.
plugins {
id("com.android.application")
@@ -22,22 +22,22 @@ android {
// claiming a lower floor would be claiming something untrue.
minSdk = 33
targetSdk = 35
// 🔴 **Bump BOTH on every published build, and never reuse a code.**
// Bump BOTH on every published build, and never reuse a code.
// Nothing installs this app but the person holding the phone, so
// `versionCode` is not bookkeeping — it is the only thing that tells an
// `versionCode` is not bookkeeping, it is the only thing that tells an
// existing install a newer build exists. `UpdateCheck` compares this
// against what the relay publishes in `downloads/android-latest.json`,
// and `scripts/release-manifest.sh` reads it back out of the built APK
// to write that file, so the two cannot drift.
//
// ⚠️ Android also refuses to install an APK whose `versionCode` is
// Android also refuses to install an APK whose `versionCode` is
// lower than the installed one, so a code that goes backwards makes a
// release uninstallable for everybody already on the higher number.
// ⚠️ **The code is one ahead of the name, deliberately.** 0.11 was built
// The code is one ahead of the name, deliberately. 0.11 was built
// on 2026-09-07 and never published; the build that followed it went out
// as versionCode 12 before the name was settled on. Going back to code
// 11 would make the release uninstallable for anyone already on 12 — see
// the paragraph above — so the code keeps climbing and the NAME is what
// 11 would make the release uninstallable for anyone already on 12, see
// the paragraph above, so the code keeps climbing and the NAME is what
// closes the 0.10 -> 0.12 gap. They are allowed to differ: the code is
// ordering, the name is what a person reads.
versionCode = 13
@@ -61,13 +61,13 @@ android {
// Read from a properties file outside the repository, so the keystore and
// its password are never in git and a checkout without them still builds
// (unsigned, which is exactly what an unsigned build should be).
// 🔴 **This app's OWN key, not the one every Flamenet Android app shares.**
// This app's OWN key, not the one every Flamenet Android app shares.
// `.flamenet-android-signing.properties` was made for Flamenet Files, and
// because the path is app-agnostic this app silently inherited it — every
// because the path is app-agnostic this app silently inherited it, every
// release before 2026-08-25 went out as `CN=Flamenet Files`. Nobody chose
// that for a messenger; it fell out of the layout.
//
// It matters because **an Android signing key is permanent**: once a device
// It matters because an Android signing key is permanent: once a device
// installs a build signed with one key, it REFUSES every later build signed
// with another. Sharing one key across products also means one compromised
// keystore takes out both.
@@ -76,8 +76,8 @@ android {
// that only has the old setup still produces a signed build rather than
// failing confusingly.
//
// ⚠️ Both files live outside the repo and hold a password in the clear. They
// are never in git, and a checkout without them still builds — unsigned,
// Both files live outside the repo and hold a password in the clear. They
// are never in git, and a checkout without them still builds, unsigned,
// which is exactly what an unsigned build should be.
val signingProperties = Properties()
val home = File(System.getProperty("user.home"))
@@ -100,8 +100,8 @@ android {
buildTypes {
release {
// ⚠️ **WebRTC ships native libraries for four ABIs and they
// dominate the APK** — about 41 MB uncompressed, of which x86 and
// **WebRTC ships native libraries for four ABIs and they
// dominate the APK**, about 41 MB uncompressed, of which x86 and
// x86_64 are ~24 MB and exist only for emulators. Real phones are
// arm64-v8a, with armeabi-v7a for old ones.
//
@@ -112,15 +112,15 @@ android {
// Debug keeps all four, because that is what runs on an emulator.
ndk { abiFilters += setOf("arm64-v8a", "armeabi-v7a") }
isMinifyEnabled = true
// 🔴 **R8 accepts a missing rules file in silence.** This one was
// R8 accepts a missing rules file in silence. This one was
// referenced here and did not exist, so the release build shipped
// with the platform defaults alone — and answering a call aborted
// with the platform defaults alone, and answering a call aborted
// the process, because WebRTC's native JNI_OnLoad resolves its Java
// classes by name and R8 had renamed them. Nothing failed, nothing
// warned, and no debug test could see it. Fail the build instead.
file("proguard-rules.pro").takeIf { it.isFile }
?: throw GradleException(
"app/proguard-rules.pro is missing. The release build needs it — " +
"app/proguard-rules.pro is missing. The release build needs it, " +
"without the org.webrtc keep rules, answering a call aborts the " +
"process inside JNI_OnLoad with no Kotlin stack trace.")
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
@@ -132,13 +132,13 @@ android {
resources.excludes += setOf("META-INF/*.kotlin_module", "META-INF/DEPENDENCIES")
}
// 🔴 **So screens can be rendered without an emulator.** Robolectric in
// So screens can be rendered without an emulator. Robolectric in
// native-graphics mode draws real Compose pixels on the JVM, which is what
// makes `./gradlew :app:test` able to produce a PNG of a screen. The
// emulator died four times in one afternoon under memory pressure, and an
// 8 GB AVD to look at a layout is a poor trade.
//
// ⚠️ `isIncludeAndroidResources` is not optional here — without it the
// `isIncludeAndroidResources` is not optional here, without it the
// theme cannot resolve and every render throws on a missing resource,
// which reads as a Compose problem rather than a configuration one.
testOptions {
@@ -148,23 +148,23 @@ android {
all {
it.systemProperty("robolectric.graphicsMode", "NATIVE")
// 🔴 **The screenshot tests are debug-variant tests, and
// **The screenshot tests are debug-variant tests, and
// `./gradlew test` runs BOTH variants.**
//
// They launch a `ComponentActivity`, which is declared by
// `androidx.compose.ui:ui-test-manifest` — a `debugImplementation`
// `androidx.compose.ui:ui-test-manifest`, a `debugImplementation`
// dependency, so the release variant's merged manifest has no
// such activity and all fifteen die with "Unable to resolve
// activity for Intent".
//
// ⚠️ The tempting fix is to add that dependency to release as
// The tempting fix is to add that dependency to release as
// well. Do not: it exists to inject a test-only activity into
// the manifest, and putting it on `releaseImplementation` would
// ship that into the APK. The tests belong to the variant that
// can run them.
//
// ⚠️ Excluded by name rather than by disabling
// `testReleaseUnitTest` wholesale — everything else in the
// Excluded by name rather than by disabling
// `testReleaseUnitTest` wholesale, everything else in the
// suite still runs in both variants, so a genuine
// release-variant difference would still be caught.
if (it.name.contains("Release")) {
@@ -178,7 +178,7 @@ android {
dependencies {
implementation(project(":engine"))
implementation(project(":client"))
// ⚠️ A direct dependency rather than leaning on `:client` to re-export it.
// A direct dependency rather than leaning on `:client` to re-export it.
// The settings screen reads bridge lines and the handshake counter itself,
// so the app genuinely uses these types; borrowing them through another
// module's classpath would make `:client` unable to change its own
@@ -193,12 +193,12 @@ dependencies {
implementation("androidx.activity:activity-compose:1.9.3")
// The app lock. `BiometricPrompt` covers fingerprint, face AND the device
// credential, so a phone with a PIN and no biometric hardware can still use
// it — matching iOS, which asks for `.deviceOwnerAuthentication` rather
// it, matching iOS, which asks for `.deviceOwnerAuthentication` rather
// than biometrics specifically for the same reason.
implementation("androidx.biometric:biometric:1.1.0")
// 🔴 A version FLOOR, not a feature bump. biometric:1.1.0 drags in a
// A version FLOOR, not a feature bump. biometric:1.1.0 drags in a
// 2019-era androidx.fragment whose FragmentActivity still enforces the
// 16-bit requestCode check — and Compose's ActivityResultRegistry mints
// 16-bit requestCode check, and Compose's ActivityResultRegistry mints
// codes far above that, so tapping ANY picker (post a story, change the
// avatar) threw "Can only use lower 16 bits for requestCode" and closed
// the app. Fragment 1.3+ removed the check; pin a current one explicitly.
@@ -213,18 +213,18 @@ dependencies {
// The one third-party dependency in this app, and it is here because the
// alternative is implementing ICE, DTLS-SRTP and an audio pipeline, which
// is not a thing to hand-roll. `webrtc-sdk` is the maintained build of
// Google's own libwebrtc — the upstream project stopped publishing usable
// Google's own libwebrtc, the upstream project stopped publishing usable
// Android artifacts, and every Android client of consequence uses either
// this or its own build of the same source.
//
// ⚠️ It is a large native blob: roughly 15-20 MB of the APK, and not
// It is a large native blob: roughly 15-20 MB of the APK, and not
// something that can be audited line by line. Worth stating rather than
// absorbing quietly, because everything else in this app deliberately is
// auditable.
implementation("io.github.webrtc-sdk:android:125.6422.07")
// Reads the QR on somebody's Address screen. Decode only, pure Java, no
// services — the camera frames come from the WebRTC capture stack above,
// services, the camera frames come from the WebRTC capture stack above,
// so this is the algorithm (finder patterns + Reed-Solomon), not a second
// camera framework. The encoder half stays our own (`engine`'s QrCode).
implementation("com.google.zxing:core:3.5.3")
@@ -237,7 +237,7 @@ dependencies {
// lives in :app and needs no device belongs here, not in androidTest.
testImplementation(kotlin("test"))
// 🔴 The Android framework's `org.json` is STUBBED in unit tests — every
// The Android framework's `org.json` is STUBBED in unit tests, every
// method returns null or throws. A test that builds a JSONObject fails with
// a NullPointerException that says nothing about the cause. The engine and
// client modules already pull the real implementation in for exactly this;
@@ -245,14 +245,14 @@ dependencies {
testImplementation("org.json:json:20240303")
testImplementation("junit:junit:4.13.2")
// Screenshot tests that need no device. ⚠️ Test-only dependencies: none of
// Screenshot tests that need no device. Test-only dependencies: none of
// this reaches the APK. Robolectric is the standard JVM Android runtime and
// is what AndroidX's own screenshot tooling is built on — the alternative
// is what AndroidX's own screenshot tooling is built on, the alternative
// to adopting it is not writing our own, it is keeping an 8 GB emulator
// alive to look at a layout.
testImplementation("org.robolectric:robolectric:4.14.1")
// Drives Session against a real HTTP server rather than a hand-written
// fake, the same way :client's tests do — a fake agrees with the client
// fake, the same way :client's tests do, a fake agrees with the client
// about every field name, including the ones it gets wrong.
testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
testImplementation("androidx.test:core-ktx:1.6.1")