curation pass on gui_2.py

This commit is contained in:
ed
2026-06-12 23:38:31 -04:00
parent d4fbcb16d9
commit 26b1ec77a4
+136 -236
View File
@@ -22,7 +22,7 @@ _thirdparty = os.path.join(_project_root, "thirdparty")
if _thirdparty not in sys.path: if _thirdparty not in sys.path:
sys.path.insert(0, _thirdparty) sys.path.insert(0, _thirdparty)
from contextlib import ExitStack, nullcontext from contextlib import ExitStack, nullcontext
from pathlib import Path from pathlib import Path
from typing import Optional, Any from typing import Optional, Any
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced
@@ -41,6 +41,7 @@ import importlib as _importlib
from typing import Any as _Any from typing import Any as _Any
from typing import Optional as _Optional from typing import Optional as _Optional
#TODO(Ed): Remove Excpetion based errors
class _LazyModule: class _LazyModule:
"""Lazy proxy that defers an import until first attribute access or call. """Lazy proxy that defers an import until first attribute access or call.
@@ -89,11 +90,10 @@ class _FiledialogStub:
def askdirectory(self, *args: _Any, **kwargs: _Any) -> str: return "" def askdirectory(self, *args: _Any, **kwargs: _Any) -> str: return ""
def asksaveasfilename(self, *args: _Any, **kwargs: _Any) -> str: return "" def asksaveasfilename(self, *args: _Any, **kwargs: _Any) -> str: return ""
# Heavy modules that were previously top-level imports (now lazy): # Heavy modules that were previously top-level imports (now lazy):
np = _LazyModule("numpy") # was: import numpy as np np = _LazyModule("numpy") # was: import numpy as np
filedialog = _LazyModule("tkinter", "filedialog") # was: from tkinter import filedialog filedialog = _LazyModule("tkinter", "filedialog") # was: from tkinter import filedialog
Tk = _LazyModule("tkinter", "Tk") # was: from tkinter import Tk Tk = _LazyModule("tkinter", "Tk") # was: from tkinter import Tk
from src.diff_viewer import apply_patch_to_file from src.diff_viewer import apply_patch_to_file
from src import ai_client from src import ai_client
@@ -186,6 +186,7 @@ def _detect_refresh_rate_win32() -> float:
shelled out to PowerShell + WMI (Get-CimInstance Win32_VideoController), which shelled out to PowerShell + WMI (Get-CimInstance Win32_VideoController), which
cost ~350ms on every startup and blocked the first frame. cost ~350ms on every startup and blocked the first frame.
""" """
#Note(Ed): Exception(Thirdparty)
try: try:
import ctypes import ctypes
from ctypes import wintypes from ctypes import wintypes
@@ -233,6 +234,7 @@ def _resolve_font_path(font_path: str, assets_dir: Path) -> str:
p = Path(font_path) p = Path(font_path)
if not p.is_absolute(): if not p.is_absolute():
return font_path # already relative; hello_imgui searches the assets folder return font_path # already relative; hello_imgui searches the assets folder
#Note(Ed): Exception(Thirdparty)
try: try:
if p.is_relative_to(assets_dir): if p.is_relative_to(assets_dir):
return str(p.relative_to(assets_dir)).replace("\\", "/") return str(p.relative_to(assets_dir)).replace("\\", "/")
@@ -285,7 +287,7 @@ def _render_v2_capability_badges(caps: "VendorCapabilities") -> None:
] ]
enabled: list[tuple[str, str]] = [] enabled: list[tuple[str, str]] = []
for field_name, label in badged_fields: for field_name, label in badged_fields:
if getattr(caps, field_name, False): if getattr(caps, field_name, False):
enabled.append((field_name, label)) enabled.append((field_name, label))
if not enabled: return if not enabled: return
imgui.text("Capabilities") imgui.text("Capabilities")
@@ -297,16 +299,9 @@ class App:
"""The main ImGui interface orchestrator for Manual Slop.""" """The main ImGui interface orchestrator for Manual Slop."""
def __init__(self) -> None: def __init__(self) -> None:
""" """Initializes core app dependencies (controller, history, performance monitor,
Initializes core app dependencies (controller, history, performance monitor,
command palette, workspace manager) and registers app callback handlers. command palette, workspace manager) and registers app callback handlers.
SSDL Shape: `[I:init_controller] -> [I:init_workspace] -> [I:load_profiles]`
State Mutations:
self.controller, self.perf_monitor, self.history,
self.show_command_palette, self.workspace_manager.
SSDL Shape:
`[I:init_controller] -> [I:init_workspace] -> [I:load_profiles]`
""" """
#region: --- Core Dependencies & State --- #region: --- Core Dependencies & State ---
from src.startup_profiler import startup_profiler from src.startup_profiler import startup_profiler
@@ -505,22 +500,22 @@ class App:
self.node_editor_ctx = ed.create_editor(self.node_editor_config) self.node_editor_ctx = ed.create_editor(self.node_editor_config)
# --- Context & AST State --- # --- Context & AST State ---
self.ui_selected_ticket_id: Optional[str] = None self.ui_selected_ticket_id: Optional[str] = None
self.ui_selected_tickets: set[str] = set() self.ui_selected_tickets: set[str] = set()
self.ui_selected_context_files: set[str] = set() self.ui_selected_context_files: set[str] = set()
self.ui_new_ticket_priority: str = 'medium' self.ui_new_ticket_priority: str = 'medium'
self._autofocus_response_tab = False self._autofocus_response_tab = False
self._last_selected_context_index = -1 self._last_selected_context_index = -1
self.ui_inspecting_ast_file = None self.ui_inspecting_ast_file = None
self._show_ast_inspector = False self._show_ast_inspector = False
self._cached_ast_nodes = [] self._cached_ast_nodes = []
self._cached_ast_file_path = '' self._cached_ast_file_path = ''
self._cached_ast_file_lines = [] self._cached_ast_file_lines = []
self.ui_editing_slices_file = None self.ui_editing_slices_file = None
self._slice_sel_start = -1 self._slice_sel_start = -1
self._slice_sel_end = -1 self._slice_sel_end = -1
self.context_files = [] self.context_files = []
self.ui_synthesis_prompt: str = "" self.ui_synthesis_prompt: str = ""
self.ui_synthesis_selected_takes: dict[str, bool] = {} self.ui_synthesis_selected_takes: dict[str, bool] = {}
# --- Rendering & Theme State --- # --- Rendering & Theme State ---
@@ -564,18 +559,17 @@ class App:
# is safe. The render_warmup_status_indicator() function reads # is safe. The render_warmup_status_indicator() function reads
# the timestamp to show a brief "ready" tag for 3 seconds. # the timestamp to show a brief "ready" tag for 3 seconds.
if hasattr(self.controller, "on_warmup_complete"): if hasattr(self.controller, "on_warmup_complete"):
#Note(Ed): Exception(Thirdparty)
try: try:
self.controller.on_warmup_complete(lambda status: _on_warmup_complete_callback(self, status)) self.controller.on_warmup_complete(lambda status: _on_warmup_complete_callback(self, status))
except Exception: pass except Exception: pass
self._diag_layout_state() self._diag_layout_state()
def _diag_layout_state(self) -> None: def _diag_layout_state(self) -> None:
""" """One-shot startup diagnostic: log show_windows state and warn if the
One-shot startup diagnostic: log show_windows state and warn if the
on-disk manualslop_layout.ini references window names that no longer on-disk manualslop_layout.ini references window names that no longer
exist in the current code. Helps users and test operators detect exist in the current code. Helps users and test operators detect
stale layout state at a glance instead of debugging missing panels. stale layout state at a glance instead of debugging missing panels.
[C: src/gui_2.py:App._post_init]
""" """
import os as _os import os as _os
visible_by_default = [w for w, v in self.show_windows.items() if v] visible_by_default = [w for w, v in self.show_windows.items() if v]
@@ -588,6 +582,7 @@ class App:
return return
ini_size = _os.path.getsize(ini_path) ini_size = _os.path.getsize(ini_path)
sys.stderr.write(f"[GUI] layout file: {ini_path} ({ini_size} bytes)\n") sys.stderr.write(f"[GUI] layout file: {ini_path} ({ini_size} bytes)\n")
#Note(Ed): Exception(Thirdparty)
try: try:
with open(ini_path, encoding="utf-8") as _f: with open(ini_path, encoding="utf-8") as _f:
_ini_text = _f.read() _ini_text = _f.read()
@@ -611,14 +606,9 @@ class App:
return result return result
def run(self) -> None: def run(self) -> None:
""" """Initializes the ImGui runner (HelloImGui) and starts the main application loop.
Initializes the ImGui runner (HelloImGui) and starts the main application loop.
Loads system themes, default styling metrics, fonts, and sets up window docking layouts. Loads system themes, default styling metrics, fonts, and sets up window docking layouts.
SSDL: `[I:hello_imgui] -> o-> [I:main_loop]`
SSDL Shape:
`[I:hello_imgui] -> o-> [I:main_loop]`
[C: simulation/sim_base.py:run_sim, src/mcp_client.py:get_git_diff, src/project_manager.py:get_git_commit, src/rag_engine.py:RAGEngine._search_mcp, src/shell_runner.py:run_powershell, tests/conftest.py:kill_process_tree, tests/conftest.py:live_gui, tests/test_conductor_abort_event.py:test_conductor_abort_event_populated, tests/test_conductor_engine_v2.py:test_conductor_engine_dynamic_parsing_and_execution, tests/test_conductor_engine_v2.py:test_conductor_engine_run_executes_tickets_in_order, tests/test_extended_sims.py:test_ai_settings_sim_live, tests/test_extended_sims.py:test_context_sim_live, tests/test_extended_sims.py:test_execution_sim_live, tests/test_extended_sims.py:test_tools_sim_live, tests/test_external_editor_gui.py:get_vscode_processes, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui_custom_window.py:test_app_window_is_borderless, tests/test_headless_simulation.py:module, tests/test_headless_verification.py:test_headless_verification_error_and_qa_interceptor, tests/test_headless_verification.py:test_headless_verification_full_run, tests/test_mock_gemini_cli.py:run_mock, tests/test_orchestration_logic.py:test_conductor_engine_run, tests/test_parallel_execution.py:test_conductor_engine_pool_integration, tests/test_sim_ai_settings.py:test_ai_settings_simulation_run, tests/test_sim_context.py:test_context_simulation_run, tests/test_sim_execution.py:test_execution_simulation_run, tests/test_sim_tools.py:test_tools_simulation_run]
""" """
if "--headless" in sys.argv: if "--headless" in sys.argv:
print("Headless mode active") print("Headless mode active")
@@ -642,15 +632,15 @@ class App:
# (removed stale _t-based print; the phase() above already logs RunnerParams_init) # (removed stale _t-based print; the phase() above already logs RunnerParams_init)
if sys.platform == "win32": if sys.platform == "win32":
self.runner_params.app_window_params.borderless = True self.runner_params.app_window_params.borderless = True
self.runner_params.app_window_params.borderless_closable = False self.runner_params.app_window_params.borderless_closable = False
self.runner_params.app_window_params.borderless_movable = False self.runner_params.app_window_params.borderless_movable = False
self.runner_params.app_window_params.borderless_resizable = True self.runner_params.app_window_params.borderless_resizable = True
self.runner_params.app_window_params.window_geometry.size = (1680, 1200) self.runner_params.app_window_params.window_geometry.size = (1680, 1200)
self.runner_params.imgui_window_params.enable_viewports = getattr(self, "ui_multi_viewport", False) self.runner_params.imgui_window_params.enable_viewports = getattr(self, "ui_multi_viewport", False)
self.runner_params.imgui_window_params.remember_theme = True self.runner_params.imgui_window_params.remember_theme = True
self.runner_params.imgui_window_params.tweaked_theme = theme.get_tweaked_theme() self.runner_params.imgui_window_params.tweaked_theme = theme.get_tweaked_theme()
self.runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space self.runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space
# Enforce DPI Awareness and User Scale # Enforce DPI Awareness and User Scale
@@ -667,25 +657,26 @@ class App:
# Enable idling with monitor refresh rate to effectively cap FPS # Enable idling with monitor refresh rate to effectively cap FPS
self.runner_params.fps_idling.enable_idling = True self.runner_params.fps_idling.enable_idling = True
self.runner_params.fps_idling.fps_idle = fps_cap self.runner_params.fps_idling.fps_idle = fps_cap
self.runner_params.imgui_window_params.show_menu_bar = True self.runner_params.imgui_window_params.show_menu_bar = True
self.runner_params.imgui_window_params.show_menu_view_themes = True self.runner_params.imgui_window_params.show_menu_view_themes = True
self.runner_params.ini_folder_type = hello_imgui.IniFolderType.current_folder self.runner_params.ini_folder_type = hello_imgui.IniFolderType.current_folder
self.runner_params.ini_filename = "manualslop_layout.ini" self.runner_params.ini_filename = "manualslop_layout.ini"
def _profiled_setup_style() -> None: def _profiled_setup_style() -> None:
with startup_profiler.phase("setup_imgui_style"): with startup_profiler.phase("setup_imgui_style"):
theme.apply_current() theme.apply_current()
def _profiled_post_init() -> None: def _profiled_post_init() -> None:
with startup_profiler.phase("post_init"): with startup_profiler.phase("post_init"):
self._post_init() self._post_init()
self.runner_params.callbacks.show_gui = self._gui_func self.runner_params.callbacks.show_gui = self._gui_func
self.runner_params.callbacks.show_menus = self._show_menus self.runner_params.callbacks.show_menus = self._show_menus
self.runner_params.callbacks.load_additional_fonts = self._load_fonts self.runner_params.callbacks.load_additional_fonts = self._load_fonts
self.runner_params.callbacks.setup_imgui_style = _profiled_setup_style self.runner_params.callbacks.setup_imgui_style = _profiled_setup_style
self.runner_params.callbacks.post_init = _profiled_post_init self.runner_params.callbacks.post_init = _profiled_post_init
self._fetch_models(self.current_provider) self._fetch_models(self.current_provider)
md_options = markdown_helper.get_renderer().options md_options = markdown_helper.get_renderer().options
#Note(Ed): Exception(Thirdparty)
try: try:
immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options)) immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options))
except RuntimeError as _immapp_exc: except RuntimeError as _immapp_exc:
@@ -703,8 +694,8 @@ class App:
self.controller._last_imgui_assert = traceback.format_exc() self.controller._last_imgui_assert = traceback.format_exc()
print( print(
f"[GUI-DEGRADED] immapp.run raised: {_immapp_exc}", f"[GUI-DEGRADED] immapp.run raised: {_immapp_exc}",
file=sys.stderr, file = sys.stderr,
flush=True, flush = True,
) )
print(self.controller._last_imgui_assert if hasattr(self, "controller") and self.controller else "", print(self.controller._last_imgui_assert if hasattr(self, "controller") and self.controller else "",
file=sys.stderr, flush=True) file=sys.stderr, flush=True)
@@ -722,7 +713,7 @@ class App:
hello_imgui.set_assets_folder(str(assets_dir.absolute())) hello_imgui.set_assets_folder(str(assets_dir.absolute()))
# Improved font rendering with oversampling # Improved font rendering with oversampling
config = imgui.ImFontConfig() config = imgui.ImFontConfig()
config.oversample_h = 3 config.oversample_h = 3
config.oversample_v = 3 config.oversample_v = 3
@@ -730,6 +721,7 @@ class App:
if font_path: if font_path:
font_path = _resolve_font_path(font_path, assets_dir) font_path = _resolve_font_path(font_path, assets_dir)
#Note(Ed): Exception(Thirdparty)
# Just try loading it directly; hello_imgui will look in the assets folder # Just try loading it directly; hello_imgui will look in the assets folder
try: try:
with startup_profiler.phase("load_fonts.main_with_fontawesome"): with startup_profiler.phase("load_fonts.main_with_fontawesome"):
@@ -739,10 +731,11 @@ class App:
self.main_font = None self.main_font = None
else: else:
self.main_font = None self.main_font = None
#Note(Ed): Exception(Thirdparty)
try: try:
with startup_profiler.phase("load_fonts.mono"): with startup_profiler.phase("load_fonts.mono"):
params = hello_imgui.FontLoadingParams(font_config=config) params = hello_imgui.FontLoadingParams(font_config=config)
self.mono_font = hello_imgui.load_font("fonts/MapleMono-Regular.ttf", font_size, params) self.mono_font = hello_imgui.load_font("fonts/MapleMono-Regular.ttf", font_size, params)
except Exception as e: except Exception as e:
print(f"Failed to load mono font: {e}") print(f"Failed to load mono font: {e}")
@@ -756,6 +749,7 @@ class App:
"""UI-level wrapper for approving a pending MMA sub-agent spawn.""" """UI-level wrapper for approving a pending MMA sub-agent spawn."""
self._handle_mma_respond(approved=True) self._handle_mma_respond(approved=True)
#TODO(Ed): Remove Exception based errors.
def __getattr__(self, name: str) -> Any: def __getattr__(self, name: str) -> Any:
if name == 'controller': if name == 'controller':
raise AttributeError(name) raise AttributeError(name)
@@ -772,7 +766,7 @@ class App:
def _handle_generate_send(self) -> None: def _handle_generate_send(self) -> None:
if not self.ui_selected_context_files and not getattr(self, "_pending_proceed_generate", False): if not self.ui_selected_context_files and not getattr(self, "_pending_proceed_generate", False):
self._pending_generation_action = 'generate' self._pending_generation_action = 'generate'
self.show_empty_context_modal = True self.show_empty_context_modal = True
else: else:
self._pending_proceed_generate = False self._pending_proceed_generate = False
self.controller._handle_generate_send() self.controller._handle_generate_send()
@@ -780,13 +774,13 @@ class App:
def _handle_md_only(self) -> None: def _handle_md_only(self) -> None:
if not self.ui_selected_context_files and not getattr(self, "_pending_proceed_md_only", False): if not self.ui_selected_context_files and not getattr(self, "_pending_proceed_md_only", False):
self._pending_generation_action = 'md_only' self._pending_generation_action = 'md_only'
self.show_empty_context_modal = True self.show_empty_context_modal = True
else: else:
self._pending_proceed_md_only = False self._pending_proceed_md_only = False
self.controller._handle_md_only() self.controller._handle_md_only()
@property @property
def current_provider(self) -> str: def current_provider(self) -> str:
return self.controller.current_provider return self.controller.current_provider
@current_provider.setter @current_provider.setter
@@ -801,8 +795,10 @@ class App:
def current_model(self, value: str) -> None: def current_model(self, value: str) -> None:
self.controller.current_model = value self.controller.current_model = value
#TODO(Ed): Remove Exception based errors.
def _get_active_capabilities(self) -> "VendorCapabilities": def _get_active_capabilities(self) -> "VendorCapabilities":
from src.vendor_capabilities import VendorCapabilities, get_capabilities from src.vendor_capabilities import VendorCapabilities, get_capabilities
#TODO(Ed): Remove Exception based errors.
try: try:
caps = get_capabilities(self.current_provider, self.current_model) caps = get_capabilities(self.current_provider, self.current_model)
except KeyError: except KeyError:
@@ -824,27 +820,21 @@ class App:
@property @property
def app_debug_info(self) -> dict: def app_debug_info(self) -> dict:
return { return {
"context_files": [f.path for f in self.context_files], "context_files": [f.path for f in self.context_files],
"missing_files": self.missing_files, "missing_files": self.missing_files,
"screenshots": self.screenshots, "screenshots": self.screenshots,
"ui_new_context_preset_name": self.ui_new_context_preset_name, "ui_new_context_preset_name": self.ui_new_context_preset_name,
"target_context_preset_name": self.target_context_preset_name, "target_context_preset_name": self.target_context_preset_name,
"show_missing_files_modal": self.show_missing_files_modal, "show_missing_files_modal": self.show_missing_files_modal,
"active_project_root": str(self.controller.active_project_root), "active_project_root": str(self.controller.active_project_root),
"project_keys": list(self.controller.project.keys()), "project_keys": list(self.controller.project.keys()),
"presets": list(self.controller.project.get('context_presets', {}).keys()) "presets": list(self.controller.project.get('context_presets', {}).keys())
} }
def _take_snapshot(self) -> history.UISnapshot: def _take_snapshot(self) -> history.UISnapshot:
""" """ Captures the current state of UI input parameters, system prompts, active
Captures the current state of UI input parameters, system prompts, active
discussions, and files list, returning a UISnapshot for history management. discussions, and files list, returning a UISnapshot for history management.
SSDL: `[Q:ui_state] -> [I:copy] -> [T:snapshot]`
State Mutations:
None (read-only state capture).
SSDL Shape:
`[Q:ui_state] -> [I:copy] -> [T:snapshot]`
""" """
from src import history from src import history
import copy import copy
@@ -865,16 +855,9 @@ class App:
) )
def _apply_snapshot(self, snapshot: history.UISnapshot) -> None: def _apply_snapshot(self, snapshot: history.UISnapshot) -> None:
""" """Applies a previously captured UISnapshot back to the active UI state.
Applies a previously captured UISnapshot back to the active UI state.
Restores input fields, parameters, discussions, screenshots, and context files. Restores input fields, parameters, discussions, screenshots, and context files.
SSDL Shape: `[I:lock_flag] -> [S:ui_state] -> [I:unlock]`
State Mutations:
Modifies active UI variables (self.ui_ai_input, self.temperature, self.files, self.context_files, etc.)
self._is_applying_snapshot (temporarily set to True)
SSDL Shape:
`[I:lock_flag] -> [S:ui_state] -> [I:unlock]`
""" """
self._is_applying_snapshot = True self._is_applying_snapshot = True
try: try:
@@ -893,42 +876,31 @@ class App:
from src import models from src import models
self.files = [] self.files = []
for f in snapshot.files: for f in snapshot.files:
if isinstance(f, dict): if isinstance(f, dict): self.files.append(models.FileItem.from_dict(f))
self.files.append(models.FileItem.from_dict(f)) else: self.files.append(models.FileItem(path=str(f)))
else:
self.files.append(models.FileItem(path=str(f)))
self.context_files = [] self.context_files = []
for f in snapshot.context_files: for f in snapshot.context_files:
if isinstance(f, dict): if isinstance(f, dict): self.context_files.append(models.FileItem.from_dict(f))
self.context_files.append(models.FileItem.from_dict(f)) else: self.context_files.append(models.FileItem(path=str(f)))
else:
self.context_files.append(models.FileItem(path=str(f)))
self.screenshots = list(snapshot.screenshots) self.screenshots = list(snapshot.screenshots)
self._last_ui_snapshot = snapshot # Update last snapshot to avoid immediate re-push self._last_ui_snapshot = snapshot # Update last snapshot to avoid immediate re-push
finally: finally:
self._is_applying_snapshot = False self._is_applying_snapshot = False # ?? TODO(Ed): Whats the point of this??
def _capture_workspace_profile(self, name: str) -> models.WorkspaceProfile: def _capture_workspace_profile(self, name: str) -> models.WorkspaceProfile:
""" """Serializes the current window visibility states, popped-out panel layouts, and
Serializes the current window visibility states, popped-out panel layouts, and
ImGui INI configurations into a WorkspaceProfile object. ImGui INI configurations into a WorkspaceProfile object.
SSDL Shape: `[Q:ui_states] -> [B:ini_ready] -> [T:profile]`
State Mutations:
self._ini_capture_ready (set to True on first invocation to bypass initial ImGui frame bugs).
SSDL Shape:
`[Q:ui_states] -> [B:ini_ready] -> [T:profile]`
""" """
if not getattr(self, "_ini_capture_ready", False): if not getattr(self, "_ini_capture_ready", False):
self._ini_capture_ready = True self._ini_capture_ready = True
ini = "" ini = ""
else: else:
try: #Note(Ed): Thirdparty Exception
ini = str(imgui.save_ini_settings_to_memory() or "") try: ini = str(imgui.save_ini_settings_to_memory() or "")
except Exception: except Exception: ini = ""
ini = ""
panel_states = { panel_states = {
"ui_separate_context_preview": getattr(self, "ui_separate_context_preview", False), "ui_separate_context_preview": getattr(self, "ui_separate_context_preview", False),
"ui_separate_message_panel": getattr(self, "ui_separate_message_panel", False), "ui_separate_message_panel": getattr(self, "ui_separate_message_panel", False),
@@ -944,73 +916,46 @@ class App:
"ui_discussion_split_h": getattr(self, "ui_discussion_split_h", 300.0), "ui_discussion_split_h": getattr(self, "ui_discussion_split_h", 300.0),
} }
return models.WorkspaceProfile( return models.WorkspaceProfile(
name=name, name = name,
ini_content=ini, ini_content = ini,
show_windows=copy.deepcopy(self.show_windows), show_windows = copy.deepcopy(self.show_windows),
panel_states=panel_states panel_states = panel_states
) )
def _apply_workspace_profile(self, profile: models.WorkspaceProfile): def _apply_workspace_profile(self, profile: models.WorkspaceProfile):
""" """Restores the window docking layout and popped-out panel visibility states
Restores the window docking layout and popped-out panel visibility states
from a saved WorkspaceProfile. from a saved WorkspaceProfile.
SSDL Shape: `[I:load_ini] -> [S:ui_states]`
State Mutations:
Modifies window visibility and panel configuration state variables.
SSDL Shape:
`[I:load_ini] -> [S:ui_states]`
""" """
imgui.load_ini_settings_from_memory(profile.ini_content) imgui.load_ini_settings_from_memory(profile.ini_content)
self.show_windows.update(profile.show_windows) self.show_windows.update(profile.show_windows)
for k, v in profile.panel_states.items(): for k, v in profile.panel_states.items():
if hasattr(self, k): if hasattr(self, k): setattr(self, k, v)
setattr(self, k, v)
def _handle_undo(self) -> None: def _handle_undo(self) -> None:
""" """Reverts the application UI state to the previous snapshot in the history stack.
Reverts the application UI state to the previous snapshot in the history stack.
State Mutations:
self.history (mutated to record index changes)
Modifies active UI variables via _apply_snapshot()
DAG Render Context: DAG Render Context:
Called by: _gui_func() (via hotkey Ctrl+Z) or undo button click. Called by: _gui_func() (via hotkey Ctrl+Z) or undo button click.
Calls: _take_snapshot(), _apply_snapshot(), HistoryManager.undo() Calls: _take_snapshot(), _apply_snapshot(), HistoryManager.undo()
Threading & Safety:
Must run synchronously on the Main Thread.
""" """
sys.stderr.write(f"[DEBUG History] _handle_undo called. can_undo={self.history.can_undo}\n") sys.stderr.write(f"[DEBUG History] _handle_undo called. can_undo={self.history.can_undo}\n"); sys.stderr.flush()
sys.stderr.flush() if not self.history.can_undo: return
if not self.history.can_undo:
return
current = self._take_snapshot() current = self._take_snapshot()
entry = self.history.undo(current, "Undo Action") entry = self.history.undo(current, "Undo Action")
if entry: if entry:
sys.stderr.write(f"[DEBUG History] Undoing to: {entry.description}\n") sys.stderr.write(f"[DEBUG History] Undoing to: {entry.description}\n"); sys.stderr.flush()
sys.stderr.flush()
self._apply_snapshot(entry.state) self._apply_snapshot(entry.state)
def _handle_jump_to_history(self, index: int) -> None: def _handle_jump_to_history(self, index: int) -> None:
sys.stderr.write(f"[DEBUG History] Jumping to index {index}\n") sys.stderr.write(f"[DEBUG History] Jumping to index {index}\n"); sys.stderr.flush()
sys.stderr.flush()
current = self._take_snapshot() current = self._take_snapshot()
entry = self.history.jump_to_undo(index, current, "Before Jump") entry = self.history.jump_to_undo(index, current, "Before Jump")
if entry: if entry:
self._apply_snapshot(entry.state) self._apply_snapshot(entry.state)
def _handle_redo(self) -> None: def _handle_redo(self) -> None:
""" """Re-applies the next snapshot in the history stack (forward navigation).
Re-applies the next snapshot in the history stack (forward navigation). SSDL Shape: `[I:snapshot] -> [B:history] => [I:state]`
State Mutations:
self.history (mutated to record index changes)
Modifies active UI variables via _apply_snapshot()
SSDL Shape:
`[I:snapshot] -> [B:history] => [I:state]`
""" """
sys.stderr.write(f"[DEBUG History] _handle_redo called. can_redo={self.history.can_redo}\n") sys.stderr.write(f"[DEBUG History] _handle_redo called. can_redo={self.history.can_redo}\n")
sys.stderr.flush() sys.stderr.flush()
@@ -1024,17 +969,9 @@ class App:
self._apply_snapshot(entry.state) self._apply_snapshot(entry.state)
def shutdown(self) -> None: def shutdown(self) -> None:
""" """Cleanly shuts down the app's background tasks, saves workspace layout configurations,
Cleanly shuts down the app's background tasks, saves workspace layout configurations,
forces a save of dirty registries/caches, and terminates the active thread pools. forces a save of dirty registries/caches, and terminates the active thread pools.
SSDL Shape: `[I:save_ini] -> [I:controller_shutdown]`
State Mutations:
runner_params settings are flushed to disk (imgui.save_ini_settings_to_disk).
SSDL Shape:
`[I:save_ini] -> [I:controller_shutdown]`
[C: tests/conftest.py:app_instance, tests/conftest.py:mock_app]
""" """
try: try:
if hasattr(self, 'runner_params') and self.runner_params.ini_filename: if hasattr(self, 'runner_params') and self.runner_params.ini_filename:
@@ -1044,29 +981,23 @@ class App:
self.controller.shutdown() self.controller.shutdown()
def load_context_preset(self, name: str) -> None: def load_context_preset(self, name: str) -> None:
"""
[C: tests/test_context_presets.py:test_load_context_preset, tests/test_context_presets.py:test_load_nonexistent_preset]
"""
preset = self.controller.load_context_preset(name) preset = self.controller.load_context_preset(name)
from src import models from src import models
import copy import copy
self.context_files = [] self.context_files = []
for f in preset.files: for f in preset.files:
fi = models.FileItem(path=f.path, view_mode=f.view_mode) fi = models.FileItem(path=f.path, view_mode=f.view_mode)
fi.custom_slices = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else [] fi.custom_slices = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
fi.ast_mask = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {} fi.ast_mask = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
fi.ast_signatures = getattr(f, 'ast_signatures', False) fi.ast_signatures = getattr(f, 'ast_signatures', False)
fi.ast_definitions = getattr(f, 'ast_definitions', False) fi.ast_definitions = getattr(f, 'ast_definitions', False)
self.context_files.append(fi) self.context_files.append(fi)
self.screenshots = list(preset.screenshots) self.screenshots = list(preset.screenshots)
self.ui_file_paths = [f.path for f in preset.files] self.ui_file_paths = [f.path for f in preset.files]
self.ui_screenshot_paths = list(preset.screenshots) self.ui_screenshot_paths = list(preset.screenshots)
self._update_context_file_stats() self._update_context_file_stats()
def delete_context_preset(self, name: str) -> None: def delete_context_preset(self, name: str) -> None:
"""
[C: tests/test_context_presets.py:test_delete_context_preset, tests/test_context_presets.py:test_delete_nonexistent_preset_no_error]
"""
self.controller.delete_context_preset(name) self.controller.delete_context_preset(name)
if getattr(self, "ui_active_context_preset", "") == name: if getattr(self, "ui_active_context_preset", "") == name:
self.ui_active_context_preset = "" self.ui_active_context_preset = ""
@@ -1103,12 +1034,9 @@ class App:
since the keyboard shortcut (Ctrl+Shift+P) cannot be simulated via the hook API.""" since the keyboard shortcut (Ctrl+Shift+P) cannot be simulated via the hook API."""
self.show_command_palette = not self.show_command_palette self.show_command_palette = not self.show_command_palette
if self.show_command_palette: if self.show_command_palette:
if hasattr(self, '_command_palette_query'): if hasattr(self, '_command_palette_query'): self._command_palette_query = ""
self._command_palette_query = "" if hasattr(self, '_command_palette_selected'): self._command_palette_selected = 0
if hasattr(self, '_command_palette_selected'): if hasattr(self, '_command_palette_input_focused'): self._command_palette_input_focused = False
self._command_palette_selected = 0
if hasattr(self, '_command_palette_input_focused'):
self._command_palette_input_focused = False
def _test_callback_func_write_to_file(self, data: str) -> None: def _test_callback_func_write_to_file(self, data: str) -> None:
"""A dummy function that a custom_callback would execute for testing.""" """A dummy function that a custom_callback would execute for testing."""
@@ -1118,17 +1046,11 @@ class App:
f.write(data) f.write(data)
def _gui_func(self) -> None: def _gui_func(self) -> None:
""" """Main immediate-mode render loop callback executed on every frame.
Main immediate-mode render loop callback executed on every frame.
Dispatches keyboard shortcuts, renders the background shader, custom title bar, Dispatches keyboard shortcuts, renders the background shader, custom title bar,
main dockspace, and handles popups/modals. main dockspace, and handles popups/modals.
State Mutations: SSDL Shape: `o-> [I:hotkeys] -> [I:title_bar] -> [I:main_interface] -> [I:modals]`
self.show_command_palette (toggled via Ctrl+Shift+P)
self._hot_reload_error (updated on Ctrl+Alt+R)
SSDL Shape:
`o-> [I:hotkeys] -> [I:title_bar] -> [I:main_interface] -> [I:modals]`
ASCII Layout Map: ASCII Layout Map:
+---------------------------------------------------------+ +---------------------------------------------------------+
@@ -1155,7 +1077,7 @@ class App:
sys.stderr.write(f"[startup] first _gui_func entry at {(time.time() - init_ts) * 1000:.1f}ms after init (window/GL + font/style/post_init callbacks done)\n") sys.stderr.write(f"[startup] first _gui_func entry at {(time.time() - init_ts) * 1000:.1f}ms after init (window/GL + font/style/post_init callbacks done)\n")
sys.stderr.flush() sys.stderr.flush()
except Exception: pass except Exception: pass
# One-shot: kick off the controller's heavy-module warmup on the shared # One-shot: kick off the controller's heavy-module warmup on the shared
# io_pool once the FIRST frame has actually been painted. Waiting one frame # io_pool once the FIRST frame has actually been painted. Waiting one frame
# keeps the ~2s of SDK C-extension imports from holding the GIL during # keeps the ~2s of SDK C-extension imports from holding the GIL during
@@ -1168,33 +1090,29 @@ class App:
self._preload_started = True self._preload_started = True
else: else:
self._first_frame_painted = True self._first_frame_painted = True
io = imgui.get_io() io = imgui.get_io()
if io.key_ctrl and io.key_alt and imgui.is_key_down(imgui.Key.r): if io.key_ctrl and io.key_alt and imgui.is_key_down(imgui.Key.r): self._trigger_hot_reload()
self._trigger_hot_reload()
if (io.key_ctrl and io.key_shift if (io.key_ctrl and io.key_shift
and not io.key_alt and not io.key_super and not io.key_alt and not io.key_super
and imgui.is_key_pressed(imgui.Key.p)): and imgui.is_key_pressed(imgui.Key.p)):
self.show_command_palette = not self.show_command_palette self.show_command_palette = not self.show_command_palette
if self.show_command_palette: if self.show_command_palette:
if hasattr(self, '_command_palette_query'): if hasattr(self, '_command_palette_query'): self._command_palette_query = ""
self._command_palette_query = "" if hasattr(self, '_command_palette_selected'): self._command_palette_selected = 0
if hasattr(self, '_command_palette_selected'):
self._command_palette_selected = 0
render_custom_title_bar(self) render_custom_title_bar(self)
render_shader_live_editor(self) render_shader_live_editor(self)
render_history_window(self) render_history_window(self)
pushed_prior_tint = False pushed_prior_tint = False
# Render background shader # Render background shader
bg = bg_shader.get_bg() bg = bg_shader.get_bg()
ws = imgui.get_io().display_size ws = imgui.get_io().display_size
if bg.enabled: if bg.enabled: bg.render(ws.x, ws.y)
bg.render(ws.x, ws.y)
theme.render_post_fx(ws.x, ws.y, self.ai_status, self.ui_crt_filter) theme.render_post_fx(ws.x, ws.y, self.ai_status, self.ui_crt_filter)
if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func") if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func")
try: try:
if self.is_viewing_prior_session: if self.is_viewing_prior_session:
@@ -1205,11 +1123,9 @@ class App:
except Exception as e: except Exception as e:
sys.stderr.write(f"ERROR in _gui_func: {e}\n") sys.stderr.write(f"ERROR in _gui_func: {e}\n")
traceback.print_exc() traceback.print_exc()
self._handle_history_logic() self._handle_history_logic()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_gui_func")
if self.perf_profiling_enabled:
self.perf_monitor.end_component("_gui_func")
return return
def _render_window_if_open(self, name: str, render_func: Callable[[], None], flag_condition: bool = True) -> None: def _render_window_if_open(self, name: str, render_func: Callable[[], None], flag_condition: bool = True) -> None:
@@ -1220,9 +1136,6 @@ class App:
if exp: render_func() if exp: render_func()
def _show_menus(self) -> None: def _show_menus(self) -> None:
"""
[C: tests/test_gui_window_controls.py:test_gui_window_controls_minimize_maximize_close]
"""
global win32gui, win32con global win32gui, win32con
if win32gui is None: if win32gui is None:
import win32con import win32con
@@ -1231,11 +1144,10 @@ class App:
win32gui = win32gui win32gui = win32gui
with imscope.menu("manual slop") as (active): with imscope.menu("manual slop") as (active):
if active and imgui.menu_item("Quit", "Ctrl+Q", False)[0]: if active and imgui.menu_item("Quit", "Ctrl+Q", False)[0]: self.runner_params.app_shall_exit = True
self.runner_params.app_shall_exit = True
with imscope.menu("Windows") as (active): with imscope.menu("Windows") as (active):
if (active): if (active):
for w in self.show_windows.keys(): for w in self.show_windows.keys():
_, self.show_windows[w] = imgui.menu_item(w, "", self.show_windows[w]) _, self.show_windows[w] = imgui.menu_item(w, "", self.show_windows[w])
with imscope.menu("Project") as (active): with imscope.menu("Project") as (active):
if active: if active:
@@ -1249,21 +1161,21 @@ class App:
ai_client.clear_comms_log() ai_client.clear_comms_log()
self._tool_log.clear() self._tool_log.clear()
self._comms_log.clear() self._comms_log.clear()
self.ai_status = "session reset" self.ai_status = "session reset"
self.ai_response = "" self.ai_response = ""
if imgui.menu_item("Generate MD Only", "", False)[0]: if imgui.menu_item("Generate MD Only", "", False)[0]:
try: try:
md, path, *_ = self._do_generate() md, path, *_ = self._do_generate()
self.last_md = md self.last_md = md
self.last_md_path = path self.last_md_path = path
self.ai_status = f"md written: {path.name}" self.ai_status = f"md written: {path.name}"
except Exception as e: except Exception as e:
self.ai_status = f"error: {e}" self.ai_status = f"error: {e}"
with imscope.menu("Layout") as (active): with imscope.menu("Layout") as (active):
if active: if active:
if imgui.menu_item("Save Current...", "", False)[0]: if imgui.menu_item("Save Current...", "", False)[0]:
self._show_save_workspace_profile_modal = True self._show_save_workspace_profile_modal = True
self._new_workspace_profile_name = "" self._new_workspace_profile_name = ""
imgui.separator() imgui.separator()
for profile_id, profile in self.workspace_profiles.items(): for profile_id, profile in self.workspace_profiles.items():
if imgui.menu_item(profile.name, "", False)[0]: if imgui.menu_item(profile.name, "", False)[0]:
@@ -1306,33 +1218,22 @@ class App:
win32gui.SendMessage(hwnd, win32con.WM_NCLBUTTONDOWN, win32con.HTCAPTION, 0) win32gui.SendMessage(hwnd, win32con.WM_NCLBUTTONDOWN, win32con.HTCAPTION, 0)
imgui.push_style_color(imgui.Col_.button, imgui.ImVec4(0, 0, 0, 0)) imgui.push_style_color(imgui.Col_.button, imgui.ImVec4(0, 0, 0, 0))
#Note(Ed): Thirdparty Exception
try: try: is_max = win32gui.GetWindowPlacement(hwnd)[1] == win32con.SW_SHOWMAXIMIZED
is_max = win32gui.GetWindowPlacement(hwnd)[1] == win32con.SW_SHOWMAXIMIZED except Exception: is_max = False
except Exception:
is_max = False
# Explicitly set Y to 0 and match button height to bar height for perfect alignment # Explicitly set Y to 0 and match button height to bar height for perfect alignment
imgui.set_cursor_pos((right_x, 0)) imgui.set_cursor_pos((right_x, 0))
if imgui.button("_", (btn_w, bar_h)): if imgui.button("_", (btn_w, bar_h)): win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
imgui.set_cursor_pos((right_x + btn_w, 0)) imgui.set_cursor_pos((right_x + btn_w, 0))
if imgui.button("[=]" if is_max else "[]", (btn_w, bar_h)): if imgui.button("[=]" if is_max else "[]", (btn_w, bar_h)): win32gui.ShowWindow(hwnd, win32con.SW_RESTORE if is_max else win32con.SW_MAXIMIZE)
win32gui.ShowWindow(hwnd, win32con.SW_RESTORE if is_max else win32con.SW_MAXIMIZE)
imgui.set_cursor_pos((right_x + btn_w * 2, 0)) imgui.set_cursor_pos((right_x + btn_w * 2, 0))
imgui.push_style_color(imgui.Col_.button_hovered, theme.get_color("status_error")) imgui.push_style_color(imgui.Col_.button_hovered, theme.get_color("status_error"))
if imgui.button("X", (btn_w, bar_h)): if imgui.button("X", (btn_w, bar_h)): win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
imgui.pop_style_color() imgui.pop_style_color()
imgui.pop_style_color() imgui.pop_style_color()
def _handle_history_logic(self) -> None: def _handle_history_logic(self) -> None:
""" """Logic for capturing UI state for undo/redo."""
Logic for capturing UI state for undo/redo.
"""
if self._is_applying_snapshot: if self._is_applying_snapshot:
return return
@@ -2495,12 +2396,10 @@ def render_paths_panel(app: App) -> None:
#region: AI Settings #region: AI Settings
def render_ai_settings_hub(app: App) -> None: def render_ai_settings_hub(app: App) -> None:
""" """Groups and renders all AI-related configuration panels in a unified hub sidebar.
Groups and renders all AI-related configuration panels in a unified hub sidebar.
Includes persona selection, LLM provider settings, system prompts, RAG config, and tools. Includes persona selection, LLM provider settings, system prompts, RAG config, and tools.
SSDL Shape: SSDL Shape: `[I:persona_selector] -> [B:provider_header] -> [B:system_prompts_header] -> [B:rag_header] -> [I:agent_tools]`
`[I:persona_selector] -> [B:provider_header] -> [B:system_prompts_header] -> [B:rag_header] -> [I:agent_tools]`
ASCII Layout Map: ASCII Layout Map:
+---------------------------------------------------------+ +---------------------------------------------------------+
@@ -2669,6 +2568,7 @@ def render_agent_tools_panel(app: App) -> None:
| Bias Profile: [None v] | | Bias Profile: [None v] |
+---------------------------------------------------------+ +---------------------------------------------------------+
""" """
caps = app._get_active_capabilities()
if not caps.tool_calling: if not caps.tool_calling:
if imgui.collapsing_header("Active Tool Presets & Biases", imgui.TreeNodeFlags_.default_open): if imgui.collapsing_header("Active Tool Presets & Biases", imgui.TreeNodeFlags_.default_open):
imgui.text_disabled(f"(tools not supported by {app.current_provider}/{app.current_model})") imgui.text_disabled(f"(tools not supported by {app.current_provider}/{app.current_model})")