Private
Public Access
0
0

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.
This commit is contained in:
2026-06-02 00:18:48 -04:00
parent b33a213697
commit 0f859d81d6
5 changed files with 39 additions and 63 deletions
+1 -1
View File
@@ -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/)*
@@ -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)
- [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)
+1 -1
View File
@@ -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()
+1 -3
View File
@@ -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,
}
+22 -44
View File
@@ -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")