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:
+1
-1
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user