Skip to main content

stakpak_tui/
app.rs

1mod events;
2mod types;
3
4pub use events::{InputEvent, OutputEvent};
5use stakpak_shared::models::llm::{LLMModel, LLMTokenUsage};
6pub use types::*;
7
8use crate::services::approval_bar::ApprovalBar;
9use crate::services::auto_approve::AutoApproveManager;
10use crate::services::board_tasks::TaskProgress;
11use crate::services::changeset::{Changeset, SidePanelSection, TodoItem};
12use crate::services::detect_term::AdaptiveColors;
13use crate::services::file_search::{FileSearch, file_search_worker, find_at_trigger};
14use crate::services::helper_block::push_error_message;
15use crate::services::helper_block::push_styled_message;
16use crate::services::message::Message;
17#[cfg(not(unix))]
18use crate::services::shell_mode::run_background_shell_command;
19#[cfg(unix)]
20use crate::services::shell_mode::run_pty_command;
21use crate::services::shell_mode::{SHELL_PROMPT_PREFIX, ShellCommand, ShellEvent};
22use crate::services::textarea::{TextArea, TextAreaState};
23use ratatui::layout::Size;
24use ratatui::text::Line;
25use stakpak_api::models::ListRuleBook;
26use stakpak_shared::models::integrations::openai::{AgentModel, ToolCall, ToolCallResult};
27use stakpak_shared::secret_manager::SecretManager;
28use std::collections::HashMap;
29use tokio::sync::mpsc;
30use uuid::Uuid;
31
32pub struct AppState {
33    // ========== Input & TextArea State ==========
34    pub text_area: TextArea,
35    pub text_area_state: TextAreaState,
36    pub cursor_visible: bool,
37    pub helpers: Vec<HelperCommand>,
38    pub show_helper_dropdown: bool,
39    pub helper_selected: usize,
40    pub helper_scroll: usize,
41    pub filtered_helpers: Vec<HelperCommand>,
42    pub filtered_files: Vec<String>,
43    pub file_search: FileSearch,
44    pub file_search_tx: Option<mpsc::Sender<(String, usize)>>,
45    pub file_search_rx: Option<mpsc::Receiver<FileSearchResult>>,
46    pub is_pasting: bool,
47    pub pasted_long_text: Option<String>,
48    pub pasted_placeholder: Option<String>,
49    pub pending_pastes: Vec<(String, String)>,
50    /// Images attached via pasted file paths (and future clipboard image support).
51    pub attached_images: Vec<AttachedImage>,
52    pub pending_path_start: Option<usize>,
53    pub interactive_commands: Vec<String>,
54
55    // ========== Messages & Scrolling State ==========
56    pub messages: Vec<Message>,
57    pub scroll: usize,
58    pub scroll_to_bottom: bool,
59    pub stay_at_bottom: bool,
60    pub content_changed_while_scrolled_up: bool,
61    pub message_lines_cache: Option<MessageLinesCache>,
62    pub collapsed_message_lines_cache: Option<MessageLinesCache>,
63    pub processed_lines_cache: Option<(Vec<Message>, usize, Vec<Line<'static>>)>,
64    pub show_collapsed_messages: bool,
65    pub collapsed_messages_scroll: usize,
66    pub collapsed_messages_selected: usize,
67    pub has_user_messages: bool,
68    /// Per-message rendered line cache for efficient incremental rendering
69    pub per_message_cache: PerMessageCache,
70    /// Assembled lines cache (the final combined output of all message lines)
71    /// Format: (cache_key, lines, generation_counter)
72    pub assembled_lines_cache: Option<(usize, Vec<Line<'static>>, u64)>,
73    /// Cache for visible lines on screen (avoids cloning on every frame)
74    pub visible_lines_cache: Option<VisibleLinesCache>,
75    /// Generation counter for assembled cache (increments on each rebuild)
76    pub cache_generation: u64,
77    /// Performance metrics for render operations
78    pub render_metrics: RenderMetrics,
79    /// Last width used for rendering (to detect width changes)
80    pub last_render_width: usize,
81
82    // ========== Loading State ==========
83    pub loading: bool,
84    pub loading_type: LoadingType,
85    pub spinner_frame: usize,
86    pub loading_manager: LoadingStateManager,
87
88    // ========== Shell Popup State ==========
89    pub shell_popup_visible: bool,
90    pub shell_popup_expanded: bool,
91    pub shell_popup_scroll: usize,
92    pub shell_cursor_visible: bool,
93    pub shell_cursor_blink_timer: u8,
94    pub active_shell_command: Option<ShellCommand>,
95    pub active_shell_command_output: Option<String>,
96    pub waiting_for_shell_input: bool,
97    pub shell_tool_calls: Option<Vec<ToolCallResult>>,
98    pub shell_loading: bool,
99    pub shell_pending_command_value: Option<String>,
100    pub shell_pending_command_executed: bool,
101    pub shell_pending_command_output: Option<String>,
102    // Backward compatibility aliases (to be removed after full migration)
103    pub show_shell_mode: bool, // alias for shell_popup_visible && shell_popup_expanded
104    pub shell_mode_input: String, // unused, kept for compatibility
105    pub is_tool_call_shell_command: bool,
106    pub ondemand_shell_mode: bool,
107    pub shell_pending_command: Option<String>,
108    pub shell_pending_command_output_count: usize,
109    /// Tracks if the initial shell prompt has been shown (before command is typed)
110    pub shell_initial_prompt_shown: bool,
111    /// Tracks if the command has been typed into the shell (after initial prompt)
112    pub shell_command_typed: bool,
113
114    // ========== Tool Call State ==========
115    pub pending_bash_message_id: Option<Uuid>,
116    pub streaming_tool_results: HashMap<Uuid, String>,
117    pub streaming_tool_result_id: Option<Uuid>,
118    pub completed_tool_calls: std::collections::HashSet<Uuid>,
119    pub is_streaming: bool,
120    pub latest_tool_call: Option<ToolCall>,
121    pub retry_attempts: usize,
122    pub max_retry_attempts: usize,
123    pub last_user_message_for_retry: Option<String>,
124    pub is_retrying: bool,
125
126    // ========== Dialog & Approval State ==========
127    pub is_dialog_open: bool,
128    pub dialog_command: Option<ToolCall>,
129    pub dialog_selected: usize,
130    pub dialog_message_id: Option<Uuid>,
131    pub dialog_focused: bool,
132    pub approval_bar: ApprovalBar,
133    pub message_tool_calls: Option<Vec<ToolCall>>,
134    pub message_approved_tools: Vec<ToolCall>,
135    pub message_rejected_tools: Vec<ToolCall>,
136    pub toggle_approved_message: bool,
137    pub show_shortcuts: bool,
138
139    // ========== Sessions State ==========
140    pub sessions: Vec<SessionInfo>,
141    pub session_selected: usize,
142    pub account_info: String,
143
144    // ========== Session Tool Calls Queue ==========
145    pub session_tool_calls_queue: std::collections::HashMap<String, ToolCallStatus>,
146    pub tool_call_execution_order: Vec<String>,
147    pub last_message_tool_calls: Vec<ToolCall>,
148
149    // ========== Profile Switcher State ==========
150    pub show_profile_switcher: bool,
151    pub available_profiles: Vec<String>,
152    pub profile_switcher_selected: usize,
153    pub current_profile_name: String,
154    pub profile_switching_in_progress: bool,
155    pub profile_switch_status_message: Option<String>,
156
157    // ========== Rulebook Switcher State ==========
158    pub show_rulebook_switcher: bool,
159    pub available_rulebooks: Vec<ListRuleBook>,
160    pub selected_rulebooks: std::collections::HashSet<String>,
161    pub rulebook_switcher_selected: usize,
162    pub rulebook_search_input: String,
163    pub filtered_rulebooks: Vec<ListRuleBook>,
164    pub rulebook_config: Option<crate::RulebookConfig>,
165
166    // ========== Command Palette State ==========
167    pub show_command_palette: bool,
168    pub command_palette_selected: usize,
169    pub command_palette_scroll: usize,
170    pub command_palette_search: String,
171
172    // ========== Shortcuts Popup State ==========
173    pub show_shortcuts_popup: bool,
174    pub shortcuts_scroll: usize,
175    pub shortcuts_popup_mode: ShortcutsPopupMode,
176
177    // ========== File Changes Popup State ==========
178    pub show_file_changes_popup: bool,
179    pub file_changes_selected: usize,
180    pub file_changes_scroll: usize,
181    pub file_changes_search: String,
182
183    // ========== Usage Tracking State ==========
184    pub current_message_usage: LLMTokenUsage,
185    pub total_session_usage: LLMTokenUsage,
186    pub context_usage_percent: u64,
187
188    // ========== Configuration State ==========
189    pub secret_manager: SecretManager,
190    pub latest_version: Option<String>,
191    pub is_git_repo: bool,
192    pub auto_approve_manager: AutoApproveManager,
193    pub allowed_tools: Option<Vec<String>>,
194    pub agent_model: AgentModel,
195    pub llm_model: Option<LLMModel>,
196    /// Auth display info: (config_provider, auth_provider, subscription_name) for local providers
197    pub auth_display_info: (Option<String>, Option<String>, Option<String>),
198
199    // ========== Misc State ==========
200    pub ctrl_c_pressed_once: bool,
201    pub ctrl_c_timer: Option<std::time::Instant>,
202    pub mouse_capture_enabled: bool,
203    pub terminal_size: Size,
204    pub shell_screen: vt100::Parser,
205    pub shell_scroll: u16,
206    pub shell_history_lines: Vec<ratatui::text::Line<'static>>, // Accumulated styled history
207    pub interactive_shell_message_id: Option<Uuid>,
208    pub shell_interaction_occurred: bool,
209
210    // ========== Side Panel State ==========
211    pub show_side_panel: bool,
212    pub side_panel_focus: SidePanelSection,
213    pub side_panel_section_collapsed: std::collections::HashMap<SidePanelSection, bool>,
214    /// Stores the screen area for each side panel section to handle mouse clicks
215    pub side_panel_areas: HashMap<SidePanelSection, ratatui::layout::Rect>,
216    /// Current session ID for backup paths
217    pub session_id: String,
218    pub changeset: Changeset,
219
220    pub todos: Vec<TodoItem>,
221    /// Task progress (completed/total checklist items)
222    pub task_progress: Option<TaskProgress>,
223    pub session_start_time: std::time::Instant,
224
225    // Auto-show side panel tracking
226    pub side_panel_auto_shown: bool,
227
228    /// Agent board ID for task tracking (from AGENT_BOARD_AGENT_ID or created)
229    pub board_agent_id: Option<String>,
230
231    /// External editor command (vim, nvim, or nano)
232    pub editor_command: String,
233
234    /// Pending file to open in editor (set by handler, consumed by event loop)
235    pub pending_editor_open: Option<String>,
236
237    /// Billing info for the side panel
238    pub billing_info: Option<stakpak_shared::models::billing::BillingResponse>,
239}
240
241pub struct AppStateOptions<'a> {
242    pub latest_version: Option<String>,
243    pub redact_secrets: bool,
244    pub privacy_mode: bool,
245    pub is_git_repo: bool,
246    pub auto_approve_tools: Option<&'a Vec<String>>,
247    pub allowed_tools: Option<&'a Vec<String>>,
248    pub input_tx: Option<mpsc::Sender<InputEvent>>,
249    pub agent_model: AgentModel,
250    pub editor_command: Option<String>,
251    /// Auth display info: (config_provider, auth_provider, subscription_name) for local providers
252    pub auth_display_info: (Option<String>, Option<String>, Option<String>),
253    /// Agent board ID for task tracking (from AGENT_BOARD_AGENT_ID env var)
254    pub board_agent_id: Option<String>,
255}
256
257impl AppState {
258    pub fn get_helper_commands() -> Vec<HelperCommand> {
259        // Use unified command system
260        crate::services::commands::commands_to_helper_commands()
261    }
262
263    /// Initialize file search channels and spawn worker
264    fn init_file_search_channels(
265        helpers: &[HelperCommand],
266    ) -> (
267        mpsc::Sender<(String, usize)>,
268        mpsc::Receiver<FileSearchResult>,
269    ) {
270        let (file_search_tx, file_search_rx) = mpsc::channel::<(String, usize)>(10);
271        let (result_tx, result_rx) = mpsc::channel::<FileSearchResult>(10);
272        let helpers_clone = helpers.to_vec();
273        let file_search_instance = FileSearch::default();
274        // Spawn file_search worker from file_search.rs
275        tokio::spawn(file_search_worker(
276            file_search_rx,
277            result_tx,
278            helpers_clone,
279            file_search_instance,
280        ));
281        (file_search_tx, result_rx)
282    }
283
284    pub fn new(options: AppStateOptions) -> Self {
285        let AppStateOptions {
286            latest_version,
287            redact_secrets,
288            privacy_mode,
289            is_git_repo,
290            auto_approve_tools,
291            allowed_tools,
292            input_tx,
293            agent_model,
294            editor_command,
295            auth_display_info,
296            board_agent_id,
297        } = options;
298
299        let helpers = Self::get_helper_commands();
300        let (file_search_tx, result_rx) = Self::init_file_search_channels(&helpers);
301
302        AppState {
303            text_area: TextArea::new(),
304            text_area_state: TextAreaState::default(),
305            cursor_visible: true,
306            messages: Vec::new(), // Will be populated after state is created
307            scroll: 0,
308            scroll_to_bottom: false,
309            stay_at_bottom: true,
310            content_changed_while_scrolled_up: false,
311            helpers: helpers.clone(),
312            show_helper_dropdown: false,
313            helper_selected: 0,
314            helper_scroll: 0,
315            filtered_helpers: helpers,
316            filtered_files: Vec::new(),
317            show_shortcuts: false,
318            is_dialog_open: false,
319            dialog_command: None,
320            dialog_selected: 0,
321            loading: false,
322            loading_type: LoadingType::Llm,
323            spinner_frame: 0,
324            sessions: Vec::new(),
325            session_selected: 0,
326            account_info: String::new(),
327            pending_bash_message_id: None,
328            streaming_tool_results: HashMap::new(),
329            streaming_tool_result_id: None,
330            completed_tool_calls: std::collections::HashSet::new(),
331            shell_popup_visible: false,
332            shell_popup_expanded: false,
333            shell_popup_scroll: 0,
334            shell_cursor_visible: true,
335            shell_cursor_blink_timer: 0,
336            active_shell_command: None,
337            active_shell_command_output: None,
338            waiting_for_shell_input: false,
339            is_pasting: false,
340            shell_tool_calls: None,
341            shell_loading: false,
342            shell_pending_command_value: None,
343            shell_pending_command_executed: false,
344            shell_pending_command_output: None,
345            // Backward compatibility aliases
346            show_shell_mode: false,
347            shell_mode_input: String::new(),
348            is_tool_call_shell_command: false,
349            ondemand_shell_mode: false,
350            shell_pending_command: None,
351            shell_pending_command_output_count: 0,
352            shell_initial_prompt_shown: false,
353            shell_command_typed: false,
354            attached_images: Vec::new(),
355            pending_path_start: None,
356            dialog_message_id: None,
357            file_search: FileSearch::default(),
358            secret_manager: SecretManager::new(redact_secrets, privacy_mode),
359            latest_version: latest_version.clone(),
360            ctrl_c_pressed_once: false,
361            ctrl_c_timer: None,
362            pasted_long_text: None,
363            pasted_placeholder: None,
364            file_search_tx: Some(file_search_tx),
365            file_search_rx: Some(result_rx),
366            is_streaming: false,
367            interactive_commands: crate::constants::INTERACTIVE_COMMANDS
368                .iter()
369                .map(|s| s.to_string())
370                .collect(),
371            auto_approve_manager: AutoApproveManager::new(auto_approve_tools, input_tx),
372            allowed_tools: allowed_tools.cloned(),
373            dialog_focused: false, // Default to messages view focused
374            latest_tool_call: None,
375            retry_attempts: 0,
376            max_retry_attempts: 3,
377            last_user_message_for_retry: None,
378            is_retrying: false,
379            show_collapsed_messages: false,
380            collapsed_messages_scroll: 0,
381            collapsed_messages_selected: 0,
382            is_git_repo,
383            message_lines_cache: None,
384            collapsed_message_lines_cache: None,
385            processed_lines_cache: None,
386            per_message_cache: HashMap::new(),
387            assembled_lines_cache: None,
388            visible_lines_cache: None,
389            cache_generation: 0,
390            render_metrics: RenderMetrics::new(),
391            last_render_width: 0,
392            pending_pastes: Vec::new(),
393            mouse_capture_enabled: false, // Will be set based on terminal detection in event_loop
394            loading_manager: LoadingStateManager::new(),
395            has_user_messages: false,
396            message_tool_calls: None,
397            approval_bar: ApprovalBar::new(),
398            message_approved_tools: Vec::new(),
399            message_rejected_tools: Vec::new(),
400            toggle_approved_message: true,
401            terminal_size: Size {
402                width: 0,
403                height: 0,
404            },
405            session_tool_calls_queue: std::collections::HashMap::new(),
406            tool_call_execution_order: Vec::new(),
407            last_message_tool_calls: Vec::new(),
408            shell_screen: vt100::Parser::new(24, 80, 1000),
409            shell_scroll: 0,
410            shell_history_lines: Vec::new(),
411            interactive_shell_message_id: None,
412            shell_interaction_occurred: false,
413
414            // Profile switcher initialization
415            show_profile_switcher: false,
416            available_profiles: Vec::new(),
417            profile_switcher_selected: 0,
418            current_profile_name: "default".to_string(),
419            profile_switching_in_progress: false,
420            profile_switch_status_message: None,
421            rulebook_config: None,
422
423            // Shortcuts popup initialization
424            show_shortcuts_popup: false,
425            shortcuts_scroll: 0,
426            shortcuts_popup_mode: ShortcutsPopupMode::default(),
427            // Rulebook switcher initialization
428            show_rulebook_switcher: false,
429            available_rulebooks: Vec::new(),
430            selected_rulebooks: std::collections::HashSet::new(),
431            rulebook_switcher_selected: 0,
432            rulebook_search_input: String::new(),
433            filtered_rulebooks: Vec::new(),
434            // Command palette initialization
435            show_command_palette: false,
436            command_palette_selected: 0,
437            command_palette_scroll: 0,
438            command_palette_search: String::new(),
439
440            // File changes popup initialization
441            show_file_changes_popup: false,
442            file_changes_selected: 0,
443            file_changes_scroll: 0,
444            file_changes_search: String::new(),
445
446            // Usage tracking
447            current_message_usage: LLMTokenUsage {
448                prompt_tokens: 0,
449                completion_tokens: 0,
450                total_tokens: 0,
451                prompt_tokens_details: None,
452            },
453            total_session_usage: LLMTokenUsage {
454                prompt_tokens: 0,
455                completion_tokens: 0,
456                total_tokens: 0,
457                prompt_tokens_details: None,
458            },
459            context_usage_percent: 0,
460            agent_model,
461            llm_model: None,
462
463            // Side panel initialization
464            show_side_panel: false,
465            side_panel_focus: SidePanelSection::Context,
466            side_panel_section_collapsed: {
467                let mut collapsed = std::collections::HashMap::new();
468                collapsed.insert(SidePanelSection::Context, false); // Always expanded
469                collapsed.insert(SidePanelSection::Billing, false); // Expanded by default
470                collapsed.insert(SidePanelSection::Tasks, false); // Expanded by default
471                collapsed.insert(SidePanelSection::Changeset, false); // Expanded by default
472                collapsed
473            },
474            side_panel_areas: HashMap::new(),
475            changeset: Changeset::new(),
476            todos: Vec::new(),
477            task_progress: None,
478            session_start_time: std::time::Instant::now(),
479            side_panel_auto_shown: false,
480            session_id: String::new(), // Will be set when session starts
481            board_agent_id,
482            editor_command: crate::services::editor::detect_editor(editor_command)
483                .unwrap_or_else(|| "nano".to_string()),
484            pending_editor_open: None,
485            billing_info: None,
486            auth_display_info,
487        }
488    }
489
490    pub fn update_session_empty_status(&mut self) {
491        // Check if there are any user messages (not just any messages)
492        let session_empty = !self.has_user_messages && self.text_area.text().is_empty();
493        self.text_area.set_session_empty(session_empty);
494    }
495
496    // Convenience methods for accessing input and cursor
497    pub fn input(&self) -> &str {
498        self.text_area.text()
499    }
500
501    pub fn cursor_position(&self) -> usize {
502        self.text_area.cursor()
503    }
504
505    pub fn set_input(&mut self, input: &str) {
506        self.text_area.set_text(input);
507    }
508
509    pub fn set_cursor_position(&mut self, pos: usize) {
510        self.text_area.set_cursor(pos);
511    }
512
513    pub fn insert_char(&mut self, c: char) {
514        self.text_area.insert_str(&c.to_string());
515    }
516
517    pub fn insert_str(&mut self, s: &str) {
518        self.text_area.insert_str(s);
519    }
520
521    pub fn clear_input(&mut self) {
522        self.text_area.set_text("");
523    }
524
525    /// Check if user input should be blocked (during profile switch)
526    pub fn is_input_blocked(&self) -> bool {
527        self.profile_switching_in_progress
528    }
529
530    pub fn run_shell_command(&mut self, command: String, input_tx: &mpsc::Sender<InputEvent>) {
531        let (shell_tx, mut shell_rx) = mpsc::channel::<ShellEvent>(100);
532        self.messages.push(Message::plain_text("SPACING_MARKER"));
533        push_styled_message(
534            self,
535            &command,
536            AdaptiveColors::text(),
537            SHELL_PROMPT_PREFIX,
538            AdaptiveColors::dark_magenta(),
539        );
540        self.messages.push(Message::plain_text("SPACING_MARKER"));
541        let rows = if self.terminal_size.height > 0 {
542            self.terminal_size.height
543        } else {
544            24
545        };
546        let cols = if self.terminal_size.width > 0 {
547            self.terminal_size.width
548        } else {
549            80
550        };
551
552        #[cfg(unix)]
553        let shell_cmd = match run_pty_command(command.clone(), None, shell_tx, rows, cols) {
554            Ok(cmd) => cmd,
555            Err(e) => {
556                push_error_message(self, &format!("Failed to run command: {}", e), None);
557                return;
558            }
559        };
560
561        #[cfg(not(unix))]
562        let shell_cmd = run_background_shell_command(command.clone(), shell_tx);
563
564        self.active_shell_command = Some(shell_cmd.clone());
565        self.active_shell_command_output = Some(String::new());
566        self.shell_screen = vt100::Parser::new(rows, cols, 0);
567        let input_tx = input_tx.clone();
568        tokio::spawn(async move {
569            while let Some(event) = shell_rx.recv().await {
570                match event {
571                    ShellEvent::Output(line) => {
572                        let _ = input_tx.send(InputEvent::ShellOutput(line)).await;
573                    }
574                    ShellEvent::Error(line) => {
575                        let _ = input_tx.send(InputEvent::ShellError(line)).await;
576                    }
577
578                    ShellEvent::Completed(code) => {
579                        let _ = input_tx.send(InputEvent::ShellCompleted(code)).await;
580                        break;
581                    }
582                    ShellEvent::Clear => {
583                        let _ = input_tx.send(InputEvent::ShellClear).await;
584                    }
585                }
586            }
587        });
588    }
589
590    // --- NEW: Poll file_search results and update state ---
591    pub fn poll_file_search_results(&mut self) {
592        if let Some(rx) = &mut self.file_search_rx {
593            while let Ok(result) = rx.try_recv() {
594                // Get input text before any mutable operations
595                let input_text = self.text_area.text().to_string();
596
597                let filtered_files = result.filtered_files.clone();
598                self.filtered_files = filtered_files;
599                self.file_search.filtered_files = self.filtered_files.clone();
600                self.file_search.is_file_mode = !self.filtered_files.is_empty();
601                self.file_search.trigger_char = if !self.filtered_files.is_empty() {
602                    Some('@')
603                } else {
604                    None
605                };
606
607                // Update filtered_helpers from async worker
608                self.filtered_helpers = result.filtered_helpers;
609
610                // Reset selection index if it's out of bounds
611                if !self.filtered_helpers.is_empty()
612                    && self.helper_selected >= self.filtered_helpers.len()
613                {
614                    self.helper_selected = 0;
615                }
616
617                // Show dropdown if input is exactly '/' or if filtered_helpers is not empty and input starts with '/'
618                let has_at_trigger =
619                    find_at_trigger(&result.input, result.cursor_position).is_some();
620                self.show_helper_dropdown = (input_text.trim().starts_with('/'))
621                    || (!self.filtered_helpers.is_empty() && input_text.starts_with('/'))
622                    || (has_at_trigger && !self.waiting_for_shell_input);
623            }
624        }
625    }
626    pub fn auto_show_side_panel(&mut self) {
627        if !self.side_panel_auto_shown && !self.show_side_panel {
628            self.show_side_panel = true;
629            self.side_panel_auto_shown = true;
630        }
631    }
632}