From 57a5ef87962cd29269dcbcd7fd6cd924a590e774 Mon Sep 17 00:00:00 2001 From: mrambossek Date: Sat, 1 Aug 2026 16:16:36 +0200 Subject: [PATCH] app: the stable-pseudonym switch was live at a level that pseudonymizes nothing At `full` the anonymizer returns the document unchanged, so the salt has nothing to act on - but the switch was enabled and looked like it did something. A control that silently does nothing is the same class of fault as the preview button and the archived-level label: the screen implying more than is true. Shown disabled with the reason rather than hidden. The setting is still stored and applies the moment the level changes, so making it vanish would hide state that is still there; and a settings screen whose controls appear and disappear as you touch other controls is harder to trust, not easier. The label dims with the switch so "not active right now" reads at a glance. Co-Authored-By: Claude Fable 5 --- .../kotlin/app/echo_lot/app/SettingsScreen.kt | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/echolot-app/app/src/main/kotlin/app/echo_lot/app/SettingsScreen.kt b/echolot-app/app/src/main/kotlin/app/echo_lot/app/SettingsScreen.kt index 5d59bac..f82bb00 100644 --- a/echolot-app/app/src/main/kotlin/app/echo_lot/app/SettingsScreen.kt +++ b/echolot-app/app/src/main/kotlin/app/echo_lot/app/SettingsScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.FilterChip +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Switch @@ -130,12 +131,21 @@ fun SettingsScreen( } Text(privacyExplanation(privacy), style = MaterialTheme.typography.bodySmall) + // At FULL nothing is pseudonymized, so a salt has nothing to act on. Shown + // disabled rather than hidden: the setting is still stored and still applies the + // moment the level changes, and a control that vanishes hides that fact. Toggle( label = "Stable pseudonyms across runs", - detail = "Lets you compare uploaded runs over time (same SSID reads the same " + - "each time). It also links your uploads together, so leave it off on a " + - "server you don't run yourself.", - checked = stableSalt, + detail = if (privacy == PrivacyLevel.FULL) { + "Not used at this level — nothing is pseudonymized, so there is nothing " + + "to keep stable. Choose balanced or strict to use this." + } else { + "Lets you compare uploaded runs over time (same SSID reads the same " + + "each time). It also links your uploads together, so leave it off on " + + "a server you don't run yourself." + }, + checked = stableSalt && privacy != PrivacyLevel.FULL, + enabled = privacy != PrivacyLevel.FULL, ) { stableSalt = it; settings.stableSalt = it } TextButton(onClick = onPreviewUpload) { Text("Preview what an upload would send") } @@ -237,13 +247,22 @@ private fun privacyExplanation(level: PrivacyLevel): String = when (level) { } @Composable -private fun Toggle(label: String, detail: String, checked: Boolean, onChange: (Boolean) -> Unit) { +private fun Toggle( + label: String, + detail: String, + checked: Boolean, + enabled: Boolean = true, + onChange: (Boolean) -> Unit, +) { Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.Top) { Column(Modifier.weight(1f)) { - Text(label, style = MaterialTheme.typography.bodyMedium) - Text(detail, style = MaterialTheme.typography.bodySmall) + // Dimmed together with the switch, so "this does nothing right now" reads at a glance + // instead of only on close inspection. + val alpha = if (enabled) 1f else 0.5f + Text(label, style = MaterialTheme.typography.bodyMedium, color = LocalContentColor.current.copy(alpha = alpha)) + Text(detail, style = MaterialTheme.typography.bodySmall, color = LocalContentColor.current.copy(alpha = alpha)) } - Switch(checked = checked, onCheckedChange = onChange) + Switch(checked = checked, onCheckedChange = onChange, enabled = enabled) } }