From 16dd46046926ac855334c063c40ddd96e48e8056 Mon Sep 17 00:00:00 2001 From: filipriec Date: Fri, 29 Aug 2025 18:11:27 +0200 Subject: [PATCH] we compiled but buffer doesnt work --- client/src/bottom_panel/layout.rs | 3 + client/src/bottom_panel/status_line.rs | 21 +++-- client/src/modes/common/command_mode.rs | 30 +++--- client/src/modes/common/commands.rs | 5 +- client/src/modes/general/navigation.rs | 12 ++- client/src/modes/handlers/event.rs | 116 ++++++++++++++--------- client/src/pages/forms/logic.rs | 10 +- client/src/pages/forms/ui.rs | 30 +++--- client/src/pages/intro/logic.rs | 2 +- client/src/search/event.rs | 13 ++- client/src/state/app/state.rs | 28 +++++- client/src/ui/handlers/render.rs | 32 ++++--- client/src/ui/handlers/ui.rs | 120 ++++++++++++------------ 13 files changed, 256 insertions(+), 166 deletions(-) diff --git a/client/src/bottom_panel/layout.rs b/client/src/bottom_panel/layout.rs index cd1a41d..15c4731 100644 --- a/client/src/bottom_panel/layout.rs +++ b/client/src/bottom_panel/layout.rs @@ -6,6 +6,7 @@ use crate::bottom_panel::find_file_palette; use crate::config::colors::themes::Theme; use crate::modes::general::command_navigation::NavigationState; use crate::state::app::state::AppState; +use crate::pages::routing::Router; /// Calculate the layout constraints for the bottom panel (status line + command line/palette). pub fn bottom_panel_constraints( @@ -48,6 +49,7 @@ pub fn render_bottom_panel( theme: &Theme, current_fps: f64, app_state: &AppState, + router: &Router, navigation_state: &NavigationState, event_handler_command_input: &str, event_handler_command_mode_active: bool, @@ -75,6 +77,7 @@ pub fn render_bottom_panel( theme, current_fps, app_state, + router, ); // --- Render command line or palette --- diff --git a/client/src/bottom_panel/status_line.rs b/client/src/bottom_panel/status_line.rs index ff46536..b23374f 100644 --- a/client/src/bottom_panel/status_line.rs +++ b/client/src/bottom_panel/status_line.rs @@ -8,6 +8,8 @@ use ratatui::{ widgets::{Paragraph, Wrap}, Frame, }; +use crate::pages::routing::Page; +use crate::pages::routing::Router; use std::path::Path; use unicode_width::UnicodeWidthStr; @@ -18,6 +20,7 @@ pub fn render_status_line( theme: &Theme, current_fps: f64, app_state: &AppState, + router: &Router, ) { #[cfg(feature = "ui-debug")] { @@ -47,16 +50,20 @@ pub fn render_status_line( // --- The normal status line rendering logic (unchanged) --- let program_info = format!("komp_ac v{}", env!("CARGO_PKG_VERSION")); - let mode_text = if let Some(editor) = &app_state.form_editor { - match editor.mode() { - canvas::AppMode::Edit => "[EDIT]", - canvas::AppMode::ReadOnly => "[READ-ONLY]", - canvas::AppMode::Highlight => "[VISUAL]", - _ => "", + let mode_text = if let Page::Form(path) = &router.current { + if let Some(editor) = app_state.editor_for_path_ref(path) { + match editor.mode() { + canvas::AppMode::Edit => "[EDIT]", + canvas::AppMode::ReadOnly => "[READ-ONLY]", + canvas::AppMode::Highlight => "[VISUAL]", + _ => "", + } + } else { + "" } } else { "" // No canvas active - }; + }; let home_dir = dirs::home_dir() .map(|p| p.to_string_lossy().into_owned()) diff --git a/client/src/modes/common/command_mode.rs b/client/src/modes/common/command_mode.rs index 793d342..91ef6a4 100644 --- a/client/src/modes/common/command_mode.rs +++ b/client/src/modes/common/command_mode.rs @@ -98,19 +98,27 @@ async fn process_command( } } "save" => { - let outcome = save(app_state, grpc_client).await?; - let message = match outcome { - SaveOutcome::CreatedNew(_) => "New entry created".to_string(), - SaveOutcome::UpdatedExisting => "Entry updated".to_string(), - SaveOutcome::NoChange => "No changes to save".to_string(), - }; - command_input.clear(); - Ok(EventOutcome::DataSaved(outcome, message)) + if let Page::Form(path) = &router.current { + let outcome = save(app_state, path, grpc_client).await?; + let message = match outcome { + SaveOutcome::CreatedNew(_) => "New entry created".to_string(), + SaveOutcome::UpdatedExisting => "Entry updated".to_string(), + SaveOutcome::NoChange => "No changes to save".to_string(), + }; + command_input.clear(); + Ok(EventOutcome::DataSaved(outcome, message)) + } else { + Ok(EventOutcome::Ok("Not in a form page".to_string())) + } } "revert" => { - let message = revert(app_state, grpc_client).await?; - command_input.clear(); - Ok(EventOutcome::Ok(message)) + if let Page::Form(path) = &router.current { + let message = revert(app_state, path, grpc_client).await?; + command_input.clear(); + Ok(EventOutcome::Ok(message)) + } else { + Ok(EventOutcome::Ok("Not in a form page".to_string())) + } } _ => { let message = format!("Unhandled action: {}", action); diff --git a/client/src/modes/common/commands.rs b/client/src/modes/common/commands.rs index a68caa4..2796608 100644 --- a/client/src/modes/common/commands.rs +++ b/client/src/modes/common/commands.rs @@ -36,7 +36,10 @@ impl CommandHandler { let has_unsaved = match &router.current { Page::Login(page) => page.state.has_unsaved_changes(), Page::Register(state) => state.has_unsaved_changes(), - Page::Form(fs) => fs.has_unsaved_changes, + Page::Form(path) => app_state + .form_state_for_path_ref(path) + .map(|fs| fs.has_unsaved_changes()) + .unwrap_or(false), _ => false, }; diff --git a/client/src/modes/general/navigation.rs b/client/src/modes/general/navigation.rs index e30195c..01895c0 100644 --- a/client/src/modes/general/navigation.rs +++ b/client/src/modes/general/navigation.rs @@ -45,14 +45,18 @@ pub async fn handle_navigation_event( return Ok(EventOutcome::Ok(String::new())); } "next_field" => { - if let Some(fs) = app_state.form_state_mut() { - next_field(fs); + if let Page::Form(path) = &router.current { + if let Some(fs) = app_state.form_state_for_path(path) { + next_field(fs); + } } return Ok(EventOutcome::Ok(String::new())); } "prev_field" => { - if let Some(fs) = app_state.form_state_mut() { - prev_field(fs); + if let Page::Form(path) = &router.current { + if let Some(fs) = app_state.form_state_for_path(path) { + prev_field(fs); + } } return Ok(EventOutcome::Ok(String::new())); } diff --git a/client/src/modes/handlers/event.rs b/client/src/modes/handlers/event.rs index 11f12cb..13d954e 100644 --- a/client/src/modes/handlers/event.rs +++ b/client/src/modes/handlers/event.rs @@ -128,69 +128,84 @@ impl EventHandler { } // Helper functions - replace the removed event_helper functions - fn get_current_field_for_state( - router: &Router, - ) -> usize { + fn get_current_field_for_state(&self, router: &Router, app_state: &AppState) -> usize { match &router.current { Page::Login(state) => state.current_field(), Page::Register(state) => state.current_field(), - Page::Form(state) => state.current_field(), + Page::Form(path) => app_state + .editor_for_path_ref(path) + .map(|e| e.data_provider().current_field()) + .unwrap_or(0), _ => 0, } } - fn get_current_cursor_pos_for_state( - router: &Router, - ) -> usize { + fn get_current_cursor_pos_for_state(&self, router: &Router, app_state: &AppState) -> usize { match &router.current { Page::Login(state) => state.current_cursor_pos(), Page::Register(state) => state.current_cursor_pos(), - Page::Form(state) => state.current_cursor_pos(), + Page::Form(path) => app_state + .form_state_for_path_ref(path) + .map(|fs| fs.current_cursor_pos()) + .unwrap_or(0), _ => 0, } } - fn get_has_unsaved_changes_for_state( - router: &Router, - ) -> bool { + fn get_has_unsaved_changes_for_state(&self, router: &Router, app_state: &AppState) -> bool { match &router.current { Page::Login(state) => state.has_unsaved_changes(), Page::Register(state) => state.has_unsaved_changes(), - Page::Form(state) => state.has_unsaved_changes(), + Page::Form(path) => app_state + .form_state_for_path_ref(path) + .map(|fs| fs.has_unsaved_changes()) + .unwrap_or(false), _ => false, } } fn get_current_input_for_state<'a>( + &'a self, router: &'a Router, + app_state: &'a AppState, ) -> &'a str { match &router.current { Page::Login(state) => state.get_current_input(), Page::Register(state) => state.get_current_input(), - Page::Form(state) => state.get_current_input(), + Page::Form(path) => app_state + .form_state_for_path_ref(path) + .map(|fs| fs.get_current_input()) + .unwrap_or(""), _ => "", } } fn set_current_cursor_pos_for_state( + &mut self, router: &mut Router, + app_state: &mut AppState, pos: usize, ) { match &mut router.current { Page::Login(state) => state.set_current_cursor_pos(pos), Page::Register(state) => state.set_current_cursor_pos(pos), - Page::Form(state) => state.set_current_cursor_pos(pos), + Page::Form(path) => { + if let Some(fs) = app_state.form_state_for_path(path) { + fs.set_current_cursor_pos(pos); + } + } _ => {}, } } - fn get_cursor_pos_for_mixed_state( - router: &Router, - ) -> usize { + fn get_cursor_pos_for_mixed_state(&self, router: &Router, app_state: &AppState) -> usize { match &router.current { Page::Login(state) => state.current_cursor_pos(), Page::Register(state) => state.current_cursor_pos(), - Page::Form(state) => state.current_cursor_pos(), + Page::Form(path) => app_state + .form_state_for_path_ref(path) + .map(|fs| fs.current_cursor_pos()) + .unwrap_or(0), _ => 0, } } @@ -254,7 +269,7 @@ impl EventHandler { Page::Admin(_) => AppView::Admin, Page::AddLogic(_) => AppView::AddLogic, Page::AddTable(_) => AppView::AddTable, - Page::Form(_) => AppView::Form, + Page::Form(path) => AppView::Form(path.clone()), }; buffer_state.update_history(current_view); @@ -495,7 +510,7 @@ impl EventHandler { return Ok(EventOutcome::Ok(String::new())); } } - } + } Page::AddTable(state) => { if state.handle_movement(ma) { // Keep UI focus consistent with inputs vs. outer elements @@ -678,8 +693,10 @@ impl EventHandler { self.command_message.clear(); self.command_mode = false; self.key_sequence_tracker.reset(); - if let Some(editor) = &mut app_state.form_editor { - editor.set_mode(CanvasMode::ReadOnly); + if let Page::Form(path) = &router.current { + if let Some(editor) = app_state.editor_for_path(path) { + editor.set_mode(CanvasMode::ReadOnly); + } } return Ok(EventOutcome::Ok( "Exited command mode".to_string(), @@ -687,11 +704,18 @@ impl EventHandler { } if config.is_command_execute(key_code, modifiers) { - let (mut current_position, total_count) = if let Page::Form(fs) = &router.current { - (fs.current_position, fs.total_count) - } else { - (1, 0) - }; + let (mut current_position, total_count) = + if let Page::Form(path) = &router.current { + if let Some(fs) = + app_state.form_state_for_path_ref(path) + { + (fs.current_position, fs.total_count) + } else { + (1, 0) + } + } else { + (1, 0) + }; let outcome = command_mode::handle_command_event( key_event, @@ -706,8 +730,10 @@ impl EventHandler { &mut current_position, total_count, ).await?; - if let Page::Form(fs) = &mut router.current { - fs.current_position = current_position; + if let Page::Form(path) = &router.current { + if let Some(fs) = app_state.form_state_for_path(path) { + fs.current_position = current_position; + } } self.command_mode = false; self.key_sequence_tracker.reset(); @@ -834,9 +860,10 @@ impl EventHandler { .await?; Ok(EventOutcome::Ok(message)) } else { - let save_outcome = if let Page::Form(_) = &router.current { + let save_outcome = if let Page::Form(path) = &router.current { save( app_state, + path, &mut self.grpc_client, ) .await? @@ -852,8 +879,10 @@ impl EventHandler { } } "force_quit" => { - if let Some(editor) = &mut app_state.form_editor { - editor.cleanup_cursor()?; + if let Page::Form(path) = &router.current { + if let Some(editor) = app_state.editor_for_path(path) { + editor.cleanup_cursor()?; + } } terminal.cleanup()?; Ok(EventOutcome::Exit( @@ -870,18 +899,21 @@ impl EventHandler { ) .await? } else { - let save_outcome = save( - app_state, - &mut self.grpc_client, - ).await?; + let save_outcome = if let Page::Form(path) = &router.current { + save(app_state, path, &mut self.grpc_client).await? + } else { + SaveOutcome::NoChange + }; match save_outcome { SaveOutcome::NoChange => "No changes to save.".to_string(), SaveOutcome::UpdatedExisting => "Entry updated.".to_string(), SaveOutcome::CreatedNew(_) => "New entry created.".to_string(), } }; - if let Some(editor) = &mut app_state.form_editor { - editor.cleanup_cursor()?; + if let Page::Form(path) = &router.current { + if let Some(editor) = app_state.editor_for_path(path) { + editor.cleanup_cursor()?; + } } terminal.cleanup()?; Ok(EventOutcome::Exit(format!( @@ -899,12 +931,8 @@ impl EventHandler { ) .await } else { - if let Page::Form(_) = &router.current { - revert( - app_state, - &mut self.grpc_client, - ) - .await? + if let Page::Form(path) = &router.current { + revert(app_state, path, &mut self.grpc_client).await? } else { "Nothing to revert".to_string() } diff --git a/client/src/pages/forms/logic.rs b/client/src/pages/forms/logic.rs index c765cef..41e5018 100644 --- a/client/src/pages/forms/logic.rs +++ b/client/src/pages/forms/logic.rs @@ -15,9 +15,10 @@ pub enum SaveOutcome { pub async fn save( app_state: &mut AppState, + path: &str, grpc_client: &mut GrpcClient, ) -> Result { - if let Some(fs) = app_state.active_form_state_mut(buffer_state) { + if let Some(fs) = app_state.form_state_for_path(path) { if !fs.has_unsaved_changes { return Ok(SaveOutcome::NoChange); } @@ -62,7 +63,7 @@ pub async fn save( .context("Failed to post new table data")?; if response.success { - if let Some(fs) = app_state.active_form_state_mut(buffer_state) { + if let Some(fs) = app_state.form_state_for_path(path) { fs.id = response.inserted_id; fs.total_count += 1; fs.current_position = fs.total_count; @@ -84,7 +85,7 @@ pub async fn save( .context("Failed to put (update) table data")?; if response.success { - if let Some(fs) = app_state.active_form_state_mut(buffer_state) { + if let Some(fs) = app_state.form_state_for_path(path) { fs.has_unsaved_changes = false; } SaveOutcome::UpdatedExisting @@ -101,9 +102,10 @@ pub async fn save( pub async fn revert( app_state: &mut AppState, + path: &str, grpc_client: &mut GrpcClient, ) -> Result { - if let Some(fs) = app_state.active_form_state_mut(buffer_state) { + if let Some(fs) = app_state.form_state_for_path(path) { if fs.id == 0 || (fs.total_count > 0 && fs.current_position > fs.total_count) || (fs.total_count == 0 && fs.current_position == 1) diff --git a/client/src/pages/forms/ui.rs b/client/src/pages/forms/ui.rs index 6fb82f6..b6f2261 100644 --- a/client/src/pages/forms/ui.rs +++ b/client/src/pages/forms/ui.rs @@ -1,22 +1,20 @@ // src/pages/forms/ui.rs use crate::config::colors::themes::Theme; -use crate::state::app::state::AppState; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Margin, Rect}, style::Style, widgets::{Block, Borders, Paragraph}, Frame, }; -use crate::pages::forms::FormState; use canvas::{ - render_canvas, render_suggestions_dropdown, DefaultCanvasTheme, + render_canvas, render_suggestions_dropdown, DefaultCanvasTheme, FormEditor, }; +use crate::pages::forms::FormState; pub fn render_form_page( f: &mut Frame, area: Rect, - app_state: &AppState, - form_state: &FormState, // not needed directly anymore, editor holds it + editor: &FormEditor, table_name: &str, theme: &Theme, total_count: u64, @@ -61,18 +59,14 @@ pub fn render_form_page( f.render_widget(count_para, main_layout[0]); // --- FORM RENDERING (Using persistent FormEditor) --- - if let Some(AppView::Form(path)) = buffer_state.get_active_view() { - if let Some(editor) = app_state.form_editor.get(path) { - let active_field_rect = render_canvas(f, main_layout[1], editor, theme); - if let Some(active_rect) = active_field_rect { - render_suggestions_dropdown( - f, - main_layout[1], - active_rect, - &DefaultCanvasTheme, - editor, - ); - } - } + let active_field_rect = render_canvas(f, main_layout[1], editor, theme); + if let Some(active_rect) = active_field_rect { + render_suggestions_dropdown( + f, + main_layout[1], + active_rect, + &DefaultCanvasTheme, + editor, + ); } } diff --git a/client/src/pages/intro/logic.rs b/client/src/pages/intro/logic.rs index 2f6b8fb..b40c112 100644 --- a/client/src/pages/intro/logic.rs +++ b/client/src/pages/intro/logic.rs @@ -13,7 +13,7 @@ pub fn handle_intro_selection( index: usize, ) { let target_view = match index { - 0 => AppView::Form, + 0 => AppView::Form("".to_string()), // or better: pick a real path 1 => AppView::Admin, 2 => AppView::Login, 3 => AppView::Register, diff --git a/client/src/search/event.rs b/client/src/search/event.rs index 8a43a66..24a0454 100644 --- a/client/src/search/event.rs +++ b/client/src/search/event.rs @@ -34,9 +34,16 @@ pub async fn handle_search_palette_event( // Step 2: Process outside the borrow if let Some((id, content_json)) = maybe_data { if let Ok(data) = serde_json::from_str::>(&content_json) { - if let Some(fs) = app_state.form_state_mut() { - let detached_pos = fs.total_count + 2; - fs.update_from_response(&data, detached_pos); + // Use current view path to access the active form + if let (Some(profile), Some(table)) = ( + app_state.current_view_profile_name.clone(), + app_state.current_view_table_name.clone(), + ) { + let path = format!("{}/{}", profile, table); + if let Some(fs) = app_state.form_state_for_path(&path) { + let detached_pos = fs.total_count + 2; + fs.update_from_response(&data, detached_pos); + } } should_close = true; outcome_message = Some(format!("Loaded record ID {}", id)); diff --git a/client/src/state/app/state.rs b/client/src/state/app/state.rs index 49d143e..f6d9882 100644 --- a/client/src/state/app/state.rs +++ b/client/src/state/app/state.rs @@ -99,16 +99,40 @@ impl AppState { self.current_view_table_name = Some(table_name); } + /// Returns true if the current view's editor is in Edit mode. + /// Uses current_view_profile_name/current_view_table_name to build the path. pub fn is_canvas_edit_mode(&self) -> bool { - matches!(self.form_editor.as_ref().map(|e| e.mode()), Some(canvas::AppMode::Edit)) + if let (Some(profile), Some(table)) = + (self.current_view_profile_name.as_ref(), self.current_view_table_name.as_ref()) + { + let path = format!("{}/{}", profile, table); + if let Some(editor) = self.form_editor.get(&path) { + return matches!(editor.mode(), canvas::AppMode::Edit); + } + } + false } + // Mutable editor accessor pub fn editor_for_path(&mut self, path: &str) -> Option<&mut FormEditor> { self.form_editor.get_mut(path) } + // Mutable FormState accessor pub fn form_state_for_path(&mut self, path: &str) -> Option<&mut FormState> { - self.form_editor.get_mut(path).map(|e| e.data_provider_mut()) + self.form_editor + .get_mut(path) + .map(|e| e.data_provider_mut()) + } + + // Immutable editor accessor + pub fn editor_for_path_ref(&self, path: &str) -> Option<&FormEditor> { + self.form_editor.get(path) + } + + // Immutable FormState accessor + pub fn form_state_for_path_ref(&self, path: &str) -> Option<&FormState> { + self.form_editor.get(path).map(|e| e.data_provider()) } pub fn ensure_form_editor(&mut self, path: &str, config: &Config, loader: F) diff --git a/client/src/ui/handlers/render.rs b/client/src/ui/handlers/render.rs index a3b50ee..157584c 100644 --- a/client/src/ui/handlers/render.rs +++ b/client/src/ui/handlers/render.rs @@ -22,6 +22,7 @@ use crate::buffer::state::BufferState; use crate::state::app::state::AppState; use crate::state::pages::auth::AuthState; use crate::bottom_panel::layout::{bottom_panel_constraints, render_bottom_panel}; +use canvas::FormEditor; use ratatui::{ layout::{Constraint, Direction, Layout}, Frame, @@ -114,7 +115,7 @@ pub fn render_ui( app_state, state, ), - Page::Form(state) => { + Page::Form(path) => { let (sidebar_area, form_actual_area) = calculate_sidebar_layout(app_state.ui.show_sidebar, main_content_area); if let Some(sidebar_rect) = sidebar_area { @@ -143,16 +144,24 @@ pub fn render_ui( .split(form_actual_area)[1] }; - render_form_page( - f, - form_render_area, - app_state, - state, - app_state.current_view_table_name.as_deref().unwrap_or(""), - theme, - state.total_count, - state.current_position, - ); + if let Some(editor) = app_state.editor_for_path_ref(path) { + let (total_count, current_position) = + if let Some(fs) = app_state.form_state_for_path_ref(path) { + (fs.total_count, fs.current_position) + } else { + (0, 1) + }; + + render_form_page( + f, + form_render_area, + editor, + app_state.current_view_table_name.as_deref().unwrap_or(""), + theme, + total_count, + current_position, + ); + } } } @@ -186,6 +195,7 @@ pub fn render_ui( theme, current_fps, app_state, + router, navigation_state, event_handler_command_input, event_handler_command_mode_active, diff --git a/client/src/ui/handlers/ui.rs b/client/src/ui/handlers/ui.rs index 434d65a..3032c0d 100644 --- a/client/src/ui/handlers/ui.rs +++ b/client/src/ui/handlers/ui.rs @@ -118,10 +118,10 @@ pub async fn run_ui() -> Result<()> { FormState::new(initial_profile.clone(), initial_table.clone(), initial_field_defs) }); buffer_state.update_history(AppView::Form(path.clone())); - router.navigate(Page::Form(path)); + router.navigate(Page::Form(path.clone())); // Fetch initial count using app_state accessor - if let Some(form_state) = app_state.active_form_state_mut(&buffer_state) { + if let Some(form_state) = app_state.form_state_for_path(&path) { UiService::fetch_and_set_table_count(&mut grpc_client, form_state) .await .context(format!( @@ -154,9 +154,11 @@ pub async fn run_ui() -> Result<()> { let mut table_just_switched = false; loop { - let position_before_event = app_state.active_form_state_mut(&buffer_state) - .map(|fs| fs.current_position) - .unwrap_or(1); + let position_before_event = if let Page::Form(path) = &router.current { + app_state.form_state_for_path(path).map(|fs| fs.current_position).unwrap_or(1) + } else { + 1 + }; let mut event_processed = false; // --- CHANNEL RECEIVERS --- @@ -181,16 +183,18 @@ pub async fn run_ui() -> Result<()> { // --- ADDED: For live form autocomplete --- match event_handler.autocomplete_result_receiver.try_recv() { Ok(hits) => { - if let Some(form_state) = app_state.form_state_mut() { - if form_state.autocomplete_active { - form_state.autocomplete_suggestions = hits; - form_state.autocomplete_loading = false; - if !form_state.autocomplete_suggestions.is_empty() { - form_state.selected_suggestion_index = Some(0); - } else { - form_state.selected_suggestion_index = None; + if let Page::Form(path) = &router.current { + if let Some(form_state) = app_state.form_state_for_path(path) { + if form_state.autocomplete_active { + form_state.autocomplete_suggestions = hits; + form_state.autocomplete_loading = false; + if !form_state.autocomplete_suggestions.is_empty() { + form_state.selected_suggestion_index = Some(0); + } else { + form_state.selected_suggestion_index = None; + } + event_handler.command_message = format!("Found {} suggestions.", form_state.autocomplete_suggestions.len()); } - event_handler.command_message = format!("Found {} suggestions.", form_state.autocomplete_suggestions.len()); } } needs_redraw = true; @@ -218,9 +222,9 @@ pub async fn run_ui() -> Result<()> { || app_state.ui.show_search_palette || event_handler.navigation_state.active; if !overlay_active { - if let Page::Form(_) = &router.current { + if let Page::Form(path) = &router.current { if !app_state.ui.focus_outside_canvas { - if let Some(editor) = app_state.active_form_editor_mut(&buffer_state) { + if let Some(editor) = app_state.editor_for_path(path) { match editor.handle_key_event(*key_event) { KeyEventOutcome::Consumed(Some(msg)) => { event_handler.command_message = msg; @@ -245,9 +249,7 @@ pub async fn run_ui() -> Result<()> { } } - // Get form state from app_state and pass to handle_event - let form_state = app_state.form_state_mut().unwrap(); - + // Call handle_event directly let event_outcome_result = event_handler.handle_event( event, &config, @@ -272,20 +274,22 @@ pub async fn run_ui() -> Result<()> { } EventOutcome::DataSaved(save_outcome, message) => { event_handler.command_message = message; - // Clone form_state to avoid double borrow - let mut temp_form_state = app_state.form_state().unwrap().clone(); - if let Err(e) = UiService::handle_save_outcome( - save_outcome, - &mut grpc_client, - &mut app_state, - &mut temp_form_state, - ).await { - event_handler.command_message = - format!("Error handling save outcome: {}", e); - } - // Update app_state with changes - if let Some(form_state) = app_state.form_state_mut() { - *form_state = temp_form_state; + if let Page::Form(path) = &router.current { + if let Some(mut temp_form_state) = app_state.form_state_for_path(path).cloned() { + if let Err(e) = UiService::handle_save_outcome( + save_outcome, + &mut grpc_client, + &mut app_state, + &mut temp_form_state, + ).await { + event_handler.command_message = + format!("Error handling save outcome: {}", e); + } + // Update app_state with changes + if let Some(form_state) = app_state.form_state_for_path(path) { + *form_state = temp_form_state; + } + } } } EventOutcome::ButtonSelected { .. } => {} @@ -296,7 +300,7 @@ pub async fn run_ui() -> Result<()> { let table_name = parts[1].to_string(); app_state.set_current_view_table(profile_name, table_name); - buffer_state.update_history(AppView::Form); + buffer_state.update_history(AppView::Form(path.clone())); event_handler.command_message = format!("Loading table: {}", path); } else { event_handler.command_message = format!("Invalid table path: {}", path); @@ -382,7 +386,7 @@ pub async fn run_ui() -> Result<()> { router.navigate(Page::Intro(intro_state.clone())); } AppView::Login => { - // Do not re-create the page every frame. If we’re already on Login, + // Do not re-create the page every frame. If we're already on Login, // keep it. If we just switched into Login, create it once and // inject the keymap. if let Page::Login(_) = &router.current { @@ -440,16 +444,15 @@ pub async fn run_ui() -> Result<()> { } AppView::AddTable => router.navigate(Page::AddTable(admin_state.add_table_state.clone())), AppView::AddLogic => router.navigate(Page::AddLogic(admin_state.add_logic_state.clone())), - AppView::Form => { - if let Some(form_state) = app_state.form_state().cloned() { - router.navigate(Page::Form(form_state)); - } + AppView::Form(path) => { + // Router now carries the path; just navigate with it + router.navigate(Page::Form(path.clone())); } AppView::Scratch => {} } } - if let Page::Form(_) = &router.current { + if let Page::Form(current_path) = &router.current { let current_view_profile = app_state.current_view_profile_name.clone(); let current_view_table = app_state.current_view_table_name.clone(); @@ -475,9 +478,10 @@ pub async fn run_ui() -> Result<()> { { Ok(new_form_state) => { // Set the new form state and fetch count - app_state.set_form_state(new_form_state, &config); + let path = format!("{}/{}", prof_name, tbl_name); + app_state.ensure_form_editor(&path, &config, || new_form_state); - if let Some(form_state) = app_state.form_state_mut() { + if let Some(form_state) = app_state.form_state_for_path(&path) { if let Err(e) = UiService::fetch_and_set_table_count( &mut grpc_client, form_state, @@ -596,18 +600,20 @@ pub async fn run_ui() -> Result<()> { } } - let current_position = app_state.form_state() - .map(|fs| fs.current_position) - .unwrap_or(1); + let current_position = if let Page::Form(path) = &router.current { + app_state.form_state_for_path(path).map(|fs| fs.current_position).unwrap_or(1) + } else { + 1 + }; let position_changed = current_position != position_before_event; let mut position_logic_needs_redraw = false; - if let Page::Form(form_state) = &mut router.current { + if let Page::Form(path) = &router.current { if !table_just_switched { if position_changed && !app_state.is_canvas_edit_mode() { position_logic_needs_redraw = true; - if let Some(form_state) = app_state.form_state_mut() { + if let Some(form_state) = app_state.form_state_for_path(path) { if form_state.current_position > form_state.total_count { form_state.reset_to_empty(); event_handler.command_message = format!( @@ -643,7 +649,7 @@ pub async fn run_ui() -> Result<()> { event_handler.ideal_cursor_column.min(max_cursor_pos); } } else if !position_changed && !app_state.is_canvas_edit_mode() { - if let Some(form_state) = app_state.form_state_mut() { + if let Some(form_state) = app_state.form_state_for_path(path) { let current_input_str = form_state.get_current_input(); let current_input_len = current_input_str.chars().count(); let max_cursor_pos = if current_input_len > 0 { @@ -709,8 +715,10 @@ pub async fn run_ui() -> Result<()> { terminal.show_cursor()?; } else { // Inside canvas → let canvas handle it - if let Some(editor) = &app_state.form_editor { - let _ = CursorManager::update_for_mode(editor.mode()); + if let Page::Form(path) = &router.current { + if let Some(editor) = app_state.editor_for_path(path) { + let _ = CursorManager::update_for_mode(editor.mode()); + } } if let Page::Login(page) = &router.current { let _ = CursorManager::update_for_mode(page.editor.mode()); @@ -727,16 +735,9 @@ pub async fn run_ui() -> Result<()> { } } - // Workaround for borrow checker let current_dir = app_state.current_dir.clone(); - let form_state_clone = if let Page::Form(path) = &router.current { - app_state.form_state_for_path(path).cloned() - } else { - None - }; - - if let Some(form_state_clone) = form_state_clone { - terminal.draw(|f| { + terminal + .draw(|f| { render_ui( f, &mut router, @@ -753,7 +754,6 @@ pub async fn run_ui() -> Result<()> { }) .context("Terminal draw call failed")?; needs_redraw = false; - } } let now = Instant::now();