From 4a3744309fb945c14267b578b837d7cb54da7978 Mon Sep 17 00:00:00 2001 From: multiplex55 <6619098+multiplex55@users.noreply.github.com> Date: Sat, 17 Jan 2026 13:42:13 -0500 Subject: [PATCH 1/3] Add dashboard diagnostics metrics and widget --- src/dashboard/dashboard.rs | 82 +++++++++++-- src/dashboard/diagnostics.rs | 169 +++++++++++++++++++++++++++ src/dashboard/mod.rs | 2 + src/dashboard/widgets/diagnostics.rs | 90 ++++++++++++++ src/dashboard/widgets/mod.rs | 3 + src/gui/mod.rs | 15 +++ src/plugin_editor.rs | 1 + src/settings.rs | 4 + src/settings_editor.rs | 12 ++ 9 files changed, 368 insertions(+), 10 deletions(-) create mode 100644 src/dashboard/diagnostics.rs create mode 100644 src/dashboard/widgets/diagnostics.rs diff --git a/src/dashboard/dashboard.rs b/src/dashboard/dashboard.rs index e9329f6..37a3268 100644 --- a/src/dashboard/dashboard.rs +++ b/src/dashboard/dashboard.rs @@ -1,5 +1,6 @@ use crate::dashboard::config::{DashboardConfig, OverflowMode}; use crate::dashboard::data_cache::DashboardDataCache; +use crate::dashboard::diagnostics::{DashboardDiagnostics, DashboardDiagnosticsSnapshot}; use crate::dashboard::layout::{normalize_slots, NormalizedSlot}; use crate::dashboard::widgets::{Widget, WidgetAction, WidgetRegistry}; use crate::{actions::Action, common::json_watch::JsonWatcher}; @@ -12,6 +13,7 @@ use siphasher::sip::SipHasher24; use std::collections::HashMap; use std::hash::Hasher; use std::path::{Path, PathBuf}; +use std::time::{Duration, Instant}; #[cfg(test)] use std::sync::Mutex; @@ -46,6 +48,8 @@ pub struct DashboardContext<'a> { pub dashboard_visible: bool, pub dashboard_focused: bool, pub reduce_dashboard_work_when_unfocused: bool, + pub diagnostics: Option, + pub show_diagnostics_widget: bool, } struct SlotRuntime { @@ -107,6 +111,7 @@ pub struct Dashboard { watcher: Option, pub warnings: Vec, event_cb: Option>, + diagnostics: DashboardDiagnostics, } impl Dashboard { @@ -126,6 +131,7 @@ impl Dashboard { watcher: None, warnings, event_cb, + diagnostics: DashboardDiagnostics::new(), }; dashboard.rebuild_runtime_slots(slots); dashboard @@ -217,6 +223,9 @@ impl Dashboard { for slot in &mut self.runtime_slots { let normalized = &slot.slot; + if !ctx.show_diagnostics_widget && normalized.widget == "diagnostics" { + continue; + } let slot_rect = egui::Rect::from_min_size( rect.min + egui::vec2( @@ -232,7 +241,15 @@ impl Dashboard { let response = child.allocate_ui_at_rect(slot_rect, |slot_ui| { slot_ui.set_clip_rect(slot_clip); slot_ui.set_min_size(slot_rect.size()); - Self::render_slot(slot, slot_rect, slot_clip, slot_ui, ctx, activation) + Self::render_slot( + slot, + slot_rect, + slot_clip, + slot_ui, + ctx, + activation, + &mut self.diagnostics, + ) }); clicked = clicked.or(response.inner); } @@ -247,12 +264,9 @@ impl Dashboard { ui: &mut egui::Ui, ctx: &DashboardContext<'_>, activation: WidgetActivation, + diagnostics: &mut DashboardDiagnostics, ) -> Option { - let heading = slot - .slot - .id - .clone() - .unwrap_or_else(|| slot.slot.widget.clone()); + let (heading, diag_key) = slot_diagnostics_label(&slot.slot); ui.set_clip_rect(slot_clip); ui.set_min_size(slot_rect.size()); @@ -282,7 +296,16 @@ impl Dashboard { let action = match overflow { OverflowPolicy::Clip => { - Self::render_clipped_widget(slot, ui, ctx, activation, body_height) + Self::render_clipped_widget( + slot, + ui, + ctx, + activation, + body_height, + diagnostics, + &heading, + &diag_key, + ) } OverflowPolicy::Scroll { visibility } => Self::render_scrollable_widget( slot, @@ -292,6 +315,9 @@ impl Dashboard { body_height, slot_clip, visibility, + diagnostics, + &heading, + &diag_key, ), }; @@ -308,10 +334,13 @@ impl Dashboard { ctx: &DashboardContext<'_>, activation: WidgetActivation, body_height: f32, + diagnostics: &mut DashboardDiagnostics, + heading: &str, + diag_key: &str, ) -> Option { ui.set_min_height(body_height); ui.set_max_height(body_height); - Self::render_widget_content(slot, ui, ctx, activation) + Self::render_widget_content(slot, ui, ctx, activation, diagnostics, heading, diag_key) } fn render_scrollable_widget( @@ -322,6 +351,9 @@ impl Dashboard { body_height: f32, slot_clip: egui::Rect, visibility: ScrollBarVisibility, + diagnostics: &mut DashboardDiagnostics, + heading: &str, + diag_key: &str, ) -> Option { let scroll_id = egui::Id::new(( "slot-scroll", @@ -345,7 +377,7 @@ impl Dashboard { let _ = viewport; ui.set_clip_rect(ui.clip_rect().intersect(slot_clip)); ui.set_min_height(body_height); - Self::render_widget_content(slot, ui, ctx, activation) + Self::render_widget_content(slot, ui, ctx, activation, diagnostics, heading, diag_key) }) .inner } @@ -355,13 +387,41 @@ impl Dashboard { ui: &mut egui::Ui, ctx: &DashboardContext<'_>, activation: WidgetActivation, + diagnostics: &mut DashboardDiagnostics, + heading: &str, + diag_key: &str, ) -> Option { - slot.widget.render(ui, ctx, activation) + let start = Instant::now(); + let response = slot.widget.render(ui, ctx, activation); + diagnostics.record_widget_refresh( + diag_key.to_string(), + heading.to_string(), + start.elapsed(), + ); + response } pub fn registry(&self) -> &WidgetRegistry { &self.registry } + + pub fn update_frame_timing(&mut self, frame_time: Duration) { + self.diagnostics.update_frame_timing(frame_time); + } + + pub fn diagnostics_snapshot(&self) -> DashboardDiagnosticsSnapshot { + self.diagnostics.snapshot() + } +} + +fn slot_diagnostics_label(slot: &NormalizedSlot) -> (String, String) { + if let Some(id) = &slot.id { + (id.clone(), id.clone()) + } else { + let label = format!("{} ({}, {})", slot.widget, slot.row, slot.col); + let key = format!("{}@{}x{}", slot.widget, slot.row, slot.col); + (label, key) + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -546,6 +606,8 @@ mod tests { dashboard_visible: true, dashboard_focused: true, reduce_dashboard_work_when_unfocused: false, + diagnostics: None, + show_diagnostics_widget: true, } } diff --git a/src/dashboard/diagnostics.rs b/src/dashboard/diagnostics.rs new file mode 100644 index 0000000..ca4772c --- /dev/null +++ b/src/dashboard/diagnostics.rs @@ -0,0 +1,169 @@ +use std::collections::HashMap; +use std::time::{Duration, Instant}; + +pub const DIAGNOSTICS_REFRESH_INTERVAL: Duration = Duration::from_millis(500); +pub const REFRESH_WARNING_THRESHOLD: Duration = Duration::from_millis(75); + +#[derive(Clone, Debug)] +pub struct WidgetRefreshSnapshot { + pub label: String, + pub last_refresh_at: Instant, + pub last_duration: Duration, +} + +#[derive(Clone, Debug, Default)] +pub struct DashboardDiagnosticsSnapshot { + pub fps: f32, + pub frame_time: Duration, + pub widget_refreshes: Vec, +} + +struct WidgetRefreshState { + label: String, + last_refresh_at: Instant, + last_duration: Duration, + last_sample: Instant, +} + +pub struct DashboardDiagnostics { + widget_states: HashMap, + fps: f32, + frame_time: Duration, + refresh_interval: Duration, + warning_threshold: Duration, + last_frame_sample: Instant, +} + +impl DashboardDiagnostics { + pub fn new() -> Self { + Self::new_with_config(DIAGNOSTICS_REFRESH_INTERVAL, REFRESH_WARNING_THRESHOLD) + } + + pub fn new_with_config(refresh_interval: Duration, warning_threshold: Duration) -> Self { + let now = Instant::now(); + Self { + widget_states: HashMap::new(), + fps: 0.0, + frame_time: Duration::from_millis(0), + refresh_interval, + warning_threshold, + last_frame_sample: now - refresh_interval, + } + } + + pub fn update_frame_timing(&mut self, frame_time: Duration) { + let now = Instant::now(); + if now.duration_since(self.last_frame_sample) < self.refresh_interval { + return; + } + self.frame_time = frame_time; + let secs = frame_time.as_secs_f32(); + self.fps = if secs > 0.0 { 1.0 / secs } else { 0.0 }; + self.last_frame_sample = now; + } + + pub fn record_widget_refresh(&mut self, key: String, label: String, duration: Duration) { + let now = Instant::now(); + let update_due = match self.widget_states.get(&key) { + Some(state) => now.duration_since(state.last_sample) >= self.refresh_interval, + None => true, + }; + if update_due || duration >= self.warning_threshold { + let entry = self.widget_states.entry(key).or_insert(WidgetRefreshState { + label, + last_refresh_at: now, + last_duration: duration, + last_sample: now, + }); + entry.label = label; + entry.last_refresh_at = now; + entry.last_duration = duration; + entry.last_sample = now; + } + } + + pub fn snapshot(&self) -> DashboardDiagnosticsSnapshot { + let mut widget_refreshes: Vec = self + .widget_states + .values() + .map(|state| WidgetRefreshSnapshot { + label: state.label.clone(), + last_refresh_at: state.last_refresh_at, + last_duration: state.last_duration, + }) + .collect(); + widget_refreshes.sort_by(|a, b| a.label.cmp(&b.label)); + DashboardDiagnosticsSnapshot { + fps: self.fps, + frame_time: self.frame_time, + widget_refreshes, + } + } + + pub fn warning_threshold(&self) -> Duration { + self.warning_threshold + } +} + +#[cfg(test)] +impl DashboardDiagnostics { + fn set_last_frame_sample_for_test(&mut self, instant: Instant) { + self.last_frame_sample = instant; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn frame_metrics_throttle_until_interval() { + let mut diagnostics = DashboardDiagnostics::new_with_config( + Duration::from_secs(10), + REFRESH_WARNING_THRESHOLD, + ); + diagnostics.update_frame_timing(Duration::from_millis(16)); + let first = diagnostics.snapshot(); + + diagnostics.update_frame_timing(Duration::from_millis(33)); + let second = diagnostics.snapshot(); + assert_eq!(first.frame_time, second.frame_time); + + diagnostics.set_last_frame_sample_for_test(Instant::now() - Duration::from_secs(11)); + diagnostics.update_frame_timing(Duration::from_millis(33)); + let third = diagnostics.snapshot(); + assert_ne!(first.frame_time, third.frame_time); + } + + #[test] + fn widget_refresh_updates_on_threshold() { + let mut diagnostics = DashboardDiagnostics::new_with_config( + Duration::from_secs(10), + Duration::from_millis(50), + ); + diagnostics.record_widget_refresh( + "widget-a".to_string(), + "Widget A".to_string(), + Duration::from_millis(10), + ); + let first = diagnostics.snapshot(); + assert_eq!(first.widget_refreshes.len(), 1); + assert_eq!(first.widget_refreshes[0].last_duration, Duration::from_millis(10)); + + diagnostics.record_widget_refresh( + "widget-a".to_string(), + "Widget A".to_string(), + Duration::from_millis(5), + ); + let second = diagnostics.snapshot(); + assert_eq!(second.widget_refreshes[0].last_duration, Duration::from_millis(10)); + + diagnostics.record_widget_refresh( + "widget-a".to_string(), + "Widget A".to_string(), + Duration::from_millis(75), + ); + let third = diagnostics.snapshot(); + assert_eq!(third.widget_refreshes[0].last_duration, Duration::from_millis(75)); + } +} diff --git a/src/dashboard/mod.rs b/src/dashboard/mod.rs index 400d265..3768247 100644 --- a/src/dashboard/mod.rs +++ b/src/dashboard/mod.rs @@ -1,9 +1,11 @@ pub mod config; pub mod dashboard; pub mod data_cache; +pub mod diagnostics; pub mod layout; pub mod widgets; pub use dashboard::{Dashboard, DashboardContext, DashboardEvent, WidgetActivation}; pub use data_cache::{DashboardDataCache, DashboardDataSnapshot}; +pub use diagnostics::{DashboardDiagnosticsSnapshot, DIAGNOSTICS_REFRESH_INTERVAL}; pub use widgets::{WidgetAction, WidgetFactory, WidgetRegistry}; diff --git a/src/dashboard/widgets/diagnostics.rs b/src/dashboard/widgets/diagnostics.rs new file mode 100644 index 0000000..d2e48ef --- /dev/null +++ b/src/dashboard/widgets/diagnostics.rs @@ -0,0 +1,90 @@ +use crate::dashboard::dashboard::{DashboardContext, WidgetActivation}; +use crate::dashboard::diagnostics::{DashboardDiagnosticsSnapshot, REFRESH_WARNING_THRESHOLD}; +use crate::dashboard::widgets::{Widget, WidgetAction}; +use eframe::egui; +use serde::{Deserialize, Serialize}; +use std::time::{Duration, Instant}; + +#[derive(Default, Serialize, Deserialize)] +pub struct DiagnosticsSettings; + +#[derive(Default)] +pub struct DiagnosticsWidget; + +impl DiagnosticsWidget { + pub fn new(_settings: DiagnosticsSettings) -> Self { + Self + } + + fn format_duration(duration: Duration) -> String { + if duration.as_secs() >= 1 { + format!("{:.2}s", duration.as_secs_f32()) + } else { + format!("{:.1}ms", duration.as_secs_f32() * 1000.0) + } + } + + fn format_elapsed(now: Instant, then: Instant) -> String { + Self::format_duration(now.duration_since(then)) + } + + fn render_frame_metrics(ui: &mut egui::Ui, diagnostics: &DashboardDiagnosticsSnapshot) { + let frame_ms = diagnostics.frame_time.as_secs_f32() * 1000.0; + ui.label(format!("FPS: {:.1}", diagnostics.fps)); + ui.label(format!("Frame time: {:.2} ms", frame_ms)); + } +} + +impl Widget for DiagnosticsWidget { + fn render( + &mut self, + ui: &mut egui::Ui, + ctx: &DashboardContext<'_>, + _activation: WidgetActivation, + ) -> Option { + let Some(diagnostics) = ctx.diagnostics.as_ref() else { + ui.label("Diagnostics unavailable"); + return None; + }; + + ui.vertical(|ui| { + Self::render_frame_metrics(ui, diagnostics); + ui.separator(); + + let snapshot = ctx.data_cache.snapshot(); + ui.label(format!( + "Cache sizes: clipboard {}, todos {}, notes {}, snippets {}", + snapshot.clipboard_history.len(), + snapshot.todos.len(), + snapshot.notes.len(), + snapshot.snippets.len() + )); + ui.separator(); + + ui.label("Widget refresh"); + egui::Grid::new("diagnostics-widget-refresh") + .striped(true) + .show(ui, |ui| { + ui.label("Widget"); + ui.label("Last refresh"); + ui.label("Duration"); + ui.end_row(); + + let now = Instant::now(); + for stat in &diagnostics.widget_refreshes { + ui.label(stat.label.as_str()); + ui.label(Self::format_elapsed(now, stat.last_refresh_at)); + ui.horizontal(|ui| { + ui.label(Self::format_duration(stat.last_duration)); + if stat.last_duration >= REFRESH_WARNING_THRESHOLD { + ui.colored_label(egui::Color32::YELLOW, "⚠"); + } + }); + ui.end_row(); + } + }); + }); + + None + } +} diff --git a/src/dashboard/widgets/mod.rs b/src/dashboard/widgets/mod.rs index 8ed303f..62685a5 100644 --- a/src/dashboard/widgets/mod.rs +++ b/src/dashboard/widgets/mod.rs @@ -13,6 +13,7 @@ mod calendar; mod clipboard_recent; mod clipboard_snippets; mod command_history; +mod diagnostics; mod frequent_commands; mod layouts; mod notes_recent; @@ -47,6 +48,7 @@ pub use calendar::CalendarWidget; pub use clipboard_recent::ClipboardRecentWidget; pub use clipboard_snippets::ClipboardSnippetsWidget; pub use command_history::CommandHistoryWidget; +pub use diagnostics::DiagnosticsWidget; pub use frequent_commands::FrequentCommandsWidget; pub use layouts::LayoutsWidget; pub use notes_recent::NotesRecentWidget; @@ -222,6 +224,7 @@ impl WidgetRegistry { WidgetFactory::new(CommandHistoryWidget::new) .with_settings_ui(CommandHistoryWidget::settings_ui), ); + reg.register("diagnostics", WidgetFactory::new(DiagnosticsWidget::new)); reg.register( "recent_commands", WidgetFactory::new(RecentCommandsWidget::new) diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 161f8db..2b8e2eb 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -363,6 +363,7 @@ pub struct LauncherApp { pub dashboard_path: String, pub dashboard_default_location: Option, pub reduce_dashboard_work_when_unfocused: bool, + pub show_dashboard_diagnostics: bool, pub dashboard_editor: DashboardEditorDialog, pub show_dashboard_editor: bool, rx: Receiver, @@ -618,6 +619,7 @@ impl LauncherApp { note_always_overwrite: Option, note_images_as_links: Option, note_more_limit: Option, + show_dashboard_diagnostics: Option, ) { self.plugin_dirs = plugin_dirs; self.index_paths = index_paths; @@ -718,6 +720,9 @@ impl LauncherApp { if let Some(v) = note_more_limit { self.note_more_limit = v; } + if let Some(v) = show_dashboard_diagnostics { + self.show_dashboard_diagnostics = v; + } } pub fn new( @@ -1003,6 +1008,7 @@ impl LauncherApp { dashboard_path: dashboard_path.to_string_lossy().to_string(), dashboard_default_location: settings.dashboard.default_location.clone(), reduce_dashboard_work_when_unfocused: settings.reduce_dashboard_work_when_unfocused, + show_dashboard_diagnostics: settings.show_dashboard_diagnostics, dashboard_editor: DashboardEditorDialog::default(), show_dashboard_editor: false, rx, @@ -3138,6 +3144,8 @@ impl eframe::App for LauncherApp { if self.enable_toasts { self.toasts.show(ctx); } + let frame_time = Duration::from_secs_f32(ctx.input(|i| i.unstable_dt).max(0.0)); + self.dashboard.update_frame_timing(frame_time); if let Some(pending) = self.pending_query.take() { self.query = pending; self.search(); @@ -3549,6 +3557,11 @@ impl eframe::App for LauncherApp { } let dashboard_visible = self.visible_flag.load(Ordering::SeqCst); let dashboard_focused = ctx.input(|i| i.viewport().focused).unwrap_or(true); + let diagnostics = if self.show_dashboard_diagnostics { + Some(self.dashboard.diagnostics_snapshot()) + } else { + None + }; let dash_ctx = DashboardContext { actions: &self.actions, actions_by_id: &self.actions_by_id, @@ -3568,6 +3581,8 @@ impl eframe::App for LauncherApp { dashboard_focused, reduce_dashboard_work_when_unfocused: self .reduce_dashboard_work_when_unfocused, + diagnostics, + show_diagnostics_widget: self.show_dashboard_diagnostics, }; ctx.request_repaint_after(Duration::from_millis(250)); if let Some(action) = self.dashboard.ui(ui, &dash_ctx, WidgetActivation::Click) { diff --git a/src/plugin_editor.rs b/src/plugin_editor.rs index 20f8401..b5999ab 100644 --- a/src/plugin_editor.rs +++ b/src/plugin_editor.rs @@ -135,6 +135,7 @@ impl PluginEditor { Some(s.note_always_overwrite), Some(s.note_images_as_links), Some(s.note_more_limit), + Some(s.show_dashboard_diagnostics), ); let dirs = s.plugin_dirs.clone().unwrap_or_default(); let actions_arc = Arc::clone(&app.actions); diff --git a/src/settings.rs b/src/settings.rs index 155e158..e76347a 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -201,6 +201,9 @@ pub struct Settings { /// Reduce dashboard refresh work when the launcher is not focused. #[serde(default = "default_true")] pub reduce_dashboard_work_when_unfocused: bool, + /// Show the dashboard diagnostics widget (developer option). + #[serde(default)] + pub show_dashboard_diagnostics: bool, #[serde(default)] pub dashboard: DashboardSettings, } @@ -352,6 +355,7 @@ impl Default for Settings { plugin_settings: std::collections::HashMap::new(), pinned_panels: Vec::new(), reduce_dashboard_work_when_unfocused: true, + show_dashboard_diagnostics: false, dashboard: DashboardSettings::default(), } } diff --git a/src/settings_editor.rs b/src/settings_editor.rs index 961272b..d5d64e5 100644 --- a/src/settings_editor.rs +++ b/src/settings_editor.rs @@ -62,6 +62,7 @@ pub struct SettingsEditor { screenshot_auto_save: bool, screenshot_use_editor: bool, reduce_dashboard_work_when_unfocused: bool, + show_dashboard_diagnostics: bool, dashboard_enabled: bool, dashboard_path: String, dashboard_default_location: String, @@ -158,6 +159,7 @@ impl SettingsEditor { screenshot_auto_save: settings.screenshot_auto_save, screenshot_use_editor: settings.screenshot_use_editor, reduce_dashboard_work_when_unfocused: settings.reduce_dashboard_work_when_unfocused, + show_dashboard_diagnostics: settings.show_dashboard_diagnostics, dashboard_enabled: settings.dashboard.enabled, dashboard_path: settings .dashboard @@ -292,6 +294,7 @@ impl SettingsEditor { show_examples: current.show_examples, pinned_panels: current.pinned_panels.clone(), reduce_dashboard_work_when_unfocused: self.reduce_dashboard_work_when_unfocused, + show_dashboard_diagnostics: self.show_dashboard_diagnostics, dashboard: crate::settings::DashboardSettings { enabled: self.dashboard_enabled, config_path: if self.dashboard_path.trim().is_empty() { @@ -511,6 +514,12 @@ impl SettingsEditor { &mut self.reduce_dashboard_work_when_unfocused, "Reduce dashboard work when not focused", ); + if self.debug_logging || cfg!(debug_assertions) { + ui.checkbox( + &mut self.show_dashboard_diagnostics, + "Show dashboard diagnostics (dev)", + ); + } if ui.button("Customize Dashboard...").clicked() { app.show_dashboard_editor = true; } @@ -737,6 +746,7 @@ impl SettingsEditor { Some(new_settings.note_always_overwrite), Some(new_settings.note_images_as_links), Some(new_settings.note_more_limit), + Some(new_settings.show_dashboard_diagnostics), ); ctx.send_viewport_cmd( egui::ViewportCommand::WindowLevel( @@ -776,6 +786,8 @@ impl SettingsEditor { new_settings.screenshot_use_editor; app.reduce_dashboard_work_when_unfocused = new_settings.reduce_dashboard_work_when_unfocused; + app.show_dashboard_diagnostics = + new_settings.show_dashboard_diagnostics; app.dashboard_enabled = new_settings.dashboard.enabled; app.dashboard_show_when_empty = new_settings.dashboard.show_when_query_empty; From 4fc89344f7b0e57271b5eeaa2c80a1202e11a61e Mon Sep 17 00:00:00 2001 From: multiplex55 <6619098+multiplex55@users.noreply.github.com> Date: Sat, 17 Jan 2026 13:52:28 -0500 Subject: [PATCH 2/3] Fix diagnostics label reuse --- src/dashboard/diagnostics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dashboard/diagnostics.rs b/src/dashboard/diagnostics.rs index ca4772c..1355ca3 100644 --- a/src/dashboard/diagnostics.rs +++ b/src/dashboard/diagnostics.rs @@ -70,7 +70,7 @@ impl DashboardDiagnostics { }; if update_due || duration >= self.warning_threshold { let entry = self.widget_states.entry(key).or_insert(WidgetRefreshState { - label, + label: label.clone(), last_refresh_at: now, last_duration: duration, last_sample: now, From b9ad678800c3a29226667dca917e6589e2dd025f Mon Sep 17 00:00:00 2001 From: multiplex55 <6619098+multiplex55@users.noreply.github.com> Date: Sat, 17 Jan 2026 13:55:52 -0500 Subject: [PATCH 3/3] Update tests for diagnostics settings --- tests/hide_after_run.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/hide_after_run.rs b/tests/hide_after_run.rs index e31b37b..a319419 100644 --- a/tests/hide_after_run.rs +++ b/tests/hide_after_run.rs @@ -80,6 +80,7 @@ fn run_action(action: &str) -> bool { None, None, None, + None, ); flag.store(true, Ordering::SeqCst); let a = app.results[0].clone();