notedeck

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

commit 09eeb57bd918e34e9539d6b823e318f8be43892c
parent a8c6baeacb8247f1f2eb5f6690efdcdafde90624
Author: William Casarin <jb55@jb55.com>
Date:   Thu, 31 Jul 2025 16:05:43 -0700

Merge notebook (feature gated)

This merges the experimental notebook app, which can be
enabled with --notebook.

We also switch to bitflags for notedeck options

Diffstat:
MCargo.lock | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
MCargo.toml | 2++
Mcrates/notedeck/Cargo.toml | 1+
Mcrates/notedeck/src/app.rs | 9+++++++--
Mcrates/notedeck/src/args.rs | 40++++++++++++++--------------------------
Mcrates/notedeck/src/lib.rs | 2++
Acrates/notedeck/src/options.rs | 39+++++++++++++++++++++++++++++++++++++++
Mcrates/notedeck_chrome/Cargo.toml | 1+
Mcrates/notedeck_chrome/src/android.rs | 13+++++++++++--
Mcrates/notedeck_chrome/src/app.rs | 7+++++--
Mcrates/notedeck_chrome/src/chrome.rs | 54++++++++++++++++++++++++++++++++++++++++++++++--------
Mcrates/notedeck_chrome/src/notedeck.rs | 13++++++++++---
Mcrates/notedeck_chrome/src/setup.rs | 16++++++++++------
Mcrates/notedeck_columns/src/app.rs | 7+++++--
Acrates/notedeck_notebook/Cargo.toml | 9+++++++++
Acrates/notedeck_notebook/demo.canvas | 232+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/notedeck_notebook/src/debug.rs | 26++++++++++++++++++++++++++
Acrates/notedeck_notebook/src/lib.rs | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/notedeck_notebook/src/ui.rs | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
19 files changed, 812 insertions(+), 63 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1008,6 +1008,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] @@ -1263,6 +1264,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -1409,6 +1411,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" [[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] name = "ecolor" version = "0.31.1" source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd" @@ -1626,7 +1634,7 @@ version = "0.3.0" dependencies = [ "bech32", "ewebsock", - "hashbrown", + "hashbrown 0.15.4", "hex", "mio", "nostr 0.37.0", @@ -2300,7 +2308,7 @@ checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ "bitflags 2.9.1", "gpu-descriptor-types", - "hashbrown", + "hashbrown 0.15.4", ] [[package]] @@ -2324,6 +2332,12 @@ dependencies = [ [[package]] name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" @@ -2367,6 +2381,17 @@ dependencies = [ ] [[package]] +name = "hex_color" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37f101bf4c633f7ca2e4b5e136050314503dd198e78e325ea602c327c484ef0" +dependencies = [ + "arrayvec", + "rand 0.8.5", + "serde", +] + +[[package]] name = "hex_lit" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2697,12 +2722,23 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.4", "serde", ] @@ -2891,6 +2927,19 @@ dependencies = [ ] [[package]] +name = "jsoncanvas" +version = "0.1.6" +source = "git+https://github.com/jb55/jsoncanvas?rev=ae60f96e4d022cf037e086b793cacc3225bc14e5#ae60f96e4d022cf037e086b793cacc3225bc14e5" +dependencies = [ + "hex_color", + "serde", + "serde_json", + "serde_with", + "thiserror 1.0.69", + "url", +] + +[[package]] name = "khronos-egl" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3212,7 +3261,7 @@ dependencies = [ "cfg_aliases", "codespan-reporting", "hexf-parse", - "indexmap", + "indexmap 2.9.0", "log", "rustc-hash 1.1.0", "spirv", @@ -3434,6 +3483,7 @@ dependencies = [ "base32", "bech32", "bincode", + "bitflags 2.9.1", "blurhash", "dirs", "eframe", @@ -3445,7 +3495,7 @@ dependencies = [ "fluent", "fluent-langneg", "fluent-resmgr", - "hashbrown", + "hashbrown 0.15.4", "hex", "image", "jni 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3490,6 +3540,7 @@ dependencies = [ "notedeck", "notedeck_columns", "notedeck_dave", + "notedeck_notebook", "notedeck_ui", "profiling", "puffin", @@ -3523,11 +3574,11 @@ dependencies = [ "egui_virtual_list", "ehttp", "enostr", - "hashbrown", + "hashbrown 0.15.4", "hex", "human_format", "image", - "indexmap", + "indexmap 2.9.0", "nostrdb", "notedeck", "notedeck_ui", @@ -3585,6 +3636,15 @@ dependencies = [ ] [[package]] +name = "notedeck_notebook" +version = "0.5.9" +dependencies = [ + "egui", + "jsoncanvas", + "notedeck", +] + +[[package]] name = "notedeck_ui" version = "0.5.9" dependencies = [ @@ -3595,7 +3655,7 @@ dependencies = [ "egui_extras", "ehttp", "enostr", - "hashbrown", + "hashbrown 0.15.4", "image", "nostrdb", "notedeck", @@ -4424,7 +4484,7 @@ source = "git+https://github.com/jb55/puffin?rev=c6a6242adaf90b6292c0f462d2acd34 dependencies = [ "egui", "egui_extras", - "indexmap", + "indexmap 2.9.0", "natord", "once_cell", "parking_lot", @@ -4753,6 +4813,26 @@ dependencies = [ ] [[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5114,6 +5194,30 @@ dependencies = [ ] [[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5266,7 +5370,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "indexmap", + "indexmap 2.9.0", "itoa", "memchr", "ryu", @@ -5306,6 +5410,38 @@ dependencies = [ ] [[package]] +name = "serde_with" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.9.0", + "schemars 0.9.0", + "schemars 1.0.4", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5905,7 +6041,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap", + "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", @@ -6688,7 +6824,7 @@ dependencies = [ "bitflags 2.9.1", "cfg_aliases", "document-features", - "indexmap", + "indexmap 2.9.0", "log", "naga", "once_cell", diff --git a/Cargo.toml b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "crates/notedeck_chrome", "crates/notedeck_columns", "crates/notedeck_dave", + "crates/notedeck_notebook", "crates/notedeck_ui", "crates/enostr", "crates/tokenator", "crates/notedeck_dave", "crates/notedeck_ui", @@ -48,6 +49,7 @@ notedeck = { path = "crates/notedeck" } notedeck_chrome = { path = "crates/notedeck_chrome" } notedeck_columns = { path = "crates/notedeck_columns" } notedeck_dave = { path = "crates/notedeck_dave" } +notedeck_notebook = { path = "crates/notedeck_notebook" } notedeck_ui = { path = "crates/notedeck_ui" } tokenator = { path = "crates/tokenator" } once_cell = "1.19.0" diff --git a/crates/notedeck/Cargo.toml b/crates/notedeck/Cargo.toml @@ -47,6 +47,7 @@ fluent-langneg = { workspace = true } unic-langid = { workspace = true } once_cell = { workspace = true } md5 = { workspace = true } +bitflags = { workspace = true } regex = "1" [dev-dependencies] diff --git a/crates/notedeck/src/app.rs b/crates/notedeck/src/app.rs @@ -4,6 +4,7 @@ use crate::persist::{AppSizeHandler, SettingsHandler}; use crate::wallet::GlobalWallet; use crate::zaps::Zaps; use crate::JobPool; +use crate::NotedeckOptions; use crate::{ frame_history::FrameHistory, AccountStorage, Accounts, AppContext, Args, DataPath, DataPathType, Directory, Images, NoteAction, NoteCache, RelayDebugView, UnknownIds, @@ -109,7 +110,7 @@ impl eframe::App for Notedeck { }); self.app_size.try_save_app_size(ctx); - if self.args.relay_debug { + if self.args.options.contains(NotedeckOptions::RelayDebug) { if self.pool.debug.is_none() { self.pool.use_debug(); } @@ -170,7 +171,7 @@ impl Notedeck { let config = Config::new().set_ingester_threads(2).set_mapsize(map_size); - let keystore = if parsed_args.use_keystore { + let keystore = if parsed_args.options.contains(NotedeckOptions::UseKeystore) { let keys_path = path.path(DataPathType::Keys); let selected_key_path = path.path(DataPathType::SelectedKey); Some(AccountStorage::new( @@ -276,6 +277,10 @@ impl Notedeck { } } + pub fn options(&self) -> NotedeckOptions { + self.args.options + } + pub fn app<A: App + 'static>(mut self, app: A) -> Self { self.set_app(app); self diff --git a/crates/notedeck/src/args.rs b/crates/notedeck/src/args.rs @@ -1,23 +1,15 @@ use std::collections::BTreeSet; +use crate::NotedeckOptions; use enostr::{Keypair, Pubkey, SecretKey}; use tracing::error; use unic_langid::{LanguageIdentifier, LanguageIdentifierError}; pub struct Args { pub relays: Vec<String>, - pub is_mobile: Option<bool>, pub locale: Option<LanguageIdentifier>, - pub show_note_client: bool, pub keys: Vec<Keypair>, - pub light: bool, - pub debug: bool, - pub relay_debug: bool, - - /// Enable when running tests so we don't panic on app startup - pub tests: bool, - - pub use_keystore: bool, + pub options: NotedeckOptions, pub dbpath: Option<String>, pub datapath: Option<String>, } @@ -28,14 +20,8 @@ impl Args { let mut unrecognized_args = BTreeSet::new(); let mut res = Args { relays: vec![], - is_mobile: None, keys: vec![], - light: false, - show_note_client: false, - debug: false, - relay_debug: false, - tests: false, - use_keystore: true, + options: NotedeckOptions::default(), dbpath: None, datapath: None, locale: None, @@ -47,9 +33,9 @@ impl Args { let arg = &args[i]; if arg == "--mobile" { - res.is_mobile = Some(true); + res.options.set(NotedeckOptions::Mobile, true); } else if arg == "--light" { - res.light = true; + res.options.set(NotedeckOptions::LightTheme, true); } else if arg == "--locale" { i += 1; let Some(locale) = args.get(i) else { @@ -68,11 +54,11 @@ impl Args { } } } else if arg == "--dark" { - res.light = false; + res.options.set(NotedeckOptions::LightTheme, false); } else if arg == "--debug" { - res.debug = true; + res.options.set(NotedeckOptions::Debug, true); } else if arg == "--testrunner" { - res.tests = true; + res.options.set(NotedeckOptions::Tests, true); } else if arg == "--pub" || arg == "--npub" { i += 1; let pubstr = if let Some(next_arg) = args.get(i) { @@ -135,11 +121,13 @@ impl Args { }; res.relays.push(relay.clone()); } else if arg == "--no-keystore" { - res.use_keystore = false; + res.options.set(NotedeckOptions::UseKeystore, true); } else if arg == "--relay-debug" { - res.relay_debug = true; - } else if arg == "--show-note-client" { - res.show_note_client = true; + res.options.set(NotedeckOptions::RelayDebug, true); + } else if arg == "--show-client" { + res.options.set(NotedeckOptions::ShowClient, true); + } else if arg == "--notebook" { + res.options.set(NotedeckOptions::FeatureNotebook, true); } else { unrecognized_args.insert(arg.clone()); } diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs @@ -18,6 +18,7 @@ mod muted; pub mod name; pub mod note; mod notecache; +mod options; mod persist; pub mod platform; pub mod profile; @@ -68,6 +69,7 @@ pub use note::{ RootIdError, RootNoteId, RootNoteIdBuf, ScrollInfo, ZapAction, }; pub use notecache::{CachedNote, NoteCache}; +pub use options::NotedeckOptions; pub use persist::*; pub use profile::get_profile_url; pub use relay_debug::RelayDebugView; diff --git a/crates/notedeck/src/options.rs b/crates/notedeck/src/options.rs @@ -0,0 +1,39 @@ +use bitflags::bitflags; + +bitflags! { + #[repr(transparent)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct NotedeckOptions: u64 { + // ===== Settings ====== + /// Are we on light theme? + const LightTheme = 1 << 0; + + /// Debug controls, fps stats + const Debug = 1 << 1; + + /// Show relay debug window? + const RelayDebug = 1 << 2; + + /// Are we running as tests? + const Tests = 1 << 3; + + /// Use keystore? + const UseKeystore = 1 << 4; + + /// Show client on notes? + const ShowClient = 1 << 5; + + /// Simulate is_compiled_as_mobile ? + const Mobile = 1 << 6; + + // ===== Feature Flags ====== + /// Is notebook enabled? + const FeatureNotebook = 1 << 32; + } +} + +impl Default for NotedeckOptions { + fn default() -> Self { + NotedeckOptions::UseKeystore + } +} diff --git a/crates/notedeck_chrome/Cargo.toml b/crates/notedeck_chrome/Cargo.toml @@ -16,6 +16,7 @@ egui = { workspace = true } notedeck_columns = { workspace = true } notedeck_ui = { workspace = true } notedeck_dave = { workspace = true } +notedeck_notebook = { workspace = true } notedeck = { workspace = true } nostrdb = { workspace = true } puffin = { workspace = true, optional = true } diff --git a/crates/notedeck_chrome/src/android.rs b/crates/notedeck_chrome/src/android.rs @@ -5,6 +5,7 @@ use egui_winit::winit::platform::android::activity::AndroidApp; use notedeck::enostr::Error; use notedeck_columns::Damus; use notedeck_dave::Dave; +use notedeck_notebook::Notebook; use crate::{app::NotedeckApp, chrome::Chrome, setup::setup_chrome}; use notedeck::Notedeck; @@ -80,6 +81,7 @@ pub async fn android_main(app: AndroidApp) { let context = &mut notedeck.app_context(); let dave = Dave::new(cc.wgpu_render_state.as_ref()); let columns = Damus::new(context, &app_args); + let notebook = Notebook::new(); let mut chrome = Chrome::new(); // ensure we recognized all the arguments @@ -93,8 +95,15 @@ pub async fn android_main(app: AndroidApp) { return Err(Error::Empty.into()); } - chrome.add_app(NotedeckApp::Columns(columns)); - chrome.add_app(NotedeckApp::Dave(dave)); + chrome.add_app(NotedeckApp::Columns(Box::new(columns))); + chrome.add_app(NotedeckApp::Dave(Box::new(dave))); + + if notedeck + .options() + .contains(NotedeckOptions::FeaturesNotebook) + { + chrome.add_app(NotedeckApp::Notebook(Box::default())); + } // test dav chrome.set_active(0); diff --git a/crates/notedeck_chrome/src/app.rs b/crates/notedeck_chrome/src/app.rs @@ -1,11 +1,13 @@ use notedeck::{AppAction, AppContext}; use notedeck_columns::Damus; use notedeck_dave::Dave; +use notedeck_notebook::Notebook; #[allow(clippy::large_enum_variant)] pub enum NotedeckApp { - Dave(Dave), - Columns(Damus), + Dave(Box<Dave>), + Columns(Box<Damus>), + Notebook(Box<Notebook>), Other(Box<dyn notedeck::App>), } @@ -14,6 +16,7 @@ impl notedeck::App for NotedeckApp { match self { NotedeckApp::Dave(dave) => dave.update(ctx, ui), NotedeckApp::Columns(columns) => columns.update(ctx, ui), + NotedeckApp::Notebook(notebook) => notebook.update(ctx, ui), NotedeckApp::Other(other) => other.update(ctx, ui), } } diff --git a/crates/notedeck_chrome/src/chrome.rs b/crates/notedeck_chrome/src/chrome.rs @@ -6,12 +6,14 @@ use egui::{vec2, Button, Color32, Label, Layout, Rect, RichText, ThemePreference use egui_extras::{Size, StripBuilder}; use nostrdb::{ProfileRecord, Transaction}; use notedeck::{ - tr, App, AppAction, AppContext, Localization, NotedeckTextStyle, UserAccount, WalletType, + tr, App, AppAction, AppContext, Localization, NotedeckOptions, NotedeckTextStyle, UserAccount, + WalletType, }; use notedeck_columns::{ column::SelectionResult, timeline::kind::ListKind, timeline::TimelineKind, Damus, }; use notedeck_dave::{Dave, DaveAvatar}; +use notedeck_notebook::Notebook; use notedeck_ui::{app_images, AnimationHelper, ProfilePic}; static ICON_WIDTH: f32 = 40.0; @@ -199,6 +201,16 @@ impl Chrome { None } + fn get_notebook(&mut self) -> Option<&mut Notebook> { + for app in &mut self.apps { + if let NotedeckApp::Notebook(notebook) = app { + return Some(notebook); + } + } + + None + } + fn switch_to_dave(&mut self) { for (i, app) in self.apps.iter().enumerate() { if let NotedeckApp::Dave(_) = app { @@ -207,6 +219,14 @@ impl Chrome { } } + fn switch_to_notebook(&mut self) { + for (i, app) in self.apps.iter().enumerate() { + if let NotedeckApp::Notebook(_) = app { + self.active = i as i32; + } + } + } + fn switch_to_columns(&mut self) { for (i, app) in self.apps.iter().enumerate() { if let NotedeckApp::Columns(_) = app { @@ -428,13 +448,11 @@ impl Chrome { ui.add(milestone_name(i18n)); ui.add_space(16.0); //let dark_mode = ui.ctx().style().visuals.dark_mode; + if columns_button(ui) + .on_hover_cursor(egui::CursorIcon::PointingHand) + .clicked() { - if columns_button(ui) - .on_hover_cursor(egui::CursorIcon::PointingHand) - .clicked() - { - self.active = 0; - } + self.active = 0; } ui.add_space(32.0); @@ -446,6 +464,16 @@ impl Chrome { self.switch_to_dave(); } } + //ui.add_space(32.0); + + if let Some(_notebook) = self.get_notebook() { + if notebook_button(ui) + .on_hover_cursor(egui::CursorIcon::PointingHand) + .clicked() + { + self.switch_to_notebook(); + } + } } } @@ -583,6 +611,16 @@ fn accounts_button(ui: &mut egui::Ui) -> egui::Response { ) } +fn notebook_button(ui: &mut egui::Ui) -> egui::Response { + expanding_button( + "notebook-button", + 40.0, + app_images::algo_image(), + app_images::algo_image(), + ui, + ) +} + fn dave_sidebar_rect(ui: &mut egui::Ui) -> Rect { let size = vec2(60.0, 60.0); let available = ui.available_rect_before_wrap(); @@ -861,7 +899,7 @@ fn bottomup_sidebar( .add(wallet_button()) .on_hover_cursor(egui::CursorIcon::PointingHand); - if ctx.args.debug { + if ctx.args.options.contains(NotedeckOptions::Debug) { ui.weak(format!("{}", ctx.frame_history.fps() as i32)); ui.weak(format!( "{:10.1}", diff --git a/crates/notedeck_chrome/src/notedeck.rs b/crates/notedeck_chrome/src/notedeck.rs @@ -10,7 +10,7 @@ static GLOBAL: AccountingAllocator<std::alloc::System> = AccountingAllocator::new(std::alloc::System); use notedeck::enostr::Error; -use notedeck::{DataPath, DataPathType, Notedeck}; +use notedeck::{DataPath, DataPathType, Notedeck, NotedeckOptions}; use notedeck_chrome::{ setup::{generate_native_options, setup_chrome}, Chrome, NotedeckApp, @@ -117,8 +117,15 @@ async fn main() { return Err(Error::Empty.into()); } - chrome.add_app(NotedeckApp::Columns(columns)); - chrome.add_app(NotedeckApp::Dave(dave)); + chrome.add_app(NotedeckApp::Columns(Box::new(columns))); + chrome.add_app(NotedeckApp::Dave(Box::new(dave))); + + if notedeck + .options() + .contains(NotedeckOptions::FeatureNotebook) + { + chrome.add_app(NotedeckApp::Notebook(Box::default())); + } chrome.set_active(0); diff --git a/crates/notedeck_chrome/src/setup.rs b/crates/notedeck_chrome/src/setup.rs @@ -2,7 +2,7 @@ use crate::{fonts, theme}; use eframe::NativeOptions; use egui::{FontId, ThemePreference}; -use notedeck::{AppSizeHandler, DataPath, NotedeckTextStyle}; +use notedeck::{AppSizeHandler, DataPath, NotedeckOptions, NotedeckTextStyle}; use notedeck_ui::app_images; use tracing::info; @@ -13,16 +13,20 @@ pub fn setup_chrome( note_body_font_size: f32, zoom_factor: f32, ) { - let is_mobile = args - .is_mobile - .unwrap_or(notedeck::ui::is_compiled_as_mobile()); + let is_mobile = + args.options.contains(NotedeckOptions::Mobile) || notedeck::ui::is_compiled_as_mobile(); let is_oled = notedeck::ui::is_oled(); // Some people have been running notedeck in debug, let's catch that! - if !args.tests && cfg!(debug_assertions) && !args.debug { + if !args.options.contains(NotedeckOptions::Tests) + && cfg!(debug_assertions) + && !args.options.contains(NotedeckOptions::Debug) + { println!("--- WELCOME TO DAMUS NOTEDECK! ---"); - println!("It looks like are running notedeck in debug mode, unless you are a developer, this is not likely what you want."); + println!( + "It looks like are running notedeck in debug mode, unless you are a developer, this is not likely what you want." + ); println!("If you are a developer, run `cargo run -- --debug` to skip this message."); println!("For everyone else, try again with `cargo run --release`. Enjoy!"); println!("---------------------------------"); diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs @@ -19,7 +19,7 @@ use enostr::{ClientMessage, PoolRelay, Pubkey, RelayEvent, RelayMessage, RelayPo use nostrdb::Transaction; use notedeck::{ tr, ui::is_narrow, Accounts, AppAction, AppContext, DataPath, DataPathType, FilterState, - Images, JobsCache, Localization, SettingsHandler, UnknownIds, + Images, JobsCache, Localization, NotedeckOptions, SettingsHandler, UnknownIds, }; use notedeck_ui::{ media::{MediaViewer, MediaViewerFlags, MediaViewerState}, @@ -442,7 +442,10 @@ impl Damus { let mut options = AppOptions::default(); let tmp_columns = !parsed_args.columns.is_empty(); options.set(AppOptions::TmpColumns, tmp_columns); - options.set(AppOptions::Debug, app_context.args.debug); + options.set( + AppOptions::Debug, + app_context.args.options.contains(NotedeckOptions::Debug), + ); options.set( AppOptions::SinceOptimize, parsed_args.is_flag_set(ColumnsFlag::SinceOptimize), diff --git a/crates/notedeck_notebook/Cargo.toml b/crates/notedeck_notebook/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "notedeck_notebook" +edition = "2024" +version.workspace = true + +[dependencies] +jsoncanvas = { git = "https://github.com/jb55/jsoncanvas", rev = "ae60f96e4d022cf037e086b793cacc3225bc14e5" } +notedeck = { workspace = true } +egui = { workspace = true } diff --git a/crates/notedeck_notebook/demo.canvas b/crates/notedeck_notebook/demo.canvas @@ -0,0 +1,232 @@ +{ + "nodes": [ + { + "id": "c450e9832ffe7a3d", + "type": "text", + "text": "```\nIncident Identifier: 3AAF0AF2-37A5-4104-957C-D0205F54B5BB\nDistributor ID: com.apple.TestFlight\nHardware Model: iPhone15,2\nProcess: DamusNotificationService [15759]\nPath: /private/var/containers/Bundle/Application/58C02D0F-9151-4F13-BD68-547D0D89A8E6/damus.app/PlugIns/DamusNotificationService.appex/DamusNotificationService\nIdentifier: com.jb55.damus2.DamusNotificationService\nVersion: 1.15 (1048)\nAppVariant: 1:iPhone15,2:18\nBeta: YES\nCode Type: ARM-64 (Native)\nRole: Unspecified\nParent Process: launchd [1]\nCoalition: com.jb55.damus2.DamusNotificationService [7022]\n\nDate/Time: 2025-07-14 12:26:33.8942 -1000\nLaunch Time: 2025-07-14 12:15:17.0329 -1000\nOS Version: iPhone OS 18.5 (22F76)\nRelease Type: User\nBaseband Version: 3.60.02\nReport Version: 104\n\nException Type: EXC_CRASH (SIGKILL)\nException Codes: 0x0000000000000000, 0x0000000000000000\nTermination Reason: RUNNINGBOARD 0xdead10cc \n\nTriggered by Thread: 0\n\n\nThread 0 name:\nThread 0 Crashed:\n0 libsystem_kernel.dylib \t0x00000001d9dc7ce4 mach_msg2_trap + 8\n1 libsystem_kernel.dylib \t0x00000001d9dcb39c mach_msg2_internal + 76 (mach_msg.c:201)\n2 libsystem_kernel.dylib \t0x00000001d9dcb2b8 mach_msg_overwrite + 428 (mach_msg.c:0)\n3 libsystem_kernel.dylib \t0x00000001d9dcb100 mach_msg + 24 (mach_msg.c:323)\n4 CoreFoundation \t0x0000000188bba900 __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2637)\n5 CoreFoundation \t0x0000000188bb91f0 __CFRunLoopRun + 1208 (CFRunLoop.c:3021)\n6 CoreFoundation \t0x0000000188bbac3c CFRunLoopRunSpecific + 572 (CFRunLoop.c:3434)\n7 Foundation \t0x000000018783279c -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212 (NSRunLoop.m:375)\n8 Foundation \t0x0000000187837108 -[NSRunLoop(NSRunLoop) run] + 64 (NSRunLoop.m:400)\n9 libxpc.dylib \t0x0000000213332d34 _xpc_objc_main + 336 (main.m:267)\n10 libxpc.dylib \t0x00000002133352a4 _xpc_main + 64 (init.c:1293)\n11 libxpc.dylib \t0x0000000213335484 xpc_main + 64 (init.c:1376)\n12 Foundation \t0x00000001879409dc -[NSXPCListener resume] + 308 (NSXPCListener.m:471)\n13 PlugInKit \t0x00000001b9e67f84 -[PKService runUsingServiceListener:] + 364 (PKService.m:219)\n14 PlugInKit \t0x00000001b9e67e10 -[PKService run] + 20 (PKService.m:185)\n15 PlugInKit \t0x00000001b9e67adc +[PKService main] + 520 (PKService.m:126)\n16 PlugInKit \t0x00000001b9e682d8 +[PKService _defaultRun:arguments:] + 16 (PKService.m:265)\n17 ExtensionFoundation \t0x000000019816fc20 EXExtensionMain + 288 (EXExtensionMain.m:0)\n18 Foundation \t0x00000001879ac274 NSExtensionMain + 200 (NSExtensionMain.m:21)\n19 dyld \t0x00000001afa8ff08 start + 6040 (dyldMain.cpp:1450)\n\nThread 1:\n0 libsystem_pthread.dylib \t0x00000002132ccaa4 start_wqthread + 0\n\nThread 2:\n0 libsystem_kernel.dylib \t0x00000001d9dc7c60 semaphore_wait_trap + 8\n1 libdispatch.dylib \t0x0000000190b368e0 _dispatch_sema4_wait + 28 (lock.c:139)\n2 libdispatch.dylib \t0x0000000190b36e90 _dispatch_semaphore_wait_slow + 132 (semaphore.c:132)\n3 CFNetwork \t0x000000018a27d12c CFURLConnectionSendSynchronousRequest + 356 (CFURLConnection_Synchronous.c:209)\n4 CFNetwork \t0x000000018a2982c0 +[NSURLConnection sendSynchronousRequest:returningResponse:error:] + 296 (NSURLConnection.mm:478)\n5 Foundation \t0x00000001878c9dec -[NSData(NSData) initWithContentsOfURL:options:maxLength:error:] + 240 (NSData.m:992)\n6 ImageIO \t0x000000018e7cabf8 IIOCreateDataWithContentsOfURL + 56 (IIOObjUtils.mm:50)\n7 ImageIO \t0x000000018e685cd4 IIOImageSource::IIOImageSource(__CFURL const*, IIODictionary*) + 920 (CGImageSource.cpp:561)\n8 ImageIO \t0x000000018e6856e8 CGImageSourceCreateWithURL + 260 (CGImageSource.cpp:4600)\n9 DamusNotificationService \t0x0000000102733770 closure #2 in NotificationService.didReceive(_:withContentHandler:) + 104 (NotificationService.swift:113)\n10 DamusNotificationService \t0x000000010273d03d <deduplicated_symbol> + 1\n11 DamusNotificationService \t0x0000000102778c55 specialized thunk for @escaping @isolated(any) @callee_guaranteed @async () -> (@out A) + 1 (/<compiler-generated>:0)\n12 DamusNotificationService \t0x000000010273e885 <deduplicated_symbol> + 1\n13 libswift_Concurrency.dylib \t0x000000019478d241 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1 (Task.cpp:537)\n\nThread 3:\n0 libsystem_pthread.dylib \t0x00000002132ccaa4 start_wqthread + 0\n\nThread 4:\n0 libsystem_pthread.dylib \t0x00000002132ccaa4 start_wqthread + 0\n\nThread 5:\n0 libsystem_kernel.dylib \t0x00000001d9dcd438 __psynch_cvwait + 8\n1 libsystem_pthread.dylib \t0x00000002132cde50 _pthread_cond_wait + 984 (pthread_cond.c:862)\n2 DamusNotificationService \t0x0000000102710b90 prot_queue_pop_all + 52 (protected_queue.h:190)\n3 DamusNotificationService \t0x00000001027107ac ndb_writer_thread + 120 (nostrdb.c:2709)\n4 libsystem_pthread.dylib \t0x00000002132cf344 _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x00000002132ccab8 thread_start + 8\n\nThread 6:\n0 libsystem_kernel.dylib \t0x00000001d9dcd438 __psynch_cvwait + 8\n1 libsystem_pthread.dylib \t0x00000002132cde50 _pthread_cond_wait + 984 (pthread_cond.c:862)\n2 DamusNotificationService \t0x0000000102710b90 prot_queue_pop_all + 52 (protected_queue.h:190)\n3 DamusNotificationService \t0x00000001027115c0 ndb_ingester_thread + 156 (nostrdb.c:2809)\n4 libsystem_pthread.dylib \t0x00000002132cf344 _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x00000002132ccab8 thread_start + 8\n\nThread 7:\n0 libsystem_kernel.dylib \t0x00000001d9dcd438 __psynch_cvwait + 8\n1 libsystem_pthread.dylib \t0x00000002132cde50 _pthread_cond_wait + 984 (pthread_cond.c:862)\n2 DamusNotificationService \t0x0000000102710b90 prot_queue_pop_all + 52 (protected_queue.h:190)\n3 DamusNotificationService \t0x00000001027115c0 ndb_ingester_thread + 156 (nostrdb.c:2809)\n4 libsystem_pthread.dylib \t0x00000002132cf344 _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x00000002132ccab8 thread_start + 8\n\nThread 8:\n0 libsystem_kernel.dylib \t0x00000001d9dcd438 __psynch_cvwait + 8\n1 libsystem_pthread.dylib \t0x00000002132cde50 _pthread_cond_wait + 984 (pthread_cond.c:862)\n2 DamusNotificationService \t0x0000000102710b90 prot_queue_pop_all + 52 (protected_queue.h:190)\n3 DamusNotificationService \t0x00000001027115c0 ndb_ingester_thread + 156 (nostrdb.c:2809)\n4 libsystem_pthread.dylib \t0x00000002132cf344 _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x00000002132ccab8 thread_start + 8\n\nThread 9:\n0 libsystem_kernel.dylib \t0x00000001d9dcd438 __psynch_cvwait + 8\n1 libsystem_pthread.dylib \t0x00000002132cde50 _pthread_cond_wait + 984 (pthread_cond.c:862)\n2 DamusNotificationService \t0x0000000102710b90 prot_queue_pop_all + 52 (protected_queue.h:190)\n3 DamusNotificationService \t0x00000001027115c0 ndb_ingester_thread + 156 (nostrdb.c:2809)\n4 libsystem_pthread.dylib \t0x00000002132cf344 _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x00000002132ccab8 thread_start + 8\n\nThread 10:\n0 libsystem_pthread.dylib \t0x00000002132ccaa4 start_wqthread + 0\n\n\nThread 0 crashed with ARM Thread State (64-bit):\n x0: 0x0000000010004005 x1: 0x0000000507000806 x2: 0x0000000200000000 x3: 0x0000240300000000\n x4: 0x0000000000000000 x5: 0x0000240300000000 x6: 0x0000000000000002 x7: 0x00000000ffffffff\n x8: 0x0000000000000000 x9: 0x0000000000000000 x10: 0x0000000000000002 x11: 0x0000000000000000\n x12: 0x0000000000000000 x13: 0x0000000000002403 x14: 0x0000000000000000 x15: 0x0000000000000000\n x16: 0xffffffffffffffd1 x17: 0x0000000000000002 x18: 0x0000000000000000 x19: 0x00000000ffffffff\n x20: 0x0000000000000002 x21: 0x0000240300000000 x22: 0x0000000000000000 x23: 0x0000240300000000\n x24: 0x000000016d702058 x25: 0x0000000200000000 x26: 0x0000000507000806 x27: 0xfffffffffffffbbf\n x28: 0x00000001f38ed000 fp: 0x000000016d701fc0 lr: 0x00000001d9dcb39c\n sp: 0x000000016d701f70 pc: 0x00000001d9dc7ce4 cpsr: 0x1000\n esr: 0x56000080 Address size fault\n\n\nBinary Images:\n 0x1026fc000 - 0x102aaffff DamusNotificationService arm64 <a3f29d049d8d30afa8e2a546b137c2a3> /private/var/containers/Bundle/Application/58C02D0F-9151-4F13-BD68-547D0D89A8E6/damus.app/PlugIns/DamusNotificationService.appex/DamusNotificationService\n 0x187823000 - 0x188496ddf Foundation arm64e <34de055d8683380a9198c3347211d13d> /System/Library/Frameworks/Foundation.framework/Foundation\n 0x188ba9000 - 0x189125fff CoreFoundation arm64e <7821f73c378b3a10be90ef526b7dba93> /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation\n 0x18a13a000 - 0x18a4ffb9f CFNetwork arm64e <a35a109c49d23986965d4ed7e0b6681e> /System/Library/Frameworks/CFNetwork.framework/CFNetwork\n 0x18e65e000 - 0x18ec3259f ImageIO arm64e <10cc4cb3264c3269b6a2dc7a2d7b2179> /System/Library/Frameworks/ImageIO.framework/ImageIO\n 0x190b33000 - 0x190b78b1f libdispatch.dylib arm64e <395da84f715d334e8d41a16cd93fc83c> /usr/lib/system/libdispatch.dylib\n 0x194728000 - 0x1947a7a3f libswift_Concurrency.dylib arm64e <dcb9e73a92ba3782bc6d3e1906622689> /usr/lib/swift/libswift_Concurrency.dylib\n 0x198147000 - 0x19820363f ExtensionFoundation arm64e <c7396624315c328aa5c247cfe6e3f88e> /System/Library/Frameworks/ExtensionFoundation.framework/ExtensionFoundation\n 0x1afa51000 - 0x1afaeb857 dyld arm64e <86d5253d4fd136f3b4ab25982c90cbf4> /usr/lib/dyld\n 0x1b9e4f000 - 0x1b9e89a7f PlugInKit arm64e <931fdec36ed5300796a4eea5aadd47bb> /System/Library/PrivateFrameworks/PlugInKit.framework/PlugInKit\n 0x1d9dc7000 - 0x1d9e00ebf libsystem_kernel.dylib arm64e <9e195be11733345ea9bf50d0d7059647> /usr/lib/system/libsystem_kernel.dylib\n 0x2132cc000 - 0x2132d83f3 libsystem_pthread.dylib arm64e <b37430d8e3af33e481e1faed9ee26e8a> /usr/lib/system/libsystem_pthread.dylib\n 0x213317000 - 0x21335edbf libxpc.dylib arm64e <a46c2755958633b89ea9377f71175516> /usr/lib/system/libxpc.dylib\n\nEOF\n```", + "x": -69, + "y": -82, + "width": 889, + "height": 1062 + }, + { + "id": "8ba807ac67740bc0", + "type": "text", + "text": "# 1st most common crash", + "x": 167, + "y": -320, + "width": 373, + "height": 80 + }, + { + "id": "906aae4882968fc5", + "type": "text", + "text": "`0xdead10cc` (`3735883980`) — pronounced “dead lock”\n\nThe operating system terminated the app because it held on to a file lock or SQLite database lock during suspension. Request additional background execution time on the main thread with [`beginBackgroundTask(withName:expirationHandler:)`](https://developer.apple.com/documentation/UIKit/UIApplication/beginBackgroundTask\\(withName:expirationHandler:\\)). Make this request well before starting to write to the file in order to complete those operations and relinquish the lock before the app suspends. In an app extension, use [`beginActivity(options:reason:)`](https://developer.apple.com/documentation/Foundation/ProcessInfo/beginActivity\\(options:reason:\\)) to manage this work.", + "x": -480, + "y": 1260, + "width": 659, + "height": 273 + }, + { + "id": "e619c24d803fc838", + "type": "text", + "text": "Holding ndb db file in notification target", + "x": 279, + "y": 1192, + "width": 281, + "height": 108, + "color": "2" + }, + { + "id": "4f83ab4aa30b00f1", + "type": "text", + "text": "```\nIncident Identifier: F46AD3DE-40FA-483E-9E70-D48DAB7643E6\nDistributor ID: com.apple.TestFlight\nHardware Model: iPhone16,1\nProcess: damus [1140]\nPath: /private/var/containers/Bundle/Application/3D9243CC-8814-4347-80D5-C0BACEF9D2CD/damus.app/damus\nIdentifier: com.jb55.damus2\nVersion: 1.12 (682)\nAppStoreTools: 16C5031b\nAppVariant: 1:iPhone16,1:18\nBeta: YES\nCode Type: ARM-64 (Native)\nRole: unknown\nParent Process: launchd [1]\nCoalition: com.jb55.damus2 [1234]\n\nDate/Time: 2024-12-21 13:26:52.2609 +0800\nLaunch Time: 2024-12-21 13:26:50.6386 +0800\nOS Version: iPhone OS 18.1.1 (22B91)\nRelease Type: User\nBaseband Version: 2.20.03\nReport Version: 104\n\nException Type: EXC_CRASH (SIGKILL)\nException Codes: 0x0000000000000000, 0x0000000000000000\nTermination Reason: RUNNINGBOARD 0xdead10cc \n\nTriggered by Thread: 0\n\n\nThread 0 name:\nThread 0 Crashed:\n0 libsystem_kernel.dylib \t0x00000001e4352688 mach_msg2_trap + 8 (:-1)\n1 libsystem_kernel.dylib \t0x00000001e4355d98 mach_msg2_internal + 80 (mach_msg.c:201)\n2 libsystem_kernel.dylib \t0x00000001e4355cb0 mach_msg_overwrite + 424 (mach_msg.c:0)\n3 libsystem_kernel.dylib \t0x00000001e4355afc mach_msg + 24 (mach_msg.c:323)\n4 CoreFoundation \t0x0000000193f91a84 __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2637)\n5 CoreFoundation \t0x0000000193f91130 __CFRunLoopRun + 1212 (CFRunLoop.c:3021)\n6 CoreFoundation \t0x0000000193f90830 CFRunLoopRunSpecific + 588 (CFRunLoop.c:3434)\n7 GraphicsServices \t0x00000001dff701c4 GSEventRunModal + 164 (GSEvent.c:2196)\n8 UIKitCore \t0x0000000196af6eb0 -[UIApplication _run] + 816 (UIApplication.m:3844)\n9 UIKitCore \t0x0000000196ba55b4 UIApplicationMain + 340 (UIApplication.m:5496)\n10 SwiftUI \t0x00000001986f9f98 closure #1 in KitRendererCommon(_:) + 168 (UIKitApp.swift:68)\n11 SwiftUI \t0x00000001986da664 runApp<A>(_:) + 100 (UIKitApp.swift:16)\n12 SwiftUI \t0x00000001986dd490 static App.main() + 180 (App.swift:121)\n13 damus \t0x000000010516f630 static damusApp.$main() + 56 (damusApp.swift:0)\n14 damus \t0x000000010516f630 main + 68\n15 dyld \t0x00000001b997eec8 start + 2724 (dyldMain.cpp:1334)\n\nThread 1:\n0 libsystem_pthread.dylib \t0x000000021c4c4480 start_wqthread + 0 (:-1)\n\nThread 2:\n0 libsystem_pthread.dylib \t0x000000021c4c4480 start_wqthread + 0 (:-1)\n\nThread 3:\n0 libsystem_pthread.dylib \t0x000000021c4c4480 start_wqthread + 0 (:-1)\n\nThread 4:\n0 libsystem_pthread.dylib \t0x000000021c4c4480 start_wqthread + 0 (:-1)\n\nThread 5:\n0 libsystem_pthread.dylib \t0x000000021c4c4480 start_wqthread + 0 (:-1)\n\nThread 6:\n0 libsystem_pthread.dylib \t0x000000021c4c4480 start_wqthread + 0 (:-1)\n\nThread 7:\n0 libsystem_pthread.dylib \t0x000000021c4c4480 start_wqthread + 0 (:-1)\n\nThread 8:\n0 libsystem_pthread.dylib \t0x000000021c4c4480 start_wqthread + 0 (:-1)\n\nThread 9 name:\nThread 9:\n0 libsystem_kernel.dylib \t0x00000001e4352688 mach_msg2_trap + 8 (:-1)\n1 libsystem_kernel.dylib \t0x00000001e4355d98 mach_msg2_internal + 80 (mach_msg.c:201)\n2 libsystem_kernel.dylib \t0x00000001e4355cb0 mach_msg_overwrite + 424 (mach_msg.c:0)\n3 libsystem_kernel.dylib \t0x00000001e4355afc mach_msg + 24 (mach_msg.c:323)\n4 CoreFoundation \t0x0000000193f91a84 __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2637)\n5 CoreFoundation \t0x0000000193f91130 __CFRunLoopRun + 1212 (CFRunLoop.c:3021)\n6 CoreFoundation \t0x0000000193f90830 CFRunLoopRunSpecific + 588 (CFRunLoop.c:3434)\n7 Foundation \t0x0000000192c38500 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212 (NSRunLoop.m:373)\n8 Foundation \t0x0000000192c38350 -[NSRunLoop(NSRunLoop) runUntilDate:] + 64 (NSRunLoop.m:420)\n9 UIKitCore \t0x0000000196b0a358 -[UIEventFetcher threadMain] + 420 (UIEventFetcher.m:1241)\n10 Foundation \t0x0000000192c496c8 __NSThread__start__ + 724 (NSThread.m:991)\n11 libsystem_pthread.dylib \t0x000000021c4c937c _pthread_start + 136 (pthread.c:931)\n12 libsystem_pthread.dylib \t0x000000021c4c4494 thread_start + 8 (:-1)\n\nThread 10:\n0 libsystem_kernel.dylib \t0x00000001e4357f90 __psynch_cvwait + 8 (:-1)\n1 libsystem_pthread.dylib \t0x000000021c4c6a50 _pthread_cond_wait + 1204 (pthread_cond.c:862)\n2 damus \t0x0000000104e6703c prot_queue_pop_all + 60 (protected_queue.h:190)\n3 damus \t0x0000000104e66c5c ndb_writer_thread + 140 (nostrdb.c:2709)\n4 libsystem_pthread.dylib \t0x000000021c4c937c _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x000000021c4c4494 thread_start + 8 (:-1)\n\nThread 11:\n0 libsystem_kernel.dylib \t0x00000001e4357f90 __psynch_cvwait + 8 (:-1)\n1 libsystem_pthread.dylib \t0x000000021c4c6a50 _pthread_cond_wait + 1204 (pthread_cond.c:862)\n2 damus \t0x0000000104e6703c prot_queue_pop_all + 60 (protected_queue.h:190)\n3 damus \t0x0000000104e67a7c ndb_ingester_thread + 168 (nostrdb.c:2809)\n4 libsystem_pthread.dylib \t0x000000021c4c937c _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x000000021c4c4494 thread_start + 8 (:-1)\n\nThread 12:\n0 libsystem_kernel.dylib \t0x00000001e4357f90 __psynch_cvwait + 8 (:-1)\n1 libsystem_pthread.dylib \t0x000000021c4c6a50 _pthread_cond_wait + 1204 (pthread_cond.c:862)\n2 damus \t0x0000000104e6703c prot_queue_pop_all + 60 (protected_queue.h:190)\n3 damus \t0x0000000104e67a7c ndb_ingester_thread + 168 (nostrdb.c:2809)\n4 libsystem_pthread.dylib \t0x000000021c4c937c _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x000000021c4c4494 thread_start + 8 (:-1)\n\nThread 13:\n0 libsystem_kernel.dylib \t0x00000001e4357f90 __psynch_cvwait + 8 (:-1)\n1 libsystem_pthread.dylib \t0x000000021c4c6a50 _pthread_cond_wait + 1204 (pthread_cond.c:862)\n2 damus \t0x0000000104e6703c prot_queue_pop_all + 60 (protected_queue.h:190)\n3 damus \t0x0000000104e67a7c ndb_ingester_thread + 168 (nostrdb.c:2809)\n4 libsystem_pthread.dylib \t0x000000021c4c937c _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x000000021c4c4494 thread_start + 8 (:-1)\n\nThread 14:\n0 libsystem_kernel.dylib \t0x00000001e4357f90 __psynch_cvwait + 8 (:-1)\n1 libsystem_pthread.dylib \t0x000000021c4c6a50 _pthread_cond_wait + 1204 (pthread_cond.c:862)\n2 damus \t0x0000000104e6703c prot_queue_pop_all + 60 (protected_queue.h:190)\n3 damus \t0x0000000104e67a7c ndb_ingester_thread + 168 (nostrdb.c:2809)\n4 libsystem_pthread.dylib \t0x000000021c4c937c _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x000000021c4c4494 thread_start + 8 (:-1)\n\nThread 15:\n0 libsystem_pthread.dylib \t0x000000021c4c4480 start_wqthread + 0 (:-1)\n\nThread 16 name:\nThread 16:\n0 libsystem_kernel.dylib \t0x00000001e435261c semaphore_timedwait_trap + 8 (:-1)\n1 libdispatch.dylib \t0x000000019bc966e8 _dispatch_sema4_timedwait + 64 (lock.c:154)\n2 libdispatch.dylib \t0x000000019bc96ce8 _dispatch_semaphore_wait_slow + 76 (semaphore.c:116)\n3 libdispatch.dylib \t0x000000019bca7b60 _dispatch_worker_thread + 324 (queue.c:7509)\n4 libsystem_pthread.dylib \t0x000000021c4c937c _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x000000021c4c4494 thread_start + 8 (:-1)\n\n\nThread 0 crashed with ARM Thread State (64-bit):\n x0: 0x0000000010004005 x1: 0x0000000507000806 x2: 0x0000000200000000 x3: 0x00002e0300000000\n x4: 0x0000000000000000 x5: 0x00002e0300000000 x6: 0x0000000000000002 x7: 0x00000000ffffffff\n x8: 0xfffffffffffffbbf x9: 0x0000000000000002 x10: 0x0000000000000000 x11: 0x0000000000000000\n x12: 0x0000000000000000 x13: 0x0000000000002e03 x14: 0x0000000000000000 x15: 0x0000000000000000\n x16: 0xffffffffffffffd1 x17: 0x000000019bcde358 x18: 0x0000000000000000 x19: 0x00000000ffffffff\n x20: 0x0000000000000002 x21: 0x00002e0300000000 x22: 0x0000000000000000 x23: 0x00002e0300000000\n x24: 0x000000016afae6d8 x25: 0x0000000200000000 x26: 0x0000000507000806 x27: 0x0000000507000806\n x28: 0x0000000107000806 fp: 0x000000016afae640 lr: 0x00000001e4355d98\n sp: 0x000000016afae5f0 pc: 0x00000001e4352688 cpsr: 0x1000\n esr: 0x56000080 Address size fault\n\n\nBinary Images:\n 0x104e50000 - 0x105653fff damus arm64 <43d5ad08c0d13100afedd27aeb88d9ff> /private/var/containers/Bundle/Application/3D9243CC-8814-4347-80D5-C0BACEF9D2CD/damus.app/damus\n 0x1062c8000 - 0x1062d3fff libobjc-trampolines.dylib arm64e <35a44678195b39c2bdd7072893564b45> /private/preboot/Cryptexes/OS/usr/lib/libobjc-trampolines.dylib\n 0x192b81000 - 0x19388efff Foundation arm64e <6d0212cc3b9e32c9be2072989ce3acb8> /System/Library/Frameworks/Foundation.framework/Foundation\n 0x193f3e000 - 0x194480fff CoreFoundation arm64e <1532d3d89b3b3f2fb35f55a20ddf411b> /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation\n 0x196724000 - 0x1985f7fff UIKitCore arm64e <575e5140fa6a37c2b00ba4eacedfda53> /System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore\n 0x1985f8000 - 0x1998ecfff SwiftUI arm64e <9f67c19cfcde3e979fc23bba36998297> /System/Library/Frameworks/SwiftUI.framework/SwiftUI\n 0x19bc92000 - 0x19bcd7fff libdispatch.dylib arm64e <7de7ec03cfb7349d9b9e8782b38f231d> /usr/lib/system/libdispatch.dylib\n 0x19bcd8000 - 0x19bd57ff3 libsystem_c.dylib arm64e <0150f750db0a3f54b23ad21c55af8824> /usr/lib/system/libsystem_c.dylib\n 0x1a5e11000 - 0x1a6060fff MediaExperience arm64e <e2f69071040233a7ba6dc247b73ec092> /System/Library/PrivateFrameworks/MediaExperience.framework/MediaExperience\n 0x1b994b000 - 0x1b99ce99f dyld arm64e <3060d36a16ce3c3a92583881459f5714> /usr/lib/dyld\n 0x1dff6f000 - 0x1dff77fff GraphicsServices arm64e <8425ea11000e3e5e8abcbddf3ff3fa32> /System/Library/PrivateFrameworks/GraphicsServices.framework/GraphicsServices\n 0x1e4351000 - 0x1e438aff3 libsystem_kernel.dylib arm64e <b9618c71c0cb31b6825f92a4737c890e> /usr/lib/system/libsystem_kernel.dylib\n 0x21c4c3000 - 0x21c4cfff3 libsystem_pthread.dylib arm64e <3ca98e388eee3c269862c5f66aad93c0> /usr/lib/system/libsystem_pthread.dylib\n\nEOF\n```", + "x": 2320, + "y": -82, + "width": 1060, + "height": 1120 + }, + { + "id": "b27baddab9517a38", + "type": "text", + "text": "# 2nd most common crash", + "x": 1415, + "y": -300, + "width": 385, + "height": 77 + }, + { + "id": "0df74aca0192124e", + "type": "text", + "text": "Holding ndb db file in main target", + "x": 2710, + "y": 1160, + "width": 281, + "height": 108, + "color": "2" + }, + { + "id": "e48e5b32c0265d73", + "type": "text", + "text": "# 3rd most common crash", + "x": 2635, + "y": -280, + "width": 430, + "height": 102 + }, + { + "id": "c04db70a0e694555", + "type": "text", + "text": "```swift\nlet sender_profile = {\n let txn = state.ndb.lookup_profile(nostr_event.pubkey)\n let profile = txn?.unsafeUnownedValue?.profile\n let picture = ((profile?.picture.map { URL(string: $0) }) ?? URL(string: robohash(nostr_event.pubkey)))! // <-- Force unwrap error\n return ProfileBuf(picture: picture,\n name: profile?.name,\n display_name: profile?.display_name,\n nip05: profile?.nip05)\n }()\n```", + "x": 3596, + "y": 45, + "width": 1384, + "height": 375 + }, + { + "id": "d275d9d3cca27202", + "type": "text", + "text": "# 4th most common crash", + "x": 4073, + "y": -240, + "width": 430, + "height": 102 + }, + { + "id": "39b704e37669fc83", + "type": "text", + "text": "```\nIncident Identifier: 0B12A949-43C3-4B05-820B-643210EAA9FB\nDistributor ID: com.apple.TestFlight\nHardware Model: iPhone14,4\nProcess: damus [2492]\nPath: /private/var/containers/Bundle/Application/7FFD6048-6DD6-4092-A83F-B80314A637A5/damus.app/damus\nIdentifier: com.jb55.damus2\nVersion: 1.14 (914)\nAppStoreTools: 16E137\nAppVariant: 1:iPhone14,4:18\nBeta: YES\nCode Type: ARM-64 (Native)\nRole: Background\nParent Process: launchd [1]\nCoalition: com.jb55.damus2 [1048]\n\nDate/Time: 2025-04-16 14:36:08.5474 -0700\nLaunch Time: 2025-04-16 14:34:59.0919 -0700\nOS Version: iPhone OS 18.4 (22E240)\nRelease Type: User\nBaseband Version: 4.51.04\nReport Version: 104\n\nException Type: EXC_BAD_ACCESS (SIGSEGV)\nException Subtype: KERN_INVALID_ADDRESS at 0x00000000000000d0\nException Codes: 0x0000000000000001, 0x00000000000000d0\nVM Region Info: 0xd0 is not in any region. Bytes before following region: 4331470640\n REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL\n UNUSED SPACE AT START\n---> \n __TEXT 1022d0000-102618000 [ 3360K] r-x/r-x SM=COW /var/containers/Bundle/Application/7FFD6048-6DD6-4092-A83F-B80314A637A5/damus.app/damus\nTermination Reason: SIGNAL 11 Segmentation fault: 11\nTerminating Process: exc handler [2492]\n\nTriggered by Thread: 38\n```\n\n```\nThread 38 name:\nThread 38 Crashed:\n0 libsystem_pthread.dylib \t0x00000002234344c0 pthread_mutex_lock + 12 (pthread_mutex.c:1709)\n1 damus \t0x00000001022e5a28 prot_queue_push + 32 (protected_queue.h:91)\n2 damus \t0x00000001022e4570 threadpool_dispatch + 48 (threadpool.h:76)\n3 damus \t0x00000001022e4570 ndb_ingester_queue_event + 64 (nostrdb.c:2950)\n4 damus \t0x00000001022e4570 ndb_process_event + 136 (nostrdb.c:3211)\n5 damus \t0x0000000102305394 closure #1 in Ndb.process_client_event(_:) + 76\n6 damus \t0x0000000102305340 closure #1 in Ndb.process_event(_:) + 20\n7 damus \t0x0000000102353f08 partial apply for closure #1 in Ndb.process_event(_:) + 20 (/<compiler-generated>:0)\n8 libswiftCore.dylib \t0x000000019776c384 _StringGuts._slowWithCString<A>(_:) + 76 (StringGuts.swift:239)\n9 damus \t0x0000000102702420 specialized String.withCString<A>(_:) + 176 (/<compiler-generated>:0)\n10 damus \t0x000000010234e70c Ndb.process_event(_:) + 28 (Ndb.swift:530)\n11 damus \t0x000000010234e70c $s5damus9RelayPoolC9add_relayyyAC0B10DescriptorVAC0B5ErrorOYKFyAA14WebSocketEventOcfU0_ + 436 (RelayPool.swift:139)\n12 damus \t0x00000001025e8bb4 RelayConnection.receive(event:) + 416 (RelayConnection.swift:118)\n13 damus \t0x00000001025e9670 closure #2 in RelayConnection.connect(force:) + 68 (RelayConnection.swift:79)\n14 Combine \t0x00000001a2605b18 Subscribers.Sink.receive(_:) + 92 (Sink.swift:128)\n15 Combine \t0x00000001a2605aac protocol witness for Subscriber.receive(_:) in conformance Subscribers.Sink<A, B> + 24 (<compiler-generated>:0)\n16 Combine \t0x00000001a260494c closure #1 in Publishers.ReceiveOn.Inner.receive(_:) + 284 (ReceiveOn.swift:169)\n17 libswiftDispatch.dylib \t0x00000001a2525a48 thunk for @escaping @callee_guaranteed () -> () + 36 (:-1)\n18 libdispatch.dylib \t0x00000001a0e49aac _dispatch_call_block_and_release + 32 (init.c:1575)\n19 libdispatch.dylib \t0x00000001a0e63584 _dispatch_client_callout + 16 (client_callout.mm:85)\n20 libdispatch.dylib \t0x00000001a0e7fe84 <deduplicated_symbol> + 32 (:-1)\n21 libdispatch.dylib \t0x00000001a0e5bf24 _dispatch_root_queue_drain + 736 (queue.c:7342)\n22 libdispatch.dylib \t0x00000001a0e5c54c _dispatch_worker_thread2 + 156 (queue.c:7410)\n23 libsystem_pthread.dylib \t0x0000000223435624 _pthread_wqthread + 232 (pthread.c:2709)\n24 libsystem_pthread.dylib \t0x00000002234329f8 start_wqthread + 8 (:-1)\n```", + "x": 5120, + "y": -2, + "width": 1240, + "height": 1722 + }, + { + "id": "e182bb08633f0c8e", + "type": "text", + "text": "# 5th most common crash", + "x": 5525, + "y": -240, + "width": 430, + "height": 102 + }, + { + "id": "a7b2dd98e8fb8b98", + "type": "text", + "text": "Unsafe \"force unwrap\"", + "x": 4157, + "y": 489, + "width": 250, + "height": 60, + "color": "2" + }, + { + "id": "a3718eb8e2f9f28d", + "type": "text", + "text": "Probably memory or concurrency issue related to Ndb", + "x": 5619, + "y": 1899, + "width": 341, + "height": 101, + "color": "2" + }, + { + "id": "915e462d7852f6bb", + "type": "text", + "text": "Memory error related to ndb", + "x": 1483, + "y": 2520, + "width": 250, + "height": 60, + "color": "2" + }, + { + "id": "3d398a7223484f22", + "type": "text", + "text": "```\nIncident Identifier: CF913FAA-5091-4EF7-8D0B-E29C25BFC366\nDistributor ID: com.apple.TestFlight\nHardware Model: iPhone17,2\nProcess: DamusNotificationService [35444]\nPath: /private/var/containers/Bundle/Application/C48A3744-BAD0-41F2-812F-AA9F65FE651E/damus.app/PlugIns/DamusNotificationService.appex/DamusNotificationService\nIdentifier: com.jb55.damus2.DamusNotificationService\nVersion: 1.14 (914)\nAppVariant: 1:iPhone17,2:18\nBeta: YES\nCode Type: ARM-64 (Native)\nRole: Unspecified\nParent Process: launchd [1]\nCoalition: com.jb55.damus2.DamusNotificationService [1098]\n\nDate/Time: 2025-04-22 11:08:47.5725 -0700\nLaunch Time: 2025-04-22 10:20:47.0779 -0700\nOS Version: iPhone OS 18.3.2 (22D82)\nRelease Type: User\nBaseband Version: 1.40.03\nReport Version: 104\n\nException Type: EXC_BAD_ACCESS (SIGSEGV)\nException Subtype: KERN_INVALID_ADDRESS at 0x0000000000000009\nException Codes: 0x0000000000000001, 0x0000000000000009\nVM Region Info: 0x9 is not in any region. Bytes before following region: 4332535799\n REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL\n UNUSED SPACE AT START\n---> \n __TEXT 1023d4000-102608000 [ 2256K] r-x/r-x SM=COW /var/containers/Bundle/Application/C48A3744-BAD0-41F2-812F-AA9F65FE651E/damus.app/PlugIns/DamusNotificationService.appex/DamusNotificationService\nTermination Reason: SIGNAL 11 Segmentation fault: 11\nTerminating Process: exc handler [35444]\n\nTriggered by Thread: 1\n\n\nThread 0 name:\nThread 0:\n0 libsystem_kernel.dylib \t0x00000001e63b0788 mach_msg2_trap + 8 (:-1)\n1 libsystem_kernel.dylib \t0x00000001e63b3e98 mach_msg2_internal + 80 (mach_msg.c:201)\n2 libsystem_kernel.dylib \t0x00000001e63b3db0 mach_msg_overwrite + 424 (mach_msg.c:0)\n3 libsystem_kernel.dylib \t0x00000001e63b3bfc mach_msg + 24 (mach_msg.c:323)\n4 CoreFoundation \t0x0000000194ccb804 __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2637)\n5 CoreFoundation \t0x0000000194ccaeb0 __CFRunLoopRun + 1212 (CFRunLoop.c:3021)\n6 CoreFoundation \t0x0000000194d1d284 CFRunLoopRunSpecific + 588 (CFRunLoop.c:3434)\n7 Foundation \t0x00000001938830e8 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212 (NSRunLoop.m:373)\n8 Foundation \t0x000000019387f2b0 -[NSRunLoop(NSRunLoop) run] + 64 (NSRunLoop.m:398)\n9 libxpc.dylib \t0x000000021fa7fdb4 _xpc_objc_main + 336 (main.m:267)\n10 libxpc.dylib \t0x000000021fa82320 _xpc_main + 64 (init.c:1291)\n11 libxpc.dylib \t0x000000021fa82500 xpc_main + 64 (init.c:1374)\n12 Foundation \t0x0000000193a509a8 -[NSXPCListener resume] + 308 (NSXPCListener.m:471)\n13 PlugInKit \t0x00000001c5a3944c -[PKService runUsingServiceListener:] + 364 (PKService.m:219)\n14 PlugInKit \t0x00000001c5a392d8 -[PKService run] + 20 (PKService.m:185)\n15 PlugInKit \t0x00000001c5a38f94 +[PKService main] + 524 (PKService.m:126)\n16 PlugInKit \t0x00000001c5a397a8 +[PKService _defaultRun:arguments:] + 16 (PKService.m:265)\n17 ExtensionFoundation \t0x00000001a401e724 EXExtensionMain + 288 (EXExtensionMain.m:0)\n18 Foundation \t0x0000000193b17350 NSExtensionMain + 204 (NSExtensionMain.m:21)\n19 dyld \t0x00000001baf79de8 start + 2724 (dyldMain.cpp:1338)\n\nThread 1 name:\nThread 1 Crashed:\n0 DamusNotificationService \t0x00000001023e0fe8 mdb_cursor_open + 60 (mdb.c:7687)\n1 DamusNotificationService \t0x00000001023e4270 ndb_get_tsid + 92 (nostrdb.c:1335)\n2 DamusNotificationService \t0x00000001023e4384 ndb_lookup_tsid + 64 (nostrdb.c:1390)\n3 DamusNotificationService \t0x000000010241e13c specialized closure #1 in Ndb.lookup_profile_with_txn_inner<A>(pubkey:txn:) + 100 (Ndb.swift:416)\n4 DamusNotificationService \t0x000000010241e3e8 specialized closure #1 in Ndb.lookup_profile_with_txn_inner<A>(pubkey:txn:) + 16 (/<compiler-generated>:0)\n5 DamusNotificationService \t0x000000010241e3e8 specialized closure #1 in Ndb.lookup_profile_with_txn_inner<A>(pubkey:txn:) + 16\n6 DamusNotificationService \t0x000000010241e3e8 specialized __DataStorage.withUnsafeBytes<A>(in:apply:) + 20\n7 DamusNotificationService \t0x000000010241e3e8 specialized __DataStorage.withUnsafeBytes<A>(in:apply:) + 20\n8 DamusNotificationService \t0x000000010241e3e8 specialized Data._Representation.withUnsafeBytes<A>(_:) + 508\n9 DamusNotificationService \t0x000000010241e3e8 0x1023d4000 + 304104 (/<compiler-generated>:0)\n10 DamusNotificationService \t0x000000010241e3e8 0x1023d4000 + 304104 (/<compiler-generated>:0)\n11 DamusNotificationService \t0x000000010241e3e8 0x1023d4000 + 304104 (/<compiler-generated>:0)\n12 DamusNotificationService \t0x000000010241e3e8 0x1023d4000 + 304104 (/<compiler-generated>:0)\n13 DamusNotificationService \t0x000000010241e9d8 specialized Data._Representation.withUnsafeBytes<A>(_:) + 12 (Ndb.swift:0)\n14 DamusNotificationService \t0x000000010241e9d8 specialized Ndb.lookup_profile_with_txn_inner<A>(pubkey:txn:) + 12 (Ndb.swift:492)\n15 DamusNotificationService \t0x000000010241e9d8 closure #1 in Ndb.lookup_profile(_:txn_name:) + 12 (/<compiler-generated>:0)\n16 DamusNotificationService \t0x000000010241e9d8 specialized NdbTxn.init(ndb:with:name:) + 1220\n17 DamusNotificationService \t0x000000010240b094 specialized NdbTxn.init(ndb:with:name:) + 28 (/<compiler-generated>:0)\n18 DamusNotificationService \t0x000000010240b094 Ndb.lookup_profile(_:txn_name:) + 28 (Ndb.swift:56)\n19 DamusNotificationService \t0x000000010240b094 closure #1 in NotificationService.didReceive(_:withContentHandler:) + 348 (NotificationService.swift:61)\n20 DamusNotificationService \t0x0000000102414170 specialized NotificationService.didReceive(_:withContentHandler:) + 1144 (NotificationService.swift:60)\n21 DamusNotificationService \t0x000000010240e0fc specialized NotificationService.didReceive(_:withContentHandler:) + 12 (/<compiler-generated>:0)\n22 DamusNotificationService \t0x000000010240e0fc @objc NotificationService.didReceive(_:withContentHandler:) + 76\n23 UserNotifications \t0x00000001c089a73c -[_UNNotificationServiceExtensionRemoteContext didReceiveNotificationRequest:withCompletionHandler:] + 356 (_UNNotificationServiceExtensionRemoteContext.m:55)\n24 Foundation \t0x0000000193910d8c __NSXPCCONNECTION_IS_CALLING_OUT_TO_EXPORTED_OBJECT_S1__ + 16 (NSXPCConnection.m:182)\n25 Foundation \t0x000000019390ffc8 -[NSXPCConnection _decodeAndInvokeMessageWithEvent:reply:flags:] + 1632 (NSXPCConnection.m:706)\n26 Foundation \t0x000000019390f220 message_handler_message + 88 (NSXPCConnection.m:824)\n27 Foundation \t0x000000019390f0d8 message_handler + 152 (NSXPCConnection.m:795)\n28 libxpc.dylib \t0x000000021fa75a50 _xpc_connection_call_event_handler + 144 (connection.c:837)\n29 libxpc.dylib \t0x000000021fa775cc _xpc_connection_mach_event + 1140 (connection.c:2457)\n30 libdispatch.dylib \t0x000000019ca17068 _dispatch_client_callout4 + 20 (object.m:616)\n31 libdispatch.dylib \t0x000000019ca33424 _dispatch_mach_msg_invoke + 464 (mach.c:2511)\n32 libdispatch.dylib \t0x000000019ca1e42c _dispatch_lane_serial_drain + 352 (queue.c:3934)\n33 libdispatch.dylib \t0x000000019ca34178 _dispatch_mach_invoke + 456 (mach.c:2861)\n34 libdispatch.dylib \t0x000000019ca1e42c _dispatch_lane_serial_drain + 352 (queue.c:3934)\n35 libdispatch.dylib \t0x000000019ca1f158 _dispatch_lane_invoke + 432 (queue.c:4025)\n36 libdispatch.dylib \t0x000000019ca2a38c _dispatch_root_queue_drain_deferred_wlh + 288 (queue.c:7193)\n37 libdispatch.dylib \t0x000000019ca29bd8 _dispatch_workloop_worker_thread + 540 (queue.c:6787)\n38 libsystem_pthread.dylib \t0x000000021fa1c680 _pthread_wqthread + 288 (pthread.c:2696)\n39 libsystem_pthread.dylib \t0x000000021fa1a474 start_wqthread + 8 (:-1)\n\nThread 2:\n0 libsystem_pthread.dylib \t0x000000021fa1a46c start_wqthread + 0 (:-1)\n\nThread 3 name:\nThread 3:\n0 libsystem_kernel.dylib \t0x00000001e63b657c __getdirentries64 + 8 (:-1)\n1 libsystem_c.dylib \t0x000000019ca5bc84 _readdir_unlocked + 220 (readdir.c:97)\n2 libsystem_c.dylib \t0x000000019ca5bb8c readdir + 44 (readdir.c:137)\n3 Foundation \t0x00000001939d8d9c _POSIXDirectoryContentsSequence.Iterator.next() + 76 (FileOperations+Enumeration.swift:331)\n4 Foundation \t0x00000001939d88bc specialized _NSFileManagerBridge.contentsOfDirectory(atPath:) + 672 (FileManager+Bridge.swift:63)\n5 Foundation \t0x00000001939d85a4 @objc _NSFileManagerBridge.contentsOfDirectory(atPath:) + 76 (<compiler-generated>:62)\n6 DamusNotificationService \t0x0000000102469bd0 specialized closure #1 in DiskStorage.Backend.setupCacheChecking() + 360 (DiskStorage.swift:105)\n7 DamusNotificationService \t0x0000000102402bd4 <deduplicated_symbol> + 28\n8 libdispatch.dylib \t0x000000019ca15248 _dispatch_call_block_and_release + 32 (init.c:1549)\n9 libdispatch.dylib \t0x000000019ca16fa8 _dispatch_client_callout + 20 (object.m:576)\n10 libdispatch.dylib \t0x000000019ca29094 _dispatch_root_queue_drain + 860 (queue.c:7331)\n11 libdispatch.dylib \t0x000000019ca296c4 _dispatch_worker_thread2 + 156 (queue.c:7399)\n12 libsystem_pthread.dylib \t0x000000021fa1c644 _pthread_wqthread + 228 (pthread.c:2709)\n13 libsystem_pthread.dylib \t0x000000021fa1a474 start_wqthread + 8 (:-1)\n\nThread 4:\n0 libsystem_kernel.dylib \t0x00000001e63b6090 __psynch_cvwait + 8 (:-1)\n1 libsystem_pthread.dylib \t0x000000021fa1cf98 _pthread_cond_wait + 1204 (pthread_cond.c:862)\n2 DamusNotificationService \t0x00000001023e88b8 prot_queue_pop_all + 52 (protected_queue.h:190)\n3 DamusNotificationService \t0x00000001023e84d4 ndb_writer_thread + 120 (nostrdb.c:2709)\n4 libsystem_pthread.dylib \t0x000000021fa1a7d0 _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x000000021fa1a480 thread_start + 8 (:-1)\n\nThread 5:\n0 libsystem_kernel.dylib \t0x00000001e63b6090 __psynch_cvwait + 8 (:-1)\n1 libsystem_pthread.dylib \t0x000000021fa1cf98 _pthread_cond_wait + 1204 (pthread_cond.c:862)\n2 DamusNotificationService \t0x00000001023e88b8 prot_queue_pop_all + 52 (protected_queue.h:190)\n3 DamusNotificationService \t0x00000001023e92e8 ndb_ingester_thread + 156 (nostrdb.c:2809)\n4 libsystem_pthread.dylib \t0x000000021fa1a7d0 _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x000000021fa1a480 thread_start + 8 (:-1)\n\nThread 6:\n0 libsystem_kernel.dylib \t0x00000001e63b6090 __psynch_cvwait + 8 (:-1)\n1 libsystem_pthread.dylib \t0x000000021fa1cf98 _pthread_cond_wait + 1204 (pthread_cond.c:862)\n2 DamusNotificationService \t0x00000001023e88b8 prot_queue_pop_all + 52 (protected_queue.h:190)\n3 DamusNotificationService \t0x00000001023e92e8 ndb_ingester_thread + 156 (nostrdb.c:2809)\n4 libsystem_pthread.dylib \t0x000000021fa1a7d0 _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x000000021fa1a480 thread_start + 8 (:-1)\n\nThread 7:\n0 libsystem_kernel.dylib \t0x00000001e63b6090 __psynch_cvwait + 8 (:-1)\n1 libsystem_pthread.dylib \t0x000000021fa1cf98 _pthread_cond_wait + 1204 (pthread_cond.c:862)\n2 DamusNotificationService \t0x00000001023e88b8 prot_queue_pop_all + 52 (protected_queue.h:190)\n3 DamusNotificationService \t0x00000001023e92e8 ndb_ingester_thread + 156 (nostrdb.c:2809)\n4 libsystem_pthread.dylib \t0x000000021fa1a7d0 _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x000000021fa1a480 thread_start + 8 (:-1)\n\nThread 8:\n0 libsystem_kernel.dylib \t0x00000001e63b6090 __psynch_cvwait + 8 (:-1)\n1 libsystem_pthread.dylib \t0x000000021fa1cf98 _pthread_cond_wait + 1204 (pthread_cond.c:862)\n2 DamusNotificationService \t0x00000001023e88b8 prot_queue_pop_all + 52 (protected_queue.h:190)\n3 DamusNotificationService \t0x00000001023e92e8 ndb_ingester_thread + 156 (nostrdb.c:2809)\n4 libsystem_pthread.dylib \t0x000000021fa1a7d0 _pthread_start + 136 (pthread.c:931)\n5 libsystem_pthread.dylib \t0x000000021fa1a480 thread_start + 8 (:-1)\n\n\nThread 1 crashed with ARM Thread State (64-bit):\n x0: 0x0000000000000016 x1: 0x0000000000000009 x2: 0x000000016dbc8b88 x3: 0x000000016dbc8c10\n x4: 0x000000016dbc8c58 x5: 0x000000016dbc8c50 x6: 0x0000000000000001 x7: 0x0000000000000001\n x8: 0x0000000000000000 x9: 0x0000000103625a50 x10: 0x0000000000000003 x11: 0x0000000200000003\n x12: 0x000000000000000c x13: 0x00000001034ee260 x14: 0x00000001fd308d90 x15: 0x00000001fd308d90\n x16: 0x000000019360e150 x17: 0x00000001feb28b98 x18: 0x0000000000000000 x19: 0x000000016dbc8b88\n x20: 0x0000000116074000 x21: 0x0000000000000009 x22: 0x000000010343f4e0 x23: 0x40000001034658b0\n x24: 0x0000002000000000 x25: 0x000000010f45c040 x26: 0x0000000000000000 x27: 0x0000000103625a40\n x28: 0x0000000116074000 fp: 0x000000016dbc8b70 lr: 0x00000001023e4270\n sp: 0x000000016dbc8b40 pc: 0x00000001023e0fe8 cpsr: 0xa0000000\n esr: 0x92000006 (Data Abort) byte read Translation fault\n```", + "x": 1000, + "y": -82, + "width": 1225, + "height": 2442 + } + ], + "edges": [ + { + "id": "7921adc708cb3a7b", + "fromNode": "8ba807ac67740bc0", + "fromSide": "bottom", + "toNode": "c450e9832ffe7a3d", + "toSide": "top" + }, + { + "id": "8cd54328766e3a74", + "fromNode": "b27baddab9517a38", + "fromSide": "bottom", + "toNode": "3d398a7223484f22", + "toSide": "top" + }, + { + "id": "141570202b1594fa", + "fromNode": "c450e9832ffe7a3d", + "fromSide": "bottom", + "toNode": "906aae4882968fc5", + "toSide": "top" + }, + { + "id": "c04c77ad678215f6", + "fromNode": "c450e9832ffe7a3d", + "fromSide": "bottom", + "toNode": "e619c24d803fc838", + "toSide": "top" + }, + { + "id": "b1642de657a7849f", + "fromNode": "3d398a7223484f22", + "fromSide": "bottom", + "toNode": "915e462d7852f6bb", + "toSide": "top" + }, + { + "id": "fa6c9971a72041f4", + "fromNode": "e48e5b32c0265d73", + "fromSide": "bottom", + "toNode": "4f83ab4aa30b00f1", + "toSide": "top" + }, + { + "id": "0b9d1757cec27068", + "fromNode": "4f83ab4aa30b00f1", + "fromSide": "bottom", + "toNode": "0df74aca0192124e", + "toSide": "top" + }, + { + "id": "fcf498e888535135", + "fromNode": "d275d9d3cca27202", + "fromSide": "bottom", + "toNode": "c04db70a0e694555", + "toSide": "top" + }, + { + "id": "ac60f0136f93b0da", + "fromNode": "e182bb08633f0c8e", + "fromSide": "bottom", + "toNode": "39b704e37669fc83", + "toSide": "top" + }, + { + "id": "a5963df395dff2cb", + "fromNode": "c04db70a0e694555", + "fromSide": "bottom", + "toNode": "a7b2dd98e8fb8b98", + "toSide": "top" + }, + { + "id": "f18aa25135d3bf72", + "fromNode": "39b704e37669fc83", + "fromSide": "bottom", + "toNode": "a3718eb8e2f9f28d", + "toSide": "top" + } + ] +} diff --git a/crates/notedeck_notebook/src/debug.rs b/crates/notedeck_notebook/src/debug.rs @@ -0,0 +1,26 @@ + +/* +fn debug_slider( + ui: &mut egui::Ui, + id: egui::Id, + point: Pos2, + initial: f32, + range: std::ops::RangeInclusive<f32>, +) -> f32 { + let mut val = ui.data_mut(|d| *d.get_temp_mut_or::<f32>(id, initial)); + let nudge = vec2(10.0, 10.0); + let slider = Rect::from_min_max(point - nudge, point + nudge); + let label = Rect::from_min_max(point + nudge * 2.0, point - nudge * 2.0); + + let old_val = val; + ui.put(slider, egui::Slider::new(&mut val, range)); + ui.put(label, egui::Label::new(format!("{val}"))); + + if val != old_val { + ui.data_mut(|d| d.insert_temp(id, val)) + } + + val +} +*/ + diff --git a/crates/notedeck_notebook/src/lib.rs b/crates/notedeck_notebook/src/lib.rs @@ -0,0 +1,60 @@ +use crate::ui::{edge_ui, node_ui}; +use egui::{Pos2, Rect}; +use jsoncanvas::JsonCanvas; +use notedeck::{AppAction, AppContext}; + +mod ui; + +pub struct Notebook { + canvas: JsonCanvas, + scene_rect: Rect, + loaded: bool, +} + +impl Notebook { + pub fn new() -> Self { + Notebook::default() + } +} + +impl Default for Notebook { + fn default() -> Self { + Notebook { + canvas: demo_canvas(), + scene_rect: Rect::from_min_max(Pos2::ZERO, Pos2::ZERO), + loaded: false, + } + } +} + +impl notedeck::App for Notebook { + fn update(&mut self, _ctx: &mut AppContext<'_>, ui: &mut egui::Ui) -> Option<AppAction> { + //let app_action: Option<AppAction> = None; + + if !self.loaded { + self.scene_rect = ui.available_rect_before_wrap(); + self.loaded = true; + } + + egui::Scene::new().show(ui, &mut self.scene_rect, |ui| { + // render nodes + for (_node_id, node) in self.canvas.get_nodes().iter() { + let _resp = node_ui(ui, node); + } + + // render edges + for (_edge_id, edge) in self.canvas.get_edges().iter() { + let _resp = edge_ui(ui, self.canvas.get_nodes(), edge); + } + }); + + None + } +} + +fn demo_canvas() -> JsonCanvas { + let demo_json: String = include_str!("../demo.canvas").to_string(); + + let canvas: JsonCanvas = demo_json.parse().unwrap_or_else(|_| JsonCanvas::default()); + canvas +} diff --git a/crates/notedeck_notebook/src/ui.rs b/crates/notedeck_notebook/src/ui.rs @@ -0,0 +1,184 @@ +use egui::{Align, Label, Pos2, Rect, Shape, Stroke, TextWrapMode, epaint::CubicBezierShape, vec2}; +use jsoncanvas::{ + FileNode, GroupNode, LinkNode, Node, NodeId, TextNode, + edge::{Edge, Side}, + node::GenericNode, +}; +use std::collections::HashMap; +use std::ops::Neg; + +fn node_rect(node: &GenericNode) -> Rect { + let x = node.x as f32; + let y = node.y as f32; + let width = node.width as f32; + let height = node.height as f32; + + let min = Pos2::new(x, y); + let max = Pos2::new(x + width, y + height); + + Rect::from_min_max(min, max) +} + +fn side_point(side: &Side, node: &GenericNode) -> Pos2 { + let rect = node_rect(node); + + match side { + Side::Top => rect.center_top(), + Side::Left => rect.left_center(), + Side::Right => rect.right_center(), + Side::Bottom => rect.center_bottom(), + } +} + +/// a unit vector pointing outward from the given side +fn side_tangent(side: &Side) -> egui::Vec2 { + match side { + Side::Top => vec2(0.0, -1.0), + Side::Bottom => vec2(0.0, 1.0), + Side::Left => vec2(-1.0, 0.0), + Side::Right => vec2(1.0, 0.0), + } +} + +pub fn edge_ui( + ui: &mut egui::Ui, + nodes: &HashMap<NodeId, Node>, + edge: &Edge, +) -> Option<egui::Response> { + let from_node = nodes.get(edge.from_node())?; + let to_node = nodes.get(edge.to_node())?; + let to_side = edge.to_side()?; + let from_side = edge.from_side()?; + + // anchor from-side + let p0 = side_point(from_side, from_node.node()); + + // anchor b + let to_anchor = side_point(to_side, to_node.node()); + + // to-point is slightly offset to accomidate arrow + let p3 = to_anchor + side_tangent(to_side) * 2.0; + + // bend debug + //let bend = debug_slider(ui, ui.id().with("bend"), p3, 0.25, 0.0..=1.0); + let bend = 0.28; + + // How far to pull the tangents. + // ¼ of the distance between anchors feels very “Obsidian”. + let d = (p3 - p0).length() * bend; + + // c1 = anchor A + (outward tangent) * d + let c1 = p0 + side_tangent(from_side) * d; + + // c2 = anchor B + (inward tangent) * d + let c2 = p3 - side_tangent(to_side).neg() * d; + + let color = ui.visuals().noninteractive().bg_stroke.color; + let stroke = egui::Stroke::new(4.0, color); + let bezier = CubicBezierShape::from_points_stroke([p0, c1, c2, p3], false, color, stroke); + + ui.painter().add(Shape::CubicBezier(bezier)); + arrow_ui(ui, to_side, to_anchor, color); + + None +} + +/// Paint a tiny triangular “arrow”. +/// +/// * `ui` – the egui `Ui` you’re painting in +/// * `side` – which edge of the box we’re attaching to +/// * `point` – the exact spot on that edge the arrow’s tip should touch +/// * `fill` – colour to fill the arrow with (usually your popup’s background) +pub fn arrow_ui(ui: &mut egui::Ui, side: &Side, point: Pos2, fill: egui::Color32) { + let len: f32 = 12.0; // distance from tip to base + let width: f32 = 16.0; // length of the base + let stroke: f32 = 1.0; // length of the base + + let verts = match side { + Side::Top => [ + point, // tip + Pos2::new(point.x - width * 0.5, point.y - len), // base‑left (above) + Pos2::new(point.x + width * 0.5, point.y - len), // base‑right (above) + ], + Side::Bottom => [ + point, + Pos2::new(point.x + width * 0.5, point.y + len), // below + Pos2::new(point.x - width * 0.5, point.y + len), + ], + Side::Left => [ + point, + Pos2::new(point.x - len, point.y + width * 0.5), // left + Pos2::new(point.x - len, point.y - width * 0.5), + ], + Side::Right => [ + point, + Pos2::new(point.x + len, point.y - width * 0.5), // right + Pos2::new(point.x + len, point.y + width * 0.5), + ], + }; + + ui.painter().add(egui::Shape::convex_polygon( + verts.to_vec(), + fill, + Stroke::new(stroke, fill), // add a stroke here if you want an outline + )); +} + +pub fn node_ui(ui: &mut egui::Ui, node: &Node) -> egui::Response { + match node { + Node::Text(text_node) => text_node_ui(ui, text_node), + Node::File(file_node) => file_node_ui(ui, file_node), + Node::Link(link_node) => link_node_ui(ui, link_node), + Node::Group(group_node) => group_node_ui(ui, group_node), + } +} + +fn text_node_ui(ui: &mut egui::Ui, node: &TextNode) -> egui::Response { + node_box_ui(ui, node.node(), |ui| { + egui::ScrollArea::vertical() + .show(ui, |ui| { + ui.with_layout(egui::Layout::left_to_right(Align::Min), |ui| { + ui.add(Label::new(node.text()).wrap_mode(TextWrapMode::Wrap)) + }) + }) + .inner + .response + }) +} + +fn file_node_ui(ui: &mut egui::Ui, node: &FileNode) -> egui::Response { + node_box_ui(ui, node.node(), |ui| ui.label("file node")) +} + +fn link_node_ui(ui: &mut egui::Ui, node: &LinkNode) -> egui::Response { + node_box_ui(ui, node.node(), |ui| ui.label("link node")) +} + +fn group_node_ui(ui: &mut egui::Ui, node: &GroupNode) -> egui::Response { + node_box_ui(ui, node.node(), |ui| ui.label("group node")) +} + +fn node_box_ui( + ui: &mut egui::Ui, + node: &GenericNode, + contents: impl FnOnce(&mut egui::Ui) -> egui::Response, +) -> egui::Response { + let pos = node_rect(node); + + ui.put(pos, |ui: &mut egui::Ui| { + egui::Frame::default() + .fill(ui.visuals().noninteractive().weak_bg_fill) + .inner_margin(egui::Margin::same(16)) + .corner_radius(egui::CornerRadius::same(10)) + .stroke(egui::Stroke::new( + 2.0, + ui.visuals().noninteractive().bg_stroke.color, + )) + .show(ui, |ui| { + let rect = ui.available_rect_before_wrap(); + ui.allocate_at_least(ui.available_size(), egui::Sense::click()); + ui.put(rect, contents); + }) + .response + }) +}