Skip to main content

max / goingson

Remove the stranded biometric app lock The frontend that drove it went with the rest of the JS in the port, so what was left was a plugin initialised on every mobile launch, a registered command nothing called, and a preference nothing read or wrote. Gone: the dependency, the plugin init, set_require_biometric_unlock and its registration, the require_biometric_unlock field, and the two mobile capability grants. Preferences carries no deny_unknown_fields, so an existing preferences.json with requireBiometricUnlock still loads; the round-trip test now pins that. Why in 458e1753, along with the two open taste calls a rebuild inherits (the grace period, and whether enabling prompts on the spot). The real work is kept where it belongs: quasi/settings/about.rs still records why the row cannot be offered from a startup check, since availability is a live question to the OS.
Author: Max Johnson <me@maxj.phd> · 2026-08-28 20:07 UTC
Signed with PGP, not checked
Commit: 77911ad7d40d757a12974d325c521aecb1daa6d8
Parent: 2bd36bc
8 files changed, +16 insertions, -77 deletions
M Cargo.lock -16
@@ -2292,7 +2292,6 @@
2292 2292 "synckit-config",
2293 2293 "tauri",
2294 2294 "tauri-build",
2295 - "tauri-plugin-biometric",
2296 2295 "tauri-plugin-dialog",
2297 2296 "tauri-plugin-haptics",
2298 2297 "tauri-plugin-notification",
@@ -6288,21 +6287,6 @@
6288 6287 "walkdir",
6289 6288 ]
6290 6289
6291 - [[package]]
6292 - name = "tauri-plugin-biometric"
6293 - version = "2.3.2"
6294 - source = "registry+https://github.com/rust-lang/crates.io-index"
6295 - checksum = "306175b744890b5e4aeb8add6aae7debec1b1504c5087bd0310ed7c9c6feae38"
6296 - dependencies = [
6297 - "log",
6298 - "serde",
6299 - "serde_json",
6300 - "serde_repr",
6301 - "tauri",
6302 - "tauri-plugin",
6303 - "thiserror 2.0.20",
6304 - ]
6305 -
6306 6290 [[package]]
6307 6291 name = "tauri-plugin-dialog"
6308 6292 version = "2.7.2"
M Cargo.toml -1
@@ -99,7 +99,6 @@
99 99 # no-op, so they are gated to iOS/Android at the target level in src-tauri rather
100 100 # than carried into the desktop binary and guarded at the call site.
101 101 tauri-plugin-haptics = "2.3.2"
102 - tauri-plugin-biometric = "2.3.2"
103 102
104 103 # Filesystem watching (db change notifications)
105 104 notify = "8.2"
@@ -180,7 +180,6 @@
180 180 # what keeps a call on desktop a compile error instead of a runtime one.
181 181 [target.'cfg(any(target_os = "ios", target_os = "android"))'.dependencies]
182 182 tauri-plugin-haptics = { workspace = true }
183 - tauri-plugin-biometric = { workspace = true }
184 183
185 184 # SMTP trust source, everywhere lettre can express it: the OS-native store, the
186 185 # same one reqwest and IMAP use.
@@ -1,14 +1,12 @@
1 1 {
2 2 "$schema": "https://schemas.tauri.app/capabilities/2",
3 3 "identifier": "mobile",
4 - "description": "Mobile-only capabilities (haptic feedback, biometric app lock). Granted command by command rather than through the plugins' default sets: vibrate is not granted because nothing calls raw vibration, and a grant nothing uses is a grant nobody notices going wrong.",
4 + "description": "Mobile-only capabilities (haptic feedback). Granted command by command rather than through the plugins' default sets: vibrate is not granted because nothing calls raw vibration, and a grant nothing uses is a grant nobody notices going wrong.",
5 5 "platforms": ["iOS", "android"],
6 6 "windows": ["main"],
7 7 "permissions": [
8 8 "haptics:allow-impact-feedback",
9 9 "haptics:allow-notification-feedback",
10 - "haptics:allow-selection-feedback",
11 - "biometric:allow-status",
12 - "biometric:allow-authenticate"
10 + "haptics:allow-selection-feedback"
13 11 ]
14 12 }
@@ -353,7 +353,6 @@
353 353 // Preferences
354 354 $crate::commands::get_preferences,
355 355 $crate::commands::set_update_check_on_launch,
356 - $crate::commands::set_require_biometric_unlock,
357 356 ]
358 357 };
359 358 }
@@ -405,13 +404,10 @@
405 404 }
406 405
407 406 // Mobile-only plugins. Haptics carries gesture confirmation the desktop
408 - // build answers with hover and focus states instead; biometric backs the
409 - // app lock, which is offered only where the OS has an enrolled biometry.
407 + // build answers with hover and focus states instead.
410 408 #[cfg(any(target_os = "ios", target_os = "android"))]
411 409 {
412 - builder = builder
413 - .plugin(tauri_plugin_haptics::init())
414 - .plugin(tauri_plugin_biometric::init());
410 + builder = builder.plugin(tauri_plugin_haptics::init());
415 411 }
416 412
417 413 builder
@@ -1,1 +1,1 @@
1 - {"default":{"identifier":"default","description":"Default capabilities for the main window","local":true,"windows":["main","compose-*"],"permissions":["core:default","core:window:allow-create","core:window:allow-close","core:window:allow-center","dialog:default"]},"desktop":{"identifier":"desktop","description":"Desktop-only capabilities (notifications, OTA updater). External URLs are opened via the validated open_external_url command, not a JS opener binding.","local":true,"windows":["main","compose-*"],"permissions":["notification:default","updater:default","process:allow-restart"],"platforms":["linux","macOS","windows"]},"mobile":{"identifier":"mobile","description":"Mobile-only capabilities (haptic feedback, biometric app lock). Granted command by command rather than through the plugins' default sets: vibrate is not granted because nothing calls raw vibration, and a grant nothing uses is a grant nobody notices going wrong.","local":true,"windows":["main"],"permissions":["haptics:allow-impact-feedback","haptics:allow-notification-feedback","haptics:allow-selection-feedback","biometric:allow-status","biometric:allow-authenticate"],"platforms":["iOS","android"]}}
1 + {"default":{"identifier":"default","description":"Default capabilities for the main window","local":true,"windows":["main","compose-*"],"permissions":["core:default","core:window:allow-create","core:window:allow-close","core:window:allow-center","dialog:default"]},"desktop":{"identifier":"desktop","description":"Desktop-only capabilities (notifications, OTA updater). External URLs are opened via the validated open_external_url command, not a JS opener binding.","local":true,"windows":["main","compose-*"],"permissions":["notification:default","updater:default","process:allow-restart"],"platforms":["linux","macOS","windows"]},"mobile":{"identifier":"mobile","description":"Mobile-only capabilities (haptic feedback). Granted command by command rather than through the plugins' default sets: vibrate is not granted because nothing calls raw vibration, and a grant nothing uses is a grant nobody notices going wrong.","local":true,"windows":["main"],"permissions":["haptics:allow-impact-feedback","haptics:allow-notification-feedback","haptics:allow-selection-feedback"],"platforms":["iOS","android"]}}
@@ -17,25 +17,12 @@
17 17 #[serde(rename_all = "camelCase")]
18 18 pub struct Preferences {
19 19 pub update_check_on_launch: bool,
20 - /// Whether the app hides its contents behind a system biometric prompt.
21 - ///
22 - /// Here rather than in `user_config` for two reasons. It is read before the
23 - /// database is open, which is the whole point of this file: a lock that
24 - /// appears after the task list has painted has already failed. And it is a
25 - /// per-device choice, so it must not sync — enrolled biometry on a phone
26 - /// says nothing about the desktop the same account syncs to, and
27 - /// `user_config` would have to carry it as `Local` to say the same thing.
28 - ///
29 - /// Defaults off. Turning it on is the user asking for it.
30 - #[serde(default)]
31 - pub require_biometric_unlock: bool,
32 20 }
33 21
34 22 impl Default for Preferences {
35 23 fn default() -> Self {
36 24 Self {
37 25 update_check_on_launch: true,
38 - require_biometric_unlock: false,
39 26 }
40 27 }
41 28 }
@@ -123,44 +110,20 @@
123 110 save(&app, &prefs)
124 111 }
125 112
126 - /// Turn the biometric app lock on or off.
127 - ///
128 - /// Registered on every platform, not only mobile: the preference is written by
129 - /// the same settings screen everywhere and a desktop build that rejected the
130 - /// command would make the row's absence a runtime error rather than a layout
131 - /// decision. Whether the lock is *offered* is decided in the frontend by asking
132 - /// the biometric plugin for its status, which only mobile answers.
133 - #[tauri::command]
134 - #[instrument(skip_all)]
135 - #[allow(
136 - clippy::needless_pass_by_value,
137 - reason = "Tauri command handler: AppHandle and payload args are supplied by value per the #[tauri::command] contract; command parameters cannot be borrowed"
138 - )]
139 - pub fn set_require_biometric_unlock(app: AppHandle, enabled: bool) -> Result<(), ApiError> {
140 - let mut prefs = load(&app);
141 - prefs.require_biometric_unlock = enabled;
142 - save(&app, &prefs)
143 - }
144 -
145 113 #[cfg(test)]
146 114 mod tests {
147 115 use super::Preferences;
148 116
149 117 #[test]
150 - fn a_file_written_before_the_app_lock_keeps_its_other_settings() {
151 - // `load` falls back to Default on any parse failure, so a missing
152 - // `#[serde(default)]` would not fail loudly -- it would silently turn
153 - // the update check back on for everyone who had switched it off. This
154 - // is the test that catches that, and the reason to add one per new
155 - // field rather than trusting the fallback.
156 - let old = r#"{"updateCheckOnLaunch": false}"#;
118 + fn a_file_written_before_a_setting_was_removed_keeps_its_others() {
119 + // `load` falls back to Default on any parse failure, so a struct that
120 + // rejected an unknown key would not fail loudly -- it would silently
121 + // turn the update check back on for everyone who had switched it off.
122 + // `requireBiometricUnlock` is the key an install written before the app
123 + // lock was removed still carries; there is no `deny_unknown_fields`, so
124 + // serde drops it.
125 + let old = r#"{"updateCheckOnLaunch": false, "requireBiometricUnlock": true}"#;
157 126 let prefs: Preferences = serde_json::from_str(old).expect("old file still parses");
158 127 assert!(!prefs.update_check_on_launch);
159 - assert!(!prefs.require_biometric_unlock, "absent means off");
160 - }
161 -
162 - #[test]
163 - fn the_app_lock_is_off_until_asked_for() {
164 - assert!(!Preferences::default().require_biometric_unlock);
165 128 }
166 129 }
@@ -52,9 +52,9 @@
52 52 //! platform, not a fact a startup can resolve and hold: biometry can be
53 53 //! enrolled and unenrolled while the app runs. Offering a toggle that cannot
54 54 //! work is worse than not offering one, which is the reasoning the JS carried
55 - //! and it is unchanged. The preference itself still exists and still syncs
56 - //! nowhere; nothing in the described app writes it, and on desktop nothing ever
57 - //! did.
55 + //! and it is unchanged. It is also the work a rebuild owes, and it is why the
56 + //! row is recorded here rather than in the plugin, command and preference that
57 + //! were removed on 2026-08-28 (goingson `458e1753`) for reaching nothing.
58 58 //!
59 59 //! **The Keyboard Shortcuts button.** There is no described help screen to open
60 60 //! and no chrome binding to open it with; see [`super::super::shell`], and