Second pass with the avoid-ai-writing detector
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>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
4dd544083c
commit
8a7f40fb13
@@ -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.
|
||||
//
|
||||
@@ -148,8 +148,8 @@ android {
|
||||
all {
|
||||
it.systemProperty("robolectric.graphicsMode", "NATIVE")
|
||||
|
||||
// **The screenshot tests are debug-variant tests, and
|
||||
// `./gradlew test` runs BOTH variants.**
|
||||
// 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`
|
||||
@@ -180,7 +180,7 @@ dependencies {
|
||||
implementation(project(":client"))
|
||||
// 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
|
||||
// so the app uses these types; borrowing them through another
|
||||
// module's classpath would make `:client` unable to change its own
|
||||
// dependencies without breaking a screen that never mentioned it.
|
||||
implementation(project(":obfs4"))
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.junit.runner.RunWith
|
||||
*
|
||||
* Run with `adb shell am instrument`, not `connectedAndroidTest`, which
|
||||
* uninstalls the app afterwards and takes the vault, and therefore the account
|
||||
* with it. The whole point is that the account survives between steps.
|
||||
* with it. The point is that the account survives between steps.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CrossPlatformTest {
|
||||
|
||||
@@ -31,8 +31,8 @@ import androidx.compose.ui.unit.sp
|
||||
* platforms disagreeing about a security guarantee is how one of them ends up
|
||||
* being the one that leaks.
|
||||
*
|
||||
* **What this defends against, stated plainly, because the alternative is
|
||||
* implying more:** somebody who picks up your unlocked phone. That is the
|
||||
* What this defends against, stated plainly, because the alternative is
|
||||
* implying more: somebody who picks up your unlocked phone. That is the
|
||||
* threat people actually have, and it is worth something.
|
||||
*
|
||||
* What it does not defend against: anyone with the device and the tools to
|
||||
@@ -168,8 +168,8 @@ class AppLock(private val context: Context) {
|
||||
/**
|
||||
* When the next attempt is allowed, or null if one is allowed now.
|
||||
*
|
||||
* **Wall-clock, and somebody who can change the device clock can skip
|
||||
* the wait.** Said rather than hidden: this slows a person holding the
|
||||
* Wall-clock, and somebody who can change the device clock can skip
|
||||
* the wait. Said rather than hidden: this slows a person holding the
|
||||
* phone, and it is not a defence against somebody with the device and the
|
||||
* patience to work at it. What actually protects the messages is the
|
||||
* keystore, exactly as the note at the top of this file says.
|
||||
@@ -186,7 +186,7 @@ class AppLock(private val context: Context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Only a genuine wrong answer counts. Dismissing the prompt is not an
|
||||
* Only a wrong answer counts. Dismissing the prompt is not an
|
||||
* attempt, counting a cancel would let the owner back themselves into a
|
||||
* wait by changing their mind twice. See the error handling in
|
||||
* [authenticate], which is where that distinction is actually made.
|
||||
|
||||
@@ -238,8 +238,8 @@ class CallManager(
|
||||
}
|
||||
override fun onIceCandidatesRemoved(candidates: Array<out IceCandidate>?) = Unit
|
||||
override fun onSignalingChange(state: PeerConnection.SignalingState?) = Unit
|
||||
// **Nothing may throw out of here, and nothing may call back
|
||||
// into WebRTC from here.** This runs on WebRTC's signaling thread,
|
||||
// Nothing may throw out of here, and nothing may call back
|
||||
// into WebRTC from here. This runs on WebRTC's signaling thread,
|
||||
// and its JNI asserts RTC_CHECK(!env->ExceptionCheck()) on the way
|
||||
// out, so ANY escaping Kotlin exception aborts the process:
|
||||
// SIGABRT, no AndroidRuntime trace, nothing to catch.
|
||||
|
||||
@@ -649,8 +649,8 @@ fun ColumnScope.NotificationsScreen(
|
||||
return
|
||||
}
|
||||
|
||||
// **Rows on the kit's 72dp pitch with an avatar, like every other list
|
||||
// in the app.** This was a lazy list of two stacked text lines with an 8dp
|
||||
// Rows on the kit's 72dp pitch with an avatar, like every other list
|
||||
// in the app. This was a lazy list of two stacked text lines with an 8dp
|
||||
// dot beside the unread ones, a different rhythm, a different type scale
|
||||
// and a different unread marker from the contact list two taps away. One
|
||||
// app, one row grammar.
|
||||
@@ -2008,8 +2008,8 @@ fun ColumnScope.ChatScreen(
|
||||
)
|
||||
}
|
||||
DropdownMenuItem(
|
||||
// **Said "Unsend" on your own messages and did
|
||||
// nothing of the kind (android#18).** `deleteMessage`
|
||||
// Said "Unsend" on your own messages and did
|
||||
// nothing of the kind (android#18). `deleteMessage`
|
||||
// reaches `vault.deleteLine` and stops there, no
|
||||
// envelope leaves the phone, and the recipient's copy
|
||||
// stays exactly where it is. Somebody who sent to the
|
||||
@@ -2367,7 +2367,7 @@ private fun ColumnScope.VoiceComposer(
|
||||
|
||||
// Only the ACTIVE recording. Starting one is the mic icon in the
|
||||
// composer row now; this is the timer and the two decisions, which the kit
|
||||
// has no equivalent for and which genuinely need a line of their own.
|
||||
// has no equivalent for and which need a line of their own.
|
||||
val seconds = chat.recordingSeconds ?: return
|
||||
run {
|
||||
Row(
|
||||
@@ -2467,8 +2467,8 @@ private fun Bubble(
|
||||
Text("Forwarded", fontSize = 10.sp, color = Palette.SecondaryText,
|
||||
modifier = Modifier.testTag("forwarded-marker"))
|
||||
}
|
||||
// **A message nobody received must not look like one that was
|
||||
// received.** That sameness is the whole of what makes a ghost
|
||||
// A message nobody received must not look like one that was
|
||||
// received. That sameness is the whole of what makes a ghost
|
||||
// message possible; every specific cause behind one is downstream
|
||||
// of it. Only `false` marks, null means nothing was claimed, which
|
||||
// is every received message and everything stored before the field
|
||||
@@ -2964,8 +2964,8 @@ private fun ColumnScope.GroupMembers(
|
||||
) {
|
||||
Text("${group.memberIds.size} members", style = MaterialTheme.typography.bodySmall)
|
||||
Row {
|
||||
// **The only way a group gets a name at all after the first
|
||||
// one.** Names used to be fixed at creation and served by the
|
||||
// The only way a group gets a name at all after the first
|
||||
// one. Names used to be fixed at creation and served by the
|
||||
// relay; the relay has stopped holding them, so every group made
|
||||
// before that change reads as a placeholder on every phone and
|
||||
// there is nobody left to ask what it was called.
|
||||
@@ -3250,8 +3250,8 @@ fun ColumnScope.AddContactForm(
|
||||
return
|
||||
}
|
||||
|
||||
// **A scanned code is an ADDRESS, and this form treated everything
|
||||
// as a username.** The scanner already prefills here, so somebody who
|
||||
// A scanned code is an ADDRESS, and this form treated everything
|
||||
// as a username. The scanner already prefills here, so somebody who
|
||||
// scanned a card from another relay got their address looked up as a
|
||||
// handle on ours, was told no such person exists, and had no other way
|
||||
// in. One field either way, what it is, is a fact about the text, not
|
||||
@@ -4124,8 +4124,8 @@ private fun EditMessageSheet(
|
||||
/**
|
||||
* Pass a message's words on to another conversation (§6.16).
|
||||
*
|
||||
* **The list is people and groups, and the message travels without its
|
||||
* author.** What is sent is the text with `fwd` set, no original thread, no
|
||||
* The list is people and groups, and the message travels without its
|
||||
* author. What is sent is the text with `fwd` set, no original thread, no
|
||||
* name, no quote. A forward that named its source would republish a private
|
||||
* message's author to somebody who was never in that conversation, which is
|
||||
* the one thing §6.16 exists to prevent.
|
||||
|
||||
@@ -13,8 +13,8 @@ import android.content.Context
|
||||
* Security, and somebody switching them off to disappear would have been wrong.
|
||||
* They were renamed on 2026-09-06; these are named that way from the start.
|
||||
*
|
||||
* **There is no last-seen setting here, because Android does not show last
|
||||
* seen at all.** iOS does. That is a missing feature rather than a missing
|
||||
* There is no last-seen setting here, because Android does not show last
|
||||
* seen at all. iOS does. That is a missing feature rather than a missing
|
||||
* toggle, and adding a switch for something the app never draws would be the
|
||||
* exact decoration the parity scrub exists to catch.
|
||||
*/
|
||||
|
||||
@@ -118,8 +118,8 @@ private fun ColumnScope.FilesSetUp(
|
||||
var passphrase by remember { mutableStateOf("") }
|
||||
var again by remember { mutableStateOf("") }
|
||||
|
||||
// **"A passphrase of their own" told people to invent a second secret,
|
||||
// and iOS never did.** iOS says "your passphrase" and takes whatever is
|
||||
// "A passphrase of their own" told people to invent a second secret,
|
||||
// and iOS never did. iOS says "your passphrase" and takes whatever is
|
||||
// typed, so a person naturally uses their Flamenet one and it simply
|
||||
// works. Android's copy sent them off to make up something else, then
|
||||
// asked for it again weeks later, and forgetting it is the one-way door
|
||||
@@ -425,8 +425,8 @@ private fun bytes(count: Int): String = when {
|
||||
// ---- Sheets --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* **The choice between copying and linking is made here, with both consequences
|
||||
* written out**, rather than by a default somebody discovers later. They are not
|
||||
* The choice between copying and linking is made here, with both consequences
|
||||
* written out, rather than by a default somebody discovers later. They are not
|
||||
* two ways of doing one thing: one produces ciphertext this app owns, the other
|
||||
* produces a pointer at a file it does not.
|
||||
*/
|
||||
@@ -641,8 +641,8 @@ fun ColumnScope.PickFileToSendSheet(
|
||||
fontSize = 12.sp, color = Palette.SecondaryText,
|
||||
)
|
||||
Spacer(Modifier.height(Space.xs))
|
||||
// **The photo button strips location; this one does not
|
||||
// (android#17).** A picture attached from the picker is re-encoded
|
||||
// The photo button strips location; this one does not
|
||||
// (android#17). A picture attached from the picker is re-encoded
|
||||
// through ImagePrep and video through MediaScrub, so no metadata
|
||||
// container survives. `Session.sendFile` passes the vault's bytes
|
||||
// straight through, EXIF and all, correct for "send this file",
|
||||
|
||||
@@ -87,8 +87,8 @@ object ImagePrep {
|
||||
/**
|
||||
* Decode bytes that came from somebody else, bounded.
|
||||
*
|
||||
* **The render paths were calling `decodeByteArray` with no options at
|
||||
* all.** Sending was careful, bounds first, then a sampled decode, but
|
||||
* The render paths were calling `decodeByteArray` with no options at
|
||||
* all. Sending was careful, bounds first, then a sampled decode, but
|
||||
* the two places that draw an image RECEIVED from a peer decoded at full
|
||||
* size. Inbound attachments are capped at 12 MiB, and 12 MiB of cleverly
|
||||
* chosen JPEG is a few hundred megabytes of ARGB_8888 once expanded. A
|
||||
@@ -97,7 +97,7 @@ object ImagePrep {
|
||||
* way for the victim to get past it.
|
||||
*
|
||||
* Bounds are read first so an enormous picture is never fully decoded
|
||||
* just to discover how big it is. That is the whole point; a size check on
|
||||
* just to discover how big it is. That is the point; a size check on
|
||||
* the byte count alone does not bound the decoded bitmap.
|
||||
*/
|
||||
fun decodeBounded(source: ByteArray, maxSide: Int = STANDARD_MAX_SIDE): Bitmap? {
|
||||
|
||||
@@ -21,8 +21,8 @@ import javax.crypto.SecretKey
|
||||
* The key does not leave this device, so neither does the vault. It is
|
||||
* generated here on first launch and is not exportable or backupable, which is
|
||||
* the Android equivalent of the iOS client's `ThisDeviceOnly` Keychain item. A
|
||||
* consequence worth stating plainly: **uninstalling the app destroys the key,
|
||||
* and every stored conversation with it.** That is the intended behaviour,
|
||||
* consequence worth stating plainly: uninstalling the app destroys the key,
|
||||
* and every stored conversation with it. That is the intended behaviour,
|
||||
* "deleted means deleted", but it means the vault is not a backup, and the
|
||||
* manifest turns `allowBackup` off so the records cannot be restored somewhere
|
||||
* their key can never follow.
|
||||
|
||||
@@ -88,7 +88,7 @@ class MainActivity : androidx.fragment.app.FragmentActivity() {
|
||||
// Two platforms disagreeing about a security guarantee is how one of
|
||||
// them ends up being the one that leaks.
|
||||
//
|
||||
// It also blocks screen recording and casting, which is the point,
|
||||
// It also blocks screen recording and casting,
|
||||
// and is not something to make optional: a setting here is a setting
|
||||
// people leave off and then get surprised by.
|
||||
window.setFlags(
|
||||
@@ -223,8 +223,8 @@ private fun Root(model: MessengerViewModel = viewModel()) {
|
||||
// already happening.
|
||||
CallBanner(model, current)
|
||||
StoryComposerDialog(model, current)
|
||||
// **Here, above the screen switch, for the same reason
|
||||
// the call banner is.** Nothing installs this app but the
|
||||
// Here, above the screen switch, for the same reason
|
||||
// the call banner is. Nothing installs this app but the
|
||||
// person holding the phone, so the one moment they can be
|
||||
// told a fix exists is while they have it open, whatever
|
||||
// screen that happens to be.
|
||||
@@ -236,8 +236,8 @@ private fun Root(model: MessengerViewModel = viewModel()) {
|
||||
ChatScreen(model, chat, current.error, current.buddies, current.avatars,
|
||||
myUserId = current.userId, groups = current.groups)
|
||||
} else {
|
||||
// **The content area is weighted, and the tab bar is
|
||||
// not.** These used to be plain siblings, so the tab bar
|
||||
// The content area is weighted, and the tab bar is
|
||||
// not. These used to be plain siblings, so the tab bar
|
||||
// sat immediately under whatever the screen had drawn:
|
||||
// near the TOP of an empty Alerts tab, and pushed clean
|
||||
// OFF the bottom by the Account tab, which is taller
|
||||
@@ -326,8 +326,8 @@ private fun ColumnScope.SignIn(model: MessengerViewModel, phase: MessengerViewMo
|
||||
// Only a relay keeping its own accounts needs an address for reset mail.
|
||||
val needsEmail = registering && site?.accountProvider == SiteConfig.AccountProvider.LOCAL
|
||||
|
||||
// **The kit's Welcome screen, which is a masthead and not a form
|
||||
// heading.** A logo mark, a 40pt greeting, and one line saying what this
|
||||
// The kit's Welcome screen, which is a masthead and not a form
|
||||
// heading. A logo mark, a 40pt greeting, and one line saying what this
|
||||
// is, centred, with room above it. This was a 24pt product name jammed
|
||||
// against a Server field, which is the layout of a login dialog rather
|
||||
// than the first thing anyone sees of the app. See `DESIGN.md`.
|
||||
@@ -752,8 +752,8 @@ internal fun ColumnScope.SignedIn(
|
||||
return
|
||||
}
|
||||
|
||||
// **Home is two things now, on purpose: your people, and their
|
||||
// stories.** The conversation list, the groups and the search moved to
|
||||
// Home is two things now, on purpose: your people, and their
|
||||
// stories. The conversation list, the groups and the search moved to
|
||||
// the Messages tab, this screen is the kit's social front page: a
|
||||
// horizontal row of contacts, then everyone's stories. Managing the
|
||||
// roster stays here (the Add action, and a long-press on a circle for
|
||||
|
||||
@@ -108,8 +108,8 @@ class MediaSettings(context: Context) {
|
||||
/**
|
||||
* Refuse to send at all when the send cannot be sealed (§14.5, ios#16).
|
||||
*
|
||||
* **Off by default, and that is the honest default rather than the lazy
|
||||
* one.** An unsealed send is still end-to-end encrypted; what leaks is that
|
||||
* Off by default, and that is the honest default rather than the lazy
|
||||
* one. An unsealed send is still end-to-end encrypted; what leaks is that
|
||||
* this account sent something to that one. Refusing turns a metadata leak
|
||||
* into an undelivered message, which for most people is the worse outcome
|
||||
* so the default degrades and SAYS SO.
|
||||
|
||||
@@ -139,8 +139,8 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
/**
|
||||
* The relays that hold mail for this account (§20.4).
|
||||
*
|
||||
* **Your address survives a relay disappearing; your mailbox
|
||||
* does not.** Mail sitting at a relay that goes away is gone, and a
|
||||
* Your address survives a relay disappearing; your mailbox
|
||||
* does not. Mail sitting at a relay that goes away is gone, and a
|
||||
* contact holding only that relay's name has nowhere to write. A
|
||||
* second home fixes both, and only if it was added first, because
|
||||
* afterwards there is nothing left to publish a new record from.
|
||||
@@ -460,8 +460,8 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
/**
|
||||
* Put this device back in the transparency log if it is not in it (android#6).
|
||||
*
|
||||
* **A device registers exactly once, at sign-in, and never again, so a
|
||||
* key-log append that did not land is permanent.** `signIn` calls
|
||||
* A device registers exactly once, at sign-in, and never again, so a
|
||||
* key-log append that did not land is permanent. `signIn` calls
|
||||
* `registerDevice`; `restore` did not. If the append failed for any reason
|
||||
* a transient write, a relay mid-restart, the device stays registered
|
||||
* with no leaf, and every peer that enforces the log then refuses to send
|
||||
@@ -501,8 +501,8 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
/**
|
||||
* Forget whatever the last sign-in attempt said.
|
||||
*
|
||||
* **Switching between signing in and creating an account has to do
|
||||
* this.** The message belongs to the mode it was produced in, "that
|
||||
* Switching between signing in and creating an account has to do
|
||||
* this. The message belongs to the mode it was produced in, "that
|
||||
* handle is already taken" is advice about signing up, and it stayed on
|
||||
* screen after the person took that advice and switched to signing in,
|
||||
* where it reads as a refusal of the thing they are now trying to do.
|
||||
@@ -600,8 +600,8 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
}
|
||||
val credential = account.appPassword
|
||||
|
||||
// **The address, derived here because this is the only moment the
|
||||
// passphrase exists.** Android could encode an address and had no way
|
||||
// The address, derived here because this is the only moment the
|
||||
// passphrase exists. Android could encode an address and had no way
|
||||
// to produce its own, so the Account screen could not show one and
|
||||
// nobody on another relay could be given one, the federation work was
|
||||
// iOS-only in practice. A shared account gets the seed back from the
|
||||
@@ -623,8 +623,8 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
}
|
||||
|
||||
val vault = vault ?: error("the vault is not open")
|
||||
// **The stored identity FIRST, and this line is why signing out and
|
||||
// back in used to cost every contact a prompt.** Sign-in only ever
|
||||
// The stored identity FIRST, and this line is why signing out and
|
||||
// back in used to cost every contact a prompt. Sign-in only ever
|
||||
// consulted the in-memory field, and sign-out clears it, so it minted
|
||||
// a fresh identity and overwrote the vault's, making this phone a brand
|
||||
// new device on every sign-in. Contacts saw a safety-number change,
|
||||
@@ -902,7 +902,7 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
runCatching {
|
||||
val record = current.relay.homes(fnid) ?: return@runCatching null
|
||||
// And not older than one already seen. An old record is
|
||||
// genuinely signed, published before a second home was added,
|
||||
// signed, published before a second home was added,
|
||||
// so a relay wanting to be somebody's only mailbox does not
|
||||
// have to forge anything; it just serves the record it saw
|
||||
// first. The signature check alone accepts that, which is what
|
||||
@@ -913,7 +913,7 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
// Advanced only after the record was ACCEPTED. Recording the
|
||||
// serial of a record that failed verification would let a
|
||||
// hostile relay raise the floor with a forgery and lock out
|
||||
// every later genuine record.
|
||||
// every later record.
|
||||
io.flamenet.client.AccountIdentity.verifiedSerial(record)
|
||||
?.let { if (relays != null) current.vault.saveHomeSerial(fnid, it) }
|
||||
relays
|
||||
@@ -1174,8 +1174,8 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
/**
|
||||
* Rename a group, and tell the other members.
|
||||
*
|
||||
* **The only way a group gets its name back after the relay stopped
|
||||
* holding one.** Names were fixed at creation and served by the relay for
|
||||
* The only way a group gets its name back after the relay stopped
|
||||
* holding one. Names were fixed at creation and served by the relay for
|
||||
* ever, so nothing needed this. Now the server has dropped them, every
|
||||
* group that existed before the change reads as the placeholder on every
|
||||
* phone, and there is nobody left to ask what it used to be called, the
|
||||
@@ -1351,8 +1351,8 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
/**
|
||||
* Make a wallet, and show the phrase once.
|
||||
*
|
||||
* **The phrase is shown and not stored anywhere it can be read back
|
||||
* casually.** It is in the returned state for as long as the screen holds
|
||||
* The phrase is shown and not stored anywhere it can be read back
|
||||
* casually. It is in the returned state for as long as the screen holds
|
||||
* it, and the record on disk is the ENTROPY, the words are regenerated
|
||||
* from it when somebody deliberately asks. Somebody who never writes them
|
||||
* down and later erases the device has nothing, which is what a recovery
|
||||
@@ -1817,8 +1817,8 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
/**
|
||||
* Send a document from the vault into this conversation.
|
||||
*
|
||||
* **This is a downgrade, and the screen says so before the tap rather
|
||||
* than after.** The content key goes in the same envelope, so the
|
||||
* This is a downgrade, and the screen says so before the tap rather
|
||||
* than after. The content key goes in the same envelope, so the
|
||||
* classification does not stop the recipient reading it, it travels so
|
||||
* their phone files it the same way. Nothing here refuses on the label,
|
||||
* because nothing here could: refusing in a client whose user holds the
|
||||
@@ -1868,8 +1868,8 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
* phone to point at, the bytes arrived over the wire, so the two storage
|
||||
* modes are not a choice here.
|
||||
*
|
||||
* **Filed under the label it arrived with, not under one this device
|
||||
* chose.** Re-labelling somebody else's document on receipt would quietly
|
||||
* Filed under the label it arrived with, not under one this device
|
||||
* chose. Re-labelling somebody else's document on receipt would quietly
|
||||
* launder its classification, and the number is the only thing that survived
|
||||
* the trip.
|
||||
*/
|
||||
@@ -2389,8 +2389,8 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
// WebRTC callback thread it is raised from.
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
runCatching { session?.sendCallSignal(peerId, signal) }
|
||||
// **This was `runCatching { }` and nothing else, so
|
||||
// every failure here vanished.** The one that matters
|
||||
// This was `runCatching { }` and nothing else, so
|
||||
// every failure here vanished. The one that matters
|
||||
// is a changed key: the send path REFUSES to encrypt to
|
||||
// an unreviewed identity. TOFU working exactly as
|
||||
// intended, and the exception was swallowed, so the
|
||||
@@ -2585,8 +2585,8 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
/**
|
||||
* Ask to place a call.
|
||||
*
|
||||
* **The first call on this device explains what a direct connection
|
||||
* discloses, and waits.** A peer-to-peer call shows the other person your
|
||||
* The first call on this device explains what a direct connection
|
||||
* discloses, and waits. A peer-to-peer call shows the other person your
|
||||
* IP address; that is what makes it direct, and it is not something to
|
||||
* find out afterwards. Asked once, not every time, a prompt people see
|
||||
* repeatedly is a prompt they stop reading.
|
||||
@@ -2670,7 +2670,7 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
/**
|
||||
* The live manager, for the call screen's video renderers only. Views
|
||||
* otherwise take plain values; a video surface is the one thing that
|
||||
* genuinely needs the track objects and the shared EGL context.
|
||||
* needs the track objects and the shared EGL context.
|
||||
*/
|
||||
fun activeCallManager(): CallManager? = calls
|
||||
|
||||
@@ -3726,8 +3726,7 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
* End the session, and optionally erase this phone with it.
|
||||
*
|
||||
* Signing out used to cost every one of your contacts a prompt. It
|
||||
* destroyed the vault unconditionally, so signing back in was a genuinely
|
||||
* NEW device: every contact had the old key pinned, saw a safety-number
|
||||
* destroyed the vault unconditionally, so signing back in was a * NEW device: every contact had the old key pinned, saw a safety-number
|
||||
* change, and could not message you until they accepted it. For someone
|
||||
* stepping away for an afternoon that is a strange price to pay, and it is
|
||||
* why signing out and back in kept looking like the app breaking.
|
||||
@@ -3742,8 +3741,8 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) {
|
||||
/**
|
||||
* Throw away a vault that cannot be opened, so sign-in is possible again.
|
||||
*
|
||||
* **The "Erase and start fresh" button used to call `signOut()`, which
|
||||
* erases nothing.** `signOut` defaults to `erasingDevice = false`, the
|
||||
* The "Erase and start fresh" button used to call `signOut()`, which
|
||||
* erases nothing. `signOut` defaults to `erasingDevice = false`, the
|
||||
* door that deliberately KEEPS the device so coming back is silent, so it
|
||||
* set the phase to SignedOut and left the unreadable vault exactly where it
|
||||
* was. The next `restore()` opened it, failed again, and went straight back
|
||||
|
||||
@@ -7,8 +7,8 @@ import java.io.ByteArrayOutputStream
|
||||
/**
|
||||
* Strips metadata from an image on its way out, whichever door it left by.
|
||||
*
|
||||
* **A photo sent as a *file* used to keep its GPS; sent as a *photo* it did
|
||||
* not** (android#17, ios#69). The picker path runs through [ImagePrep], which
|
||||
* A photo sent as a *file* used to keep its GPS; sent as a *photo* it did
|
||||
* not (android#17, ios#69). The picker path runs through [ImagePrep], which
|
||||
* decodes to a bitmap and re-encodes, the output is pixels, with no container
|
||||
* to carry EXIF. `Session.sendFile` handed the stored bytes straight to
|
||||
* `sendAttachment`. Somebody who had learned "Flamenet strips location from my
|
||||
|
||||
@@ -76,8 +76,8 @@ fun ColumnScope.NetworkSection(
|
||||
modifier = Modifier.testTag("obfs4-status"),
|
||||
)
|
||||
|
||||
// **Offers from contacts, and the reason they are a list and not a
|
||||
// setting.** A bridge line routes every relay connection through a machine
|
||||
// Offers from contacts, and the reason they are a list and not a
|
||||
// setting. A bridge line routes every relay connection through a machine
|
||||
// somebody else chose. Nothing applies one on arrival, a contact, or
|
||||
// whoever has taken their account, could otherwise move this phone's
|
||||
// traffic onto a host they control. They could not read it, but they would
|
||||
|
||||
@@ -37,8 +37,8 @@ class NotificationSettings(context: Context) {
|
||||
/**
|
||||
* Put the sender's name, and a hint of the message, on the lock screen.
|
||||
*
|
||||
* **Off by default, and that is the honest default rather than the
|
||||
* cautious one.** Turning it on is a real choice with a real cost: anyone
|
||||
* Off by default, and that is the honest default rather than the
|
||||
* cautious one. Turning it on is a real choice with a real cost: anyone
|
||||
* who can see the phone can see who is writing to you, which on some
|
||||
* phones is a more sensitive fact than what they said. The people for whom
|
||||
* that is fine can have it; nobody should acquire it by installing an
|
||||
@@ -74,8 +74,8 @@ class NotificationSettings(context: Context) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* **Pure, and takes the preference as an argument, so a test can
|
||||
* call THIS rather than a copy of it.** The first version of the test
|
||||
* Pure, and takes the preference as an argument, so a test can
|
||||
* call THIS rather than a copy of it. The first version of the test
|
||||
* beside this file reimplemented the rules against a fake and passed
|
||||
* perfectly while asserting nothing about the code that ships, the
|
||||
* same shape as two identically-wrong engines agreeing. A `Context` is
|
||||
|
||||
@@ -54,8 +54,8 @@ class SafReferences(private val context: Context) : LocalVault.ReferenceResolver
|
||||
}
|
||||
|
||||
/**
|
||||
* **Opening the stream, not asking the provider whether the file
|
||||
* exists.** `DocumentsContract` will happily describe a document that can no
|
||||
* Opening the stream, not asking the provider whether the file
|
||||
* exists. `DocumentsContract` will happily describe a document that can no
|
||||
* longer be read, a cloud provider with the account signed out is the
|
||||
* everyday case, and a row that says it is fine and then fails on tap is
|
||||
* the thing this whole distinction exists to avoid.
|
||||
|
||||
@@ -44,8 +44,8 @@ class Session(
|
||||
/**
|
||||
* What identifies this message to the other side.
|
||||
*
|
||||
* **Null for anything stored before ids existed, and that is the
|
||||
* whole compatibility story.** A reaction, an edit and a pin all say
|
||||
* Null for anything stored before ids existed, and that is the
|
||||
* whole compatibility story. A reaction, an edit and a pin all say
|
||||
* "this one" by id; a line with none simply cannot be the subject of
|
||||
* any of them. Making one up on read would be worse, two devices
|
||||
* would invent different ids for the same message and a reaction would
|
||||
@@ -427,15 +427,15 @@ class Session(
|
||||
/**
|
||||
* Delete cached attachment bytes that nothing refers to any more.
|
||||
*
|
||||
* **`attachmentBytes` writes DECRYPTED bytes to the cache and nothing
|
||||
* ever removed them.** So a disappearing message expired, its bubble went,
|
||||
* `attachmentBytes` writes DECRYPTED bytes to the cache and nothing
|
||||
* ever removed them. So a disappearing message expired, its bubble went,
|
||||
* and the plaintext photo or voice note stayed on disk indefinitely, which
|
||||
* is the exact failure the feature exists to prevent. Same for a story once
|
||||
* its expiry passed. Spec §6.11 says it plainly: attachments MUST be purged
|
||||
* with the message that referenced them.
|
||||
*
|
||||
* **A sweep, not a purge at the point of deletion, and that is a
|
||||
* deliberate choice.** Four different paths drop a row, expiry, story
|
||||
* A sweep, not a purge at the point of deletion, and that is a
|
||||
* deliberate choice. Four different paths drop a row, expiry, story
|
||||
* expiry, delete-for-me and clear-thread, and threading "here is what I
|
||||
* removed" out of each one would still miss anything a future path drops,
|
||||
* and would never reach the orphans already on disk. Asking instead "what
|
||||
@@ -501,8 +501,8 @@ class Session(
|
||||
/**
|
||||
* Send, and record it either way, with what happened.
|
||||
*
|
||||
* **This used to record only on success, and that is how a message
|
||||
* disappeared.** Sent to somebody signed out, nothing reached a device, so
|
||||
* This used to record only on success, and that is how a message
|
||||
* disappeared. Sent to somebody signed out, nothing reached a device, so
|
||||
* nothing was written and nothing was shown: the sender watched their own
|
||||
* text vanish out of the box with no bubble and no error, and sent it
|
||||
* again to see whether it would stick. The alternative the group path took
|
||||
@@ -529,8 +529,8 @@ class Session(
|
||||
/**
|
||||
* The message this one answers (android#16).
|
||||
*
|
||||
* **The preview inside it is the sender's own copy of the quoted
|
||||
* words, and it has to travel.** A recipient who joined the thread
|
||||
* The preview inside it is the sender's own copy of the quoted
|
||||
* words, and it has to travel. A recipient who joined the thread
|
||||
* later, or cleared their history, has never seen the message being
|
||||
* answered, a reply rendering as "> [missing]" is worse than one that
|
||||
* shows what was said.
|
||||
@@ -917,8 +917,8 @@ class Session(
|
||||
* receiving side enforces the same thing (`applyEdit` refuses an edit from
|
||||
* anybody but the author), so neither end is trusting the other.
|
||||
*
|
||||
* **Applied locally first, then sent, and rolled back if the send
|
||||
* fails**, the same shape as `react`. Waiting on a round trip to see your
|
||||
* Applied locally first, then sent, and rolled back if the send
|
||||
* fails, the same shape as `react`. Waiting on a round trip to see your
|
||||
* own correction appear makes it feel broken on a slow connection, and
|
||||
* leaving a correction that exists only on this phone is worse: you would
|
||||
* believe you had fixed it.
|
||||
@@ -974,7 +974,7 @@ class Session(
|
||||
* Pass somebody's words on to another conversation (§6.16).
|
||||
*
|
||||
* The forward carries the words and NOT where they came from. No
|
||||
* author, no original thread, no quote, that is the whole point of §6.16,
|
||||
* author, no original thread, no quote, that is the point of §6.16,
|
||||
* and it is why this sends a plain text envelope with `fwd` set rather than
|
||||
* a reply that references the original. A forward that named its source
|
||||
* would republish a private message's author to somebody who was never in
|
||||
@@ -1124,7 +1124,7 @@ class Session(
|
||||
// restating on the first success would mark it delivered while other
|
||||
// members are still owed it, and "will send" would quietly stop being
|
||||
// true for them. Until the set drains, PENDING remains the honest
|
||||
// label, the queue genuinely will keep sending.
|
||||
// label, the queue will keep sending.
|
||||
for ((thread, at) in groupDelivered) {
|
||||
var remaining = false
|
||||
for (i in 0 until stillWaiting.length()) {
|
||||
@@ -1541,8 +1541,8 @@ class Session(
|
||||
/**
|
||||
* Post a story to a group (§6.24).
|
||||
*
|
||||
* **The group's roster is the audience, and `storyAudience` does NOT
|
||||
* apply.** Two different lists making two different statements, who may
|
||||
* The group's roster is the audience, and `storyAudience` does NOT
|
||||
* apply. Two different lists making two different statements, who may
|
||||
* see this account's stories, and who is in a conversation, and they will
|
||||
* disagree. The roster wins: a story addressed to a group that silently
|
||||
* dropped members would be a group message part of the group cannot see.
|
||||
@@ -1608,8 +1608,8 @@ class Session(
|
||||
/**
|
||||
* File a report about a MESSAGE (§6.25, android#20 / ios#12).
|
||||
*
|
||||
* **`kind = "message"` has always been accepted and nothing ever sent
|
||||
* one.** Reporting existed on stories only, so the surface where
|
||||
* `kind = "message"` has always been accepted and nothing ever sent
|
||||
* one. Reporting existed on stories only, so the surface where
|
||||
* unsolicited contact, spam and harassment actually arrive had no way to
|
||||
* tell anybody. The person being messaged by a stranger had Block, which
|
||||
* protects them and tells the operator nothing, so the account moves on
|
||||
@@ -1936,14 +1936,14 @@ class Session(
|
||||
/**
|
||||
* Add or remove members of a sealed group.
|
||||
*
|
||||
* **Owner only, and the relay checks a signature rather than an
|
||||
* account.** We hold the owner's private key iff we created the group; a
|
||||
* Owner only, and the relay checks a signature rather than an
|
||||
* account. We hold the owner's private key iff we created the group; a
|
||||
* member who is not the owner has nothing to sign with, and is refused here
|
||||
* rather than sent to be rejected.
|
||||
*
|
||||
* The version must increase. A roster signature stays valid for ever, so
|
||||
* a stale roster could otherwise be replayed to re-add somebody who was just
|
||||
* removed, with a signature that genuinely verifies.
|
||||
* removed, with a signature that verifies.
|
||||
*/
|
||||
fun updateSealedGroupMembers(gid: String, add: List<Long>, remove: List<Long>) {
|
||||
val secrets = vault.groupSecrets(gid)
|
||||
@@ -2032,7 +2032,7 @@ class Session(
|
||||
/**
|
||||
* Seal a group that already exists in the clear, keeping its gid.
|
||||
*
|
||||
* The whole point of the feature for anybody who already has groups.
|
||||
* The point of the feature for anybody who already has groups.
|
||||
* The migration is additive by design, the relay keeps a plaintext roster
|
||||
* until a client replaces it, so a group made before sealing stays readable
|
||||
* in the database for ever unless something does this.
|
||||
@@ -2131,7 +2131,7 @@ class Session(
|
||||
* Adopt the group-name claims that arrived as control traffic, checking
|
||||
* each against the relay's roster.
|
||||
*
|
||||
* The check is the whole point. A claim naming a group we are not in
|
||||
* The check is the point. A claim naming a group we are not in
|
||||
* or one from somebody who is not in it, is dropped. Without that,
|
||||
* anyone able to reach us could write whatever they liked into our chat
|
||||
* list under a group heading the reader has no reason to doubt.
|
||||
@@ -2150,8 +2150,8 @@ class Session(
|
||||
* The only way these ever reach us. The relay holds neither, by
|
||||
* construction, so a member who is not told cannot fetch the group at all.
|
||||
*
|
||||
* **Not subject to the roster check the NAME goes through, and cannot
|
||||
* be.** That check asks the relay who is in the group, and for a group we
|
||||
* Not subject to the roster check the NAME goes through, and cannot
|
||||
* be. That check asks the relay who is in the group, and for a group we
|
||||
* have not joined yet, we cannot fetch it to ask. What guards this instead
|
||||
* is that an invitation is accepted only for a group we hold no secrets
|
||||
* for: a stranger cannot overwrite a group we are really in, which would
|
||||
|
||||
@@ -32,8 +32,8 @@ data class SiteConfig(
|
||||
/**
|
||||
* The probe was made and came back with nothing.
|
||||
*
|
||||
* **Without this, "still asking" and "asked and could not reach it" are
|
||||
* the same state.** `discovered` is null in both, so a relay that never
|
||||
* Without this, "still asking" and "asked and could not reach it" are
|
||||
* the same state. `discovered` is null in both, so a relay that never
|
||||
* answers leaves "Checking what this server uses…" on screen forever and
|
||||
* the form permanently unusable, no error, no retry, nothing to act on.
|
||||
* Seen 2026-09-05 pointing the app at a plain-HTTP relay, which Android
|
||||
|
||||
@@ -179,7 +179,7 @@ private fun StoryCard(
|
||||
Text(author, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
// The deadline, in the slot the kit gives a description.
|
||||
// It is the one fact about a story that changes while you are
|
||||
// looking at it, and the whole point of the feature.
|
||||
// looking at it, and the point of the feature.
|
||||
Text(remaining, fontSize = 12.sp, color = Palette.SecondaryText)
|
||||
}
|
||||
// Unseen is marked here rather than by styling the whole card: a
|
||||
|
||||
@@ -11,7 +11,7 @@ import android.provider.MediaStore
|
||||
*
|
||||
* MediaStore, so no storage permission is asked for. An app writing its
|
||||
* own entry through `MediaStore` needs no runtime grant on any version this app
|
||||
* supports. Asking for `WRITE_EXTERNAL_STORAGE` in order to save one picture
|
||||
* supports. Asking for `WRITE_EXTERNAL_STORAGE` to save one picture
|
||||
* would request read access to everything the person has, to perform a write,
|
||||
* a much larger ask than the feature.
|
||||
*/
|
||||
|
||||
@@ -5,8 +5,8 @@ import android.content.Context
|
||||
/**
|
||||
* Which chain node the wallet reads from.
|
||||
*
|
||||
* **Pointing at a node is a privacy decision, and the app says so rather
|
||||
* than choosing quietly.** Asking a public RPC endpoint what an address holds
|
||||
* Pointing at a node is a privacy decision, and the app says so rather
|
||||
* than choosing quietly. Asking a public RPC endpoint what an address holds
|
||||
* ties that address to this device's IP in somebody else's logs, every time
|
||||
* the screen refreshes. Running your own node is the only answer that removes
|
||||
* it. The default exists so the wallet works at all, not because it is private.
|
||||
@@ -49,8 +49,8 @@ object WalletChain {
|
||||
/**
|
||||
* What a transfer costs, and what must stay behind.
|
||||
*
|
||||
* **An account below the rent-exempt minimum is reclaimed by the
|
||||
* network.** Emptying a wallet to the last lamport does not leave it empty;
|
||||
* An account below the rent-exempt minimum is reclaimed by the
|
||||
* network. Emptying a wallet to the last lamport does not leave it empty;
|
||||
* it deletes it. Anything offering to send "everything" has to stop here,
|
||||
* and the two numbers are named rather than buried in an expression so that
|
||||
* the reason survives.
|
||||
|
||||
@@ -39,8 +39,8 @@ import java.util.Locale
|
||||
/**
|
||||
* The wallet.
|
||||
*
|
||||
* **A balance this app could not read is drawn as an em dash, never as
|
||||
* zero.** Showing 0.00 because a node did not answer tells somebody their money
|
||||
* A balance this app could not read is drawn as an em dash, never as
|
||||
* zero. Showing 0.00 because a node did not answer tells somebody their money
|
||||
* is gone, the single worst sentence this screen can say, and one it would say
|
||||
* on every flaky connection. Null is a different fact from empty and is drawn
|
||||
* as one.
|
||||
|
||||
@@ -135,8 +135,8 @@ class AttachmentPurgeTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* **An id containing a slash, which is the reason the transformation
|
||||
* exists.** `attachmentBytes` writes the file as `att-<id with / -> _>`, so
|
||||
* An id containing a slash, which is the reason the transformation
|
||||
* exists. `attachmentBytes` writes the file as `att-<id with / -> _>`, so
|
||||
* membership has to be tested against the SAME mangling. Compare raw ids to
|
||||
* mangled filenames and nothing matches, every attachment with a slash in
|
||||
* its id is deleted while still on screen.
|
||||
|
||||
@@ -7,8 +7,8 @@ import kotlin.test.assertTrue
|
||||
/**
|
||||
* A refused-because-the-key-changed failure has to say so (android#20).
|
||||
*
|
||||
* **Reported from a real session: "the calls wouldn't connect because the
|
||||
* keys were changed. it wasn't obvious why the call failed."** It was not
|
||||
* Reported from a real session: "the calls wouldn't connect because the
|
||||
* keys were changed. it wasn't obvious why the call failed." It was not
|
||||
* obvious because nothing said it. The call's signalling lambda was
|
||||
* `runCatching { … }` with no `onFailure`, so `IdentityChangedException`, the
|
||||
* send path refusing to encrypt to an unreviewed identity, which is TOFU
|
||||
|
||||
@@ -15,8 +15,8 @@ import kotlin.test.assertEquals
|
||||
class DisplaySettingsTest {
|
||||
|
||||
/**
|
||||
* **16sp with the setting off, because that is what the kit specifies
|
||||
* for a message body.** A "larger type" switch that quietly restyled every
|
||||
* 16sp with the setting off, because that is what the kit specifies
|
||||
* for a message body. A "larger type" switch that quietly restyled every
|
||||
* message when it was turned OFF would be a worse bug than the absence it
|
||||
* fixes, and the number is easy to get wrong because it lives in one file
|
||||
* and is drawn in another.
|
||||
|
||||
@@ -10,8 +10,8 @@ import kotlin.test.assertTrue
|
||||
/**
|
||||
* What the three new controls actually put on the wire (§6.15/§6.16/§6.19).
|
||||
*
|
||||
* **The privacy rule for a forward is the one worth testing, because it is
|
||||
* the one a "helpful" change would break.** §6.16 says a forward carries the
|
||||
* The privacy rule for a forward is the one worth testing, because it is
|
||||
* the one a "helpful" change would break. §6.16 says a forward carries the
|
||||
* words and nothing about where they came from. The obvious improvement,
|
||||
* carrying a reference so the reader can see the original, would republish a
|
||||
* private message's author to somebody who was never in that conversation. So
|
||||
@@ -29,7 +29,7 @@ class EditForwardPinWireTest {
|
||||
// ---- Forward (§6.16) -------------------------------------------------
|
||||
|
||||
/**
|
||||
* The whole point of the feature: forwarded, and no trail back.
|
||||
* The point of the feature: forwarded, and no trail back.
|
||||
*/
|
||||
@Test
|
||||
fun `a forward carries the words and nothing about their origin`() {
|
||||
|
||||
@@ -7,8 +7,8 @@ import kotlin.test.assertTrue
|
||||
/**
|
||||
* Files asks for the Flamenet passphrase, not a second secret.
|
||||
*
|
||||
* **The screen used to say the files were encrypted "with a passphrase of
|
||||
* their own", and iOS never said anything like it.** iOS says "your
|
||||
* The screen used to say the files were encrypted "with a passphrase of
|
||||
* their own", and iOS never said anything like it. iOS says "your
|
||||
* passphrase" and takes whatever is typed, so people use their Flamenet one
|
||||
* and it works. Android's copy sent them off to invent something else, and
|
||||
* forgetting it is a one-way door: the label keys are derived from the
|
||||
|
||||
@@ -125,7 +125,7 @@ class GroupNameClaimTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* The whole point. A stranger naming a group they are not in must get
|
||||
*A stranger naming a group they are not in must get
|
||||
* nowhere, however well-formed their envelope is.
|
||||
*/
|
||||
@Test
|
||||
|
||||
@@ -61,8 +61,8 @@ class InboundControlTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* **The case an allowlist cannot cover, and the reason the app branches
|
||||
* on `isRenderable` rather than on a list of kinds it knows.** A control
|
||||
* The case an allowlist cannot cover, and the reason the app branches
|
||||
* on `isRenderable` rather than on a list of kinds it knows. A control
|
||||
* envelope from a NEWER peer has a kind nothing here has heard of. Keying on
|
||||
* known kinds ships a bubble full of JSON to every older install the first
|
||||
* time a new control message is added on the other platform.
|
||||
|
||||
@@ -26,8 +26,8 @@ import org.robolectric.annotation.Config
|
||||
* transcript does record a `mid`, `MessageReactionTest` folds a reaction onto
|
||||
* a message by id, so the stated reason is already false.
|
||||
*
|
||||
* But relay#46's rule is the point: **do not flip a claim because the code
|
||||
* looks present; flip it because a run proved it, and say which run.**
|
||||
* But relay#46's rule is the point: do not flip a claim because the code
|
||||
* looks present; flip it because a run proved it, and say which run.
|
||||
* `applyEdit` was implemented and wired with nothing exercising it, because
|
||||
* `InboundControlTest` covers the codec and says in its own header that the
|
||||
* app-side application needs a `Vault` and is out of scope there. This is that
|
||||
|
||||
@@ -8,8 +8,8 @@ import kotlin.test.assertTrue
|
||||
/**
|
||||
* A photo sent as a file must lose its location, like one sent as a photo.
|
||||
*
|
||||
* **The bug this pins was found by an audit, not by a test, because no test
|
||||
* went down the file path at all** (android#17, ios#69). The picker path was
|
||||
* The bug this pins was found by an audit, not by a test, because no test
|
||||
* went down the file path at all (android#17, ios#69). The picker path was
|
||||
* checked and correct; `Session.sendFile` handed stored bytes straight to
|
||||
* `sendAttachment`. The two doors disagreed, and the safer one was the one that
|
||||
* looked like a shortcut.
|
||||
|
||||
@@ -8,8 +8,8 @@ import kotlin.test.assertTrue
|
||||
/**
|
||||
* What the Network section says is happening.
|
||||
*
|
||||
* **A settings screen that reads the stored line and calls it a status is
|
||||
* the failure this feature is built around.** A saved bridge that is not there
|
||||
* A settings screen that reads the stored line and calls it a status is
|
||||
* the failure this feature is built around. A saved bridge that is not there
|
||||
* leaves the app connecting directly; the preference cannot tell you that and
|
||||
* a completed handshake can. `statusLine` is lifted out of the Composable
|
||||
* precisely so this can be asserted, a string built inline in a `Text(...)` is
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.robolectric.annotation.Config
|
||||
*
|
||||
* Nothing expires. There is no TTL and nothing is discarded: a contact
|
||||
* who reinstalls in a month gets it in a month. Timing out would mean deleting
|
||||
* something somebody wrote in order to tidy up a label.
|
||||
* something somebody wrote to tidy up a label.
|
||||
*
|
||||
* The honest limit, which the UI wording has to respect: the queue is on
|
||||
* the sender's phone, because an envelope is sealed to a device's keys and
|
||||
@@ -223,7 +223,7 @@ class OutboxTest {
|
||||
* look broken to the person reading the thread.
|
||||
*
|
||||
* Serves a REAL bundle built from a second identity, not a hand-written
|
||||
* fake, so the send has to complete genuine X3DH against it. A fake bundle
|
||||
* fake, so the send has to complete X3DH against it. A fake bundle
|
||||
* would agree with whatever the client did, including the wrong thing.
|
||||
*/
|
||||
@Test
|
||||
|
||||
@@ -48,7 +48,7 @@ class StoryReportTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Without one, the picker forces a person to mis-categorise in order to
|
||||
* Without one, the picker forces a person to mis-categorise to
|
||||
* report at all.
|
||||
*/
|
||||
@Test
|
||||
|
||||
@@ -50,8 +50,8 @@ class WalletDisplayTest {
|
||||
// ---- What can actually be sent ---------------------------------------
|
||||
|
||||
/**
|
||||
* **An account below the rent-exempt minimum is RECLAIMED by the
|
||||
* network.** Emptying a wallet to the last lamport does not leave it empty,
|
||||
* An account below the rent-exempt minimum is RECLAIMED by the
|
||||
* network. Emptying a wallet to the last lamport does not leave it empty,
|
||||
* it deletes it, so "send everything" has to stop short of everything.
|
||||
*/
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user