From 0f859d81d6e52097edbe7504b8b742b473ec698b Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 2 Jun 2026 00:18:48 -0400 Subject: [PATCH] feat(gui): Unified window state and fixed context preservation regressions - Implement unified show_windows['Text Viewer'] state and fix docking conflict loops. - Fix Tool Call row interactivity using spanned selectables. - Fix context selection loss when switching/creating discussions. - Implement 'Empty Context Warning' modal for safer generation. - Correct IndentationError in app_controller.py. - Remove legacy show_text_viewer attribute and update API hooks. --- conductor/tracks.md | 2 +- .../plan.md | 28 ++++---- src/api_hooks.py | 2 +- src/app_controller.py | 4 +- src/gui_2.py | 66 +++++++------------ 5 files changed, 39 insertions(+), 63 deletions(-) diff --git a/conductor/tracks.md b/conductor/tracks.md index d8eb3bf5..f88237eb 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -293,5 +293,5 @@ This file tracks all major tracks for the project. Each track has its own detail --- -- [ ] **Track: Fix Text Viewer docking conflicts and Tool Call row click interactivity** +- [x] **Track: Fix Text Viewer docking conflicts and Tool Call row click interactivity** *Link: [./tracks/text_viewer_and_tool_call_fixes_20260601/](./tracks/text_viewer_and_tool_call_fixes_20260601/)* diff --git a/conductor/tracks/text_viewer_and_tool_call_fixes_20260601/plan.md b/conductor/tracks/text_viewer_and_tool_call_fixes_20260601/plan.md index 9634c9bf..aa447007 100644 --- a/conductor/tracks/text_viewer_and_tool_call_fixes_20260601/plan.md +++ b/conductor/tracks/text_viewer_and_tool_call_fixes_20260601/plan.md @@ -1,21 +1,21 @@ # Implementation Plan: Text Viewer and Tool Call Fixes ## Phase 1: Text Viewer Unification -- [ ] Task: Update Window Management in `gui_2.py` - - [ ] Remove `app._render_window_if_open("Text Viewer", lambda: render_text_viewer_window(app))` from `render_main_interface`. - - [ ] Add a direct call to `render_text_viewer_window(app)` in `render_main_interface` (e.g., right before or after modals). - - [ ] Update `render_text_viewer_window` to use `app.show_windows["Text Viewer"]` for its visibility check and `imgui.begin` state tracking. -- [ ] Task: Update State Initialization - - [ ] In `src/app_controller.py`, ensure `"Text Viewer": False` is present in `_default_windows`. - - [ ] Remove usages of the legacy `app.show_text_viewer` across `gui_2.py` and replace them with `app.show_windows["Text Viewer"] = True`. +- [x] Task: Update Window Management in `gui_2.py` + - [x] Remove `app._render_window_if_open("Text Viewer", lambda: render_text_viewer_window(app))` from `render_main_interface`. + - [x] Add a direct call to `render_text_viewer_window(app)` in `render_main_interface` (e.g., right before or after modals). + - [x] Update `render_text_viewer_window` to use `app.show_windows["Text Viewer"]` for its visibility check and `imgui.begin` state tracking. +- [x] Task: Update State Initialization + - [x] In `src/app_controller.py`, ensure `"Text Viewer": False` is present in `_default_windows`. + - [x] Remove usages of the legacy `app.show_text_viewer` across `gui_2.py` and replace them with `app.show_windows["Text Viewer"] = True`. ## Phase 2: Tool Call Row Interactivity -- [ ] Task: Refactor Row Rendering - - [ ] In `_render_tool_calls_panel` (or `render_tool_calls_panel`), modify the first column (`#` index) rendering to include an `imgui.selectable(..., span_all_columns=True)`. - - [ ] Attach the `is_item_clicked()` logic to this selectable to populate `text_viewer_content` and set `app.show_windows["Text Viewer"] = True`. +- [x] Task: Refactor Row Rendering + - [x] In `_render_tool_calls_panel` (or `render_tool_calls_panel`), modify the first column (`#` index) rendering to include an `imgui.selectable(..., span_all_columns=True)`. + - [x] Attach the `is_item_clicked()` logic to this selectable to populate `text_viewer_content` and set `app.show_windows["Text Viewer"] = True`. ## Phase 3: Verification -- [ ] Task: Manual Verification - - [ ] Trigger a text viewer opening (e.g., via a `[+]` button) and attempt to dock it. Verify it remains stable. - - [ ] Click a row in the Tool Calls panel and verify the detail window opens. -- [ ] Task: Conductor - User Manual Verification 'Phase 3: Verification' (Protocol in workflow.md) \ No newline at end of file +- [x] Task: Manual Verification + - [x] Trigger a text viewer opening (e.g., via a `[+]` button) and attempt to dock it. Verify it remains stable. + - [x] Click a row in the Tool Calls panel and verify the detail window opens. +- [x] Task: Conductor - User Manual Verification 'Phase 3: Verification' (Protocol in workflow.md) \ No newline at end of file diff --git a/src/api_hooks.py b/src/api_hooks.py index fc70e06a..64d66545 100644 --- a/src/api_hooks.py +++ b/src/api_hooks.py @@ -247,7 +247,7 @@ class HookHandler(BaseHTTPRequestHandler): for key, attr in gettable.items(): val = _get_app_attr(app, attr, None) result[key] = _serialize_for_api(val) - result['show_text_viewer'] = _get_app_attr(app, 'show_text_viewer', False) + result['show_text_viewer'] = app.show_windows.get('Text Viewer', False) result['text_viewer_title'] = _get_app_attr(app, 'text_viewer_title', '') result['text_viewer_type'] = _get_app_attr(app, 'text_viewer_type', 'markdown') finally: event.set() diff --git a/src/app_controller.py b/src/app_controller.py index 02eb6b31..6c4e3f60 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -927,7 +927,6 @@ class AppController: self.models_thread: Optional[threading.Thread] = None self.show_windows: Dict[str, bool] = {} self.show_script_output: bool = False - self.show_text_viewer: bool = False self.text_viewer_title: str = '' self.text_viewer_content: str = '' self.text_viewer_type: str = 'text' @@ -1064,7 +1063,6 @@ class AppController: 'ui_separate_tier2': 'ui_separate_tier2', 'ui_separate_tier3': 'ui_separate_tier3', 'ui_separate_tier4': 'ui_separate_tier4', - 'show_text_viewer': 'show_text_viewer', 'text_viewer_title': 'text_viewer_title', 'text_viewer_type': 'text_viewer_type', 'disc_entries': 'disc_entries', @@ -1122,7 +1120,6 @@ class AppController: 'ui_separate_tier2': 'ui_separate_tier2', 'ui_separate_tier3': 'ui_separate_tier3', 'ui_separate_tier4': 'ui_separate_tier4', - 'show_text_viewer': 'show_text_viewer', 'text_viewer_title': 'text_viewer_title', 'text_viewer_type': 'text_viewer_type' }) @@ -1718,6 +1715,7 @@ class AppController: "Message": False, "Response": False, "Tool Calls": False, + "Text Viewer": False, "Theme": True, "Log Management": False, } diff --git a/src/gui_2.py b/src/gui_2.py index 3dc9b04b..0d166511 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -2229,7 +2229,7 @@ def render_preset_manager_content(app: App, is_embedded: bool = False) -> None: app.text_viewer_title = f"Preset: {app._editing_preset_name}" app.text_viewer_content = app._editing_preset_system_prompt app.text_viewer_type = "markdown" - app.show_text_viewer = True + app.show_windows["Text Viewer"] = True rem_y = imgui.get_content_region_avail().y _, app._editing_preset_system_prompt = imgui.input_text_multiline("##pcont", app._editing_preset_system_prompt, imgui.ImVec2(-1, rem_y)) @@ -3242,7 +3242,7 @@ def render_context_files_table(app: App) -> None: except Exception as e: app.text_viewer_content = f"Error reading file: {e}" app.text_viewer_type = 'cpp' if f_path.endswith(('.cpp', '.hpp', '.h')) else 'python' if f_path.endswith('.py') else 'text' - app.show_text_viewer = True + app.show_windows["Text Viewer"] = True app.show_windows["Text Viewer"] = True imgui.table_set_column_index(1) @@ -3499,7 +3499,7 @@ def render_discussion_entry_read_mode(app: App, entry: dict, index: int) -> None if imgui.collapsing_header(f'Chunk {idx}: {path}'): if imgui.button(f'[Source]##rag_{index}_{idx}'): res = mcp_client.read_file(path) - if res: app.text_viewer_title, app.text_viewer_content, app.text_viewer_type, app.show_text_viewer = path, res, (Path(path).suffix.lstrip('.') if Path(path).suffix else 'text'), True + if res: app.text_viewer_title, app.text_viewer_content, app.text_viewer_type = path, res, (Path(path).suffix.lstrip('.') if Path(path).suffix else 'text'); app.show_windows["Text Viewer"] = True imgui.text_unformatted(chunk_content) content = content[:rag_match.start()] + content[rag_match.end():] pattern = re.compile(r"\[Definition: (.*?) from (.*?) \(line (\d+)\)\](\s+```[\s\S]*?```)?") @@ -3520,7 +3520,7 @@ def render_discussion_entry_read_mode(app: App, entry: dict, index: int) -> None if imgui.collapsing_header(header_text): if imgui.button(f"[Source]##{index}_{match.start()}"): res = mcp_client.read_file(path) - if res: app.text_viewer_title, app.text_viewer_content, app.text_viewer_type, app.show_text_viewer = path, res, (Path(path).suffix.lstrip('.') if Path(path).suffix else 'text'), True + if res: app.text_viewer_title, app.text_viewer_content, app.text_viewer_type = path, res, (Path(path).suffix.lstrip('.') if Path(path).suffix else 'text'); app.show_windows["Text Viewer"] = True if code_block: with theme.ai_text_style(): markdown_helper.render(code_block, context_id=f'disc_{index}_c_{m_idx}') @@ -4127,36 +4127,31 @@ def render_tool_calls_panel(app: App) -> None: entry = log_to_render[i] imgui.table_next_row() + script = entry.get("script", "") + res = entry.get("result", "") + combined = f"**COMMAND:**\n```powershell\n{script}\n```\n\n---\n**OUTPUT:**\n```text\n{res}\n```" + imgui.table_next_column() - imgui.text_colored(C_LBL, f"#{i+1}") + # Use selectable for the entire row trigger + opened_details = imgui.selectable(f"#{i+1}##row_{i}", False, imgui.SelectableFlags_.span_all_columns)[0] + if opened_details: + app.text_viewer_title = f"Tool Call #{i+1} Details" + app.text_viewer_content = combined + app.text_viewer_type = 'markdown' + app.show_windows["Text Viewer"] = True imgui.table_next_column() imgui.text_colored(C_SUB, f"[{entry.get('source_tier', 'main')}]") imgui.table_next_column() - script = entry.get("script", "") - res = entry.get("result", "") - # Use a clear, formatted combined view for the detail window - combined = f"**COMMAND:**\n```powershell\n{script}\n```\n\n---\n**OUTPUT:**\n```text\n{res}\n```" - script_preview = script.replace("\n", " ")[:150] if len(script) > 150: script_preview += "..." render_selectable_label(app, f'tc_script_{i}', script_preview, width=-1) - if imgui.is_item_clicked(): - app.text_viewer_title = f"Tool Call #{i+1} Details" - app.text_viewer_content = combined - app.text_viewer_type = 'markdown' - app.show_text_viewer = True imgui.table_next_column() res_preview = res.replace("\n", " ")[:30] if len(res) > 30: res_preview += "..." render_selectable_label(app, f'tc_res_{i}', res_preview, width=-1) - if imgui.is_item_clicked(): - app.text_viewer_title = f"Tool Call #{i+1} Details" - app.text_viewer_content = combined - app.text_viewer_type = 'markdown' - app.show_text_viewer = True imgui.end_table() @@ -4211,10 +4206,10 @@ def render_external_tools_panel(app: App) -> None: def render_text_viewer_window(app: App) -> None: """Renders the standalone text/code/markdown viewer window.""" - if not app.show_text_viewer: return + if not app.show_windows.get("Text Viewer", False): return imgui.set_next_window_size(imgui.ImVec2(900, 700), imgui.Cond_.first_use_ever) - expanded, opened = imgui.begin(f"{app.text_viewer_title or 'Text Viewer'}###Text_Viewer", app.show_text_viewer) - app.show_text_viewer = bool(opened) + expanded, opened = imgui.begin(f"{app.text_viewer_title or 'Text Viewer'}###Text_Viewer", True, imgui.WindowFlags_.no_collapse) + app.show_windows["Text Viewer"] = bool(opened) if not opened: app.ui_editing_slices_file = None app._slice_sel_start = -1 @@ -4470,8 +4465,8 @@ def render_approve_script_modal(app: App) -> None: imgui.text_colored(vec4(200, 200, 100), f"base_dir: {dlg._base_dir}") imgui.separator() # Checkbox to toggle full preview inside modal - _, app.show_text_viewer = imgui.checkbox("Show Full Preview", app.show_text_viewer) - if app.show_text_viewer: + _, app.show_windows["Text Viewer"] = imgui.checkbox("Show Full Preview", app.show_windows.get("Text Viewer", False)) + if app.show_windows.get("Text Viewer", False): imgui.begin_child("preview_child", imgui.ImVec2(600, 300), True) imgui.text_unformatted(dlg._script) imgui.end_child() @@ -4634,14 +4629,14 @@ def render_error_tint(app: App) -> None: def render_text_viewer(app: App, label: str, content: str, text_type: str = 'text', force_open: bool = False, id_suffix: str = "") -> None: if imgui.button(f"[+]##{id_suffix or str(id(content))}") or force_open: app.text_viewer_type = text_type - app.show_text_viewer = True + app.show_windows["Text Viewer"] = True app.text_viewer_title = label app.text_viewer_content = content app.show_windows["Text Viewer"] = True def render_heavy_text(app: App, label: str, content: str, id_suffix: str = "") -> None: if imgui.button(f"[+]##{label}{id_suffix}"): - app.show_text_viewer = True + app.show_windows["Text Viewer"] = True app.show_windows["Text Viewer"] = True app.text_viewer_type = 'markdown' if label in ('message', 'text', 'content', 'system') else 'json' if label in ('tool_calls', 'data') else 'powershell' if label == 'script' else 'text' app.text_viewer_title = label @@ -5458,23 +5453,6 @@ def render_empty_context_modal(app: App) -> None: def render_context_modals(app: App) -> None: render_empty_context_modal(app) - if app.show_empty_context_warning_modal: - imgui.open_popup("Empty Context Warning") - app.show_empty_context_warning_modal = False - - if imgui.begin_popup_modal("Empty Context Warning", True, imgui.WindowFlags_.always_auto_resize)[0]: - imgui.text_colored(imgui.ImVec4(1.0, 1.0, 0.0, 1.0), "WARNING: Empty Context Composition") - imgui.text("You are attempting to generate a response without any files selected.") - imgui.text("This may result in poor AI performance or loss of project context.") - imgui.separator() - if imgui.button("Proceed Anyway", imgui.ImVec2(150, 0)): - if app._empty_context_target_action == 'generate': app.controller._handle_generate_send() - elif app._empty_context_target_action == 'md_only': app.controller._handle_md_only() - imgui.close_current_popup() - imgui.same_line() - if imgui.button("Cancel", imgui.ImVec2(120, 0)): - imgui.close_current_popup() - imgui.end_popup() if app.show_missing_files_modal: imgui.open_popup("Missing Files Warning")