@@ -2698,7 +2698,7 @@ impl OsvmApp {
26982698 }
26992699
27002700 /// Handle a key event (from either local terminal or web)
2701- /// This is a simplified handler for web input - supports essential navigation keys
2701+ /// Mirrors the main event_loop key handling for consistent behavior
27022702 fn handle_key_event ( & mut self , key : KeyEvent ) {
27032703 match key. code {
27042704 // Quit/Escape - handle different contexts
@@ -2750,74 +2750,129 @@ impl OsvmApp {
27502750 KeyCode :: Char ( '?' ) => {
27512751 self . show_help = !self . show_help ;
27522752 }
2753- // Scrolling - handle tab-specific scroll states
2753+ // Navigation - Up/k
27542754 KeyCode :: Up | KeyCode :: Char ( 'k' ) if !self . chat_input_active => {
2755- match self . active_tab {
2756- TabIndex :: Chat => { self . chat_scroll = self . chat_scroll . saturating_sub ( 1 ) ; }
2757- TabIndex :: Logs => { self . log_scroll = self . log_scroll . saturating_sub ( 1 ) ; }
2758- TabIndex :: Graph => {
2759- // Pan up in graph view
2760- if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2761- let ( cx, cy, zoom) = graph. viewport ;
2762- graph. viewport = ( cx, cy + 5.0 / zoom, zoom) ;
2755+ if self . show_help {
2756+ self . help_scroll = self . help_scroll . saturating_sub ( 1 ) ;
2757+ } else {
2758+ match self . active_tab {
2759+ TabIndex :: Chat => { self . chat_scroll = self . chat_scroll . saturating_sub ( 1 ) ; }
2760+ TabIndex :: Dashboard => { self . ai_insights_scroll = self . ai_insights_scroll . saturating_sub ( 1 ) ; }
2761+ TabIndex :: Logs => { self . log_scroll = self . log_scroll . saturating_sub ( 1 ) ; }
2762+ TabIndex :: Graph => {
2763+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2764+ graph. handle_input ( GraphInput :: Up ) ;
2765+ }
27632766 }
2767+ _ => { }
27642768 }
2765- _ => { }
27662769 }
27672770 }
2771+ // Navigation - Down/j
27682772 KeyCode :: Down | KeyCode :: Char ( 'j' ) if !self . chat_input_active => {
2769- match self . active_tab {
2770- TabIndex :: Chat => { self . chat_scroll = self . chat_scroll . saturating_add ( 1 ) ; }
2771- TabIndex :: Logs => { self . log_scroll = self . log_scroll . saturating_add ( 1 ) ; }
2772- TabIndex :: Graph => {
2773- // Pan down in graph view
2774- if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2775- let ( cx, cy, zoom) = graph. viewport ;
2776- graph. viewport = ( cx, cy - 5.0 / zoom, zoom) ;
2773+ if self . show_help {
2774+ self . help_scroll = self . help_scroll . saturating_add ( 1 ) ;
2775+ } else {
2776+ match self . active_tab {
2777+ TabIndex :: Chat => { self . chat_scroll = self . chat_scroll . saturating_add ( 1 ) ; }
2778+ TabIndex :: Dashboard => { self . ai_insights_scroll = self . ai_insights_scroll . saturating_add ( 1 ) ; }
2779+ TabIndex :: Logs => { self . log_scroll = self . log_scroll . saturating_add ( 1 ) ; }
2780+ TabIndex :: Graph => {
2781+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2782+ graph. handle_input ( GraphInput :: Down ) ;
2783+ }
27772784 }
2785+ _ => { }
27782786 }
2779- _ => { }
27802787 }
27812788 }
2782- // Graph panning (left/right )
2789+ // Navigation - Left/h (graph node/edge selection )
27832790 KeyCode :: Left | KeyCode :: Char ( 'h' ) if self . active_tab == TabIndex :: Graph => {
27842791 if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2785- let ( cx, cy, zoom) = graph. viewport ;
2786- graph. viewport = ( cx - 5.0 / zoom, cy, zoom) ;
2792+ graph. handle_input ( GraphInput :: Left ) ;
27872793 }
27882794 }
2795+ // Navigation - Right/l (graph node/edge selection)
27892796 KeyCode :: Right | KeyCode :: Char ( 'l' ) if self . active_tab == TabIndex :: Graph => {
27902797 if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2791- let ( cx, cy, zoom) = graph. viewport ;
2792- graph. viewport = ( cx + 5.0 / zoom, cy, zoom) ;
2798+ graph. handle_input ( GraphInput :: Right ) ;
2799+ }
2800+ }
2801+ // Enter - Hop to selected wallet
2802+ KeyCode :: Enter if self . active_tab == TabIndex :: Graph && !self . chat_input_active => {
2803+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2804+ graph. handle_input ( GraphInput :: HopToWallet ) ;
2805+ }
2806+ }
2807+ // Space - Toggle node collapse/expand
2808+ KeyCode :: Char ( ' ' ) if self . active_tab == TabIndex :: Graph => {
2809+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2810+ graph. handle_input ( GraphInput :: Toggle ) ;
27932811 }
27942812 }
27952813 // Graph zoom
27962814 KeyCode :: Char ( '+' ) | KeyCode :: Char ( '=' ) if self . active_tab == TabIndex :: Graph => {
27972815 if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2798- let ( cx, cy, zoom) = graph. viewport ;
2799- graph. viewport = ( cx, cy, ( zoom * 1.2 ) . min ( 10.0 ) ) ;
2816+ graph. handle_input ( GraphInput :: ZoomIn ) ;
28002817 }
28012818 }
2802- KeyCode :: Char ( '-' ) if self . active_tab == TabIndex :: Graph => {
2819+ KeyCode :: Char ( '-' ) | KeyCode :: Char ( '_' ) if self . active_tab == TabIndex :: Graph => {
28032820 if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2804- let ( cx, cy, zoom) = graph. viewport ;
2805- graph. viewport = ( cx, cy, ( zoom / 1.2 ) . max ( 0.1 ) ) ;
2821+ graph. handle_input ( GraphInput :: ZoomOut ) ;
2822+ }
2823+ }
2824+ // Graph panning (w/a/s/d for pan)
2825+ KeyCode :: Char ( 'w' ) if self . active_tab == TabIndex :: Graph => {
2826+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2827+ graph. handle_input ( GraphInput :: PanUp ) ;
2828+ }
2829+ }
2830+ KeyCode :: Char ( 's' ) if self . active_tab == TabIndex :: Graph && !self . chat_input_active => {
2831+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2832+ graph. handle_input ( GraphInput :: PanDown ) ;
2833+ }
2834+ }
2835+ KeyCode :: Char ( 'a' ) if self . active_tab == TabIndex :: Graph => {
2836+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2837+ graph. handle_input ( GraphInput :: PanLeft ) ;
2838+ }
2839+ }
2840+ KeyCode :: Char ( 'd' ) if self . active_tab == TabIndex :: Graph => {
2841+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2842+ graph. handle_input ( GraphInput :: PanRight ) ;
28062843 }
28072844 }
28082845 // Graph depth control
28092846 KeyCode :: Char ( '[' ) if self . active_tab == TabIndex :: Graph => {
28102847 if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2811- if graph. current_depth > 1 {
2812- graph. current_depth -= 1 ;
2813- }
2848+ graph. handle_input ( GraphInput :: DecreaseDepth ) ;
28142849 }
28152850 }
28162851 KeyCode :: Char ( ']' ) if self . active_tab == TabIndex :: Graph => {
28172852 if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2818- if graph. current_depth < graph. max_depth {
2819- graph. current_depth += 1 ;
2820- }
2853+ graph. handle_input ( GraphInput :: IncreaseDepth ) ;
2854+ }
2855+ }
2856+ // Graph search
2857+ KeyCode :: Char ( '/' ) if self . active_tab == TabIndex :: Graph => {
2858+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2859+ graph. handle_input ( GraphInput :: StartSearch ) ;
2860+ }
2861+ }
2862+ KeyCode :: Char ( 'n' ) if self . active_tab == TabIndex :: Graph => {
2863+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2864+ graph. handle_input ( GraphInput :: SearchNext ) ;
2865+ }
2866+ }
2867+ KeyCode :: Char ( 'N' ) if self . active_tab == TabIndex :: Graph => {
2868+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2869+ graph. handle_input ( GraphInput :: SearchPrev ) ;
2870+ }
2871+ }
2872+ // Graph copy to clipboard
2873+ KeyCode :: Char ( 'y' ) if self . active_tab == TabIndex :: Graph => {
2874+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2875+ graph. handle_input ( GraphInput :: Copy ) ;
28212876 }
28222877 }
28232878 // Graph trail toggle
@@ -2832,6 +2887,17 @@ impl OsvmApp {
28322887 graph. viewport = ( 0.0 , 0.0 , 1.0 ) ;
28332888 }
28342889 }
2890+ // Detail panel scroll (< and >)
2891+ KeyCode :: Char ( '<' ) | KeyCode :: Char ( ',' ) if self . active_tab == TabIndex :: Graph => {
2892+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2893+ graph. handle_input ( GraphInput :: ScrollDetailUp ) ;
2894+ }
2895+ }
2896+ KeyCode :: Char ( '>' ) | KeyCode :: Char ( '.' ) if self . active_tab == TabIndex :: Graph => {
2897+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2898+ graph. handle_input ( GraphInput :: ScrollDetailDown ) ;
2899+ }
2900+ }
28352901 // Number keys 1-5 to select tabs
28362902 KeyCode :: Char ( '1' ) if !self . chat_input_active => {
28372903 self . active_tab = TabIndex :: Chat ;
@@ -2857,15 +2923,31 @@ impl OsvmApp {
28572923 self . chat_input . push ( c) ;
28582924 }
28592925 }
2860- KeyCode :: Backspace if self . chat_input_active => {
2861- self . chat_input . pop ( ) ;
2926+ KeyCode :: Backspace => {
2927+ if self . chat_input_active {
2928+ self . chat_input . pop ( ) ;
2929+ } else if self . active_tab == TabIndex :: Graph {
2930+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2931+ if graph. search_active {
2932+ graph. handle_input ( GraphInput :: SearchBackspace ) ;
2933+ }
2934+ }
2935+ }
28622936 }
28632937 KeyCode :: Enter if self . chat_input_active => {
28642938 if !self . chat_input . trim ( ) . is_empty ( ) {
28652939 self . send_chat_message ( ) ;
28662940 }
28672941 self . chat_input_active = false ;
28682942 }
2943+ // Handle graph search character input
2944+ KeyCode :: Char ( c) if self . active_tab == TabIndex :: Graph => {
2945+ if let Ok ( mut graph) = self . wallet_graph . lock ( ) {
2946+ if graph. search_active {
2947+ graph. handle_input ( GraphInput :: SearchChar ( c) ) ;
2948+ }
2949+ }
2950+ }
28692951 _ => { }
28702952 }
28712953 }
0 commit comments