diff --git a/conductor/tracks.md b/conductor/tracks.md index 48ff198f..37221f89 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -268,5 +268,5 @@ This file tracks all major tracks for the project. Each track has its own detail --- -- [ ] **Track: Fix GUI Crashes in Tool Preset Manager and Discussion Hub** +- [x] **Track: Fix GUI Crashes in Tool Preset Manager and Discussion Hub** *Link: [./tracks/gui_crash_fixes_20260531/](./tracks/gui_crash_fixes_20260531/)* \ No newline at end of file diff --git a/conductor/tracks/gui_crash_fixes_20260531/plan.md b/conductor/tracks/gui_crash_fixes_20260531/plan.md index a3f950ce..84222ce4 100644 --- a/conductor/tracks/gui_crash_fixes_20260531/plan.md +++ b/conductor/tracks/gui_crash_fixes_20260531/plan.md @@ -1,16 +1,16 @@ # Implementation Plan ## Phase 1: Fix Tool Preset Manager Crashes -- [ ] Task: Investigate `render_tool_preset_manager_content` in `src/gui_2.py` to identify the missing `current_cat_tools` initialization or scope. -- [ ] Task: Fix the `NameError` and ensure correct ImGui child window scoping (`EndChild` vs `End`) in the Tool Preset Manager. -- [ ] Task: Conductor - User Manual Verification 'Fix Tool Preset Manager Crashes' (Protocol in workflow.md) +- [x] Task: Investigate `render_tool_preset_manager_content` in `src/gui_2.py` to identify the missing `current_cat_tools` initialization or scope. +- [x] Task: Fix the `NameError` and ensure correct ImGui child window scoping (`EndChild` vs `End`) in the Tool Preset Manager. +- [x] Task: Conductor - User Manual Verification 'Fix Tool Preset Manager Crashes' (Protocol in workflow.md) ## Phase 2: Fix Discussion Hub Crashes -- [ ] Task: Investigate `render_discussion_entries` in `src/gui_2.py` to address the `IndexError` when deleting a discussion entry during rendering. -- [ ] Task: Fix the `IndexError` (e.g. by deferring deletion or copying the list) and associated ImGui scoping errors (`PopID() too many times!`). -- [ ] Task: Conductor - User Manual Verification 'Fix Discussion Hub Crashes' (Protocol in workflow.md) +- [x] Task: Investigate `render_discussion_entries` in `src/gui_2.py` to address the `IndexError` when deleting a discussion entry during rendering. +- [x] Task: Fix the `IndexError` (e.g. by deferring deletion or copying the list) and associated ImGui scoping errors (`PopID() too many times!`). +- [x] Task: Conductor - User Manual Verification 'Fix Discussion Hub Crashes' (Protocol in workflow.md) ## Phase 3: Global Verification -- [ ] Task: Run `python scripts/check_imgui_scopes.py` to statically verify all ImGui scopes are correct across the codebase. -- [ ] Task: Run automated test suite to ensure no regressions were introduced. -- [ ] Task: Conductor - User Manual Verification 'Global Verification' (Protocol in workflow.md) \ No newline at end of file +- [x] Task: Run `python scripts/check_imgui_scopes.py` to statically verify all ImGui scopes are correct across the codebase. +- [x] Task: Run automated test suite to ensure no regressions were introduced. +- [x] Task: Conductor - User Manual Verification 'Global Verification' (Protocol in workflow.md) \ No newline at end of file diff --git a/src/gui_2.py b/src/gui_2.py index 6a6b4de6..0c2e4206 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -2310,32 +2310,30 @@ def render_tool_preset_manager_content(app: App, is_embedded: bool = False) -> N f_idx = cat_opts.index(app.ui_tool_filter_category) if app.ui_tool_filter_category in cat_opts else 0 imgui.set_next_item_width(200); ch_cat, next_f_idx = imgui.combo("##tp_filter", f_idx, cat_opts) if ch_cat: app.ui_tool_filter_category = cat_opts[next_f_idx] - imgui.begin_child("tp_scroll", imgui.ImVec2(0, h1), True) - if True: + with imscope.child("tp_scroll", 0, h1, True): for cat_name, default_tools in models.DEFAULT_TOOL_CATEGORIES.items(): if app.ui_tool_filter_category != "All" and app.ui_tool_filter_category != cat_name: continue if imgui.tree_node(cat_name): if cat_name not in app._editing_tool_preset_categories: app._editing_tool_preset_categories[cat_name] = [] curr_cat_tools = app._editing_tool_preset_categories[cat_name] - if imgui.begin_table(f"tt_{cat_name}", 2, imgui.TableFlags_.borders_inner_v): - imgui.table_setup_column("Tool", imgui.TableColumnFlags_.width_fixed, 250); imgui.table_setup_column("Ctrls", imgui.TableColumnFlags_.width_stretch) - for tool_name in default_tools: - tool = next((t for t in curr_cat_tools if t.name == tool_name), None) - mode = "disabled" if tool is None else tool.approval - imgui.table_next_row(); imgui.table_next_column(); imgui.text(tool_name); imgui.table_next_column() - if imgui.radio_button(f"Off##{cat_name}_{tool_name}", mode == "disabled"): - if tool: curr_cat_tools.remove(tool) - imgui.same_line(); - if imgui.radio_button(f"Auto##{cat_name}_{tool_name}", mode == "auto"): - if not tool: tool = models.Tool(name=tool_name, approval="auto"); current_cat_tools.append(tool) - else: tool.approval = "auto" - imgui.same_line(); - if imgui.radio_button(f"Ask##{cat_name}_{tool_name}", mode == "ask"): - if not tool: tool = models.Tool(name=tool_name, approval="ask"); current_cat_tools.append(tool) - else: tool.approval = "ask" - imgui.end_table() + with imscope.table(f"tt_{cat_name}", 2, imgui.TableFlags_.borders_inner_v) as opened_table: + if opened_table: + imgui.table_setup_column("Tool", imgui.TableColumnFlags_.width_fixed, 250); imgui.table_setup_column("Ctrls", imgui.TableColumnFlags_.width_stretch) + for tool_name in default_tools: + tool = next((t for t in curr_cat_tools if t.name == tool_name), None) + mode = "disabled" if tool is None else tool.approval + imgui.table_next_row(); imgui.table_next_column(); imgui.text(tool_name); imgui.table_next_column() + if imgui.radio_button(f"Off##{cat_name}_{tool_name}", mode == "disabled"): + if tool: curr_cat_tools.remove(tool) + imgui.same_line(); + if imgui.radio_button(f"Auto##{cat_name}_{tool_name}", mode == "auto"): + if not tool: tool = models.Tool(name=tool_name, approval="auto"); curr_cat_tools.append(tool) + else: tool.approval = "auto" + imgui.same_line(); + if imgui.radio_button(f"Ask##{cat_name}_{tool_name}", mode == "ask"): + if not tool: tool = models.Tool(name=tool_name, approval="ask"); curr_cat_tools.append(tool) + else: tool.approval = "ask" imgui.tree_pop() - imgui.end_child() if app._bias_list_open: imgui.button("###tool_splitter", imgui.ImVec2(-1, 4)) if imgui.is_item_active(): app._tool_split_v = max(0.1, min(0.9, app._tool_split_v + imgui.get_io().mouse_delta.y / rem_y)) @@ -3449,8 +3447,10 @@ def render_discussion_entry(app: App, entry: dict, index: int) -> None: if collapsed: imgui.same_line() if imgui.button("Ins"): app.disc_entries.insert(index, {"role": "User", "content": "", "collapsed": True, "ts": project_manager.now_ts()}) - imgui.same_line() - if imgui.button("Del"): app.disc_entries.pop(index); return + imgui.same_line(); + if imgui.button("Del"): + if entry in app.disc_entries: app.disc_entries.remove(entry) + return imgui.same_line() if imgui.button("Branch"): app._branch_discussion(index) imgui.same_line(); preview = entry["content"].replace("\n", " ")[:60] @@ -3793,7 +3793,7 @@ def render_takes_panel(app: App) -> None: def render_discussion_entries(app: App) -> None: with imscope.child("disc_scroll"): - display_entries = app.disc_entries + display_entries = list(app.disc_entries) if app.ui_focus_agent: tier_usage = app.mma_tier_usage.get(app.ui_focus_agent) if tier_usage: @@ -3802,7 +3802,8 @@ def render_discussion_entries(app: App) -> None: clipper = imgui.ListClipper(); clipper.begin(len(display_entries)) while clipper.step(): for i in range(clipper.display_start, clipper.display_end): - render_discussion_entry(app, display_entries[i], i) + if i < len(display_entries): + render_discussion_entry(app, display_entries[i], i) if app._scroll_disc_to_bottom: imgui.set_scroll_here_y(1.0); app._scroll_disc_to_bottom = False def render_discussion_entry_controls(app: App) -> None: @@ -3929,11 +3930,10 @@ def render_discussion_selector(app: App) -> None: return def render_discussion_tab(app: App) -> None: - imgui.begin_child("HistoryChild", size=(0, -app.ui_discussion_split_h)) - if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_discussion_panel") - render_discussion_panel(app) - if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_discussion_panel") - imgui.end_child() + with imscope.child("HistoryChild", size_x=0, size_y=-app.ui_discussion_split_h): + if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_discussion_panel") + render_discussion_panel(app) + if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_discussion_panel") imgui.button("###discussion_splitter", imgui.ImVec2(-1, 4)) if imgui.is_item_active(): app.ui_discussion_split_h = max(150.0, min(imgui.get_window_height() - 150.0, app.ui_discussion_split_h - imgui.get_io().mouse_delta.y)) imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4)) @@ -5032,10 +5032,10 @@ def render_tier_stream_panel(app: App, tier_key: str, stream_key: str | None) -> if len(content) != app._tier_stream_last_len.get(stream_key, -1): imgui.set_scroll_here_y(1.0) app._tier_stream_last_len[stream_key] = len(content) - imgui.end_child() except (TypeError, AttributeError): - imgui.end_child() pass + finally: + imgui.end_child() else: tier3_keys = [k for k in app.mma_streams if "Tier 3" in k] if not tier3_keys: