notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 71c5db4732175d551e6988afda0e8c4a3b035bb7
parent c01ce454b1b57569f7f207c2b66e0f33558f8666
Author: William Casarin <jb55@jb55.com>
Date:   Mon,  9 Feb 2026 11:17:48 -0800

dave: gate rfd dependency for Android compatibility

Move rfd to target-specific dependencies that only compile on desktop
platforms (Windows, macOS, Linux). Gate the folder picker dialog code
in directory_picker.rs with cfg attributes.

Also consolidate rfd version to workspace and fix rand to use workspace.

Diffstat:
MCargo.lock | 224++++++-------------------------------------------------------------------------
MCargo.toml | 1+
Mcrates/notedeck_columns/Cargo.toml | 2+-
Mcrates/notedeck_dave/Cargo.toml | 6++++--
Mcrates/notedeck_dave/src/lib.rs | 37++++++++++++++++++++++++++++---------
Mcrates/notedeck_dave/src/session.rs | 7++++++-
Mcrates/notedeck_dave/src/ui/directory_picker.rs | 16++++++++++++++++
Mcrates/notedeck_dave/src/ui/keybindings.rs | 6++++--
Mcrates/notedeck_dave/src/ui/scene.rs | 3++-
9 files changed, 79 insertions(+), 223 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -260,24 +260,6 @@ dependencies = [ [[package]] name = "ashpd" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd884d7c72877a94102c3715f3b1cd09ff4fac28221add3e57cfbe25c236d093" -dependencies = [ - "async-fs", - "async-net", - "enumflags2", - "futures-channel", - "futures-util", - "rand 0.8.5", - "serde", - "serde_repr", - "url", - "zbus 4.4.0", -] - -[[package]] -name = "ashpd" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" @@ -292,7 +274,7 @@ dependencies = [ "serde", "serde_repr", "url", - "zbus 5.7.1", + "zbus", ] [[package]] @@ -1677,7 +1659,7 @@ dependencies = [ "objc2-foundation 0.3.1", "parking_lot", "percent-encoding", - "pollster 0.4.0", + "pollster", "profiling", "raw-window-handle", "static_assertions", @@ -3821,19 +3803,6 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nix" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" @@ -4124,7 +4093,7 @@ dependencies = [ "profiling", "puffin", "puffin_egui", - "rfd 0.15.3", + "rfd", "rmpv", "robius-open", "security-framework 2.11.1", @@ -4167,7 +4136,7 @@ dependencies = [ "notedeck", "notedeck_ui", "rand 0.9.2", - "rfd 0.14.1", + "rfd", "serde", "serde_json", "sha2", @@ -4348,17 +4317,6 @@ dependencies = [ ] [[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - -[[package]] name = "objc-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4632,15 +4590,6 @@ dependencies = [ ] [[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - -[[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5007,12 +4956,6 @@ dependencies = [ [[package]] name = "pollster" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" - -[[package]] -name = "pollster" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" @@ -5613,34 +5556,11 @@ dependencies = [ [[package]] name = "rfd" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a73a7337fc24366edfca76ec521f51877b114e42dab584008209cca6719251" -dependencies = [ - "ashpd 0.8.1", - "block", - "dispatch", - "js-sys", - "log", - "objc", - "objc-foundation", - "objc_id", - "pollster 0.3.0", - "raw-window-handle", - "urlencoding", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-sys 0.48.0", -] - -[[package]] -name = "rfd" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80c844748fdc82aae252ee4594a89b6e7ebef1063de7951545564cbc4e57075d" dependencies = [ - "ashpd 0.11.0", + "ashpd", "block2 0.6.1", "dispatch2 0.2.0", "js-sys", @@ -5649,7 +5569,7 @@ dependencies = [ "objc2-app-kit 0.3.1", "objc2-core-foundation", "objc2-foundation 0.3.1", - "pollster 0.4.0", + "pollster", "raw-window-handle", "urlencoding", "wasm-bindgen", @@ -8424,16 +8344,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" [[package]] -name = "xdg-home" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] name = "xkbcommon-dl" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -8496,44 +8406,6 @@ dependencies = [ [[package]] name = "zbus" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" -dependencies = [ - "async-broadcast", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix 0.29.0", - "ordered-stream", - "rand 0.8.5", - "serde", - "serde_repr", - "sha1", - "static_assertions", - "tracing", - "uds_windows", - "windows-sys 0.52.0", - "xdg-home", - "zbus_macros 4.4.0", - "zbus_names 3.0.0", - "zvariant 4.2.0", -] - -[[package]] -name = "zbus" version = "5.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3a7c7cee313d044fca3f48fa782cb750c79e4ca76ba7bc7718cd4024cdf6f68" @@ -8552,7 +8424,7 @@ dependencies = [ "futures-core", "futures-lite", "hex", - "nix 0.30.1", + "nix", "ordered-stream", "serde", "serde_repr", @@ -8560,22 +8432,9 @@ dependencies = [ "uds_windows", "windows-sys 0.59.0", "winnow", - "zbus_macros 5.7.1", - "zbus_names 4.2.0", - "zvariant 5.5.3", -] - -[[package]] -name = "zbus_macros" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.104", - "zvariant_utils 2.1.0", + "zbus_macros", + "zbus_names", + "zvariant", ] [[package]] @@ -8588,20 +8447,9 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.104", - "zbus_names 4.2.0", - "zvariant 5.5.3", - "zvariant_utils 3.2.0", -] - -[[package]] -name = "zbus_names" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" -dependencies = [ - "serde", - "static_assertions", - "zvariant 4.2.0", + "zbus_names", + "zvariant", + "zvariant_utils", ] [[package]] @@ -8613,7 +8461,7 @@ dependencies = [ "serde", "static_assertions", "winnow", - "zvariant 5.5.3", + "zvariant", ] [[package]] @@ -8768,20 +8616,6 @@ dependencies = [ [[package]] name = "zvariant" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" -dependencies = [ - "endi", - "enumflags2", - "serde", - "static_assertions", - "url", - "zvariant_derive 4.2.0", -] - -[[package]] -name = "zvariant" version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d30786f75e393ee63a21de4f9074d4c038d52c5b1bb4471f955db249f9dffb1" @@ -8791,21 +8625,8 @@ dependencies = [ "serde", "url", "winnow", - "zvariant_derive 5.5.3", - "zvariant_utils 3.2.0", -] - -[[package]] -name = "zvariant_derive" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.104", - "zvariant_utils 2.1.0", + "zvariant_derive", + "zvariant_utils", ] [[package]] @@ -8818,18 +8639,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.104", - "zvariant_utils 3.2.0", -] - -[[package]] -name = "zvariant_utils" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", + "zvariant_utils", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml @@ -106,6 +106,7 @@ blurhash = "0.2.3" android-activity = { git = "https://github.com/damus-io/android-activity", rev = "4ee16f1585e4a75031dc10785163d4b920f95805", features = [ "game-activity" ] } keyring = { version = "3.6.3", features = ["apple-native", "windows-native", "linux-native-sync-persistent", "vendored"] } android-keyring = "0.2.0" +rfd = "0.15" [profile.small] inherits = 'release' diff --git a/crates/notedeck_columns/Cargo.toml b/crates/notedeck_columns/Cargo.toml @@ -61,7 +61,7 @@ oot_bitset = { workspace = true } human_format = "1.1.0" [target.'cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))'.dependencies] -rfd = "0.15" +rfd = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/crates/notedeck_dave/Cargo.toml b/crates/notedeck_dave/Cargo.toml @@ -20,7 +20,7 @@ serde = { workspace = true } nostrdb = { workspace = true } hex = { workspace = true } chrono = { workspace = true } -rand = "0.9.0" +rand = { workspace = true } uuid = { version = "1", features = ["v4"] } bytemuck = "1.22.0" futures = "0.3.31" @@ -28,9 +28,11 @@ dashmap = "6" #reqwest = "0.12.15" egui_extras = { workspace = true } similar = "2" -rfd = "0.14" dirs = "5" +[target.'cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))'.dependencies] +rfd = { workspace = true } + [dev-dependencies] tokio = { version = "1", features = ["rt-multi-thread", "macros", "test-util"] } diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs @@ -618,7 +618,9 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr // Add agentic-specific UI state if available if let Some(agentic) = &mut session.agentic { ui_builder = ui_builder - .permission_message_state(agentic.permission_message_state) + .permission_message_state( + agentic.permission_message_state, + ) .question_answers(&mut agentic.question_answers) .question_index(&mut agentic.question_index) .is_compacting(agentic.is_compacting); @@ -714,8 +716,13 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr }); ui.separator(); } - SessionListUi::new(&self.session_manager, &self.focus_queue, ctrl_held, self.ai_mode) - .ui(ui) + SessionListUi::new( + &self.session_manager, + &self.focus_queue, + ctrl_held, + self.ai_mode, + ) + .ui(ui) }) .inner }) @@ -783,7 +790,13 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr .fill(ui.visuals().faint_bg_color) .inner_margin(egui::Margin::symmetric(8, 12)) .show(ui, |ui| { - SessionListUi::new(&self.session_manager, &self.focus_queue, ctrl_held, self.ai_mode).ui(ui) + SessionListUi::new( + &self.session_manager, + &self.focus_queue, + ctrl_held, + self.ai_mode, + ) + .ui(ui) }) .inner; if let Some(action) = session_action { @@ -872,9 +885,9 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr // Add to recent directories self.directory_picker.add_recent(cwd.clone()); - let id = self - .session_manager - .new_resumed_session(cwd, resume_session_id, title, self.ai_mode); + let id = + self.session_manager + .new_resumed_session(cwd, resume_session_id, title, self.ai_mode); // Request focus on the new session's input if let Some(session) = self.session_manager.get_mut(id) { session.focus_requested = true; @@ -890,7 +903,11 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr /// Clone the active agent, creating a new session with the same working directory fn clone_active_agent(&mut self) { - if let Some(cwd) = self.session_manager.get_active().and_then(|s| s.cwd().cloned()) { + if let Some(cwd) = self + .session_manager + .get_active() + .and_then(|s| s.cwd().cloned()) + { self.create_session_with_cwd(cwd); } } @@ -904,7 +921,9 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr // Drain all pending connections (non-blocking) while let Some(mut pending) = listener.try_recv() { // Create the session and get its ID - let id = self.session_manager.new_session(pending.cwd.clone(), self.ai_mode); + let id = self + .session_manager + .new_session(pending.cwd.clone(), self.ai_mode); self.directory_picker.add_recent(pending.cwd); // Focus on new session diff --git a/crates/notedeck_dave/src/session.rs b/crates/notedeck_dave/src/session.rs @@ -79,7 +79,12 @@ impl AgenticSessionData { } /// Update a subagent's output (appending new content, keeping only the tail) - pub fn update_subagent_output(&mut self, chat: &mut [Message], task_id: &str, new_output: &str) { + pub fn update_subagent_output( + &mut self, + chat: &mut [Message], + task_id: &str, + new_output: &str, + ) { if let Some(&idx) = self.subagent_indices.get(task_id) { if let Some(Message::Subagent(subagent)) = chat.get_mut(idx) { subagent.output.push_str(new_output); diff --git a/crates/notedeck_dave/src/ui/directory_picker.rs b/crates/notedeck_dave/src/ui/directory_picker.rs @@ -253,6 +253,11 @@ impl DirectoryPicker { paint_keybind_hint(ui, hint_center, "B", 18.0); } + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux" + ))] if response .on_hover_text("Open folder picker dialog (B)") .clicked() @@ -268,6 +273,17 @@ impl DirectoryPicker { }); self.pending_folder_pick = Some(rx); } + + // On platforms without rfd (e.g., Android), just show the button disabled + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "linux" + )))] + { + let _ = response; + let _ = trigger_browse; + } }); if self.pending_folder_pick.is_some() { diff --git a/crates/notedeck_dave/src/ui/keybindings.rs b/crates/notedeck_dave/src/ui/keybindings.rs @@ -130,7 +130,8 @@ pub fn check_keybindings( } // Ctrl+\ to toggle auto-steal focus mode (Ctrl+Space conflicts with macOS input source switching) - agentic only - if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::Backslash)) { + if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::Backslash)) + { return Some(KeyAction::ToggleAutoSteal); } @@ -178,7 +179,8 @@ pub fn check_keybindings( // IMPORTANT: Only handle these when no text input has focus, to avoid // capturing keypresses when user is typing a message in tentative state // AskUserQuestion uses number keys for option selection, so we skip these bindings - if is_agentic && has_pending_permission && !has_pending_question && !ctx.wants_keyboard_input() { + if is_agentic && has_pending_permission && !has_pending_question && !ctx.wants_keyboard_input() + { // Shift+1 = tentative accept, Shift+2 = tentative deny // Note: egui may report shifted keys as their symbol (e.g., Shift+1 as Exclamationmark) // We check for both the symbol key and Shift+Num key to handle different behaviors diff --git a/crates/notedeck_dave/src/ui/scene.rs b/crates/notedeck_dave/src/ui/scene.rs @@ -268,7 +268,8 @@ impl AgentScene { for session in session_manager.iter() { if let Some(agentic) = &session.agentic { - let agent_pos = Pos2::new(agentic.scene_position.x, agentic.scene_position.y); + let agent_pos = + Pos2::new(agentic.scene_position.x, agentic.scene_position.y); if selection_rect.contains(agent_pos) { self.selected.push(session.id); }