# gui_2.py
# defer: parse
from __future__ import annotations
import copy
import datetime
import difflib
import json
import math
import os
import re
import shutil
import subprocess
import sys
import traceback
import threading
import time
import typing

# Ensure thirdparty is in sys.path for defer
_project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_thirdparty   = os.path.join(_project_root, "thirdparty")
if _thirdparty not in sys.path:
 sys.path.insert(0, _thirdparty)

from contextlib   import ExitStack, nullcontext
from pathlib      import Path
from typing       import Optional, Any
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced

# Lazy proxies (startup_speedup_20260606 Phase 5D)
# --------------------------------------------------------------------------
# These proxy objects replace top-level imports of heavy modules that
# should NOT be in the main thread's import chain. The actual import is
# deferred until first attribute access (e.g. np.array, filedialog.X, Tk()).
# After the first access, the result is cached so subsequent uses are O(1).
# This pattern is transparent to call sites: `np.array(...)` and `Tk()`
# continue to work unchanged. The savings at startup are 65ms (numpy) +
# stdlib tkinter (variable; not on the original baseline but still real).
# --------------------------------------------------------------------------
import importlib as _importlib
from typing import Any as _Any
from typing import Optional as _Optional

#TODO(Ed): Remove Excpetion based errors
class _LazyModule:
 """Lazy proxy that defers an import until first attribute access or call.

 Use as a module-level name to replace a top-level import. The wrapped
 module is loaded once and cached. Supports both attribute access
 (e.g. np.array) and calling (e.g. Tk()).
 """
 def __init__(self, module_name: str, attr_name: _Optional[str] = None) -> None:
  self._module_name = module_name
  self._attr_name = attr_name
  self._cached: _Optional[_Any] = None

 def _resolve(self) -> _Any:
  if self._cached is None:
   mod = _importlib.import_module(self._module_name)
   if self._attr_name is None:
    self._cached = mod
   else:
    try:
     self._cached = getattr(mod, self._attr_name)
    except AttributeError:
     sub_mod_name = f"{self._module_name}.{self._attr_name}"
     try:
      self._cached = _importlib.import_module(sub_mod_name)
     except (ImportError, ModuleNotFoundError):
      self._cached = _FiledialogStub()
  return self._cached

 def __getattr__(self, name: str) -> _Any:
  return getattr(self._resolve(), name)

 def __call__(self, *args: _Any, **kwargs: _Any) -> _Any:
  return self._resolve()(*args, **kwargs)

class _FiledialogStub:
 """No-op replacement for tkinter.filedialog on Python installs where
 the Tcl/Tk runtime is missing (e.g. embedded Python, slim Docker images).
 All dialog functions return safe empty sentinels so call sites that do
 `if p and p not in app.x: app.x.append(p)` treat a missing dialog as a
 no-op. Exposes a `available` flag so the UI can detect the stub and
 offer an ImGui-based path input as an alternative.
 """
 available: bool = False
 def askopenfilename(self, *args: _Any, **kwargs: _Any) -> str: return ""
 def askopenfilenames(self, *args: _Any, **kwargs: _Any) -> tuple: return ()
 def askdirectory(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):
np         = _LazyModule("numpy")  # was: import numpy as np
filedialog = _LazyModule("tkinter", "filedialog")  # was: from tkinter import filedialog
Tk         = _LazyModule("tkinter", "Tk")  # was: from tkinter import Tk

from src.diff_viewer import apply_patch_to_file
from src import ai_client
from src import aggregate
from src import api_hooks
from src import app_controller
from src import bg_shader
from src import cost_tracker
from src import history
from src import imgui_scopes as imscope
from src import paths
# from src import presets
from src import project_manager
from src import session_logger
from src import log_registry
# from src import log_pruner
from src import models
from src.models import GenerateRequest, ConfirmRequest
from src import mcp_client
from src import markdown_helper
from src import shaders
from src import synthesis_formatter
from src import theme_2 as theme
from src import thinking_parser
from src import workspace_manager
from src.hot_reloader import HotReloader

win32gui: Any = None
win32con: Any = None

COMMS_CLAMP_CHARS: int = 300

def hide_tk_root() -> Tk:
 root = Tk()
 root.withdraw()
 root.wm_attributes("-topmost", True)
 return root

# Standard Color Constants (now bound to the theming system)
def C_OUT() -> imgui.ImVec4: return theme.get_color("status_info")
def C_IN()  -> imgui.ImVec4: return theme.get_color("status_success")
def C_REQ() -> imgui.ImVec4: return theme.get_color("status_warning")
def C_RES() -> imgui.ImVec4: return theme.get_color("status_success")
def C_TC()  -> imgui.ImVec4: return theme.get_color("status_warning")
def C_TR()  -> imgui.ImVec4: return theme.get_color("status_info")
def C_TRS() -> imgui.ImVec4: return theme.get_color("status_info")
def C_LBL() -> imgui.ImVec4: return theme.get_color("text_disabled")
def C_VAL() -> imgui.ImVec4: return theme.get_color("text")
def C_KEY() -> imgui.ImVec4: return theme.get_color("status_info")
def C_NUM() -> imgui.ImVec4: return theme.get_color("status_success")
def C_TRM() -> imgui.ImVec4: return theme.get_color("text_disabled")
def C_SUB() -> imgui.ImVec4: return theme.get_color("text_disabled")

DIR_COLORS:  dict[str, typing.Callable[[], imgui.ImVec4]] = {"OUT":     C_OUT, "IN":       C_IN}
KIND_COLORS: dict[str, typing.Callable[[], imgui.ImVec4]] = {"request": C_REQ, "response": C_RES, "tool_call": C_TC, "tool_result": C_TR, "tool_result_send": C_TRS}
HEAVY_KEYS:  set[str]                                     = {"message", "text", "script", "output", "content"}

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_windows["Text Viewer"] = True
  app.text_viewer_title = label
  app.text_viewer_content = content

def render_selectable_label(app: App, label: str, value: str, width: float = 0.0, multiline: bool = False, height: float = 0.0, color: Any = None) -> None:
 with imscope.id(label + str(hash(value))):
  with imscope.style_color(imgui.Col_.frame_bg, imgui.ImVec4(0, 0, 0, 0)), \
       imscope.style_var(imgui.StyleVar_.frame_border_size, 0.0):
   if color:
    with imscope.style_color(imgui.Col_.text, color):
     if multiline: _, _ = imgui.input_text_multiline(f"##{label}", value, imgui.ImVec2(width, height), imgui.InputTextFlags_.read_only)
     else:         _, _ = imgui.input_text(f"##{label}", value, imgui.InputTextFlags_.read_only)
   else:
    if multiline: _, _ = imgui.input_text_multiline(f"##{label}", value, imgui.ImVec2(width, height), imgui.InputTextFlags_.read_only)
    else:         _, _ = imgui.input_text(f"##{label}", value, imgui.InputTextFlags_.read_only)

def truncate_entries(entries: list[dict[str, Any]], max_pairs: int) -> list[dict[str, Any]]:
 if max_pairs <= 0: return []
 count, target = 0, max_pairs * 2
 for i in range(len(entries) - 1, -1, -1):
  if entries[i].get("role", "") in ("User", "AI"): count += 1
  if count == target: return entries[i:]
 return entries

def _detect_refresh_rate_win32() -> float:
 """Return the primary display's current refresh rate in Hz, or 0.0 on failure.

 Uses user32.EnumDisplaySettingsW (ENUM_CURRENT_SETTINGS) which reads the value
 directly from the display driver in microseconds. The previous implementation
 shelled out to PowerShell + WMI (Get-CimInstance Win32_VideoController), which
 cost ~350ms on every startup and blocked the first frame.
 """
 #Note(Ed): Exception(Thirdparty)
 try:
  import ctypes
  from ctypes import wintypes
  class _DEVMODE(ctypes.Structure):
   _fields_ = [
    ("dmDeviceName",       wintypes.WCHAR * 32), ("dmSpecVersion",  wintypes.WORD),
    ("dmDriverVersion",    wintypes.WORD),       ("dmSize",         wintypes.WORD),
    ("dmDriverExtra",      wintypes.WORD),       ("dmFields",       wintypes.DWORD),
    ("dmStuff",            ctypes.c_byte * 16),  ("dmColor",        wintypes.SHORT),
    ("dmDuplex",           wintypes.SHORT),      ("dmYResolution",  wintypes.SHORT),
    ("dmTTOption",         wintypes.SHORT),      ("dmCollate",      wintypes.SHORT),
    ("dmFormName",         wintypes.WCHAR * 32), ("dmLogPixels",    wintypes.WORD),
    ("dmBitsPerPel",       wintypes.DWORD),      ("dmPelsWidth",    wintypes.DWORD),
    ("dmPelsHeight",       wintypes.DWORD),      ("dmDisplayFlags", wintypes.DWORD),
    ("dmDisplayFrequency", wintypes.DWORD),      ("dmICMMethod",    wintypes.DWORD),
    ("dmICMIntent",        wintypes.DWORD),      ("dmMediaType",    wintypes.DWORD),
    ("dmDitherType",       wintypes.DWORD),      ("dmReserved1",    wintypes.DWORD),
    ("dmReserved2",        wintypes.DWORD),      ("dmPanningWidth", wintypes.DWORD),
    ("dmPanningHeight",    wintypes.DWORD),
   ]
  dm        = _DEVMODE()
  dm.dmSize = ctypes.sizeof(_DEVMODE)
  if ctypes.windll.user32.EnumDisplaySettingsW(None, -1, ctypes.byref(dm)):
   # dmDisplayFrequency is 0 or 1 for "default/hardware" on some drivers.
   if dm.dmDisplayFrequency > 1:
    return float(dm.dmDisplayFrequency)
 except Exception:
  pass
 return 0.0

def _resolve_font_path(font_path: str, assets_dir: Path) -> str:
 """Normalize a configured font path to something hello_imgui can load.

 hello_imgui resolves relative paths against the assets folder. A config may
 carry a stale ABSOLUTE path from a different project checkout (e.g.
 C:/projects/manual_slop/assets/fonts/MapleMono-Regular.ttf after the repo
 moved to C:/projects/sloppy). In that case the absolute file does not exist
 and the load fails. This recovers by:

  1. If the absolute path lives under the current assets folder -> relativize.
  2. If the absolute path exists on disk as-is -> keep it.
  3. Otherwise recover the basename under assets/fonts or assets.
  4. Final fallback: the bundled default Inter font.
 """
 p = Path(font_path)
 if not p.is_absolute():
  return font_path  # already relative; hello_imgui searches the assets folder
 #Note(Ed): Exception(Thirdparty)
 try:
  if p.is_relative_to(assets_dir):
   return str(p.relative_to(assets_dir)).replace("\\", "/")
 except (ValueError, AttributeError):
  pass
 if p.exists():
  return font_path  # absolute path to a real file elsewhere — load directly
 # Stale absolute path: recover by basename relative to the assets folder.
 name = p.name
 for rel in (f"fonts/{name}", name):
  if (assets_dir / rel).exists():
   return rel
 return "fonts/Inter-Regular.ttf"

def _apply_runtime_caps_override(app: "App", caps: "VendorCapabilities") -> "VendorCapabilities":
 from dataclasses import replace
 if app.current_provider == "llama":
  from src import ai_client
  base_url: str = getattr(ai_client, "_llama_base_url", "")
  if "localhost" in base_url or "127.0.0.1" in base_url:
   return replace(caps, local=True)
 return caps

def _render_v2_capability_badges(caps: "VendorCapabilities") -> None:
 """Render small colored badges for the 11 v2 capability flags.

 Only fields where caps.<field> is True are shown. Each badge
 has a tooltip with the field name. Per-field colors map to
 the existing theme convention: green for supported, grey for
 not. Fields with no entry (False) are silently omitted.

 Added 2026-06-11 as part of Phase 5 t5_4 (UI adaptations for
 new v2 fields). The 11 fields are the v2 matrix fields beyond
 the original 7 v1 fields (vision, tool_calling, caching,
 streaming, model_discovery, context_window, cost_tracking)
 which are already gated elsewhere in the GUI.
"""
 badged_fields: list[tuple[str, str]] = [
  ("reasoning",         "Reasoning"),
  ("structured_output", "JSON"),
  ("code_execution",    "Code"),
  ("web_search",        "Web"),
  ("x_search",          "X"),
  ("file_search",       "File"),
  ("mcp_support",       "MCP"),
  ("audio",             "Audio"),
  ("video",             "Video"),
  ("grounding",         "Ground"),
  ("computer_use",      "Comp"),
 ]
 enabled: list[tuple[str, str]] = []
 for field_name, label in badged_fields:
  if getattr(caps, field_name, False): 
   enabled.append((field_name, label))
 if not enabled: return
 imgui.text("Capabilities")
 for field_name, label in enabled:
  imgui.same_line(); imgui.text_colored(theme.get_color("status_success"), f" [{label}]")
  if imgui.is_item_hovered(): imgui.set_tooltip(f"caps.{field_name}=True")

class App:
 """The main ImGui interface orchestrator for Manual Slop."""

 def __init__(self) -> None:
  """Initializes core app dependencies (controller, history, performance monitor,
  command palette, workspace manager) and registers app callback handlers.
  SSDL Shape: `[I:init_controller] -> [I:init_workspace] -> [I:load_profiles]`
  """
  #region: --- Core Dependencies & State ---
  from src.startup_profiler import startup_profiler
  with startup_profiler.phase("app_init_AppController"):
   self.controller = app_controller.AppController(defer_warmup=True)
  self.controller._app = self
  with startup_profiler.phase("app_init_history_perfmon"):
   from src import history, performance_monitor
   self.perf_monitor = performance_monitor.PerformanceMonitor()
   self.history      = history.HistoryManager(max_capacity=100)

  # --- Undo/Redo & Snapshot State ---
  self._last_ui_snapshot:     Optional[history.UISnapshot] = None
  self._snapshot_timer:       float = 0.0
  self._snapshot_debounce:    float = 1.5
  self._pending_snapshot:     bool = False
  self._is_applying_snapshot: bool = False

  # --- Command Palette ---
  self.show_command_palette: bool = False

  # --- Initialization ---
  with startup_profiler.phase("app_init_state"):
   self.controller.init_state()
  from src.hot_reloader import HotReloader, HotModule
  if 'src.gui_2' not in HotReloader.HOT_MODULES:
   HotReloader.register(HotModule(
    name               = 'src.gui_2',
    file_path          = __file__,
    state_keys         = ['active_discussion', 'show_windows', 'ui_file_paths', 'ui_screenshot_paths', 'disc_entries', 'disc_roles'],
    delegation_targets = ['_render_main_interface', '_render_discussion_hub', '_render_files_and_media', '_render_ai_settings_hub', '_render_operations_hub', '_render_mma_dashboard']
   ))

  with startup_profiler.phase("app_init_workspace"):
   self.workspace_manager  = workspace_manager.WorkspaceManager(project_root=self.controller.active_project_root)
   self.disc_entries       = self.controller.disc_entries
   self.disc_roles         = self.controller.disc_roles
   self.workspace_profiles = self.workspace_manager.load_all_profiles()
  with startup_profiler.phase("app_init_start_services"): self.controller.start_services(self)

  #region: --- Controller Callbacks & Actions ---
  self.controller._predefined_callbacks['save_context_preset']        = self.save_context_preset
  self.controller._predefined_callbacks['load_context_preset']        = self.load_context_preset
  self.controller._predefined_callbacks['delete_context_preset']      = self.delete_context_preset
  self.controller._predefined_callbacks['set_ui_file_paths']          = lambda p: setattr(self, 'ui_file_paths', p)
  self.controller._predefined_callbacks['set_ui_screenshot_paths']    = lambda p: setattr(self, 'ui_screenshot_paths', p)
  self.controller._predefined_callbacks['set_context_files_for_test'] = lambda files: setattr(self, 'context_files', [models.FileItem(path=f) for f in files])
  self.controller._predefined_callbacks['set_screenshots_for_test']   = lambda ss: setattr(self, 'screenshots', ss)
  self.controller._predefined_callbacks['_toggle_command_palette']    = self._toggle_command_palette
  self.controller._gettable_fields['show_command_palette']            = 'show_command_palette'
  #endregion: --- Core Dependencies & State ---
  
  def _save_context_preset_force(name: str):
   if not name: return
   preset_files = []
   for f in self.context_files:
    p   = f.path                         if hasattr(f, 'path')            else str(f)
    vm  = f.view_mode                    if hasattr(f, 'view_mode')       else 'summary'
    slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices')   else []
    msk = copy.deepcopy(f.ast_mask)      if hasattr(f, 'ast_mask')        else {}
    sig = f.ast_signatures               if hasattr(f, 'ast_signatures')  else False
    dfn = f.ast_definitions              if hasattr(f, 'ast_definitions') else False
    preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn))
   preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(self.screenshots))
   self.controller.save_context_preset(preset)
   self.ui_new_context_preset_name = ""
   self.show_missing_files_modal = False  
  
  self.controller._predefined_callbacks['get_app_debug_info']        = lambda: self.app_debug_info
  self.controller._gettable_fields     ['app_debug_info']            = 'app_debug_info'
  self.controller._predefined_callbacks['save_context_preset_force'] = _save_context_preset_force
  self.controller._predefined_callbacks['set_ui_attr']               = lambda k, v: setattr(self, k, v)
  self.controller._predefined_callbacks['set_context_files']         = self._set_context_files
  self.controller._predefined_callbacks['simulate_save_preset']      = self._simulate_save_preset
  self.controller._clickable_actions.update({
   'btn_undo':                 self._handle_undo,
   'btn_redo':                 self._handle_redo,
   'btn_open_external_editor': self._open_patch_in_external_editor,
   'Save##ctx':                self._handle_save_ctx_click,
   'Save Anyway':              self._handle_save_anyway_click,
  })

  #region: --- UI Component State ---
  self.text_viewer_wrap = True
  self._text_viewer_editor: Optional[ced.TextEditor] = None
  self.show_windows.setdefault("Diagnostics",       False)
  self.show_windows.setdefault("Usage Analytics",   False)
  self.show_windows.setdefault("Context Preview",   False)
  self.show_windows.setdefault("Tier 1: Strategy",  False)
  self.show_windows.setdefault("Tier 2: Tech Lead", False)
  self.show_windows.setdefault("Tier 3: Workers",   False)
  self.show_windows.setdefault("Tier 4: QA",        False)
  self.show_windows.setdefault('External Tools',    False)
  self.show_windows.setdefault('Shader Editor',     False)
  self.show_windows.setdefault('Undo/Redo History', False)
  #endregion: --- UI Component State ---

  #region: --- Preset & Profile Management State ---
  self.context_preview_text                      = ""
  self.ui_separate_context_preview               = False
  self.ui_active_context_preset                  = ""
  self.target_context_preset_name                = ""
  self.show_empty_context_modal                  = False
  self._pending_generation_action                = None # 'generate' or 'md_only'
  self._pending_save_ctx_click                   = False
  self._pending_save_anyway_click                = False
  self.show_missing_files_modal                  = False
  self.show_structural_editor_modal              = False
  self.show_command_palette                      = False
  self.ui_command_search                         = ""
  self.ui_command_idx                            = 0
  self.missing_context_files                     = []
  self._new_preset_name                          = ""
  self._editing_preset_name                      = ""
  self._editing_preset_system_prompt             = ""
  self._editing_preset_temperature               = 0.0
  self._editing_preset_top_p                     = 1.0
  self._editing_preset_max_output_tokens         = 4096
  self._editing_preset_scope                     = "project"
  self._editing_preset_is_new                    = False
  self._presets_list:                            dict[str, dict] = {}
  self._selected_preset_idx                      = -1
  self._show_save_preset_modal                   = False
  self._editing_tool_preset_name                 = ''
  self._editing_tool_preset_categories           = {}
  self._editing_tool_preset_scope                = 'project'
  self._selected_tool_preset_idx                 = -1
  self._editing_bias_profile_name                = ""
  self._editing_bias_profile_tool_weights:         dict[str, int] = {}
  self._editing_bias_profile_category_multipliers: dict[str, float] = {}
  self._editing_bias_profile_scope               = "project"
  self._selected_bias_profile_idx                = -1
  self._new_bias_tool_name                       = "run_powershell"
  self._new_bias_category_name                   = "General"
  self._editing_persona_name                     = ""
  self._editing_persona_description              = ""
  self._editing_persona_provider                 = ""
  self._editing_persona_model                    = ""
  self._editing_persona_system_prompt            = ""
  self._editing_persona_temperature              = 0.7
  self._editing_persona_top_p                    = 1.0
  self._editing_persona_max_tokens               = 4096
  self._editing_persona_tool_preset_id           = ""
  self._editing_persona_bias_profile_id          = ""
  self._editing_persona_context_preset_id        = ""
  self._editing_persona_aggregation_strategy     = ""
  self._editing_persona_preferred_models_list    : list[dict] = []
  self._editing_persona_scope                    = "project"
  self._editing_persona_is_new                   = True
  self._persona_editor_opened                    = False
  self._personas_list: dict[str, dict] = {}
  #endregion --- Preset & Profile Management State ---

  # --- Layout State ---
  self._persona_split_v      = 0.4
  self._persona_models_open  = True
  self._persona_prompt_open  = True
  self._tool_split_v         = 0.4
  self._bias_split_v         = 0.6
  self._tool_list_open       = True
  self._bias_list_open       = True
  self._bias_weights_open    = True
  self._bias_cats_open       = True
  self._prompt_md_preview    = False
  self.ui_discussion_split_h = 300.0

  # --- Workspace State ---
  gui_cfg = self.config.get("gui", {})
  self.ui_multi_viewport                  = gui_cfg.get("multi_viewport", False)
  self.layout_presets                     = self.config.get("layout_presets", {})
  self._show_save_workspace_profile_modal = False
  self._new_workspace_profile_name        = ""
  self._new_workspace_profile_scope       = "project"
  self.ui_new_vp_name                     = ""
  
  # --- Controller Lock Aliases ---
  self._send_thread_lock          = self.controller._send_thread_lock
  self._disc_entries_lock         = self.controller._disc_entries_lock
  self._pending_comms_lock        = self.controller._pending_comms_lock
  self._pending_tool_calls_lock   = self.controller._pending_tool_calls_lock
  self._pending_history_adds_lock = self.controller._pending_history_adds_lock
  self._pending_gui_tasks_lock    = self.controller._pending_gui_tasks_lock
  self._pending_dialog_lock       = self.controller._pending_dialog_lock
  self._api_event_queue_lock      = self.controller._api_event_queue_lock

  # --- Cache & Discussion State ---
  self._discussion_names_cache: list[str] = []
  self._discussion_names_dirty: bool = True
  self._comms_log_cache:        list[dict[str, Any]] = []
  self._tool_log_cache:         list[dict[str, Any]] = []
  self._focus_md_cache:         dict[str, str] = {}
  self._last_ui_focus_agent:    Optional[str] = None
  self._log_registry:           Optional[log_registry.LogRegistry] = None

  # --- Node Editor State ---
  self.node_editor_config = ed.Config()
  self.node_editor_ctx = ed.create_editor(self.node_editor_config)

  # --- Context & AST State ---
  self.ui_selected_ticket_id:     Optional[str] = None
  self.ui_selected_tickets:       set[str]      = set()
  self.ui_selected_context_files: set[str]      = set()
  self.ui_new_ticket_priority:    str           = 'medium'
  self._autofocus_response_tab      = False
  self._last_selected_context_index = -1
  self.ui_inspecting_ast_file       = None
  self._show_ast_inspector          = False
  self._cached_ast_nodes            = []
  self._cached_ast_file_path        = ''
  self._cached_ast_file_lines       = []
  self.ui_editing_slices_file       = None
  self._slice_sel_start             = -1
  self._slice_sel_end               = -1
  self.context_files                = []
  self.ui_synthesis_prompt:         str             = ""
  self.ui_synthesis_selected_takes: dict[str, bool] = {}

  # --- Rendering & Theme State ---
  self.perf_show_graphs:dict[str, bool] = {}
  self.ui_crt_filter                    = False
  self.ui_tool_filter_category          = "All"
  self.shader_uniforms                  = {'crt': 1.0, 'scanline': 0.5, 'bloom': 0.8}
  self._is_generating_preview           = False
  self._pending_preview_refresh         = False
  self._preview_refresh_timer: float    = 0.0
  self._preview_refresh_debounce: float = 0.5
  self._hot_reload_error: Optional[str] = None

 def _set_context_files(self, paths: list[str]) -> None:
  from src import models
  self.context_files            = [models.FileItem(path=p) for p in paths]
  self.controller.context_files = self.context_files

 def _simulate_save_preset(self, name: str) -> None:
  from src import models
  item               = models.FileItem(path='test.py')
  self.files         = [item]
  self.context_files = [item]
  self.screenshots   = ['test.png']
  self.save_context_preset(name)

 def _handle_approve_ask(self) -> None:
  """UI-level wrapper for approving a pending tool execution ask."""
  self.controller._handle_approve_ask()

 def _handle_save_ctx_click(self) -> None:
  self._pending_save_ctx_click = True

 def _handle_save_anyway_click(self) -> None:
  self._pending_save_anyway_click = True

 def _post_init(self) -> None:
  theme.apply_current()
  # Register warmup completion callback (sub-track 4 of
  # startup_speedup_20260606). The callback runs on a background
  # _io_pool thread; it only sets primitive state on the App, which
  # is safe. The render_warmup_status_indicator() function reads
  # the timestamp to show a brief "ready" tag for 3 seconds.
  if hasattr(self.controller, "on_warmup_complete"):
   #Note(Ed): Exception(Thirdparty)
   try:
    self.controller.on_warmup_complete(lambda status: _on_warmup_complete_callback(self, status))
   except Exception: pass
   self._diag_layout_state()

 def _diag_layout_state(self) -> None:
  """One-shot startup diagnostic: log show_windows state and warn if the
  on-disk manualslop_layout.ini references window names that no longer
  exist in the current code. Helps users and test operators detect
  stale layout state at a glance instead of debugging missing panels.
  """
  import os as _os
  visible_by_default = [w for w, v in self.show_windows.items() if v]
  sys.stderr.write(f"[GUI] show_windows entries: {len(self.show_windows)}, visible by default: {len(visible_by_default)}\n")
  if visible_by_default:
   sys.stderr.write(f"[GUI] visible-by-default windows: {', '.join(sorted(visible_by_default))}\n")
  ini_path = _os.path.join(_os.getcwd(), "manualslop_layout.ini")
  if not _os.path.exists(ini_path):
   sys.stderr.write(f"[GUI] no layout file at {ini_path} (HelloImGui will create a fresh one on shutdown)\n")
   return
  ini_size = _os.path.getsize(ini_path)
  sys.stderr.write(f"[GUI] layout file: {ini_path} ({ini_size} bytes)\n")
  #Note(Ed): Exception(Thirdparty)
  try:
   with open(ini_path, encoding="utf-8") as _f:
    _ini_text = _f.read()
  except Exception as _e:
   sys.stderr.write(f"[GUI] could not read layout file: {_e}\n")
   return
  _STALE_WINDOW_NAMES = {
   "Projects",      "Files",   "Screenshots", "Discussion History",
   "Provider",      "Message", "Response",    "Tool Calls",
   "Comms History", "System Prompts",
  }
  _stale_found = [n for n in _STALE_WINDOW_NAMES if f"[Window][{n}]" in _ini_text]
  if _stale_found:
   sys.stderr.write(f"[GUI] WARNING: layout has {len(_stale_found)} stale window name(s) that no longer exist: {', '.join(_stale_found)}\n")
   sys.stderr.write(f"[GUI] Run the 'Reset Layout' command from the Command Palette (or delete {ini_path}) to regenerate.\n")

 def _trigger_hot_reload(self) -> bool:
  from src.hot_reloader import HotReloader
  result                 = HotReloader.reload_all(self)
  self._hot_reload_error = HotReloader.last_error
  return result

 def run(self) -> None:
  """Initializes the ImGui runner (HelloImGui) and starts the main application loop.
  Loads system themes, default styling metrics, fonts, and sets up window docking layouts.
  SSDL: `[I:hello_imgui] -> o-> [I:main_loop]`
  """
  if "--headless" in sys.argv:
   print("Headless mode active")
   self._fetch_models(self.current_provider)
   import uvicorn
   headless_cfg = self.config.get("headless", {})
   port         = headless_cfg.get("port", 8000)
   api          = self.create_api()
   uvicorn.run(api, host="0.0.0.0", port=port)
  else:
   from src.startup_profiler import startup_profiler
   if hasattr(self, "controller") and hasattr(self.controller, "mark_gui_run_started"):
    self.controller.mark_gui_run_started()
   with startup_profiler.phase("theme_load_from_config"):
    theme.load_from_config(self.config)
   with startup_profiler.phase("imgui_bundle_import"):
    from imgui_bundle import hello_imgui as _hi
   with startup_profiler.phase("RunnerParams_init"):
    self.runner_params = _hi.RunnerParams()
    self.runner_params.app_window_params.window_title = "manual slop"
   # (removed stale _t-based print; the phase() above already logs RunnerParams_init)

   if sys.platform == "win32":
    self.runner_params.app_window_params.borderless           = True
    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_resizable = True
   
   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.remember_theme            = True
   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
   
   # Enforce DPI Awareness and User Scale
   user_scale = theme.get_current_scale()
   self.runner_params.dpi_aware_params.dpi_window_size_factor = user_scale

   # Detect Monitor Refresh Rate for capping (Win32 only).
   # Uses the native EnumDisplaySettings call (~0.3ms) instead of spawning a
   # PowerShell/WMI subprocess (~350ms) so the first frame is not blocked.
   fps_cap = 60.0
   if sys.platform == "win32":
    rate = _detect_refresh_rate_win32()
    if rate: fps_cap = rate

   # Enable idling with monitor refresh rate to effectively cap FPS
   self.runner_params.fps_idling.enable_idling = True
   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_view_themes = True
   self.runner_params.ini_folder_type                           = hello_imgui.IniFolderType.current_folder
   self.runner_params.ini_filename                              = "manualslop_layout.ini"
   def _profiled_setup_style() -> None:
    with startup_profiler.phase("setup_imgui_style"):
     theme.apply_current()
   def _profiled_post_init() -> None:
    with startup_profiler.phase("post_init"):
     self._post_init()
   self.runner_params.callbacks.show_gui              = self._gui_func
   self.runner_params.callbacks.show_menus            = self._show_menus
   self.runner_params.callbacks.load_additional_fonts = self._load_fonts
   self.runner_params.callbacks.setup_imgui_style     = _profiled_setup_style
   self.runner_params.callbacks.post_init             = _profiled_post_init
   self._fetch_models(self.current_provider)
   md_options = markdown_helper.get_renderer().options
   #Note(Ed): Exception(Thirdparty)
   try:
    immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options))
   except RuntimeError as _immapp_exc:
    # ImGui scope errors (IM_ASSERT) and other native-bundle exceptions
    # surface as RuntimeError. Per user feedback 2026-06-08, do not
    # silently swallow — record the failure on the controller so the
    # /api/gui_health endpoint and the GUI logs can surface it. Keep the
    # process alive so the hook server (separate thread) can continue
    # serving tests; the next test can detect the degraded state and
    # fail fast with a clear message.
    if hasattr(self, "controller") and self.controller is not None:
     self.controller._gui_degraded_reason = (
      f"immapp.run raised {type(_immapp_exc).__name__}: {_immapp_exc}"
     )
     self.controller._last_imgui_assert = traceback.format_exc()
    print(
     f"[GUI-DEGRADED] immapp.run raised: {_immapp_exc}",
     file  = sys.stderr,
     flush = True,
    )
    print(self.controller._last_imgui_assert if hasattr(self, "controller") and self.controller else "",
     file=sys.stderr, flush=True)
    return
   # On exit (only reached on clean shutdown)
   self.shutdown()
   session_logger.close_session()

 def _load_fonts(self) -> None:
  from src.startup_profiler import startup_profiler
  with startup_profiler.phase("load_fonts"):
   # Set hello_imgui assets folder to the actual absolute path
   assets_dir = Path(__file__).parent.parent / "assets"
   if assets_dir.exists():
    hello_imgui.set_assets_folder(str(assets_dir.absolute()))

   # Improved font rendering with oversampling
   config              = imgui.ImFontConfig()
   config.oversample_h = 3
   config.oversample_v = 3

   font_path, font_size = theme.get_font_loading_params()

   if font_path:
    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
    try:
     with startup_profiler.phase("load_fonts.main_with_fontawesome"):
      self.main_font = hello_imgui.load_font_ttf_with_font_awesome_icons(font_path, font_size, config)
    except Exception as e:
     print(f"Failed to load main font {font_path}: {e}")
     self.main_font = None
   else:
    self.main_font = None
   
   #Note(Ed): Exception(Thirdparty)
   try:
    with startup_profiler.phase("load_fonts.mono"):
     params         = hello_imgui.FontLoadingParams(font_config=config)
     self.mono_font = hello_imgui.load_font("fonts/MapleMono-Regular.ttf", font_size, params)
   except Exception as e:
    print(f"Failed to load mono font: {e}")
    self.mono_font = None

 def _handle_approve_mma_step(self, user_data=None) -> None:
  """UI-level wrapper for approving a pending MMA step."""
  self._handle_mma_respond(approved=True)

 def _handle_approve_spawn(self, user_data=None) -> None:
  """UI-level wrapper for approving a pending MMA sub-agent spawn."""
  self._handle_mma_respond(approved=True)

 #TODO(Ed): Remove Exception based errors.
 def __getattr__(self, name: str) -> Any:
  if name == 'controller':
   raise AttributeError(name)
  if hasattr(self, 'controller') and hasattr(self.controller, name):
   return getattr(self.controller, name)
  raise AttributeError(name)

 def __setattr__(self, name: str, value: Any) -> None:
  if name != 'controller' and hasattr(self, 'controller') and hasattr(self.controller, name):
   setattr(self.controller, name, value)
  else:
   object.__setattr__(self, name, value)

 def _handle_generate_send(self) -> None:
  if not self.ui_selected_context_files and not getattr(self, "_pending_proceed_generate", False):
   self._pending_generation_action = 'generate'
   self.show_empty_context_modal   = True
  else:
   self._pending_proceed_generate = False
   self.controller._handle_generate_send()

 def _handle_md_only(self) -> None:
  if not self.ui_selected_context_files and not getattr(self, "_pending_proceed_md_only", False):
   self._pending_generation_action = 'md_only'
   self.show_empty_context_modal   = True
  else:
   self._pending_proceed_md_only = False
   self.controller._handle_md_only()

 @property
 def current_provider(self) -> str: 
  return self.controller.current_provider

 @current_provider.setter
 def current_provider(self, value: str) -> None:
  self.controller.current_provider = value

 @property
 def current_model(self) -> str:
  return self.controller.current_model

 @current_model.setter
 def current_model(self, value: str) -> None:
  self.controller.current_model = value

 #TODO(Ed): Remove Exception based errors.
 def _get_active_capabilities(self) -> "VendorCapabilities":
  from src.vendor_capabilities import VendorCapabilities, get_capabilities
   #TODO(Ed): Remove Exception based errors.
  try:
   caps = get_capabilities(self.current_provider, self.current_model)
  except KeyError:
   caps = VendorCapabilities(vendor=self.current_provider, model=self.current_model, notes="unregistered")
  return _apply_runtime_caps_override(self, caps)

 @property
 def perf_profiling_enabled(self) -> bool:
  return self.controller.perf_profiling_enabled

 @perf_profiling_enabled.setter
 def perf_profiling_enabled(self, value: bool) -> None:
  self.controller.perf_profiling_enabled = value

 @property
 def missing_files(self) -> list[str]:
  return [f.path for f in self.context_files if not os.path.exists(f.path if os.path.isabs(f.path) else os.path.join(self.controller.active_project_root, f.path))]

 @property
 def app_debug_info(self) -> dict:
  return {
   "context_files":              [f.path for f in self.context_files],
   "missing_files":              self.missing_files,
   "screenshots":                self.screenshots,
   "ui_new_context_preset_name": self.ui_new_context_preset_name,
   "target_context_preset_name": self.target_context_preset_name,
   "show_missing_files_modal":   self.show_missing_files_modal,
   "active_project_root":        str(self.controller.active_project_root),
   "project_keys":               list(self.controller.project.keys()),
   "presets":                    list(self.controller.project.get('context_presets', {}).keys())
  }

 def _take_snapshot(self) -> history.UISnapshot:
  """ Captures the current state of UI input parameters, system prompts, active
  discussions, and files list, returning a UISnapshot for history management.
  SSDL: `[Q:ui_state] -> [I:copy] -> [T:snapshot]`
  """
  from src import history
  import copy
  return history.UISnapshot(
   ai_input                = self.ui_ai_input,
   project_system_prompt   = self.ui_project_system_prompt,
   global_system_prompt    = self.ui_global_system_prompt,
   base_system_prompt      = self.ui_base_system_prompt,
   use_default_base_prompt = self.ui_use_default_base_prompt,
   temperature             = self.temperature,
   top_p                   = self.top_p,
   max_tokens              = self.max_tokens,
   auto_add_history        = self.ui_auto_add_history,
   disc_entries            = copy.deepcopy(self.disc_entries),
   files                   = [f.to_dict() if hasattr(f, 'to_dict') else f for f in self.files],
   context_files           = [f.to_dict() if hasattr(f, 'to_dict') else f for f in self.context_files],
   screenshots             = list(self.screenshots)
  )

 def _apply_snapshot(self, snapshot: history.UISnapshot) -> None:
  """Applies a previously captured UISnapshot back to the active UI state.
  Restores input fields, parameters, discussions, screenshots, and context files.
  SSDL Shape: `[I:lock_flag] -> [S:ui_state] -> [I:unlock]`
  """
  self._is_applying_snapshot = True
  try:
   self.ui_ai_input                = snapshot.ai_input
   self.ui_project_system_prompt   = snapshot.project_system_prompt
   self.ui_global_system_prompt    = snapshot.global_system_prompt
   self.ui_base_system_prompt      = snapshot.base_system_prompt
   self.ui_use_default_base_prompt = snapshot.use_default_base_prompt
   self.temperature                = snapshot.temperature
   self.top_p                      = snapshot.top_p
   self.max_tokens                 = snapshot.max_tokens
   self.ui_auto_add_history        = snapshot.auto_add_history
   self.disc_entries               = snapshot.disc_entries
   
   # Restore files as FileItem objects
   from src import models
   self.files = []
   for f in snapshot.files:
    if isinstance(f, dict): self.files.append(models.FileItem.from_dict(f))
    else:                   self.files.append(models.FileItem(path=str(f)))
   
   self.context_files = []
   for f in snapshot.context_files:
    if isinstance(f, dict): self.context_files.append(models.FileItem.from_dict(f))
    else:                   self.context_files.append(models.FileItem(path=str(f)))
   
   self.screenshots       = list(snapshot.screenshots)
   self._last_ui_snapshot = snapshot # Update last snapshot to avoid immediate re-push
  finally:
   self._is_applying_snapshot = False # ?? TODO(Ed): Whats the point of this??

 def _capture_workspace_profile(self, name: str) -> models.WorkspaceProfile:
  """Serializes the current window visibility states, popped-out panel layouts, and
  ImGui INI configurations into a WorkspaceProfile object.
  SSDL Shape: `[Q:ui_states] -> [B:ini_ready] -> [T:profile]`
  """
  if not getattr(self, "_ini_capture_ready", False):
   self._ini_capture_ready = True
   ini = ""
  else:
   #Note(Ed): Thirdparty Exception
   try:              ini = str(imgui.save_ini_settings_to_memory() or "")
   except Exception: ini = ""
  panel_states = {
   "ui_separate_context_preview":  getattr(self, "ui_separate_context_preview",  False),
   "ui_separate_message_panel":    getattr(self, "ui_separate_message_panel",    False),
   "ui_separate_response_panel":   getattr(self, "ui_separate_response_panel",   False),
   "ui_separate_tool_calls_panel": getattr(self, "ui_separate_tool_calls_panel", False),
   "ui_separate_task_dag":         getattr(self, "ui_separate_task_dag",         False),
   "ui_separate_usage_analytics":  getattr(self, "ui_separate_usage_analytics",  False),
   "ui_separate_tier1":            getattr(self, "ui_separate_tier1",            False),
   "ui_separate_tier2":            getattr(self, "ui_separate_tier2",            False),
   "ui_separate_tier3":            getattr(self, "ui_separate_tier3",            False),
   "ui_separate_tier4":            getattr(self, "ui_separate_tier4",            False),
   "ui_separate_external_tools":   getattr(self, "ui_separate_external_tools",   False),
   "ui_discussion_split_h":        getattr(self, "ui_discussion_split_h",        300.0),
  }
  return models.WorkspaceProfile(
   name         = name,
   ini_content  = ini,
   show_windows = copy.deepcopy(self.show_windows),
   panel_states = panel_states
  )

 def _apply_workspace_profile(self, profile: models.WorkspaceProfile):
  """Restores the window docking layout and popped-out panel visibility states
  from a saved WorkspaceProfile.
  SSDL Shape: `[I:load_ini] -> [S:ui_states]`
  """
  imgui.load_ini_settings_from_memory(profile.ini_content)
  self.show_windows.update(profile.show_windows)
  for k, v in profile.panel_states.items():
   if hasattr(self, k): setattr(self, k, v)

 def _handle_undo(self) -> None:
  """Reverts the application UI state to the previous snapshot in the history stack.
  DAG Render Context:
    Called by: _gui_func() (via hotkey Ctrl+Z) or undo button click.
    Calls: _take_snapshot(), _apply_snapshot(), HistoryManager.undo()
  """
  sys.stderr.write(f"[DEBUG History] _handle_undo called. can_undo={self.history.can_undo}\n"); sys.stderr.flush()
  if not self.history.can_undo: return
  current = self._take_snapshot()
  entry   = self.history.undo(current, "Undo Action")
  if entry:
   sys.stderr.write(f"[DEBUG History] Undoing to: {entry.description}\n"); sys.stderr.flush()
   self._apply_snapshot(entry.state)

 def _handle_jump_to_history(self, index: int) -> None:
  sys.stderr.write(f"[DEBUG History] Jumping to index {index}\n"); sys.stderr.flush()
  current = self._take_snapshot()
  entry   = self.history.jump_to_undo(index, current, "Before Jump")
  if entry:
   self._apply_snapshot(entry.state)

 def _handle_redo(self) -> None:
  """Re-applies the next snapshot in the history stack (forward navigation).
  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.flush()
  if not self.history.can_redo: return
  current = self._take_snapshot()
  entry   = self.history.redo(current, "Redo Action")
  if entry:
   sys.stderr.write(f"[DEBUG History] Redoing to: {entry.description}\n"); sys.stderr.flush()
   self._apply_snapshot(entry.state)

 def shutdown(self) -> None:
  """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.
  SSDL Shape: `[I:save_ini] -> [I:controller_shutdown]`
  """
  #Note(Ed): Exception(Thirdparty)
  try:
   if hasattr(self, 'runner_params') and self.runner_params.ini_filename:
    imgui.save_ini_settings_to_disk(self.runner_params.ini_filename)
  except:
   pass
  self.controller.shutdown()

 def load_context_preset(self, name: str) -> None:
  preset = self.controller.load_context_preset(name)
  from src import models
  import copy
  self.context_files = []
  for f in preset.files:
   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.ast_mask        = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
   fi.ast_signatures  = getattr(f, 'ast_signatures', False)
   fi.ast_definitions = getattr(f, 'ast_definitions', False)
   self.context_files.append(fi)
  self.screenshots         = list(preset.screenshots)
  self.ui_file_paths       = [f.path for f in preset.files]
  self.ui_screenshot_paths = list(preset.screenshots)
  self._update_context_file_stats()

 def delete_context_preset(self, name: str) -> None:
  self.controller.delete_context_preset(name)
  if getattr(self, "ui_active_context_preset", "") == name:
   self.ui_active_context_preset = ""
 
 @property
 def ui_file_paths(self) -> list[str]:
  return [f.path if hasattr(f, 'path') else str(f) for f in self.files]

 @ui_file_paths.setter
 def ui_file_paths(self, paths: list[str]) -> None:
  sys.stderr.write(f"[DEBUG] Setting ui_file_paths to: {paths}\n")
  sys.stderr.flush()
  old_files = {f.path: f for f in self.files if hasattr(f, 'path')}
  new_files = []
  now = time.time()
  for p in paths:
   if p in old_files:
    new_files.append(old_files[p])
   else:
    from src import models
    new_files.append(models.FileItem(path=p, injected_at=now))
  self.files = new_files

 @property
 def ui_screenshot_paths(self) -> list[str]:
  return self.screenshots

 @ui_screenshot_paths.setter
 def ui_screenshot_paths(self, paths: list[str]) -> None:
  self.screenshots = paths

 def _toggle_command_palette(self) -> None:
  """Toggle the Command Palette visibility. Used as a custom_callback for tests
  since the keyboard shortcut (Ctrl+Shift+P) cannot be simulated via the hook API."""
  self.show_command_palette = not self.show_command_palette
  if self.show_command_palette:
   if hasattr(self, '_command_palette_query'):         self._command_palette_query         = ""
   if hasattr(self, '_command_palette_selected'):      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:
  """A dummy function that a custom_callback would execute for testing."""
  # Ensure the directory exists if running from a different cwd
  os.makedirs("tests/artifacts", exist_ok=True)
  with open("tests/artifacts/temp_callback_output.txt", "w") as f:
   f.write(data)

 def _gui_func(self) -> None:
  """Main immediate-mode render loop callback executed on every frame.
  Dispatches keyboard shortcuts, renders the background shader, custom title bar,
  main dockspace, and handles popups/modals.

  SSDL Shape: `o-> [I:hotkeys] -> [I:title_bar] -> [I:main_interface] -> [I:modals]`

  ASCII Layout Map:
    +---------------------------------------------------------+
    | [Menu: manual slop] [Windows] [Project] [Layout]   [x]  |
    +--------------------+-------------------+----------------+
    | Project / Files    | Discussion Hub    | Operations     |
    |                    |                   |                |
    | [x] file1.py (Sig) | [User]            | [Spawn Agent]  |
    | [x] file2.py (Def) | I want you to...  |                |
    |                    |                   |                |
    | +----------------+ | [AI]              | [Exec clutch]  |
    | | Presets        | | Analyzing code... |                |
    +--------------------+-------------------+----------------+
  """
  # One-shot: log when immapp first hands control to our render callback. The
  # span init -> here is window/GL/context creation + the font/style/post_init
  # callbacks (all opaque C++); the span here -> mark_first_frame_rendered is
  # the cost of the first full-UI render. Splitting them attributes the gap.
  if not getattr(self, "_gui_func_entered", False):
   self._gui_func_entered = True
   try:
    init_ts = getattr(self.controller, "_init_start_ts", None)
    if init_ts is not None:
     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()
   except Exception: pass
  
  # 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
  # keeps the ~2s of SDK C-extension imports from holding the GIL during
  # window creation and font-atlas building, so the window appears at full
  # speed; the SDKs are then warmed by the time the user sends their first
  # message. start_warmup() is idempotent.
  if not getattr(self, "_preload_started", False):
   if getattr(self, "_first_frame_painted", False):
    self.controller.start_warmup()
    self._preload_started = True
   else:
    self._first_frame_painted = True
  
  io = imgui.get_io()
  if io.key_ctrl and io.key_alt and imgui.is_key_down(imgui.Key.r): self._trigger_hot_reload()
  if (io.key_ctrl and io.key_shift
    and not io.key_alt and not io.key_super
    and imgui.is_key_pressed(imgui.Key.p)):
   self.show_command_palette = not self.show_command_palette
   if self.show_command_palette:
    if hasattr(self, '_command_palette_query'):    self._command_palette_query    = ""
    if hasattr(self, '_command_palette_selected'): self._command_palette_selected = 0
  
  render_custom_title_bar(self)
  render_shader_live_editor(self)
  render_history_window(self)
  pushed_prior_tint = False
  
  # Render background shader
  bg = bg_shader.get_bg()
  ws = imgui.get_io().display_size
  if bg.enabled: bg.render(ws.x, ws.y)

  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")
  try:
   if self.is_viewing_prior_session:
    with imscope.style_color(imgui.Col_.window_bg, theme.get_color("bubble_vendor")):
     render_main_interface(self)
   else:
    render_main_interface(self)
  except Exception as e:
   sys.stderr.write(f"ERROR in _gui_func: {e}\n")
   traceback.print_exc()
  
  self._handle_history_logic()
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_gui_func")
  return

 def _render_window_if_open(self, name: str, render_func: Callable[[], None], flag_condition: bool = True) -> None:
  """Helper to render a window only if its toggle is active."""
  if not flag_condition or not self.show_windows.get(name, False): return
  with imscope.window(name, self.show_windows[name]) as (exp, opened):
   self.show_windows[name] = bool(opened)
   if exp: render_func()

 def _show_menus(self) -> None:
  global win32gui, win32con
  if win32gui is None:
   import win32con
   import win32gui
   win32con = win32con
   win32gui = win32gui
  
  with imscope.menu("manual slop") as (active):
   if active and imgui.menu_item("Quit", "Ctrl+Q", False)[0]: self.runner_params.app_shall_exit = True
  with imscope.menu("Windows") as (active):
   if (active):
    for w in self.show_windows.keys(): 
     _, self.show_windows[w] = imgui.menu_item(w, "", self.show_windows[w])
  with imscope.menu("Project") as (active):
   if active:
    if imgui.menu_item("Save All", "", False)[0]:
     self._flush_to_project()
     self._flush_to_config()
     self.save_config()
     self.ai_status = "config saved"
    if imgui.menu_item("Reset Session", "", False)[0]:
     ai_client.reset_session()
     ai_client.clear_comms_log()
     self._tool_log.clear()
     self._comms_log.clear()
     self.ai_status   = "session reset"
     self.ai_response = ""
    if imgui.menu_item("Generate MD Only", "", False)[0]:
     try:
      md, path, *_      = self._do_generate()
      self.last_md      = md
      self.last_md_path = path
      self.ai_status    = f"md written: {path.name}"
     except Exception as e:
      self.ai_status = f"error: {e}"
  with imscope.menu("Layout") as (active):
   if active:
    if imgui.menu_item("Save Current...", "", False)[0]:
     self._show_save_workspace_profile_modal = True
     self._new_workspace_profile_name        = ""
    imgui.separator()
    for profile_id, profile in self.workspace_profiles.items():
     if imgui.menu_item(profile.name, "", False)[0]:
      self.controller._cb_load_workspace_profile(profile_id)
    imgui.separator()
    with imscope.menu("Delete Profile") as (active):
     if active:
      for profile_id, profile in self.workspace_profiles.items():
       if imgui.menu_item(profile.name, "", False)[0]:
        self.controller._cb_delete_workspace_profile(profile_id, self._new_workspace_profile_scope)
  
  # Draw right-aligned window controls directly in the menu bar (Win32 only)
  if sys.platform == "win32":
   try:
    import ctypes
    ctypes.pythonapi.PyCapsule_GetPointer.restype  = ctypes.c_void_p
    ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p]
    hwnd_capsule = imgui.get_main_viewport().platform_handle_raw
    hwnd         = ctypes.pythonapi.PyCapsule_GetPointer(hwnd_capsule, b"nb_handle")
   except Exception:
    hwnd = 0
   
   if hwnd:
    btn_w = 40
    # Use window width (points) instead of display_size (pixels) for correct scaling
    window_w = imgui.get_window_width()
    bar_h    = imgui.get_window_height()
    right_x  = window_w - (btn_w * 3)
    # Drag area check using an explicit invisible button spanning the empty space
    curr_x = imgui.get_cursor_pos_x()
    drag_w = right_x - curr_x
    if drag_w > 0:
     imgui.invisible_button("##drag_area", (drag_w, bar_h))
     if imgui.is_item_active() and imgui.is_mouse_dragging(0):
      # CRITICAL: We must reset ImGui's mouse_down state BEFORE passing control to Windows.
      # Otherwise, the Windows modal drag loop swallows the WM_LBUTTONUP event, 
      # and ImGui thinks the mouse is permanently held down, causing "sticky" dragging.
      imgui.get_io().mouse_down[0] = False
      win32gui.ReleaseCapture()
      win32gui.SendMessage(hwnd, win32con.WM_NCLBUTTONDOWN, win32con.HTCAPTION, 0)
    
    imgui.push_style_color(imgui.Col_.button, imgui.ImVec4(0, 0, 0, 0))
    #Note(Ed): Exception(Thirdparty)
    try:              is_max = win32gui.GetWindowPlacement(hwnd)[1] == win32con.SW_SHOWMAXIMIZED
    except Exception: is_max = False
    # Explicitly set Y to 0 and match button height to bar height for perfect alignment
    imgui.set_cursor_pos((right_x, 0))
    if imgui.button("_", (btn_w, bar_h)):                       win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
    imgui.set_cursor_pos((right_x + btn_w, 0))
    if imgui.button("[=]" if is_max else "[]", (btn_w, bar_h)): win32gui.ShowWindow(hwnd, win32con.SW_RESTORE if is_max else win32con.SW_MAXIMIZE)
    imgui.set_cursor_pos((right_x + btn_w * 2, 0))
    imgui.push_style_color(imgui.Col_.button_hovered, theme.get_color("status_error"))
    if imgui.button("X", (btn_w, bar_h)):                       win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
    imgui.pop_style_color()
    imgui.pop_style_color()

 def _handle_history_logic(self) -> None:
  """Logic for capturing UI state for undo/redo."""
  if self._is_applying_snapshot:
   return
  
  try:
   # 2. Debounced snapshotting
   current = self._take_snapshot()
   if self._last_ui_snapshot is None:
    self._last_ui_snapshot = current
    return

   # Compare only core fields for performance
   changed = (
    current.ai_input                != self._last_ui_snapshot.ai_input                or
    current.project_system_prompt   != self._last_ui_snapshot.project_system_prompt   or
    current.global_system_prompt    != self._last_ui_snapshot.global_system_prompt    or
    current.base_system_prompt      != self._last_ui_snapshot.base_system_prompt      or
    current.use_default_base_prompt != self._last_ui_snapshot.use_default_base_prompt or
    abs(current.temperature - self._last_ui_snapshot.temperature) > 1e-5              or
    abs(current.top_p       - self._last_ui_snapshot.top_p)       > 1e-5              or
    current.max_tokens         != self._last_ui_snapshot.max_tokens                   or
    current.auto_add_history   != self._last_ui_snapshot.auto_add_history             or
    len(current.disc_entries)  != len(self._last_ui_snapshot.disc_entries)            or
    len(current.files)         != len(self._last_ui_snapshot.files)                   or
    len(current.context_files) != len(self._last_ui_snapshot.context_files)           or
    len(current.screenshots)   != len(self._last_ui_snapshot.screenshots)
   )
   
   if not changed and len(current.disc_entries) > 0:
    if current.disc_entries[-1].get('content') != self._last_ui_snapshot.disc_entries[-1].get('content'):
     changed = True

   if changed:
    if not self._pending_snapshot:
     self._pending_snapshot = True
     self._snapshot_timer   = time.time()
     # Capture state BEFORE current change
     self._state_to_push    = self._last_ui_snapshot
    else:
     # Reset timer for settle debounce
     self._snapshot_timer = time.time()
    
    self._last_ui_snapshot = current

   if self._pending_snapshot and (time.time() - self._snapshot_timer > self._snapshot_debounce):
    if self._state_to_push:
     self.history.push(self._state_to_push, "UI Update")
     self._state_to_push = None
    self._pending_snapshot = False
  except Exception as e:
   import sys, traceback
   sys.stderr.write(f"[DEBUG History] ERROR in _handle_history_logic: {e}\n")
   traceback.print_exc(file=sys.stderr)
   sys.stderr.flush()
 
 def cb_load_prior_log(self, path: Optional[str] = None) -> None:
  if path is None:
   root = hide_tk_root()
   path = filedialog.askdirectory(title='Select Session Directory', initialdir=str(paths.get_logs_dir()))
   root.destroy()
  if path: self.controller.cb_load_prior_log(path)

 def _set_external_editor_default(self, editor_name: str) -> None:
  from src import models
  if "tools" not in self.config:                   self.config["tools"] = {}
  if "default_editor" not in self.config["tools"]: self.config["tools"]["default_editor"] = {}
  self.config["tools"]["default_editor"]["default_editor"] = editor_name
  self.save_config()
  self.ai_status = f"Default editor set to: {editor_name}"

 def _save_paths(self) -> None:
  self.config["paths"] = {
   "logs_dir":    self.ui_logs_dir,
   "scripts_dir": self.ui_scripts_dir
  }
  cfg_path = paths.get_config_path()
  if cfg_path.exists(): shutil.copy(cfg_path, str(cfg_path) + ".bak")
  self.save_config()
  paths.reset_resolved()
  self.init_state()
  self.ai_status = 'paths applied and session reset'

 def _populate_auto_slices(self, f_item: models.FileItem) -> None:
  from src import mcp_client
  import re
  from pathlib import Path
  import os
  proj_dir = str(Path(self.controller.active_project_path).parent.resolve()) if getattr(self, 'controller', None) and self.controller.active_project_path else None
  abs_path = f_item.path if os.path.isabs(f_item.path) else os.path.join(proj_dir or '.', f_item.path)
  mcp_client.configure([{"path": abs_path}], [proj_dir] if proj_dir else None)
  
  f_path_lower = f_item.path.lower()

  #TODO(Ed): Exception(Review)
  try:
   if   f_path_lower.endswith('.py'):                           outline = mcp_client.py_get_code_outline(abs_path)
   elif f_path_lower.endswith(('.c', '.h')):                    outline = mcp_client.ts_c_get_code_outline(abs_path)
   elif f_path_lower.endswith(('.cpp', '.hpp', '.cxx', '.cc')): outline = mcp_client.ts_cpp_get_code_outline(abs_path)
   else: return
  except Exception:
   return

  if outline.startswith("ERROR") or outline.startswith("ACCESS DENIED"):
   return
  pattern = re.compile(r'^\s*\[(.*?)\] (.*?) \(Lines (\d+)-(\d+)\)', re.MULTILINE)
  try:
   with open(f_item.path, "r", encoding="utf-8") as f:
    text = f.read()
  except Exception:
   return
  #TODO(Ed): Exception(Review)
  try:
   from src.fuzzy_anchor import FuzzyAnchor
  except ImportError:
   FuzzyAnchor = None
  for match in pattern.finditer(outline):
   kind, name, s_str, e_str = match.groups()
   s_line = int(s_str)
   e_line = int(e_str)
   if any(s.get('start_line') == s_line and s.get('end_line') == e_line for s in f_item.custom_slices):
    continue
   if FuzzyAnchor: slice_data = FuzzyAnchor.create_slice(text, s_line, e_line)
   else:           slice_data = {"start_line": s_line, "end_line": e_line}
   slice_data['tag']     = 'auto-ast'
   slice_data['comment'] = name
   f_item.custom_slices.append(slice_data)

 def _update_context_file_stats(self) -> tuple[int, int]:
  if not hasattr(self, '_file_stats_cache'):         self._file_stats_cache = {}
  if not hasattr(self, '_file_stats_queue'):         self._file_stats_queue = []
  if not hasattr(self, '_file_stats_worker_active'): self._file_stats_worker_active = False
  total_lines = 0
  total_ast   = 0

  missing_keys = []
  for f in self.context_files:
   f_path    = f.path if hasattr(f, "path") else str(f)
   mtime     = os.path.getmtime(f_path) if os.path.exists(f_path) else 0
   cache_key = f"{f_path}_{mtime}"
   if cache_key not in self._file_stats_cache: missing_keys.append((f_path, cache_key))
   else:
    stats       = self._file_stats_cache[cache_key]
    total_lines += stats.get("lines", 0)
    total_ast   += stats.get("ast_elements", 0)

  if missing_keys and not self._file_stats_worker_active:
   def _stats_worker() -> None:
    self._file_stats_worker_active = True
    try:
     for path, key in missing_keys[:10]:
      self._file_stats_cache[key] = aggregate.compute_file_stats(path)
    finally:
     self._file_stats_worker_active = False

   self.submit_io(_stats_worker)
  return total_lines, total_ast
 
 def _close_vscode_diff(self) -> None:
  if hasattr(self, '_vscode_diff_process') and self._vscode_diff_process:
   try:
    self._vscode_diff_process.terminate()
   except Exception:
    pass
   self._vscode_diff_process = None

 def _apply_pending_patch(self) -> None:
  if not self._pending_patch_text:
   self._patch_error_message = "No patch to apply"
   return
  #TODO(Ed): Exception(Review)
  try:
   base_dir     = str(self.controller.current_project_dir) if hasattr(self.controller, 'current_project_dir') else "."
   success, msg = apply_patch_to_file(self._pending_patch_text, base_dir)
   if success:
    self._show_patch_modal    = False
    self._pending_patch_text  = None
    self._pending_patch_files = []
    self._patch_error_message = None
    imgui.close_current_popup()
   else:
    self._patch_error_message = msg
  except Exception as e:
   self._patch_error_message = str(e)

 def _open_patch_in_external_editor(self) -> None:
  self._external_editor_clicked = True
  try:
   from src.external_editor import get_default_launcher, create_temp_modified_file
   import os
   if not self._pending_patch_files:
    self._patch_error_message = "No files to edit"
    return
   launcher = get_default_launcher(self.config)
   editor   = launcher.config.get_default()
   if not editor:
    self._patch_error_message = "No external editor configured"
    return
   original_path = self._pending_patch_files[0]
   if not os.path.exists(original_path):
    self._patch_error_message = f"Original file not found: {original_path}"
    return
   temp_path = create_temp_modified_file(self._pending_patch_text)
   result    = launcher.launch_diff(None, original_path, temp_path)
   if result is None: self._patch_error_message = "Failed to launch external editor"
   else:
    self._patch_error_message = None
    self._vscode_diff_process = result
  except Exception as e:
   self._patch_error_message = str(e)

 def _reorder_ticket(self, src_idx: int, dst_idx: int) -> None:
  if src_idx == dst_idx: return
  new_tickets = list(self.active_tickets)
  ticket      = new_tickets.pop(src_idx)
  new_tickets.insert(dst_idx, ticket)
  # Validate dependencies: a ticket cannot be placed before any of its dependencies
  id_to_idx = {str(t.get('id', '')): i for i, t in enumerate(new_tickets)}
  valid     = True
  for i, t in enumerate(new_tickets):
   deps = t.get('depends_on', [])
   for d_id in deps:
    if d_id in id_to_idx and id_to_idx[d_id] >= i:
     valid = False
     break
   if not valid: break
  if valid:
   self.active_tickets = new_tickets
   self._push_mma_state_update()

 def request_patch_from_tier4(self, error: str, file_context: str) -> None:
  try:
   from src import ai_client
   from src.diff_viewer import parse_diff
   patch_text = ai_client.run_tier4_patch_generation(error, file_context)
   if patch_text and "---" in patch_text and "+++" in patch_text:
    diff_files                = parse_diff(patch_text)
    file_paths                = [df.old_path for df in diff_files]
    self._pending_patch_text  = patch_text
    self._pending_patch_files = file_paths
    self._show_patch_modal    = True
   else:
    self._patch_error_message = patch_text or "No patch generated"
  except Exception as e:
   self._patch_error_message = str(e)

 def bulk_execute(self) -> None:
  for tid in self.ui_selected_tickets:
   t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
   if t: t['status'] = 'in_progress'
  self._push_mma_state_update()

 def bulk_skip(self) -> None:
  for tid in self.ui_selected_tickets:
   t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
   if t: t['status'] = 'completed'
  self._push_mma_state_update()

 def bulk_block(self) -> None:
  for tid in self.ui_selected_tickets:
   t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
   if t: t['status'] = 'blocked'
  self._push_mma_state_update()

 def _cb_kill_ticket(self, ticket_id: str) -> None:
  if self.controller and hasattr(self.controller, 'engine') and self.controller.engine:
   self.controller.engine.kill_worker(ticket_id)

 def _cb_block_ticket(self, ticket_id: str) -> None:
  t = next((t for t in self.active_tickets if str(t.get('id', '')) == ticket_id), None)
  if t:
   t['status']         = 'blocked'
   t['manual_block']   = True
   t['blocked_reason'] = '[MANUAL] User blocked'
   changed = True
   while changed:
    changed = False
    for t in self.active_tickets:
     if t.get('status') == 'todo':
      for dep_id in t.get('depends_on', []):
       dep = next((x for x in self.active_tickets if str(x.get('id', '')) == dep_id), None)
       if dep and dep.get('status') == 'blocked':
        t['status'] = 'blocked'
        changed     = True
        break
   self._push_mma_state_update()

 def _cb_unblock_ticket(self, ticket_id: str) -> None:
  t = next((t for t in self.active_tickets if str(t.get('id', '')) == ticket_id), None)
  if t and t.get('manual_block', False):
   t['status']         = 'todo'
   t['manual_block']   = False
   t['blocked_reason'] = None
   changed = True
   while changed:
    changed = False
    for t in self.active_tickets:
     if t.get('status') == 'blocked' and not t.get('manual_block', False):
      can_run = True
      for dep_id in t.get('depends_on', []):
       dep = next((x for x in self.active_tickets if str(x.get('id', '')) == dep_id), None)
       if dep and dep.get('status') != 'completed':
        can_run = False
        break
      if can_run:
       t['status'] = 'todo'
       changed     = True
   self._push_mma_state_update()
 

def main() -> None:
 app = App()
 app.run()

if __name__ == "__main__":
 main()


def render_main_interface(app: App) -> None:
 """Top-level per-frame orchestrator. Dispatches every subsystem in the correct order:
 error/stale overlay tints, perf bookends, GUI task draining, modal rendering,
 auto-save, comms/tool-log caching, and all dockable windows.

 SSDL: `[I:overlays] -> [I:task_drain] -> [I:modals] -> [I:windows] -> [I:popups]`

 ASCII Layout Map:
   Full-screen dockspace (managed by imgui_bundle):
   +-------------------+-------------------------------------+
   | Project Settings  |         Discussion Hub              |
   | AI Settings       | +--------------------------------+  |
   | Files & Media     | | History entries (scrollable)   |  |
   | Usage Analytics   | +--------------------------------+  |
   | MMA Dashboard     | [splitter]                          |
   | Task DAG          | [Message] [Response]                |
   | Tier 1..4 streams |                                     |
   +-------------------+------------------+------------------+
   |     Operations Hub (tab-bar)          |   Theme window  |
   | [Comms][Tools][Usage][Ext][Layouts]   |                 |
   +---------------------------------------+-----------------+
   (Modals: Approve Script, MMA Step/Spawn, Context, Patch...)
 """
 if hasattr(app, "controller") and hasattr(app.controller, "mark_first_frame_rendered"):
  app.controller.mark_first_frame_rendered()
 render_error_tint(app)
 render_project_stale_tint(app)
 render_warmup_status_indicator(app)
 app.perf_monitor.start_frame()
 app._autofocus_response_tab = app.controller._autofocus_response_tab

 #region: Process GUI task queue
 app._process_pending_gui_tasks()
 app._process_pending_history_adds()
 if app.controller._process_pending_tool_calls(): app._tool_log_dirty = True
 #endregion: Process GUI task queue
 
 render_track_proposal_modal(app)
 render_patch_modal(app)
 render_base_prompt_diff_modal(app)
 render_save_preset_modal(app)
 render_save_workspace_profile_modal(app)
 render_add_context_files_modal(app)
 from src.command_palette import render_palette_modal
 from src.commands        import registry as _cmd_registry
 render_palette_modal(app, _cmd_registry.all())
 render_preset_manager_window(app)
 render_tool_preset_manager_window(app)
 render_persona_editor_window(app)
 
 #TODO(Ed): Exception(Review)
 # Auto-save (every 60s)
 now = time.time()
 if now - app._last_autosave >= app._autosave_interval:
  app._last_autosave = now
  try:
   app._flush_to_project()
   app._flush_to_config()
   app.save_config()
  except Exception:
   pass  # silent â€” don't disrupt the GUI loop
 
 # Sync pending comms
 with app._pending_comms_lock:
  if app._pending_comms:
   if app.ui_auto_scroll_comms: app._scroll_comms_to_bottom = True
   app._comms_log_dirty = True
   for c in app._pending_comms: app._comms_log.append(c)
   app._pending_comms.clear()
 
 if app.ui_focus_agent != app._last_ui_focus_agent:
  app._comms_log_dirty     = True
  app._tool_log_dirty      = True
  app._last_ui_focus_agent = app.ui_focus_agent
 
 if app._comms_log_dirty:
  if app.is_viewing_prior_session: app._comms_log_cache = app.prior_session_entries
  else:
   log_raw = list(app._comms_log)
   if app.ui_focus_agent: app._comms_log_cache = [e for e in log_raw if e.get("source_tier", "").startswith(app.ui_focus_agent)]
   else:                  app._comms_log_cache = log_raw
  app._comms_log_dirty = False
 
 if app._tool_log_dirty:
  if app.is_viewing_prior_session: app._tool_log_cache = app.prior_tool_calls
  else:
   log_raw = list(app._tool_log)
   if app.ui_focus_agent: app._tool_log_cache = [e for e in log_raw if e.get("source_tier", "").startswith(app.ui_focus_agent)]
   else:                  app._tool_log_cache = log_raw
  app._tool_log_dirty = False
 
 app._render_window_if_open("Project Settings", lambda: render_project_settings_hub(app))
 app._render_window_if_open("Files & Media",    lambda: render_files_and_media(app))
 app._render_window_if_open("AI Settings",      lambda: render_ai_settings_hub(app))
 app._render_window_if_open("Usage Analytics",  lambda: render_usage_analytics_panel(app), app.ui_separate_usage_analytics)
 app._render_window_if_open("MMA Dashboard",    lambda: render_mma_dashboard(app))
 app._render_window_if_open("Task DAG",         lambda: render_task_dag_panel(app),        app.ui_separate_task_dag)
 
 app._render_window_if_open("Tier 1: Strategy",  lambda: render_tier_stream_panel(app, "Tier 1", "Tier 1"),             app.ui_separate_tier1)
 app._render_window_if_open("Tier 2: Tech Lead", lambda: render_tier_stream_panel(app, "Tier 2", "Tier 2 (Tech Lead)"), app.ui_separate_tier2)
 app._render_window_if_open("Tier 3: Workers",   lambda: render_tier_stream_panel(app, "Tier 3", None),                 app.ui_separate_tier3)
 app._render_window_if_open("Tier 4: QA",        lambda: render_tier_stream_panel(app, "Tier 4", "Tier 4 (QA)"),        app.ui_separate_tier4)
 
 if app.show_windows.get("Theme", False): render_theme_panel(app)
 
 app._render_window_if_open("Discussion Hub",    lambda: render_discussion_hub(app))
 app._render_window_if_open("Operations Hub",    lambda: render_operations_hub(app))
 
 app._render_window_if_open("Message",           lambda: render_message_panel(app),        app.ui_separate_message_panel)
 app._render_window_if_open("Response",          lambda: render_response_panel(app),       app.ui_separate_response_panel)
 app._render_window_if_open("Tool Calls",        lambda: render_tool_calls_panel(app),     app.ui_separate_tool_calls_panel)
 app._render_window_if_open("External Tools",    lambda: render_external_tools_panel(app), app.ui_separate_external_tools)
 app._render_window_if_open("Log Management",    lambda: render_log_management(app))
 app._render_window_if_open("Diagnostics",       lambda: render_diagnostics_panel(app))
 app._render_window_if_open("Context Preview",   lambda: render_context_preview_window(app))

 render_text_viewer_window(app)
 app.perf_monitor.end_frame()
 
 # Modals / Popups
 render_approve_script_modal(app)
 render_mma_modals(app)
 render_context_modals(app)

def render_custom_title_bar(app: App) -> None:
 # Obsolete, removed since it renders behind the full screen dock space.
 # Controls are now embedded in _show_menus.
 pass

#region: Diagnostics & Analytics

def render_usage_analytics_panel(app: App) -> None:
 """Renders the aggregate dashboard panel for usage, budgeting, cache analytics,
 tool performance metrics, session insights, and RAG status.

 SSDL:`[I:token_budget] -> [I:cache_panel] -> [I:tool_analytics] -> [I:session_insights] -> [I:rag_status]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [Prompt Utilization sub-panel]                          |
   | ---                                                     |
   | [Cache Analytics sub-panel]                             |
   | ---                                                     |
   | [Tool Usage sub-panel]                                  |
   | ---                                                     |
   | [Session Insights sub-panel]                            |
   | ---                                                     |
   | [RAG: indexing...] (clickable status badge)             |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_usage_analytics_panel")
 render_token_budget_panel(app)
 imgui.separator()
 render_cache_panel(app)
 imgui.separator()
 render_tool_analytics_panel(app)
 imgui.separator()
 render_session_insights_panel(app)
 imgui.separator()

 # RAG status indicator
 if app.controller.rag_config and app.controller.rag_config.enabled:
  # imgui.same_line()
  status = app.controller.rag_status
  if status == "indexing...": color = theme.get_color("status_success")
  elif status == "error":     color = theme.get_color("status_error")
  else:                       color = theme.get_color("text_disabled")
  
  imgui.text_colored(color, f"[RAG: {status}]")
  if imgui.is_item_hovered(): imgui.set_tooltip(f"RAG is enabled. Status: {status}. Click to rebuild index.")
  if imgui.is_item_clicked(): app.controller.event_queue.put('click', 'btn_rebuild_rag_index')

 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_usage_analytics_panel")

def render_diagnostics_panel(app: App) -> None:
 """Renders the Diagnostics window. Shows FPS, frame time, CPU%, and input lag in a
 summary table; when profiling is on, shows per-component moving-average timings,
 optional sparkline graphs, and the diagnostic message log.

 SSDL: `[I:perf_summary_table] -> [B:enable_profiling] => [I:component_timings_table] -> [I:graphs] -> [I:diag_log]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Performance Telemetry  [ ] Enable Profiling             |
   | +----------------+---------+-------+                    |
   | | FPS            | 120.0   | [ ]   |                    |
   | | Frame Time(ms) | 8.33    | [ ]   |                    |
   | | CPU %          | 4.2     | [ ]   |                    |
   | +----------------+---------+-------+                    |
   | (when profiling enabled)                                |
   | Detailed Component Timings                              |
   | +--------------------+------+-----+------+------+----+  |
   | | Component          | Avg  | Cnt | Max  | Min  | G  |  |
   | +--------------------+------+-----+------+------+----+  |
   | Diagnostic Log                                          |
   | +--------------------+------+---------------------+     |
   | | 2026-06-12 22:00   | info | Cache rebuilt        |    |
   | +--------------------+------+---------------------+     |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_diagnostics_panel")
 with imscope.window("Diagnostics", app.show_windows.get("Diagnostics", False)) as (exp, opened):
  app.show_windows["Diagnostics"] = bool(opened)
  if exp:
   metrics = app.perf_monitor.get_metrics()
   imgui.text("Performance Telemetry")
   imgui.same_line()
   _, app.perf_profiling_enabled = imgui.checkbox("Enable Profiling", app.perf_profiling_enabled)
   imgui.separator()
   
   if imgui.begin_table("perf_table", 3, imgui.TableFlags_.borders_inner_h):
    imgui.table_setup_column("Metric")
    imgui.table_setup_column("Value")
    imgui.table_setup_column("Graph")
    imgui.table_headers_row()
    
    for label, key, format_str in [
     ("FPS", "fps", "%.1f"),
     ("Frame Time (ms)", "frame_time_ms", "%.2f"),
     ("CPU %", "cpu_percent", "%.1f"),
     ("Input Lag (ms)", "input_lag_ms", "%.1f")
    ]:
     imgui.table_next_row(); imgui.table_next_column()
     imgui.text(label);      imgui.table_next_column()
     if key == "fps": avg_val = imgui.get_io().framerate
     else:            avg_val = metrics.get(f"{key}_avg", metrics.get(key, 0.0))
     imgui.text(format_str % avg_val); imgui.table_next_column()
     app.perf_show_graphs.setdefault(key, False)
     _, app.perf_show_graphs[key] = imgui.checkbox(f"##g_{key}", app.perf_show_graphs[key])
    imgui.end_table()
   
   if app.perf_profiling_enabled:
    imgui.separator()
    imgui.text("Detailed Component Timings (Moving Average)")
    if imgui.begin_table("comp_timings", 6, imgui.TableFlags_.borders):
     imgui.table_setup_column("Component")
     imgui.table_setup_column("Avg (ms)")
     imgui.table_setup_column("Count")
     imgui.table_setup_column("Max (ms)")
     imgui.table_setup_column("Min (ms)")
     imgui.table_setup_column("Graph")
     imgui.table_headers_row()
     for key, val in metrics.items():
      if key.startswith("time_") and key.endswith("_ms") and not key.endswith("_avg"):
       comp_name = key[5:-3]
       avg_val   = metrics.get(f"{key}_avg", val)
       count     = int(metrics.get(f"count_{comp_name}", 0))
       max_val   = metrics.get(f"max_{comp_name}_ms", 0.0)
       min_val   = metrics.get(f"min_{comp_name}_ms", 0.0)
       imgui.table_next_row(); imgui.table_next_column()
       imgui.text(comp_name);  imgui.table_next_column()
       if avg_val > 10.0: imgui.text_colored(theme.get_color("status_error"), f"{avg_val:.2f}")
       else:              imgui.text(f"{avg_val:.2f}")
       imgui.table_next_column()
       imgui.text(f"{count}");       imgui.table_next_column()
       imgui.text(f"{max_val:.2f}"); imgui.table_next_column()
       imgui.text(f"{min_val:.2f}"); imgui.table_next_column()
       app.perf_show_graphs.setdefault(comp_name, False)
       _, app.perf_show_graphs[comp_name] = imgui.checkbox(f"##g_{comp_name}", app.perf_show_graphs[comp_name])
     imgui.end_table()
   
   imgui.separator()
   imgui.text("Performance Graphs")
   for key, show in app.perf_show_graphs.items():
    if show:
     imgui.text(f"History: {key}")
     hist_data = app.perf_monitor.get_history(key)
     if hist_data:
      import numpy as np
      imgui.plot_lines(f"##plot_{key}", np.array(hist_data, dtype=np.float32), graph_size=imgui.ImVec2(-1, 60))
     else:
      imgui.text_disabled(f"(no history data for {key})")

   imgui.separator()
   imgui.text("Diagnostic Log")
   if imgui.begin_table("diag_log_table", 3, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable):
    imgui.table_setup_column("Timestamp",    imgui.TableColumnFlags_.width_fixed, 150)
    imgui.table_setup_column("Type",         imgui.TableColumnFlags_.width_fixed, 100)
    imgui.table_setup_column("Message")
    imgui.table_headers_row()
    for entry in reversed(app.controller.diagnostic_log):
     imgui.table_next_row()
     imgui.table_next_column()
     imgui.text(entry.get("ts", ""));   imgui.table_next_column()
     imgui.text(entry.get("type", "")); imgui.table_next_column()
     imgui.text_wrapped(entry.get("message", ""))
    imgui.end_table()

 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_diagnostics_panel")

def render_cache_panel(app: App) -> None:
 """Renders Gemini cache analytics. Shows cache age, TTL remaining as a colour-coded
 progress bar (green > 50%, yellow > 20%, red otherwise), and a [Clear Cache] button.
 Skips rendering for non-Gemini providers.

 SSDL: `[I:cache_stats] -> [B:progress_bar] => [B:clear_cache]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Cache Analytics                                         |
   | Age: 4m 12s   TTL: 55m 48s (93%)                        |
   | [=============================================   ] 93%  |
   | [Clear Cache]                                           |
   |  Cache cleared - will rebuild on next request           |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_cache_panel")
 if app.current_provider != "gemini":
  if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_cache_panel")
  return
 imgui.text_colored(C_LBL(), 'Cache Analytics')
 stats = getattr(app.controller, '_cached_cache_stats', {})
 if not stats.get("cache_exists"):
  imgui.text_disabled("No active cache")
  if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_cache_panel")
  return
 age_sec       = stats.get("cache_age_seconds", 0)
 ttl_remaining = stats.get("ttl_remaining", 0)
 ttl_total     = stats.get("ttl_seconds", 3600)
 age_str       = f"{age_sec/60:.0f}m {age_sec%60:.0f}s"
 remaining_str = f"{ttl_remaining/60:.0f}m {ttl_remaining%60:.0f}s"
 ttl_pct       = (ttl_remaining / ttl_total * 100) if ttl_total > 0 else 0
 imgui.text(f"Age: {age_str}")
 imgui.text(f"TTL: {remaining_str} ({ttl_pct:.0f}%)")
 color = theme.get_color("status_success")
 if   ttl_pct < 20: color = theme.get_color("status_error")
 elif ttl_pct < 50: color = theme.get_color("status_warning")
 imgui.push_style_color(imgui.Col_.plot_histogram, color)
 imgui.progress_bar(ttl_pct / 100.0, imgui.ImVec2(-1, 0), f"{ttl_pct:.0f}%")
 imgui.pop_style_color()
 if imgui.button("Clear Cache"):
  app.controller.clear_cache()
  app._cache_cleared_timestamp = time.time()
 if hasattr(app, '_cache_cleared_timestamp') and time.time() - app._cache_cleared_timestamp < 5:
  imgui.text_colored(theme.get_color("status_success"), "Cache cleared - will rebuild on next request")
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_cache_panel")

def render_tool_analytics_panel(app: App) -> None:
 """Renders a breakdown of tool execution telemetry: calls, average latency ms, and
 failure rate percentage per tool. Results are sorted by invocation count descending.

 SSDL: `[I:tool_stats] -> [B:stats_table]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Tool Usage                                              |
   | +------------------------+-------+----------+-------+   |
   | | Tool                   | Count | Avg (ms) | Fail% |   |
   | +------------------------+-------+----------+-------+   |
   | | py_get_definition      | 42    | 35       | 0%    |   |
   | | run_powershell         | 18    | 820      | 5%    |   |
   | +------------------------+-------+----------+-------+   |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_tool_analytics_panel")
 imgui.text_colored(C_LBL(), 'Tool Usage')
 imgui.separator()
 now = time.time()
 if not hasattr(app, '_tool_stats_cache_time') or now - app._tool_stats_cache_time > 1.0:
  app._cached_tool_stats = getattr(app.controller, '_tool_stats', {})
 tool_stats = getattr(app.controller, '_cached_tool_stats', {})
 if not tool_stats:
  imgui.text_disabled("No tool usage data")
  if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_tool_analytics_panel")
  return
 if imgui.begin_table("tool_stats", 4, imgui.TableFlags_.borders | imgui.TableFlags_.sortable):
  imgui.table_setup_column("Tool")
  imgui.table_setup_column("Count")
  imgui.table_setup_column("Avg (ms)")
  imgui.table_setup_column("Fail %")
  imgui.table_headers_row()
  sorted_tools = sorted(tool_stats.items(), key=lambda x: -x[1].get("count", 0))
  for tool_name, stats in sorted_tools:
   count      = stats.get("count", 0)
   total_time = stats.get("total_time_ms", 0)
   failures   = stats.get("failures", 0)
   avg_time   = total_time / count if count > 0 else 0
   fail_pct   = (failures / count * 100) if count > 0 else 0
   imgui.table_next_row()
   imgui.table_set_column_index(0); imgui.text(tool_name)
   imgui.table_set_column_index(1); imgui.text(str(count))
   imgui.table_set_column_index(2); imgui.text(f"{avg_time:.0f}")
   imgui.table_set_column_index(3)
   if fail_pct > 0: imgui.text_colored(theme.get_color("status_error"), f"{fail_pct:.0f}%")
   else:            imgui.text("0%")
  imgui.end_table()
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_tool_analytics_panel")

def render_token_budget_panel(app: App) -> None:
 """Renders prompt token utilization breakdown: session totals, cache read/creation stats,
 a colour-coded utilisation progress bar, component breakdown table (System/Tools/History),
 per-tier MMA cost table, trim warnings, and cache activity badge.

 SSDL: `[I:session_tokens] -> [B:utilization_bar] -> [B:breakdown_table] -> [B:tier_costs_table]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Prompt Utilization                                      |
   | Tokens: 24,200 (In: 18,000 Out: 6,200)  Latency: 2.1s   |
   | Cache Read: 12,000  Creation: 2,000                     |
   | [==============================         ] 63.2%         |
   | 24,200 / 38,000 tokens  (13,800 remaining)              |
   | +-------------+--------+------+                         |
   | | System      | 8,000  | 33%  |                         |
   | | Tools       | 4,000  | 17%  |                         |
   | | History     | 12,200 | 50%  |                         |
   | +-------------+--------+------+                         |
   | MMA Tier Costs                                          |
   | | Tier 1 | gemini | 14,000 | $0.0012 |                  |
   | ⚠ WARNING: Next call will trim history                  |
   | Cache Usage: ACTIVE | Age: 252s / 3600s                 |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_token_budget_panel")
 imgui.text_colored(C_LBL(), 'Prompt Utilization')
 usage = app.session_usage
 total = usage["input_tokens"] + usage["output_tokens"]
 if total == 0 and usage.get("total_tokens", 0) > 0: total = usage["total_tokens"]
 render_selectable_label(app, "session_telemetry_tokens", f"Tokens: {total:,} (In: {usage['input_tokens']:,} Out: {usage['output_tokens']:,})", width=-1, color=C_RES())
 if usage.get("last_latency", 0.0) > 0: imgui.text_colored(C_LBL(), f"  Last Latency: {usage['last_latency']:.2f}s")
 if usage["cache_read_input_tokens"]:   imgui.text_colored(C_LBL(), f"  Cache Read: {usage['cache_read_input_tokens']:,}  Creation: {usage['cache_creation_input_tokens']:,}")
 if app._gemini_cache_text:             imgui.text_colored(C_SUB(), app._gemini_cache_text)
 imgui.separator()

 if app._token_stats_dirty:
  app._token_stats_dirty = False
  # Offload to background thread via event queue
  app.controller.event_queue.put("refresh_api_metrics", {"md_content": app._last_stable_md or ""})
 stats = app._token_stats
 if not stats:
  imgui.text_disabled("Token stats unavailable")
  if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_token_budget_panel")
  return
 pct      = stats.get("utilization_pct", 0.0)
 current  = stats.get("estimated_prompt_tokens", stats.get("total_tokens", 0))
 limit    = stats.get("max_prompt_tokens", 0)
 headroom = stats.get("headroom_tokens", max(0, limit - current))
 if   pct < 50.0: color = theme.get_color("status_success")
 elif pct < 80.0: color = theme.get_color("status_warning")
 else:            color = theme.get_color("status_error")
 imgui.push_style_color(imgui.Col_.plot_histogram, color)
 imgui.progress_bar(pct / 100.0, imgui.ImVec2(-1, 0), f"{pct:.1f}%")
 imgui.pop_style_color()
 imgui.text_disabled(f"{current:,} / {limit:,} tokens  ({headroom:,} remaining)")
 sys_tok   = stats.get("system_tokens", 0)
 tool_tok  = stats.get("tools_tokens", 0)
 hist_tok  = stats.get("history_tokens", 0)
 total_tok = sys_tok + tool_tok + hist_tok or 1
 if imgui.begin_table("token_breakdown", 3, imgui.TableFlags_.borders_inner_h | imgui.TableFlags_.sizing_fixed_fit):
  imgui.table_setup_column("Component")
  imgui.table_setup_column("Tokens")
  imgui.table_setup_column("Pct")
  imgui.table_headers_row()
  for lbl, tok in [("System", sys_tok), ("Tools", tool_tok), ("History", hist_tok)]:
   imgui.table_next_row()
   imgui.table_set_column_index(0); imgui.text(lbl)
   imgui.table_set_column_index(1); imgui.text(f"{tok:,}")
   imgui.table_set_column_index(2); imgui.text(f"{tok / total_tok * 100:.0f}%")
  imgui.end_table()
 imgui.separator()
 imgui.text("MMA Tier Costs")
 if hasattr(app, 'mma_tier_usage') and app.mma_tier_usage:
  if imgui.begin_table("tier_cost_breakdown", 4, imgui.TableFlags_.borders_inner_h | imgui.TableFlags_.sizing_fixed_fit):
   imgui.table_setup_column("Tier")
   imgui.table_setup_column("Model")
   imgui.table_setup_column("Tokens")
   imgui.table_setup_column("Est. Cost")
   imgui.table_headers_row()
   for tier, stats in app.mma_tier_usage.items():
    model  = stats.get('model', 'unknown')
    in_t   = stats.get('input', 0)
    out_t  = stats.get('output', 0)
    tokens = in_t + out_t
    cost   = cost_tracker.estimate_cost(model, in_t, out_t)
    imgui.table_next_row()
    imgui.table_set_column_index(0); render_selectable_label(app, f"tier_{tier}", tier, width=-1)
    imgui.table_set_column_index(1); render_selectable_label(app, f"model_{tier}", model.split("-")[0], width=-1)
    imgui.table_set_column_index(2); render_selectable_label(app, f"tokens_{tier}", f"{tokens:,}", width=-1)
    if caps.local:
     cost_str = "Free (local)"
    elif caps.cost_tracking:
     cost_str = f"${cost:.4f}"
    else:
     cost_str = "-"
    imgui.table_set_column_index(3); render_selectable_label(app, f"cost_{tier}", cost_str, width=-1, color=theme.get_color("status_success"))
   imgui.end_table()
   tier_total = sum(cost_tracker.estimate_cost(stats.get('model', ''), stats.get('input', 0), stats.get('output', 0)) for stats in app.mma_tier_usage.values())
   if caps.local:
    total_str = "Free (local)"
   elif caps.cost_tracking:
    total_str = f"${tier_total:.4f}"
   else:
    total_str = "-"
   render_selectable_label(app, "session_total_cost", f"Session Total: {total_str}", width=-1, color=theme.get_color("status_success"))
 else:
  imgui.text_disabled("No MMA tier usage data")
 if stats.get("would_trim"):
  imgui.text_colored(theme.get_color("status_warning"), "WARNING: Next call will trim history")
  trimmable = stats.get("trimmable_turns", 0)
  if trimmable: imgui.text_disabled(f"Trimmable turns: {trimmable}")
  msgs = stats.get("messages")
  if msgs:
   shown = 0
   for msg in msgs:
    if shown >= 3: break
    if msg.get("trimmable"):
     role = msg.get("role", "?")
     toks = msg.get("tokens", 0)
     imgui.text_disabled(f"  [{role}] ~{toks:,} tokens")
     shown += 1
 imgui.separator()
 caps = app._get_active_capabilities()
 if not caps.caching:
  imgui.text_disabled(f"Cache Usage: N/A (not supported by {app.current_provider}/{app.current_model})")
 else:
  cache_stats = getattr(app.controller, '_cached_cache_stats', {})
  if cache_stats.get("cache_exists"):
   age = cache_stats.get("cache_age_seconds", 0)
   ttl = cache_stats.get("ttl_seconds", 3600)
   imgui.text_colored(C_LBL(), f"Cache Usage: ACTIVE | Age: {age:.0f}s / {ttl}s | Renews at: {ttl * 0.9:.0f}s")
  else:
   imgui.text_disabled("Cache Usage: INACTIVE")
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_token_budget_panel")

#endregion: Diagnostics & Analytics

#region: Logging

def render_log_management(app: App) -> None:
 """Renders the Log Management window. Enables browsing, starring (whitelisting), and loading
 of prior log sessions from the log registry. Provides one-click prune and refresh actions.

 SSDL: `[B:refresh | load | prune] -> [I:sessions_table] => [B:load | star/unstar per row]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Log Management                                     [x]  |
   | [Refresh Registry]  [Load Log]  [Force Prune Logs]      |
   | +------------+------------------+-----+--------+------+ |
   | | Session ID | Start Time       | Star| Size KB| Msgs | |
   | +------------+------------------+-----+--------+------+ |
   | | 20260612_2 | 2026-06-12 22:00 | YES | 120    | 84   | |
   | |            |                  |     | [Load] [Unstar] |
   | | 20260611_1 | 2026-06-11 18:30 | NO  | 340    | 210  | |
   | |            |                  |     | [Load] [Star]   |
   | +------------+------------------+-----+--------+------+ |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_log_management")
 with imscope.window("Log Management", app.show_windows["Log Management"]) as (exp, opened):
  app.show_windows["Log Management"] = bool(opened)
  
  if app._log_registry is None:
   from src import log_registry
   app._log_registry = log_registry.LogRegistry(str(paths.get_logs_dir() / "log_registry.toml"))
   app._log_registry.load_registry()
   
  if exp:
   if imgui.button("Refresh Registry"):
    if app._log_registry is not None: app._log_registry.load_registry()
   imgui.same_line()
   if imgui.button("Load Log"): app.cb_load_prior_log()
   imgui.same_line()
   if imgui.button("Force Prune Logs"): app.controller.event_queue.put("gui_task", {"action": "click", "item": "btn_prune_logs"})
   
   registry = app._log_registry
   sessions = registry.data
   if imgui.begin_table("sessions_table", 7, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable):
    imgui.table_setup_column("Session ID")
    imgui.table_setup_column("Start Time")
    imgui.table_setup_column("Star")
    imgui.table_setup_column("Reason")
    imgui.table_setup_column("Size (KB)")
    imgui.table_setup_column("Msgs")
    imgui.table_setup_column("Actions")
    imgui.table_headers_row()
    for session_id, s_data in sessions.items():
     imgui.table_next_row()
     imgui.table_next_column()
     imgui.text(session_id);                   imgui.table_next_column()
     imgui.text(s_data.get("start_time", "")); imgui.table_next_column()
     whitelisted = s_data.get("whitelisted", False)
     if whitelisted: imgui.text_colored(theme.get_color("status_warning"), "YES")
     else:           imgui.text("NO")
     imgui.table_next_column()
     metadata = s_data.get("metadata") or {}
     imgui.text(metadata.get("reason", ""));             imgui.table_next_column()
     imgui.text(str(metadata.get("size_kb", "")));       imgui.table_next_column()
     imgui.text(str(metadata.get("message_count", ""))); imgui.table_next_column()
     if imgui.button(f"Load##{session_id}"): app.cb_load_prior_log(s_data.get("path"))
     imgui.same_line()
     if whitelisted:
      if imgui.button(f"Unstar##{session_id}"):
       registry.update_session_metadata(session_id,
        message_count = int(metadata.get("message_count") or 0),
        errors        = int(metadata.get("errors") or 0),
        size_kb       = int(metadata.get("size_kb") or 0),
        whitelisted   = False,
        reason        = str(metadata.get("reason") or "")
       )
     else:
      if imgui.button(f"Star##{session_id}"):
       registry.update_session_metadata(session_id,
        message_count = int(metadata.get("message_count") or 0),
        errors        = int(metadata.get("errors") or 0),
        size_kb       = int(metadata.get("size_kb") or 0),
        whitelisted   = True,
        reason        = "Manually whitelisted"
       )
    imgui.end_table()

 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_log_management")

#endregion: Logging

#region: Project Management

def render_project_settings_hub(app: App) -> None:
 """Renders the Project Settings Hub: a two-tab container for Projects and Paths configuration.

 SSDL: `[I:tab_bar] => [I:projects_panel] | [I:paths_panel]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [Projects] [Paths]                                      |
   | +-----------------------------------------------------+ |
   | | <active tab content>                                | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+
 """
 with imscope.tab_bar('context_hub_tabs'):
  with imscope.tab_item('Projects') as (exp, _):
   if exp: render_projects_panel(app)
  with imscope.tab_item('Paths') as (exp, _):
   if exp: render_paths_panel(app)

def render_projects_panel(app: App) -> None:
 """Renders the project configuration panel. Allows setting execution mode,
 repository path, output/conductor directories, managing projects list,
 and global layout toggles (word-wrap, auto-scroll).

 SSDL: `[I:active_project] -> [B:execution_mode] -> [B:directories] -> [I:project_files] -> [B:project_actions] -> [B:toggles]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Active: my-feature-project                              |
   | Execution Mode: [native v]                              |
   | Git Directory: [C:\projects\manual_slop___] [Browse]    |
   | Output Dir:    [C:\output____________________] [Browse] |
   | Conductor Dir: [C:\projects\...\conductor___] [Browse]  |
   | Project Files                                           |
   | +-----------------------------------------------------+ |
   | | [x] my-feature * (active)   C:\proj\...             | |
   | | [x] chore-cleanup           C:\proj\...             | |
   | +-----------------------------------------------------+ |
   | [Add Project]  [New Project]  [Save All]                |
   | [ ] Word-Wrap  [ ] Auto-scroll Comms  [ ] Auto-scroll   |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_projects_panel")
 proj_name = app.project.get("project", {}).get("name", Path(app.active_project_path).stem)
 imgui.text_colored(C_IN(), f"Active: {proj_name}")
 imgui.separator()
 imgui.text("Execution Mode")
 modes       = ["native", "beads"]
 current_idx = modes.index(app.ui_project_execution_mode) if app.ui_project_execution_mode in modes else 0
 ch, new_idx = imgui.combo("##exec_mode", current_idx, modes)
 if ch: app.ui_project_execution_mode = modes[new_idx]
 imgui.separator()
 imgui.text("Git Directory")
 ch, app.ui_project_git_dir = imgui.input_text("##git_dir", app.ui_project_git_dir)
 imgui.same_line()
 if imgui.button("Browse##git"):
  r = hide_tk_root()
  d = filedialog.askdirectory(title="Select Git Directory")
  r.destroy()
  if d: app.ui_project_git_dir = d
 imgui.separator()
 imgui.text("Output Dir")
 ch, app.ui_output_dir = imgui.input_text("##out_dir", app.ui_output_dir)
 imgui.same_line()
 if imgui.button("Browse##out"):
  r = hide_tk_root()
  d = filedialog.askdirectory(title="Select Output Dir")
  r.destroy()
  if d: app.ui_output_dir = d
 imgui.separator()
 imgui.text("Conductor Directory")
 ch, app.ui_project_conductor_dir = imgui.input_text("##cond_dir", app.ui_project_conductor_dir)
 imgui.same_line()
 if imgui.button("Browse##cond"):
  r = hide_tk_root()
  d = filedialog.askdirectory(title="Select Conductor Directory")
  r.destroy()
  if d: app.ui_project_conductor_dir = d
 imgui.separator()
 imgui.text("Project Files")
 imgui.begin_child("proj_files", imgui.ImVec2(0, 150), True)
 for i, pp in enumerate(app.project_paths):
  is_active = (pp == app.active_project_path)
  if imgui.button(f"x##p{i}"):
   removed = app.project_paths.pop(i)
   if removed == app.active_project_path and app.project_paths: app._switch_project(app.project_paths[0])
   break
  imgui.same_line()
  marker = " *" if is_active else ""
  if is_active:                                       imgui.push_style_color(imgui.Col_.text, C_IN())
  if imgui.button(f"{Path(pp).stem}{marker}##ps{i}"): app._switch_project(pp)
  if is_active:                                       imgui.pop_style_color()
  imgui.same_line()
  imgui.text_colored(C_LBL(), pp)
 imgui.end_child()
 if imgui.button("Add Project"):
  r = hide_tk_root()
  p = filedialog.askopenfilename(
   title     = "Select Project .toml",
   filetypes = [("TOML", "*.toml"), ("All", "*.*")],
  )
  r.destroy()
  if p and p not in app.project_paths: app.project_paths.append(p)
 imgui.same_line()
 if imgui.button("New Project"):
  r = hide_tk_root()
  p = filedialog.asksaveasfilename(title="Create New Project .toml", defaultextension=".toml", filetypes=[("TOML", "*.toml"), ("All", "*.*")])
  r.destroy()
  if p:
   name = Path(p).stem
   proj = project_manager.default_project(name)
   project_manager.save_project(proj, p)
   if p not in app.project_paths: app.project_paths.append(p)
   app._switch_project(p)
 imgui.same_line()
 if imgui.button("Save All"):
  app._flush_to_project()
  app._flush_to_config()
  app.save_config()
  app.ai_status = "config saved"
 ch, app.ui_word_wrap              = imgui.checkbox("Word-Wrap (Read-only panels)", app.ui_word_wrap)
 ch, app.ui_auto_scroll_comms      = imgui.checkbox("Auto-scroll Comms History", app.ui_auto_scroll_comms)
 ch, app.ui_auto_scroll_tool_calls = imgui.checkbox("Auto-scroll Tool History", app.ui_auto_scroll_tool_calls)
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_projects_panel")

def render_paths_panel(app: App) -> None:
 """Renders the System Path Configuration panel. Shows source-tagged logs and scripts
 directory fields, each with a Browse button, and Apply / Reset buttons.

 SSDL: `[I:path_fields] => [B:apply_reset]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | System Path Configuration                               |
   | Logs Directory (Source: env)                            |
   | [C:\projects\manual_slop\logs___________] [Browse]      |
   | Scripts Directory (Source: default)                     |
   | [C:\projects\manual_slop\scripts________] [Browse]      |
   | [Apply]  [Reset]                                        |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_paths_panel")
 path_info = paths.get_full_path_info()
 
 imgui.text_colored(C_IN(), "System Path Configuration")
 imgui.separator()

 def render_path_field(label: str, attr: str, key: str, tooltip: str):
  info = path_info.get(key, {'source': 'unknown'})
  imgui.text(label)
  if imgui.is_item_hovered(): imgui.set_tooltip(tooltip)
  imgui.same_line()
  imgui.text_disabled(f"(Source: {info['source']})")

  val              = getattr(app, attr)
  changed, new_val = imgui.input_text(f"##{key}", val)
  if imgui.is_item_hovered(): imgui.set_tooltip(tooltip)
  if changed:                 setattr(app, attr, new_val)
  imgui.same_line()
  if imgui.button(f"Browse##{key}"):
   r = hide_tk_root()
   d = filedialog.askdirectory(title=f"Select {label}")
   r.destroy()
   if d: setattr(app, attr, d)

 render_path_field("Logs Directory", "ui_logs_dir", "logs_dir", "Directory where session JSON-L logs and artifacts are stored.")
 render_path_field("Scripts Directory", "ui_scripts_dir", "scripts_dir", "Directory for AI-generated PowerShell scripts.")
 
 imgui.separator()
 if imgui.button("Apply", imgui.ImVec2(120, 0)): app._save_paths()
 imgui.same_line()
 if imgui.button("Reset", imgui.ImVec2(120, 0)):
  app.init_state()
  app.ai_status = "paths reset to defaults"

 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_paths_panel")

#endregion: Project Management

#region: AI Settings

def render_ai_settings_hub(app: App) -> None:
 """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.

 SSDL: `[I:persona_selector] -> [B:provider_header] -> [B:system_prompts_header] -> [B:rag_header] -> [I:agent_tools]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [Persona Selector sub-panel]                            |
   | > Provider & Model  (collapsible)                       |
   |   [Provider sub-panel content]                          |
   | > System Prompts    (collapsible)                       |
   |   [System Prompts sub-panel content]                    |
   | > RAG Settings      (collapsible)                       |
   |   [RAG sub-panel content]                               |
   | [Agent Tools sub-panel]                                 |
   +---------------------------------------------------------+
 """
 render_persona_selector_panel(app)
 if imgui.collapsing_header("Provider & Model"): render_provider_panel(app)
 if imgui.collapsing_header("System Prompts"):   render_system_prompts_panel(app)
 if imgui.collapsing_header("RAG Settings"):     render_rag_panel(app)
 render_agent_tools_panel(app)

def render_rag_panel(app: App) -> None:
 """Renders RAG configuration panel. Exposes enable toggle, vector-store and embedding
 provider selectors, chunk size/overlap inputs, RAG status label, and a Rebuild Index button.

 SSDL: `[B:rag_switch] -> [B:combo_selectors] -> [B:chunking_inputs] => [B:rebuild_index]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [x] Enable RAG                                          |
   | Vector Store Provider: [chroma v]                       |
   | Embedding Provider: [gemini v]                          |
   | Chunk Size:   [512]                                     |
   | Chunk Overlap: [64]                                     |
   | Status: indexing...                                     |
   | [Rebuild Index]                                         |
   +---------------------------------------------------------+
 """
 conf = app.controller.rag_config
 if not conf: return
 ch, conf.enabled = imgui.checkbox("Enable RAG", conf.enabled)
 
 imgui.text("Vector Store Provider")
 providers = ['chroma', 'qdrant', 'mock']
 #NOTE(Ed): Exception(Thirdparty)
 try:
  idx = providers.index(conf.vector_store.provider)
 except (ValueError, AttributeError):
  idx = 0
 ch2, next_idx = imgui.combo("##rag_provider", idx, providers)
 if ch2: conf.vector_store.provider = providers[next_idx]
  
 imgui.text("Embedding Provider")
 emb_providers = ['gemini', 'local']
 #NOTE(Ed): Exception(Thirdparty)
 try:
  idx_e = emb_providers.index(conf.embedding_provider)
 except (ValueError, AttributeError):
  idx_e = 0
 ch3, next_idx_e = imgui.combo("##rag_emb_provider", idx_e, emb_providers)
 if ch3: conf.embedding_provider = emb_providers[next_idx_e]
  
 imgui.text("Chunk Size")
 imgui.set_next_item_width(150)
 ch4, conf.chunk_size = imgui.input_int("##rag_chunk_size", conf.chunk_size)
 imgui.text("Chunk Overlap")
 imgui.set_next_item_width(150)
 ch5, conf.chunk_overlap = imgui.input_int("##rag_chunk_overlap", conf.chunk_overlap)
 
 imgui.separator()
 imgui.text(f"Status: {app.controller.rag_status}")
 
 if imgui.button("Rebuild Index"): app.controller.event_queue.put('click', 'btn_rebuild_rag_index')

def render_system_prompts_panel(app: App) -> None:
 """Renders the System Prompts panel. Exposes global preset selector + multiline edit,
 base prompt toggle (default vs custom) with diff + reset, and project-level preset
 selector + multiline edit.

 SSDL: `[B:global_preset_combo] -> [I:global_text] -> [B:base_prompt_header] -> [I:base_text] -> [B:project_preset_combo] -> [I:project_text]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Global System Prompt (all projects)                     |
   | [coding-assistant v]  [Manage Presets]                  |
   | +-----------------------------------------------------+ |
   | | You are a coding assistant...                       | |
   | +-----------------------------------------------------+ |
   | [x] Use Default Base System Prompt                      |
   | [Reset to Default] [Show Diff] (?)                      |
   | > Base System Prompt (foundational instructions)        |
   |   [read-only or editable text area]                     |
   | Project System Prompt                                   |
   | [feature-mode v]  [Manage Presets]                      |
   | +-----------------------------------------------------+ |
   | | Focus on the rendering pipeline refactor...         | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+
 """
 imgui.text("Global System Prompt (all projects)")
 preset_names = sorted(app.controller.presets.keys())
 current_global = app.controller.ui_global_preset_name or "Select Preset..."
 imgui.set_next_item_width(200)
 if imgui.begin_combo("##global_preset", current_global):
  for name in preset_names:
   is_sel = (name == current_global)
   if imgui.selectable(name, is_sel)[0]: app.controller._apply_preset(name, "global")
   if is_sel: imgui.set_item_default_focus()
  imgui.end_combo()
 imgui.same_line(0, 8)
 if imgui.button("Manage Presets##global"): app.show_preset_manager_window = True
 imgui.set_item_tooltip("Open preset management modal")
 ch, app.ui_global_system_prompt = imgui.input_text_multiline("##gsp", app.ui_global_system_prompt, imgui.ImVec2(-1, 100))
 imgui.separator()
 _, app.ui_use_default_base_prompt = imgui.checkbox("Use Default Base System Prompt", app.ui_use_default_base_prompt)
 imgui.same_line()
 if imgui.button("Reset to Default##btn_reset_base_prompt"): app.controller._cb_reset_base_prompt()
 imgui.same_line()
 if imgui.button("Show Diff##btn_show_base_prompt_diff"): app.controller._cb_show_base_prompt_diff()
 imgui.set_item_tooltip("Compare current base prompt with the default.")
 
 imgui.same_line()
 imgui.text_disabled("(?)")
 imgui.set_item_tooltip("The Base System Prompt contains foundational instructions for the AI, including its role as a coding assistant and safety guidelines. You can override it here if needed.")

 header_flags = imgui.TreeNodeFlags_.default_open if not app.ui_use_default_base_prompt else 0
 if imgui.collapsing_header("Base System Prompt (foundational instructions)", header_flags):
  if app.ui_use_default_base_prompt:
   imgui.begin_disabled()
   imgui.input_text_multiline("##base_prompt_def", ai_client._SYSTEM_PROMPT, imgui.ImVec2(-1, 100), imgui.InputTextFlags_.read_only)
   imgui.end_disabled()
   imgui.text_disabled(f"Characters: {len(ai_client._SYSTEM_PROMPT)}")
  else:
   ch, app.ui_base_system_prompt = imgui.input_text_multiline("##base_prompt", app.ui_base_system_prompt, imgui.ImVec2(-1, 150))
   imgui.text_disabled(f"Characters: {len(app.ui_base_system_prompt)}")
 imgui.separator()
 imgui.text("Project System Prompt")
 current_project = app.controller.ui_project_preset_name or "Select Preset..."
 imgui.set_next_item_width(200)
 if imgui.begin_combo("##project_preset", current_project):
  for name in preset_names:
   is_sel = (name == current_project)
   if imgui.selectable(name, is_sel)[0]: app.controller._apply_preset(name, "project")
   if is_sel:                            imgui.set_item_default_focus()
  imgui.end_combo()
 imgui.same_line(0, 8)
 if imgui.button("Manage Presets##project"): app.show_preset_manager_window = True
 imgui.set_item_tooltip("Open preset management modal")
 ch, app.ui_project_system_prompt = imgui.input_text_multiline("##psp", app.ui_project_system_prompt, imgui.ImVec2(-1, 100))

def render_agent_tools_panel(app: App) -> None:
 """Renders the Active Tool Presets & Biases collapsible section. Shows a preset combo,
 a Manage Presets button, and a Bias Profile combo. Displays a disabled notice when
 tool calling is unsupported by the active provider/model.

 SSDL: `[B:collapsing_header] => [B:preset_combo] -> [B:manage_presets] -> [B:bias_combo]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | > Active Tool Presets & Biases (open by default)        |
   |   Tool Preset: [mcp-only v]  [Manage Presets]           |
   |   Bias Profile: [None v]                                |
   +---------------------------------------------------------+
 """
 caps = app._get_active_capabilities()
 if not caps.tool_calling:
  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})")
  return
 if imgui.collapsing_header("Active Tool Presets & Biases", imgui.TreeNodeFlags_.default_open):
  imgui.text("Tool Preset")
  presets      = app.controller.tool_presets
  preset_names = [""] + sorted(list(presets.keys()))
  
  active = getattr(app, "ui_active_tool_preset", "")
  if active is None: active = ""
  #NOTE(Ed): Exception(Thirdparty)
  try:
   idx = preset_names.index(active)
  except ValueError:
   idx = 0
  
  ch, new_idx = imgui.combo("##tool_preset_select", idx, preset_names)
  if ch: app.ui_active_tool_preset = preset_names[new_idx]
  
  imgui.same_line()
  if imgui.button("Manage Presets##tools"): app.show_tool_preset_manager_window = True
  if imgui.is_item_hovered():               imgui.set_tooltip("Configure tool availability and default modes.")
  
  imgui.dummy(imgui.ImVec2(0, 4))
  imgui.text("Bias Profile")
  if imgui.begin_combo("##bias", getattr(app, 'ui_active_bias_profile', "") or "None"):
   if imgui.selectable("None", not getattr(app, 'ui_active_bias_profile', ""))[0]:
    app.ui_active_bias_profile = ""
    ai_client.set_bias_profile(None)
   for bname in sorted(app.controller.bias_profiles.keys()):
    if not bname: continue
    if imgui.selectable(bname, bname == getattr(app, 'ui_active_bias_profile', ""))[0]:
     app.ui_active_bias_profile = bname
     ai_client.set_bias_profile(bname)
   imgui.end_combo()
  
  imgui.dummy(imgui.ImVec2(0, 8))
  cat_options = ["All"] + sorted(list(models.DEFAULT_TOOL_CATEGORIES.keys()))
  #NOTE(Ed): Exception(Thirdparty)
  try:
   f_idx = cat_options.index(app.ui_tool_filter_category)
  except ValueError:
   f_idx = 0
   
  imgui.set_next_item_width(200)
  ch_cat, next_f_idx = imgui.combo("Filter Category##agent", f_idx, cat_options)
  if ch_cat: app.ui_tool_filter_category = cat_options[next_f_idx]
  
  imgui.dummy(imgui.ImVec2(0, 8))
  active_name = app.ui_active_tool_preset
  if active_name and active_name in presets:
   preset = presets[active_name]
   for cat_name, tools in preset.categories.items():
    if app.ui_tool_filter_category != "All" and app.ui_tool_filter_category != cat_name: continue
    if imgui.tree_node(cat_name):
     for tool in tools:
      if   tool.weight >= 5: imgui.text_colored(theme.get_color("status_error"),   "[HIGH]");   imgui.same_line()
      elif tool.weight == 4: imgui.text_colored(theme.get_color("status_warning"), "[PREF]");   imgui.same_line()
      elif tool.weight == 2: imgui.text_colored(theme.get_color("status_warning"), "[REJECT]"); imgui.same_line()
      elif tool.weight <= 1: imgui.text_colored(theme.get_color("text_disabled"),  "[LOW]");    imgui.same_line()
      
      imgui.text(tool.name); imgui.same_line(180)
      
      mode = tool.approval
      if imgui.radio_button(f"Auto##{cat_name}_{tool.name}", mode == "auto"): tool.approval = "auto"
      imgui.same_line()
      if imgui.radio_button(f"Ask##{cat_name}_{tool.name}", mode == "ask"): tool.approval = "ask"
     imgui.tree_pop()

def render_provider_panel(app: App) -> None:
 r"""Renders the LLM provider configuration panel. Allows selection of API providers,
 active models, hyper-parameters (temperature, max tokens, Top-P), history limits, and Gemini CLI binaries.
 
 SSDL: `[I:providers] -> [B:provider_combo] -> [B:model_listbox] -> [B:parameters_sliders] => [B:cli_path_browse]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Provider                                                |
   | [gemini v]  [Local]                                     |
   | [V2 Badges: JSON/STRUCT/SYS/TOOL/C_IN/C_OUT/C_TRM...]   |
   | ---                                                     |
   | Model                                                   |
   | +-----------------------------------------------------+ |
   | | gemini-1.5-pro                                      | |
   | | gemini-1.5-flash *                                  | |
   | +-----------------------------------------------------+ |
   | ---                                                     |
   | Parameters                                              |
   | [=======                     ] 0.70 Temp: |0.70|        |
   | [===========                 ] 4096 Top-P: [1.00]       |
   | [======                      ] 8192 MaxTok: [8192]      |
   | History Truncation Limit:    |900000|                   |
   | (If gemini_cli active):                                 |
   | ---                                                     |
   | Gemini CLI                                              |
   | Session ID: cba123                                      |
   | [Reset CLI Session]                                     |
   | Binary Path:                                            |
   | [C:\tools\gemini.exe_______________________] [Browse]   |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_provider_panel")
 imgui.text("Provider")
 if imgui.begin_combo("##prov", app.current_provider):
  for p in ai_client.PROVIDERS:
   if imgui.selectable(p, p == app.current_provider)[0]:
    app.current_provider = p
  imgui.end_combo()
 caps = app._get_active_capabilities()
 if caps.local:
  imgui.same_line()
  imgui.text_colored(theme.get_color("status_success"), " [Local]")
  if imgui.is_item_hovered():
   base_url: str = ""
   if app.current_provider == "llama":
    base_url = getattr(ai_client, "_llama_base_url", "")
   imgui.set_tooltip(f"Local backend: {base_url or 'unknown'}" if base_url else "Local backend")
  
 _render_v2_capability_badges(caps)

 imgui.separator()
 imgui.text("Model")
 if imgui.begin_list_box("##models", imgui.ImVec2(-1, 120)):
  for m in app.available_models:
   if imgui.selectable(m, m == app.current_model)[0]:
    app.current_model = m
  imgui.end_list_box()
 imgui.separator()
 imgui.text("Parameters")
 # Temperature
 imgui.push_id("temp")
 imgui.set_next_item_width(imgui.get_content_region_avail().x * 0.6)
 _, app.temperature = imgui.slider_float("##slider", app.temperature, 0.0, 2.0, "%.2f")
 imgui.same_line()
 imgui.set_next_item_width(-1)
 _, app.temperature = imgui.input_float("Temp", app.temperature, 0.0, 0.0, "%.2f")
 imgui.pop_id()

 # Max Tokens
 caps = app._get_active_capabilities()
 max_tokens_cap = max(1, caps.context_window)
 imgui.push_id("max_tokens")
 imgui.set_next_item_width(imgui.get_content_region_avail().x * 0.6)
 _, app.max_tokens = imgui.slider_int("##slider", app.max_tokens, 1, max_tokens_cap)
 imgui.same_line()
 imgui.set_next_item_width(-1)
 _, app.top_p = imgui.input_float("Top-P", app.top_p, 0.0, 0.0, "%.2f")
 imgui.pop_id()

 # Max Tokens
 imgui.push_id("max_tokens")
 imgui.set_next_item_width(imgui.get_content_region_avail().x * 0.6)
 _, app.max_tokens = imgui.slider_int("##slider", app.max_tokens, 1, 32768)
 imgui.same_line()
 imgui.set_next_item_width(-1)
 _, app.max_tokens = imgui.input_int("MaxTok", app.max_tokens)
 imgui.pop_id()

 ch, app.history_trunc_limit = imgui.input_int("History Truncation Limit", app.history_trunc_limit, 1024)

 if app.current_provider == "gemini_cli":
  imgui.separator()
  imgui.text("Gemini CLI")
  sid = "None"
  if hasattr(ai_client, "_gemini_cli_adapter") and ai_client._gemini_cli_adapter: sid = ai_client._gemini_cli_adapter.session_id or "None"
  imgui.text("Session ID:"); imgui.same_line(); render_selectable_label(app, "gemini_cli_sid", sid, width=200)
  if imgui.button("Reset CLI Session"): ai_client.reset_session()
  imgui.text("Binary Path")
  ch, app.ui_gemini_cli_path = imgui.input_text("##gcli_path", app.ui_gemini_cli_path)
  imgui.same_line()
  if imgui.button("Browse##gcli"):
   r = hide_tk_root()
   p = filedialog.askopenfilename(title="Select gemini CLI binary")
   r.destroy()
   if p: app.ui_gemini_cli_path = p
  if ch:
   if hasattr(ai_client, "_gemini_cli_adapter") and ai_client._gemini_cli_adapter:
    ai_client._gemini_cli_adapter.binary_path = app.ui_gemini_cli_path
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_provider_panel")

def render_persona_selector_panel(app: App) -> None:
 """Renders the Persona Selection panel. Allows selecting profiles, editing persona definitions,
 overriding system parameters (models, presets, prompts, bias profiles) and loading preset contexts.

 SSDL: `[I:personas] -> [B:persona_combo] => [B:manage_personas]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Persona                                                 |
   | [assistant v]  [Manage Personas]                        |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_persona_selector_panel")
 imgui.text("Persona")
 if not hasattr(app, 'ui_active_persona'): app.ui_active_persona = ""
 personas = getattr(app.controller, 'personas', {})
 if imgui.begin_combo("##persona", app.ui_active_persona or "None"):
  if imgui.selectable("None", not app.ui_active_persona)[0]: app.ui_active_persona = ""
  for pname in sorted(personas.keys()):
   if not pname: continue
   if imgui.selectable(pname, pname == app.ui_active_persona)[0]:
    app.ui_active_persona = pname
    if pname in personas:
     persona = personas[pname]
     app._editing_persona_name                  = persona.name
     app._editing_persona_system_prompt         = persona.system_prompt                        or ""
     app._editing_persona_tool_preset_id        = persona.tool_preset                          or ""
     app._editing_persona_bias_profile_id       = persona.bias_profile                         or ""
     app._editing_persona_context_preset_id     = getattr(persona, 'context_preset', '')       or ""
     app._editing_persona_aggregation_strategy  = getattr(persona, 'aggregation_strategy', '') or ""
     app._editing_persona_preferred_models_list = copy.deepcopy(persona.preferred_models) if persona.preferred_models else []
     app._editing_persona_is_new                = False
     
     # Apply persona to current state immediately
     if persona.preferred_models and len(persona.preferred_models) > 0:
      first_model = persona.preferred_models[0]
      if first_model.get("provider"): app.current_provider = first_model.get("provider")
      if first_model.get("model"):    app.current_model    = first_model.get("model")
      if first_model.get("temperature") is not None:
       ai_client.temperature = first_model.get("temperature")
       app.temperature       = first_model.get("temperature")
      if first_model.get("max_output_tokens"):
       ai_client.max_output_tokens = first_model.get("max_output_tokens")
       app.max_tokens              = first_model.get("max_output_tokens")
      if first_model.get("history_trunc_limit"): 
       app.history_trunc_limit = first_model.get("history_trunc_limit")
     
     if persona.system_prompt: app.ui_project_system_prompt = persona.system_prompt
     if persona.tool_preset:
      app.ui_active_tool_preset = persona.tool_preset
      ai_client.set_tool_preset(persona.tool_preset)
     if persona.bias_profile:
      app.ui_active_bias_profile = persona.bias_profile
      ai_client.set_bias_profile(persona.bias_profile)
     if getattr(persona, 'context_preset', None):
      app.ui_active_context_preset = persona.context_preset
      #TODO(Ed): Exception(Review)
      try:
       app.load_context_preset(persona.context_preset)
      except KeyError as e:
       app.ai_status = f"persona context preset missing: {e}"
       app.ui_active_context_preset = ""
  imgui.end_combo()
 imgui.same_line()
 if imgui.button("Manage Personas"):
  app.show_persona_editor_window = True
  if app.ui_active_persona and app.ui_active_persona in personas:
   persona = personas[app.ui_active_persona]
   app._editing_persona_name                  = persona.name
   app._editing_persona_system_prompt         = persona.system_prompt                        or ""
   app._editing_persona_tool_preset_id        = persona.tool_preset                          or ""
   app._editing_persona_bias_profile_id       = persona.bias_profile                         or ""
   app._editing_persona_context_preset_id     = getattr(persona, 'context_preset', '')       or ""
   app._editing_persona_aggregation_strategy  = getattr(persona, 'aggregation_strategy', '') or ""
   app._editing_persona_preferred_models_list = copy.deepcopy(persona.preferred_models) if persona.preferred_models else []
   app._editing_persona_scope                 = app.controller.persona_manager.get_persona_scope(persona.name)
   app._editing_persona_is_new                = False
  else:
   app._editing_persona_name                  = ""
   app._editing_persona_system_prompt         = ""
   app._editing_persona_tool_preset_id        = ""
   app._editing_persona_bias_profile_id       = ""
   app._editing_persona_context_preset_id     = ""
   app._editing_persona_aggregation_strategy  = ""
   app._editing_persona_preferred_models_list = [{
    "provider":            app.current_provider,
    "model":               app.current_model,
    "temperature":         getattr(app, "temperature",         0.7),
    "max_output_tokens":   getattr(app, "max_tokens",          4096),
    "history_trunc_limit": getattr(app, "history_trunc_limit", 900000)
   }]
   app._editing_persona_scope  = "project"
   app._editing_persona_is_new = True
 imgui.separator()
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_persona_selector_panel")

def render_base_prompt_diff_modal(app: App) -> None:
 """Renders a popup modal showing a unified diff between the default system prompt
 and the current custom base prompt.

 SSDL: `[I:unified_diff] -> [I:diff_view_box] => [B:close]`
 """
 if not getattr(app.controller, "_show_base_prompt_diff_modal", False):
  return
 imgui.open_popup("Base Prompt Diff")
 if imgui.begin_popup_modal("Base Prompt Diff", True, imgui.WindowFlags_.always_auto_resize)[0]:
  imgui.text_colored(C_IN(), "Difference between Default and Custom Base System Prompt")
  imgui.separator()
  
  default_lines = ai_client._SYSTEM_PROMPT.splitlines(keepends=True)
  custom_lines  = app.ui_base_system_prompt.splitlines(keepends=True)
  diff          = list(difflib.unified_diff(default_lines, custom_lines, fromfile='Default', tofile='Custom'))
  
  if not diff:
   imgui.text("No differences found.")
  else:
   imgui.begin_child("base_prompt_diff_scroll", imgui.ImVec2(800, 500), True)
   for line in diff:
    if   line.startswith("+++") or line.startswith("---") or line.startswith("@@"): imgui.text_colored(theme.get_color("diff_header"), line.rstrip())
    elif line.startswith("+"):                                                      imgui.text_colored(theme.get_color("diff_added"), line.rstrip())
    elif line.startswith("-"):                                                      imgui.text_colored(theme.get_color("diff_removed"), line.rstrip())
    else:                                                                           imgui.text(line.rstrip())
   imgui.end_child()
  
  imgui.separator()
  if imgui.button("Close", imgui.ImVec2(120, 0)):
   app.controller._show_base_prompt_diff_modal = False
   imgui.close_current_popup()
  imgui.end_popup()

def render_save_preset_modal(app: App) -> None:
 """Renders the popup modal for saving the current ImGui window layout preset.

 SSDL: `[I:preset_inputs] => [B:save_cancel]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Save Layout Preset                                  [X] |
   +---------------------------------------------------------+
   | Preset Name:                                            |
   | |                                                     | |
   | [Save]  [Cancel]                                        |
   +---------------------------------------------------------+
 """
 if not app._show_save_preset_modal: return
 imgui.open_popup("Save Layout Preset")
 with imscope.popup_modal("Save Layout Preset", True, imgui.WindowFlags_.always_auto_resize) as (opened, _):
  if opened:
   imgui.text("Preset Name:")
   _, app._new_preset_name = imgui.input_text("##preset_name", app._new_preset_name)
   if imgui.button("Save", imgui.ImVec2(120, 0)):
    if app._new_preset_name.strip():
     ini_data = imgui.save_ini_settings_to_memory()
     app.layout_presets[app._new_preset_name.strip()] = {
      "ini":            ini_data,
      "multi_viewport": app.ui_multi_viewport
     }
     app.config["layout_presets"] = app.layout_presets
     app.save_config()
     app._show_save_preset_modal = False
     app._new_preset_name        = ""
    imgui.close_current_popup()
   imgui.same_line()
   if imgui.button("Cancel", imgui.ImVec2(120, 0)):
    app._show_save_preset_modal = False
    imgui.close_current_popup()

def render_preset_manager_content(app: App, is_embedded: bool = False) -> None:
 """Renders the core prompt preset manager interface including the sidebar preset selector
 and the preset prompt content text editor.

 SSDL: `[I:presets_list] -> [B:new_preset] -> [I:editor_meta] -> [I:editor_textbox] => [B:save_delete]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [New Preset]                                            |
   | ---                                                     |
   | | preset-a         | Editing Prompt Preset: preset-a    |
   | | preset-b         | ---                                |
   |                    | Name:  |preset-a|                  |
   |                    | Scope: (o) Global  ( ) Project     |
   |                    | ---                                |
   |                    | Prompt Content: [Pop out MD Preview]
   |                    | +--------------------------------+ |
   |                    | | You are a coding assistant...  | |
   |                    | +--------------------------------+ |
   | ------------------------------------------------------- |
   | [Save] [Delete] [Close]                                 |
   +---------------------------------------------------------+
 """
 avail = imgui.get_content_region_avail()
 if not hasattr(app, "_prompt_md_preview"): app._prompt_md_preview = False
 
 if imgui.begin_table("prompt_main_split", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v):
  imgui.table_setup_column("List",   imgui.TableColumnFlags_.width_fixed, 200)
  imgui.table_setup_column("Editor", imgui.TableColumnFlags_.width_stretch)
  imgui.table_next_row()
  
  # Left Sidebar
  imgui.table_next_column()
  imgui.begin_child("prompt_list_pane", imgui.ImVec2(0, 0), False)
  if True:
   if imgui.button("New Preset", imgui.ImVec2(-1, 0)):
    app._editing_preset_name          = ""
    app._editing_preset_system_prompt = ""
    app._editing_preset_scope         = "project"
    app._selected_preset_idx          = -1
   imgui.separator()
   preset_names = sorted(app.controller.presets.keys())
   for i, name in enumerate(preset_names):
    if name and imgui.selectable(f"{name}##p_{i}", app._selected_preset_idx == i)[0]:
     app._selected_preset_idx          = i
     app._editing_preset_name          = name
     p                                 = app.controller.presets[name]
     app._editing_preset_system_prompt = p.system_prompt
     app._editing_preset_scope         = app.controller.preset_manager.get_preset_scope(name)
   imgui.end_child()

  # Right Editor
  imgui.table_next_column()
  avail_r = imgui.get_content_region_avail()
  imgui.begin_child("prompt_edit_pane", imgui.ImVec2(0, avail_r.y - 45), False)
  if True:
   p_disp = app._editing_preset_name or "(New Preset)"
   imgui.text_colored(C_IN(), f"Editing Prompt Preset: {p_disp}")
   imgui.separator()
   
   if imgui.begin_table("p_meta", 2):
    imgui.table_setup_column("L", imgui.TableColumnFlags_.width_fixed, 80)
    imgui.table_setup_column("F", imgui.TableColumnFlags_.width_stretch)
    imgui.table_next_row()
    imgui.table_next_column(); imgui.text("Name:")
    imgui.table_next_column(); imgui.set_next_item_width(-1)
    _, app._editing_preset_name = imgui.input_text("##epn", app._editing_preset_name)
    imgui.table_next_row()
    imgui.table_next_column(); imgui.text("Scope:")
    imgui.table_next_column()
    if imgui.radio_button("Global##ps", app._editing_preset_scope == "global"): app._editing_preset_scope = "global"
    imgui.same_line()
    if imgui.radio_button("Project##ps", app._editing_preset_scope == "project"): app._editing_preset_scope = "project"
    imgui.end_table()
   
   imgui.dummy(imgui.ImVec2(0, 4))
   imgui.separator()
   imgui.text("Prompt Content:")
   imgui.same_line()
   if imgui.button("Pop out MD Preview"):
    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_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))
   imgui.end_child()
  
  # Footer Buttons
  imgui.separator()
  imgui.dummy(imgui.ImVec2(0, 4))
  if imgui.button("Save##p", imgui.ImVec2(100, 0)):
   if app._editing_preset_name.strip():
    app.controller._cb_save_preset(
     app._editing_preset_name.strip(),
     app._editing_preset_system_prompt,
     app._editing_preset_scope
    )
    app.ai_status = f"Saved: {app._editing_preset_name}"
  imgui.same_line()
  if imgui.button("Delete##p", imgui.ImVec2(100, 0)):
   if app._editing_preset_name:
    app.controller._cb_delete_preset(app._editing_preset_name, app._editing_preset_scope)
    app._editing_preset_name = ""
    app._selected_preset_idx = -1
  if not is_embedded:
   imgui.same_line()
   if imgui.button("Close##p", imgui.ImVec2(100, 0)): app.show_preset_manager_window = False
  imgui.end_table()

def render_preset_manager_window(app: App, is_embedded: bool = False) -> None:
 """Renders the window container for the Prompt Presets Manager.

 SSDL: `[I] -> [I:window] => [I:preset_manager_content]`

 ASCII Layout Map:
   +=================== Prompt Presets Manager =============+
   |                                                        |
   | [ Renders render_preset_manager_content here ]         |
   |                                                        |
   +========================================================+
 """
 if not app.show_preset_manager_window and not is_embedded: return
 if not is_embedded:
  imgui.set_next_window_size(imgui.ImVec2(1000, 800), imgui.Cond_.first_use_ever)
  with imscope.window("Prompt Presets Manager", app.show_preset_manager_window) as (opened, visible):
   app.show_preset_manager_window = visible
   if opened: render_preset_manager_content(app, is_embedded=is_embedded)
 else:
  render_preset_manager_content(app, is_embedded=is_embedded)

def render_tool_preset_manager_content(app: App, is_embedded: bool = False) -> None:
 """Renders the tool presets and tool capability profiles editor layout.

 SSDL: `[I:tool_presets] -> [B:new_tool_preset] -> [I:capability_toggles] -> [I:tools_list] => [B:save_delete]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [New Preset]                                            |
   | ---                                                     |
   | | preset-a         | Editing Tool Preset: preset-a      |
   | | preset-b         | ---                                |
   |                    | Name:  |preset-a|                  |
   |                    | Scope: (o) Global  ( ) Project     |
   |                    | ---                                |
   |                    | > Categories & Tools               |
   |                    |   Filter: [All v]                  |
   |                    |   v file_io                        |
   |                    |     +----------------------------+ |
   |                    |     | read_file  ( ) Off ( ) Auto (o) Ask |
   |                    |     | write_file ( ) Off ( ) Auto (o) Ask |
   |                    |     +----------------------------+ |
   |                    | > Bias Profiles                    |
   |                    |   +------------------------------+ |
   |                    |   | [New Profile] | Name: |bias| | |
   |                    |   | profile-a     | ---          | |
   |                    |   +------------------------------+ |
   | ------------------------------------------------------- |
   | [Save] [Delete] [Close]                                 |
   +---------------------------------------------------------+
 """
 avail = imgui.get_content_region_avail()
 if not hasattr(app, "_tool_split_v"):      app._tool_split_v      = 0.4
 if not hasattr(app, "_bias_split_v"):      app._bias_split_v      = 0.6
 if not hasattr(app, "_tool_list_open"):    app._tool_list_open    = True
 if not hasattr(app, "_bias_list_open"):    app._bias_list_open    = True
 if not hasattr(app, "_bias_weights_open"): app._bias_weights_open = True
 if not hasattr(app, "_bias_cats_open"):    app._bias_cats_open    = True

 if imgui.begin_table("tp_main_split", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v):
  imgui.table_setup_column("List", imgui.TableColumnFlags_.width_fixed, 200)
  imgui.table_setup_column("Editor", imgui.TableColumnFlags_.width_stretch)
  imgui.table_next_row()
  
  # Left Sidebar
  imgui.table_next_column()
  imgui.begin_child("tp_list_pane", imgui.ImVec2(0, 0), False)
  if True:
   if imgui.button("New Preset", imgui.ImVec2(-1, 0)):
    app._editing_tool_preset_name = ""; app._editing_tool_preset_categories = {cat: {} for cat in models.DEFAULT_TOOL_CATEGORIES}
    app._editing_tool_preset_scope = "project"; app._selected_tool_preset_idx = -1
   imgui.separator()
   preset_names = sorted(app.controller.tool_presets.keys())
   for i, name in enumerate(preset_names):
    if name and imgui.selectable(f"{name}##tp_{i}", app._selected_tool_preset_idx == i)[0]:
     app._selected_tool_preset_idx = i; app._editing_tool_preset_name = name
     preset = app.controller.tool_presets[name]
     app._editing_tool_preset_categories = {cat: copy.deepcopy(tools) for cat, tools in preset.categories.items()}
   imgui.end_child()

  # Right Editor
  imgui.table_next_column()
  avail_r = imgui.get_content_region_avail()
  imgui.begin_child("tp_editor_content", imgui.ImVec2(0, avail_r.y - 45), False)
  if True:
   p_name = app._editing_tool_preset_name or "(New Tool Preset)"
   imgui.text_colored(C_IN(), f"Editing Tool Preset: {p_name}"); imgui.separator()
   
   if imgui.begin_table("tp_meta", 2):
    imgui.table_setup_column("L", imgui.TableColumnFlags_.width_fixed, 80);  imgui.table_setup_column("F", imgui.TableColumnFlags_.width_stretch)
    imgui.table_next_row(); imgui.table_next_column(); imgui.text("Name:");  imgui.table_next_column(); imgui.set_next_item_width(-1); _, app._editing_tool_preset_name = imgui.input_text("##etpn", app._editing_tool_preset_name)
    imgui.table_next_row(); imgui.table_next_column(); imgui.text("Scope:"); imgui.table_next_column()
    if imgui.radio_button("Global", app._editing_tool_preset_scope == "global"): app._editing_tool_preset_scope = "global"
    imgui.same_line(); 
    if imgui.radio_button("Project", app._editing_tool_preset_scope == "project"): app._editing_tool_preset_scope = "project"
    imgui.end_table()

   rem_y = imgui.get_content_region_avail().y - 80
   if   app._tool_list_open and app._bias_list_open: h1, h2 = rem_y * app._tool_split_v, rem_y - (rem_y * app._tool_split_v) - 10
   elif app._tool_list_open:                         h1, h2 = rem_y, 0
   elif app._bias_list_open:                         h1, h2 = 0, rem_y
   else:                                             h1, h2 = 0, 0

   imgui.dummy(imgui.ImVec2(0, 4))
   opened_t = imgui.collapsing_header("Categories & Tools", imgui.TreeNodeFlags_.default_open)
   if opened_t != app._tool_list_open: app._tool_list_open = opened_t
   if app._tool_list_open:
    imgui.text("Filter:"); imgui.same_line()
    cat_opts = ["All"] + sorted(list(models.DEFAULT_TOOL_CATEGORIES.keys()))
    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]
    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]
       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()
    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))
   
   imgui.dummy(imgui.ImVec2(0, 4))
   opened_b = imgui.collapsing_header("Bias Profiles", imgui.TreeNodeFlags_.default_open)
   if opened_b != app._bias_list_open: app._bias_list_open = opened_b
   if app._bias_list_open:
    imgui.begin_child("bias_area", imgui.ImVec2(0, h2), True)
    if True:
     if imgui.begin_table("bias_split", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v):
      imgui.table_setup_column("BList", imgui.TableColumnFlags_.width_fixed, 150); imgui.table_setup_column("BEdit", imgui.TableColumnFlags_.width_stretch)
      imgui.table_next_row(); imgui.table_next_column()
      imgui.begin_child("blist_pane", imgui.ImVec2(0, 0), False)
      if True:
       if imgui.button("New Profile", imgui.ImVec2(-1, 0)):
        app._editing_bias_profile_name                 = ""
        app._editing_bias_profile_tool_weights         = {}
        app._editing_bias_profile_category_multipliers = {}
        app._selected_bias_profile_idx                 = -1
       imgui.separator(); bnames = sorted(app.bias_profiles.keys())
       for i, bname in enumerate(bnames):
        if bname and imgui.selectable(f"{bname}##b_{i}", app._selected_bias_profile_idx == i)[0]:
         app._selected_bias_profile_idx                 = i
         app._editing_bias_profile_name                 = bname; 
         prof                                           = app.bias_profiles[bname]
         app._editing_bias_profile_tool_weights         = copy.deepcopy(prof.tool_weights)
         app._editing_bias_profile_category_multipliers = copy.deepcopy(prof.category_multipliers)
       imgui.end_child()
      
      imgui.table_next_column()
      imgui.begin_child("bedit_pane", imgui.ImVec2(0, 0), False)
      if True:
       imgui.text("Name:"); imgui.same_line(); imgui.set_next_item_width(-1)
       _, app._editing_bias_profile_name = imgui.input_text("##bname", app._editing_bias_profile_name)
       rem_bias_y                        = imgui.get_content_region_avail().y - 45 
       if app._bias_weights_open and app._bias_cats_open: bh1, bh2 = rem_bias_y * app._bias_split_v, rem_bias_y - (rem_bias_y * app._bias_split_v) - 10
       elif app._bias_weights_open:                       bh1, bh2 = rem_bias_y, 0
       elif app._bias_cats_open:                          bh1, bh2 = 0, rem_bias_y
       else:                                              bh1, bh2 = 0, 0

       opened_bw = imgui.collapsing_header("Tool Weights", imgui.TreeNodeFlags_.default_open)
       if opened_bw != app._bias_weights_open: app._bias_weights_open = opened_bw
       if app._bias_weights_open:
        imgui.begin_child("btool_scroll", imgui.ImVec2(0, bh1), True)
        if True:
         for cat_name, default_tools in models.DEFAULT_TOOL_CATEGORIES.items():
          if imgui.tree_node(f"{cat_name}##b_list"):
           if imgui.begin_table(f"bt_{cat_name}", 2):
            imgui.table_setup_column("T", imgui.TableColumnFlags_.width_fixed, 220)
            imgui.table_setup_column("W", imgui.TableColumnFlags_.width_stretch)
            for tn in default_tools:
             imgui.table_next_row(); imgui.table_next_column()
             imgui.text(tn);         imgui.table_next_column()
             curr_w    = app._editing_bias_profile_tool_weights.get(tn, 3); imgui.set_next_item_width(-1)
             ch_w, n_w = imgui.slider_int(f"##bw_{tn}", curr_w, 1, 10); 
             if ch_w: app._editing_bias_profile_tool_weights[tn] = n_w
            imgui.end_table()
           imgui.tree_pop()
         imgui.end_child()
         if app._bias_cats_open:
          imgui.button("###bias_splitter", imgui.ImVec2(-1, 4))
          if imgui.is_item_active(): 
           app._bias_split_v = max(0.1, min(0.9, app._bias_split_v + imgui.get_io().mouse_delta.y / rem_bias_y))
       
       opened_bc = imgui.collapsing_header("Category Multipliers", imgui.TreeNodeFlags_.default_open)
       if opened_bc != app._bias_cats_open: app._bias_cats_open = opened_bc
       if app._bias_cats_open:
        imgui.begin_child("bcat_scroll", imgui.ImVec2(0, bh2), True)
        if True:
         if imgui.begin_table("bcats", 2):
          imgui.table_setup_column("C", imgui.TableColumnFlags_.width_fixed, 220)
          imgui.table_setup_column("M", imgui.TableColumnFlags_.width_stretch)
          for cn in sorted(models.DEFAULT_TOOL_CATEGORIES.keys()):
           imgui.table_next_row(); imgui.table_next_column()
           imgui.text(cn);         imgui.table_next_column()
           curr_m    = app._editing_bias_profile_category_multipliers.get(cn, 1.0); imgui.set_next_item_width(-1)
           ch_m, n_m = imgui.slider_float(f"##cm_{cn}", curr_m, 0.1, 5.0, "%.1fx"); 
           if ch_m: app._editing_bias_profile_category_multipliers[cn] = n_m
          imgui.end_table()
         imgui.end_child()
       
       if imgui.button("Save Profile", imgui.ImVec2(-1, 0)):
        try:
         p = models.BiasProfile(name=app._editing_bias_profile_name, tool_weights=app._editing_bias_profile_tool_weights, category_multipliers=app._editing_bias_profile_category_multipliers)
         app.controller._cb_save_bias_profile(p, app._editing_tool_preset_scope); app.ai_status = f"Saved: {p.name}"
        except Exception as e: app.ai_status = f"Error: {e}"
       imgui.end_child()
      imgui.end_table()
     imgui.end_child()
   imgui.end_child()

  # --- Footer Buttons ---
  imgui.separator()
  if imgui.button("Save##tp", imgui.ImVec2(100, 0)):
   if app._editing_tool_preset_name.strip(): 
    app.controller._cb_save_tool_preset(app._editing_tool_preset_name.strip(), app._editing_tool_preset_categories, app._editing_tool_preset_scope)
    app.ai_status = f"Saved: {app._editing_tool_preset_name}"
  imgui.same_line()
  if imgui.button("Delete##tp", imgui.ImVec2(100, 0)):
   if app._editing_tool_preset_name: 
    app.controller._cb_delete_tool_preset(app._editing_tool_preset_name, app._editing_tool_preset_scope)
    app._editing_tool_preset_name = ""
    app._selected_tool_preset_idx = -1
  imgui.same_line()
  if not is_embedded:
   if imgui.button("Close##tp", imgui.ImVec2(100, 0)): 
    app.show_tool_preset_manager_window = False
  imgui.end_table()

def render_tool_preset_manager_window(app: App, is_embedded: bool = False) -> None:
 """Renders the window container for the Tool Preset Manager.

 SSDL: `[I] -> [I:window] => [I:tool_preset_manager_content]`

 ASCII Layout Map:
   +==================== Tool Preset Manager ===============+
   |                                                        |
   | [ Renders render_tool_preset_manager_content here ]    |
   |                                                        |
   +========================================================+
 """
 if not app.show_tool_preset_manager_window and not is_embedded: return
 if not is_embedded:
  imgui.set_next_window_size(imgui.ImVec2(1000, 800), imgui.Cond_.first_use_ever)
  with imscope.window("Tool Preset Manager", app.show_tool_preset_manager_window) as (opened, visible):
   app.show_tool_preset_manager_window = visible
   if opened: render_tool_preset_manager_content(app, is_embedded=is_embedded)
 else:
  render_tool_preset_manager_content(app, is_embedded=is_embedded)

def render_persona_editor_window(app: App, is_embedded: bool = False) -> None:
 """Renders the Persona Editor window, allowing creating, deleting, and modifying persona settings
 (prompts, preferred models list, tool presets, aggregation strategy, etc).

 SSDL: `[I:personas_list] -> [B:new_persona] -> [I:settings_editor] -> [I:models_list] -> [I:system_prompt_box] => [B:save_delete]`

 ASCII Layout Map:
   +========================== Persona Editor ========================+
   | [New Persona]                                                    |
   | ---                                                              |
   | | assistant        | Editing Persona: assistant                  |
   | | researcher       | ---                                         |
   |                    | Name:  |assistant|                          |
   |                    | Scope: (o) Global  ( ) Project              |
   |                    | ---                                         |
   |                    | > Preferred Models                          |
   |                    |   [+] 1. gemini - flash (T:0.7, P:1.0, M:0)  |
   |                    |   [-] 2. anthropic - claude-3-opus          |
   |                    |       Provider: [anthropic v]               |
   |                    |       Model:    [claude-3-opus v]           |
   |                    |       Temp: [0.7]   Max Tokens: [4096]      |
   |                    |   [Add Model]                               |
   |                    | > System Prompt                             |
   |                    |   +---------------------------------------+ |
   |                    |   | You are a helpful assistant...        | |
   |                    |   +---------------------------------------+ |
   | ---------------------------------------------------------------- |
   | [Save] [Delete] [Close]                                          |
   +==================================================================+
 """
 if not app.show_persona_editor_window and not is_embedded: return
 if not is_embedded:
  imgui.set_next_window_size(imgui.ImVec2(1000, 800), imgui.Cond_.first_use_ever)
  opened, app.show_persona_editor_window = imgui.begin("Persona Editor", app.show_persona_editor_window)
  if not opened:
   imgui.end(); return

 if imgui.begin_table("persona_main_split", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v):
  # --- Left Sidebar ---
  imgui.table_setup_column("List", imgui.TableColumnFlags_.width_fixed, 200)
  imgui.table_setup_column("Editor", imgui.TableColumnFlags_.width_stretch)
  imgui.table_next_row()
  
  imgui.table_next_column()
  imgui.begin_child("persona_list_pane", imgui.ImVec2(0, 0), False)
  if True:
   if imgui.button("New Persona", imgui.ImVec2(-1, 0)):
    app._editing_persona_name                  = ""; app._editing_persona_system_prompt = ""
    app._editing_persona_tool_preset_id        = ""; app._editing_persona_bias_profile_id = ""
    app._editing_persona_context_preset_id     = ""
    app._editing_persona_aggregation_strategy  = ""
    app._editing_persona_preferred_models_list = [{"provider": app.current_provider, "model": app.current_model, "temperature": 0.7, "top_p": 1.0, "max_output_tokens": 4096, "history_trunc_limit": 900000}]
    app._editing_persona_scope                 = "project"; app._editing_persona_is_new = True
   imgui.separator()
   personas = getattr(app.controller, 'personas', {})
   for name in sorted(personas.keys()):
    if name and imgui.selectable(f"{name}##p_list", name == app._editing_persona_name and not getattr(app, '_editing_persona_is_new', False))[0]:
     import copy; #TODO(Ed): Review local import
     p = personas[name]; app._editing_persona_name = p.name; app._editing_persona_system_prompt = p.system_prompt or ""
     app._editing_persona_tool_preset_id        = p.tool_preset or ""; app._editing_persona_bias_profile_id = p.bias_profile or ""
     app._editing_persona_context_preset_id     = getattr(p, 'context_preset', '') or ""
     app._editing_persona_aggregation_strategy  = getattr(p, 'aggregation_strategy', '') or ""
     app._editing_persona_preferred_models_list = copy.deepcopy(p.preferred_models) if p.preferred_models else []
     app._editing_persona_scope                 = app.controller.persona_manager.get_persona_scope(p.name); app._editing_persona_is_new = False
   imgui.end_child()

  # --- Right Editor ---
  imgui.table_next_column()
  avail = imgui.get_content_region_avail()
  imgui.begin_child("persona_editor_content", imgui.ImVec2(0, avail.y - 45), False)
  if True:
   header_text = "New Persona" if getattr(app, '_editing_persona_is_new', True) else f"Editing Persona: {app._editing_persona_name}"
   imgui.text_colored(C_IN(), header_text)
   imgui.separator()
   
   if imgui.begin_table("p_meta", 2):
    imgui.table_setup_column("L", imgui.TableColumnFlags_.width_fixed, 60);  imgui.table_setup_column("F", imgui.TableColumnFlags_.width_stretch)
    imgui.table_next_row(); imgui.table_next_column(); imgui.text("Name:");  imgui.table_next_column(); imgui.set_next_item_width(-1); _, app._editing_persona_name = imgui.input_text("##pname", app._editing_persona_name, 128)
    imgui.table_next_row(); imgui.table_next_column(); imgui.text("Scope:"); imgui.table_next_column()
    if imgui.radio_button("Global##pscope", getattr(app, '_editing_persona_scope', 'project') == "global"): app._editing_persona_scope = "global"
    imgui.same_line(); 
    if imgui.radio_button("Project##pscope", getattr(app, '_editing_persona_scope', 'project') == "project"): app._editing_persona_scope = "project"
    imgui.end_table()
   
   rem_y = imgui.get_content_region_avail().y - 100
   if app._persona_models_open and app._persona_prompt_open: h1, h2 = rem_y * app._persona_split_v, rem_y - (rem_y * app._persona_split_v) - 10
   elif app._persona_models_open:                            h1, h2 = rem_y, 0
   elif app._persona_prompt_open:                            h1, h2 = 0, rem_y
   else:                                                     h1, h2 = 0, 0

   imgui.dummy(imgui.ImVec2(0, 4))
   opened_models = imgui.collapsing_header("Preferred Models", imgui.TreeNodeFlags_.default_open)
   if opened_models != app._persona_models_open: app._persona_models_open = opened_models
   
   if app._persona_models_open:
    imgui.begin_child("pref_models_scroll", imgui.ImVec2(0, h1), True)
    if True:
     to_remove = []
     providers = ai_client.PROVIDERS
     if not hasattr(app, '_persona_pref_models_expanded'): app._persona_pref_models_expanded = {}
     for i, entry in enumerate(app._editing_persona_preferred_models_list):
      imgui.push_id(f"pref_model_{i}")
      prov, mod, is_expanded = entry.get("provider", "Unknown"), entry.get("model", "Unknown"), app._persona_pref_models_expanded.get(i, False)
      if imgui.button("-" if is_expanded else "+"): app._persona_pref_models_expanded[i] = not is_expanded
      imgui.same_line(); imgui.text(f"{i+1}."); imgui.same_line(); imgui.text_colored(C_LBL(), f"{prov}"); imgui.same_line(); imgui.text("-"); imgui.same_line(); imgui.text_colored(C_IN(), f"{mod}")
      if not is_expanded:
       imgui.same_line(); summary = f" (T:{entry.get('temperature', 0.7):.1f}, P:{entry.get('top_p', 1.0):.2f}, M:{entry.get('max_output_tokens', 0)})"
       imgui.text_colored(C_SUB(), summary)
      imgui.same_line(imgui.get_content_region_avail().x - 30); 
      if imgui.button("x"): to_remove.append(i)
      if is_expanded:
       with imscope.indent(20):
        if imgui.begin_table("model_settings", 2, imgui.TableFlags_.borders_inner_v):
         imgui.table_setup_column("Label", imgui.TableColumnFlags_.width_fixed, 120); imgui.table_setup_column("Control", imgui.TableColumnFlags_.width_stretch)
         imgui.table_next_row(); imgui.table_next_column(); imgui.text("Provider:"); imgui.table_next_column(); 
         imgui.set_next_item_width(-1)
         p_idx       = providers.index(prov) + 1 if prov in providers else 0; 
         ch_p, p_idx = imgui.combo("##prov", p_idx, ["None"] + providers)
         if ch_p: entry["provider"] = providers[p_idx-1] if p_idx > 0 else ""
         imgui.table_next_row(); imgui.table_next_column(); imgui.text("Model:"); imgui.table_next_column(); 
         imgui.set_next_item_width(-1)
         m_list      = app.controller.all_available_models.get(entry.get("provider", ""), [])
         m_idx       = m_list.index(mod) + 1 if mod in m_list else 0
         ch_m, m_idx = imgui.combo("##model", m_idx, ["None"] + m_list)
         if ch_m: entry["model"] = m_list[m_idx-1] if m_idx > 0 else ""
         imgui.table_next_row(); imgui.table_next_column(); imgui.text("Temperature:"); imgui.table_next_column(); cw = imgui.get_content_region_avail().x
         imgui.set_next_item_width(cw * 0.7);              _, entry["temperature"] = imgui.slider_float("##ts", entry.get("temperature", 0.7), 0.0, 2.0, "%.1f")
         imgui.same_line(); imgui.set_next_item_width(-1); _, entry["temperature"] = imgui.input_float("##ti", entry.get("temperature", 0.7), 0.1, 0.1, "%.1f")
         imgui.table_next_row(); imgui.table_next_column(); imgui.text("Top-P:"); imgui.table_next_column()
         imgui.set_next_item_width(cw * 0.7);              _, entry["top_p"] = imgui.slider_float("##tp_s", entry.get("top_p", 1.0), 0.0, 1.0, "%.2f")
         imgui.same_line(); imgui.set_next_item_width(-1); _, entry["top_p"] = imgui.input_float("##tp_i", entry.get("top_p", 1.0), 0.05, 0.05, "%.2f")
         imgui.table_next_row(); imgui.table_next_column(); imgui.text("Max Tokens:");    imgui.table_next_column(); imgui.set_next_item_width(-1); _, entry["max_output_tokens"]   = imgui.input_int("##maxt", entry.get("max_output_tokens",   4096))
         imgui.table_next_row(); imgui.table_next_column(); imgui.text("History Limit:"); imgui.table_next_column(); imgui.set_next_item_width(-1); _, entry["history_trunc_limit"] = imgui.input_int("##hist", entry.get("history_trunc_limit", 900000))
         imgui.end_table()
      imgui.pop_id()
     for i in reversed(to_remove): app._editing_persona_preferred_models_list.pop(i)
     imgui.end_child()
    if app._persona_prompt_open:
     imgui.button("###persona_splitter", imgui.ImVec2(-1, 4))
     if imgui.is_item_active(): app._persona_split_v = max(0.1, min(0.9, app._persona_split_v + imgui.get_io().mouse_delta.y / rem_y))
   
   imgui.dummy(imgui.ImVec2(0, 2))
   if imgui.button("Add Preferred Model", imgui.ImVec2(-1, 0)): app._editing_persona_preferred_models_list.append({"provider": app.current_provider, "model": app.current_model, "temperature": 0.7, "top_p": 1.0, "max_output_tokens": 4096, "history_trunc_limit": 900000})
   
   imgui.dummy(imgui.ImVec2(0, 2))
   if imgui.begin_table("p_assign", 2):
    imgui.table_setup_column("C1"); imgui.table_setup_column("C2"); imgui.table_next_row()
    imgui.table_next_column(); imgui.text("Tool Preset:"); tn = ["None"] + sorted(app.controller.tool_presets.keys())
    t_idx = tn.index(app._editing_persona_tool_preset_id) if getattr(app, '_editing_persona_tool_preset_id', '') in tn else 0
    imgui.set_next_item_width(-1); _, t_idx = imgui.combo("##ptp", t_idx, tn); app._editing_persona_tool_preset_id = tn[t_idx] if t_idx > 0 else ""
    imgui.table_next_column(); imgui.text("Bias Profile:"); bn = ["None"] + sorted(app.controller.bias_profiles.keys())
    b_idx = bn.index(app._editing_persona_bias_profile_id) if getattr(app, '_editing_persona_bias_profile_id', '') in bn else 0
    imgui.set_next_item_width(-1); _, b_idx = imgui.combo("##pbp", b_idx, bn); app._editing_persona_bias_profile_id = bn[b_idx] if b_idx > 0 else ""
    imgui.table_next_row()
    imgui.table_next_column(); imgui.text("Context Preset:"); cn = ["None"] + sorted(app.controller.project.get("context_presets", {}).keys())
    c_idx = cn.index(app._editing_persona_context_preset_id) if getattr(app, '_editing_persona_context_preset_id', '') in cn else 0
    imgui.set_next_item_width(-1); _, c_idx = imgui.combo("##pcp", c_idx, cn); app._editing_persona_context_preset_id = cn[c_idx] if c_idx > 0 else ""
    imgui.table_next_column(); imgui.text("Aggregation Strategy:"); sn = ["auto", "full", "summarize", "skeleton"]
    s_idx = sn.index(app._editing_persona_aggregation_strategy) if getattr(app, '_editing_persona_aggregation_strategy', '') in sn else 0
    imgui.set_next_item_width(-1); _, s_idx = imgui.combo("##pas", s_idx, sn); app._editing_persona_aggregation_strategy = sn[s_idx]
    imgui.end_table()
   
   if imgui.button("Manage Tools & Biases", imgui.ImVec2(-1, 0)): app.show_tool_preset_manager_window = True
   
   imgui.dummy(imgui.ImVec2(0, 4)); imgui.separator()
   opened_prompt = imgui.collapsing_header("System Prompt", imgui.TreeNodeFlags_.default_open)
   if opened_prompt != app._persona_prompt_open: app._persona_prompt_open = opened_prompt
   
   if app._persona_prompt_open:
    imgui.begin_child("p_prompt_header_pane", imgui.ImVec2(0, 30), False)
    if True:
     imgui.text("Template:"); imgui.same_line(); p_pre = ["Select..."] + sorted(app.controller.presets.keys())
     if not hasattr(app, "_load_preset_idx"): app._load_preset_idx = 0
     imgui.set_next_item_width(200); _, app._load_preset_idx = imgui.combo("##load_p", app._load_preset_idx, p_pre)
     imgui.same_line(); 
     if imgui.button("Apply"):
      if app._load_preset_idx > 0: app._editing_persona_system_prompt = app.controller.presets[p_pre[app._load_preset_idx]].system_prompt
     imgui.same_line(); 
     if imgui.button("Manage"): app.show_preset_manager_window = True
     imgui.end_child()
    _, app._editing_persona_system_prompt = imgui.input_text_multiline("##pprompt", app._editing_persona_system_prompt, imgui.ImVec2(-1, h2))
   imgui.end_child()

  # --- Footer Buttons ---
  imgui.separator()
  if imgui.button("Save##pers", imgui.ImVec2(100, 0)):
   if app._editing_persona_name.strip():
    try:
     import copy; persona = models.Persona(name=app._editing_persona_name.strip(), system_prompt=app._editing_persona_system_prompt, tool_preset=app._editing_persona_tool_preset_id or None, bias_profile=app._editing_persona_bias_profile_id or None, context_preset=app._editing_persona_context_preset_id or None, aggregation_strategy=app._editing_persona_aggregation_strategy or None, preferred_models=copy.deepcopy(app._editing_persona_preferred_models_list))
     app.controller._cb_save_persona(persona, getattr(app, '_editing_persona_scope', 'project')); app.ai_status = f"Saved: {persona.name}"
    except Exception as e: app.ai_status = f"Error: {e}"
   else: app.ai_status = "Name required"
  imgui.same_line(); 
  if imgui.button("Delete##pers", imgui.ImVec2(100, 0)):
   if not getattr(app, '_editing_persona_is_new', True) and app._editing_persona_name:
    app.controller._cb_delete_persona(app._editing_persona_name, getattr(app, '_editing_persona_scope', 'project'))
    app._editing_persona_name = ""; app._editing_persona_is_new = True
  if not is_embedded:
   imgui.same_line()
   if imgui.button("Close##pers", imgui.ImVec2(100, 0)):
    app.show_persona_editor_window = False
  imgui.end_table()
 
 if not is_embedded:
  imgui.end()

#endregion: AI Settings

#region: Context Management

def render_files_and_media(app: App) -> None:
 """Renders the inventory of files and screenshots. Allows adding files or directories to the inventory
 and attaching files or screenshots to the active context.

 SSDL: `[I:inventory] -> [B:add_files_folders] -> [I:screenshots] => [B:add_screenshots]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Files collapsing header                                 |
   |   +---------------------------------------------------+ |
   |   | [Act] [Path]                             [Status] | |
   |   |  [+]  src/gui_2.py                         Active | |
   |   +---------------------------------------------------+ |
   |   [Add Files to Inventory] [Add Directory]              |
   | Screenshots collapsing header                           |
   |   +---------------------------------------------------+ |
   |   | [x] /path/to/screenshot1.png                      | |
   |   +---------------------------------------------------+ |
   |   [Add Screenshots]                                     |
   +---------------------------------------------------------+
 """
 if imgui.collapsing_header("Files", imgui.TreeNodeFlags_.default_open):
  with imscope.group():
    to_remove_idx = -1
    app.files.sort(key=lambda f: f.path.lower() if hasattr(f, 'path') else str(f).lower())
    file_indices = {id(f): idx for idx, f in enumerate(app.files)}
    grouped      = aggregate.group_files_by_dir(app.files)
    if imgui.begin_table("files_table", 3, imgui.TableFlags_.resizable | imgui.TableFlags_.borders | imgui.TableFlags_.row_bg):
     imgui.table_setup_column("Act",    imgui.TableColumnFlags_.width_fixed, 60)
     imgui.table_setup_column("Path",   imgui.TableColumnFlags_.width_stretch)
     imgui.table_setup_column("Status", imgui.TableColumnFlags_.width_fixed, 70)
     imgui.table_headers_row()
     imgui.end_table()
    for dir_name, g_files in sorted(grouped.items()):
     with imscope.tree_node_ex(f"{dir_name}##files_dir", imgui.TreeNodeFlags_.default_open) as dir_open:
      if imgui.begin_table("files_table", 3, imgui.TableFlags_.resizable | imgui.TableFlags_.borders | imgui.TableFlags_.row_bg):
       imgui.table_setup_column("Act",    imgui.TableColumnFlags_.width_fixed, 60)
       imgui.table_setup_column("Path",   imgui.TableColumnFlags_.width_stretch)
       imgui.table_setup_column("Status", imgui.TableColumnFlags_.width_fixed, 70)
       # imgui.table_headers_row()
       if dir_open:
        for f_item in g_files:
         i = file_indices[id(f_item)]
         imgui.table_next_row()
         imgui.table_set_column_index(0)
         fpath        = f_item.path  if hasattr(f_item, 'path') else str(f_item)
         display_name = os.path.basename(fpath)
         in_context   = any((cf.path if hasattr(cf,     'path') else str(cf)) == fpath for cf in app.context_files)
         is_cached    = any(fpath in c for c in getattr(app, '_cached_files', []))
         
         if imgui.button(f"+##add_f_{i}"):
          if not in_context:
           from src import models
           new_item = models.FileItem(path=fpath)
           app.context_files.append(new_item)
           app._populate_auto_slices(new_item)
         
         imgui.same_line()
         if imgui.button(f"x##rem_f_{i}"):
          to_remove_idx = i
         
         imgui.table_set_column_index(1)
         imgui.text(display_name)
         if imgui.is_item_hovered(): imgui.set_tooltip(fpath)
         
         imgui.table_set_column_index(2)
         if   in_context: imgui.text_colored(theme.get_color("status_success"), "Active")
         elif is_cached:  imgui.text_colored(theme.get_color("status_info"),    "Cached")
         else:            imgui.text_disabled(" - ")
       imgui.end_table()
    if to_remove_idx != -1: app.files.pop(to_remove_idx)
  
  imgui.dummy(imgui.ImVec2(0, 5))
  if imgui.button("Add Files to Inventory"):
   r = hide_tk_root(); paths = filedialog.askopenfilenames(); r.destroy()
   from src import models
   for p in paths:
    if p not in [f.path if hasattr(f, "path") else f for f in app.files]: app.files.append(models.FileItem(path=p))
  imgui.same_line()
  if imgui.button("Add Directory"):
   r = hide_tk_root(); dirpath = filedialog.askdirectory(); r.destroy()
   if dirpath:
    existing = {f.path if hasattr(f, "path") else str(f) for f in app.files}
    for root, _dirs, files in os.walk(dirpath):
     for fname in files:
      full = os.path.join(root, fname)
      if full not in existing:
       app.files.append(models.FileItem(path=full))
       existing.add(full)
 
 imgui.separator()
 if imgui.collapsing_header("Screenshots", imgui.TreeNodeFlags_.default_open):
  with imscope.child("Shots_child", -1, 150, True):
   to_rem_shot = -1
   for i, s in enumerate(app.screenshots):
    if imgui.button(f"x##s{i}"): to_rem_shot = i
    imgui.same_line(); imgui.text(s)
    if to_rem_shot != -1: app.screenshots.pop(to_rem_shot)
    
    caps = app._get_active_capabilities()
    imgui.begin_disabled(not caps.vision)
    if imgui.button("Add Screenshots##adds"):
     r = hide_tk_root(); paths = filedialog.askopenfilenames(filetypes=[("Images", "*.png *.jpg *.jpeg *.gif *.bmp *.webp"), ("All", "*.*")]); r.destroy()
     for p in paths:
      if p not in app.screenshots: app.screenshots.append(p)
    imgui.end_disabled()
    if not caps.vision:
     imgui.same_line()
     imgui.text_disabled(f"(vision not supported by {app.current_model}; attachments would be ignored)")
 return

def render_context_batch_actions(app: App, total_lines: int, total_ast: int) -> None:
 """Renders a batch actions control bar. Allows batch-changing view modes of selected files,
 selecting/deselecting all, adding files, and generating context preview markdown.

 SSDL: `[I:context_files] -> [B:mode_batch_buttons] -> [B:selection_buttons] -> [B:all_add_del] => [B:preview]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Batch: [Full] [Summary] [Skeleton] [Outline] [Masked]   |
   | [Sel All] [Unsel All] [Add Files] [Add All] [Del]       |
   | [Preview] | Total: X files, Y lines, Z AST elements     |
   +---------------------------------------------------------+
 """
 imgui.text("Batch:")
 for mode in ["full", "summary", "skeleton", "outline", "masked", "none"]:
  if imgui.button(f"{mode.capitalize()}##batch"):
   for f in app.context_files:
    f_path = f.path if hasattr(f, "path") else str(f)
    if f_path in app.ui_selected_context_files: f.view_mode = mode
  imgui.same_line()
 if imgui.button("Sel All##selall"):
  for f in app.context_files:
   f_path = f.path if hasattr(f, "path") else str(f)
   app.ui_selected_context_files.add(f_path)
 imgui.same_line()
 if imgui.button("Unsel All##unselall"): app.ui_selected_context_files.clear()
 imgui.same_line()
 if imgui.button("Add Files##add_btn"): imgui.open_popup("Select Context Files")
 imgui.same_line()
 if imgui.button("Add All##addall"):
  context_paths = {f.path if hasattr(f, "path") else str(f) for f in app.context_files}
  for f in app.files:
   f_path = f.path if hasattr(f, "path") else str(f)
   if f_path not in context_paths:
    f_copy = copy.deepcopy(f)
    app.context_files.append(f_copy)
    app._populate_auto_slices(f_copy)
 imgui.same_line()
 if imgui.button("Del##batch"):
  new_files = []
  for f in app.context_files:
   f_path = f.path if hasattr(f, "path") else str(f)
   if f_path not in app.ui_selected_context_files: new_files.append(f)
  app.context_files = new_files
  app.ui_selected_context_files.clear()
 imgui.same_line()
 if imgui.button("Preview##ctx"):
  if not app.context_files:
    app.context_preview_text = "# Context Composition Empty\n\nNo files have been added to the context composition yet."
  else:
    try:
     app.controller.context_files = app.context_files
     app.context_preview_text = app.controller._do_generate()[0]
    except Exception as e:
     import traceback
     err = traceback.format_exc()
     app.context_preview_text = f"# Error generating preview\n\n```python\n{err}\n```"
  app.show_windows["Context Preview"] = True
 imgui.same_line()
 imgui.text(f" | Total: {len(app.context_files)} files, {total_lines} lines, {total_ast} AST elements")

def render_add_context_files_modal(app: App) -> None:
 """Renders a modal popup listing files in the project inventory that can be batch-added
 to the active context.

 SSDL: `[I:picker_list] -> [B:checkboxes] -> [B:add_selected_button] => [B:cancel_button]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Select Context Files                                [X] |
   +---------------------------------------------------------+
   | Select files from project to add to context:            |
   | +-----------------------------------------------------+ |
   | | [ ] src/gui_2.py                                    | |
   | | [x] src/models.py                                   | |
   | +-----------------------------------------------------+ |
   | [Add Selected] [Cancel]                                 |
   +---------------------------------------------------------+
 """
 if imgui.begin_popup_modal("Select Context Files", None, imgui.WindowFlags_.always_auto_resize)[0]:
  imgui.text("Select files from project to add to context:")
  imgui.begin_child("ctx_picker_list", imgui.ImVec2(600, 300), True)
  if True:
   # Create a temporary selection set if not initialized
   if not hasattr(app, '_ui_picker_selected'): app._ui_picker_selected = set()
   for f in app.files:
    fpath = f.path if hasattr(f, 'path') else str(f)
    # Skip if already in context
    if any((cf.path if hasattr(cf, 'path') else str(cf)) == fpath for cf in app.context_files):
     continue
    is_sel = fpath in app._ui_picker_selected
    clicked, new_sel = imgui.checkbox(f"{fpath}##picker_{fpath}", is_sel)
    if clicked:
     if new_sel:
      app._ui_picker_selected.add(fpath)
     else:
      app._ui_picker_selected.discard(fpath)
   imgui.end_child()
  imgui.separator()
  
  if imgui.button("Add Selected", imgui.ImVec2(120, 0)):
   for fpath in app._ui_picker_selected:
    f_item = models.FileItem(path=fpath)
    app.context_files.append(f_item)
    app._populate_auto_slices(f_item)
   app._ui_picker_selected.clear()
   imgui.close_current_popup()

  imgui.same_line()
  if imgui.button("Cancel", imgui.ImVec2(120, 0)):
   if hasattr(app, '_ui_picker_selected'):
    app._ui_picker_selected.clear()
   imgui.close_current_popup()
  imgui.end_popup()

def render_context_composition_panel(app: App) -> None:
 """Renders the Context Composition panel containing loaded project files, presets,
 and visual screenshot files. Displays token stats, batch files actions, and collapsible trees.

 SSDL: `[I:stats] -> [I:batch_actions] -> [I:files_table] -> [I:presets] -> [I:screenshots]`

 ASCII Layout Map:
   +-------------------------------------------------------------+
   | > Context Composition                                       |
   |   [Presets v]  [Add Files]  [Batch Actions]                 |
   |   +-------------------------------------------------------+ |
   |   | [-] src/                                              | |
   |   |   [x] gui_2.py (Sig)  [Inspect] [Slices]  [Remove]    | |
   |   |   [ ] models.py       [Inspect] [Slices]  [Remove]    | |
   |   +-------------------------------------------------------+ |
   | > Screenshots                                               |
   +-------------------------------------------------------------+
 """
 if imgui.collapsing_header("Context Composition##panel"):
  total_lines, total_ast = app._update_context_file_stats()
  render_context_batch_actions(app, total_lines, total_ast)
  render_context_files_table(app)
 
 imgui.separator()
 render_context_presets(app)
 imgui.separator()
 if imgui.collapsing_header("Screenshots"):
  render_context_screenshots(app)

def render_ast_inspector_modal(app: App) -> None:
 """Renders the 'Structural File Editor' modal dialog.
 Provides an interactive tree-sitter AST inspector to mask classes, methods, or
 functions and extract custom annotated slices (Definition, Signature, Hide).

 SSDL: `[B:open_modal?] -> [Q:outline] -> [I:render_ast_tree]`

 ASCII Layout Map:
   +-------------------------------------------------------------+
   | Structural File Editor: src/gui_2.py                    [X] |
   +-------------------------------------------------------------+
   | [Filter:           ] [Clear Filters] [Preset: Sig-Only  v]  |
   +--------------------+----------------------------------------+
   | AST Tree           | File Preview                           |
   |                    |                                        |
   | [-] Class: App     | 302: class App:                        |
   |   [x] __init__     | 305:  def __init__(self):              |
   |   [ ] run          |                                        |
   +--------------------+----------------------------------------+
   | [Add selected slices]                             [Close]   |
   +-------------------------------------------------------------+
 """
 if getattr(app, 'show_structural_editor_modal', False):
  imgui.open_popup('Structural File Editor')
  app.show_structural_editor_modal = False
 
 imgui.set_next_window_size(imgui.ImVec2(1400, 900), imgui.Cond_.first_use_ever)
 expanded, opened = imgui.begin_popup_modal('Structural File Editor', True, imgui.WindowFlags_.none)
 if opened:
  if expanded:
   if app.ui_editing_slices_file is None:
    imgui.close_current_popup()
   else:
    f_item = app.ui_editing_slices_file
    f_path = f_item.path if hasattr(f_item, "path") else str(f_item)
    
    if f_path != getattr(app, '_cached_ast_file_path', None):
     outline = ""
     try:
      proj_dir = str(Path(app.controller.active_project_path).parent.resolve()) if getattr(app, 'controller', None) and app.controller.active_project_path else None
      mcp_client.configure([{"path": f_path}], [proj_dir] if proj_dir else None)
      
      if   f_path.lower().endswith('.py'):                           outline = mcp_client.py_get_code_outline(f_path)
      elif f_path.lower().endswith(('.c', '.h')):                    outline = mcp_client.ts_c_get_code_outline(f_path)
      elif f_path.lower().endswith(('.cpp', '.hpp', '.cxx', '.cc')): outline = mcp_client.ts_cpp_get_code_outline(f_path)
     except Exception as e:
      outline = f"Error fetching outline: {e}"
     
     app._cached_ast_nodes = []
     import re #TODO(Ed): Review local import
     pattern = re.compile(r'^(\s*)\[(.*?)\] (.*?) \(Lines (\d+)-(\d+)\)')
     stack = [] # (indent, name)
     for line in outline.splitlines():
      m = pattern.match(line)
      if m:
       indent_str, kind, name, start_ln, end_ln = m.groups()
       indent = len(indent_str)
       while stack and stack[-1][0] >= indent: stack.pop()
       stack.append((indent, name))
       full_path = '::'.join([s[1] for s in stack])
       app._cached_ast_nodes.append({
        'indent':     indent,
        'kind':       kind,
        'name':       name,
        'full_path':  full_path,
        'start_line': int(start_ln),
        'end_line':   int(end_ln)
       })
     try:
      content                    = mcp_client.read_file(f_path)
      app._cached_ast_file_lines = content.splitlines()
      app.text_viewer_content    = content
     except Exception:
      app._cached_ast_file_lines = ["Error loading file content."]
      app.text_viewer_content    = "Error loading file content."
     app._cached_ast_file_path   = f_path
    
    imgui.text(f"Editing Structure: {f_path}")
    imgui.separator()
    
    avail        = imgui.get_content_region_avail()
    table_height = max(100.0, avail.y - imgui.get_frame_height_with_spacing() * 2 - 20)
    
    if imgui.begin_table('structure_dual_pane', 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v, imgui.ImVec2(0, table_height)):
     imgui.table_setup_column("AST & Slices",    imgui.TableColumnFlags_.width_fixed, 400)
     imgui.table_setup_column("Content Preview", imgui.TableColumnFlags_.width_stretch)
     imgui.table_next_row()
     imgui.table_next_column()
     
     # --- LEFT COLUMN: AST Tree & Slice Management ---
     imgui.begin_child("ast_tree_scroll", imgui.ImVec2(0, 0), True)
     # if True:
     if imgui.collapsing_header("AST Tree", imgui.TreeNodeFlags_.default_open):
      if not getattr(app, '_cached_ast_nodes', None): imgui.text("No AST nodes found.")
      else:
       with imscope.table('ast tree members', 2, imgui.TableFlags_.resizable | imgui.TableFlags_.sizing_stretch_prop):
        imgui.table_setup_column("symbol", imgui.TableColumnFlags_.width_fixed, 400)
        imgui.table_setup_column("option", imgui.TableColumnFlags_.width_stretch)
        imgui.table_next_row()
        imgui.table_next_column()
        for node in app._cached_ast_nodes:
         imgui.table_next_row()
         imgui.table_next_column()
         indent    = node['indent']
         kind      = node['kind']
         name      = node['name']
         full_path = node['full_path']
         
         imgui.dummy(imgui.ImVec2(indent * 10, 0))
         imgui.same_line()
         imgui.text(f"[{kind}] {name}")
         
         if imgui.is_item_hovered():
          app._hovered_ast_node = full_path
         imgui.table_next_column()
         
         # btn_width = 150
         # avail_width = imgui.get_content_region_avail().x
         # do_align    = avail_width > btn_width if isinstance(avail_width, (int, float)) else False
         # if do_align: imgui.same_line((avail_width - btn_width))
         # else:        imgui.same_line()
         # imgui.same_line((avail_width - btn_width) * 0.925)
         
         if not hasattr(f_item, 'ast_mask'): f_item.ast_mask = {}
         current_mode = f_item.ast_mask.get(full_path, 'hide')
         
         imgui.push_id(full_path)
         if imgui.radio_button("Def", current_mode == 'def'):   f_item.ast_mask[full_path] = 'def'
         imgui.same_line()
         if imgui.radio_button("Sig", current_mode == 'sig'):   f_item.ast_mask[full_path] = 'sig'
         imgui.same_line()
         if imgui.radio_button("Hide", current_mode == 'hide'): f_item.ast_mask[full_path] = 'hide'
         imgui.pop_id()
     
     imgui.separator()
     if imgui.collapsing_header("Custom Slices", imgui.TreeNodeFlags_.default_open):
      if not hasattr(f_item, 'custom_slices'): f_item.custom_slices = []
      imgui.text_colored(C_IN(), "Highlight lines in right pane to add slices.")
      if imgui.button("Add Selection as Slice"):
       if getattr(app, '_slice_sel_start', -1) != -1 and getattr(app, '_slice_sel_end', -1) != -1:
        s_line = min(app._slice_sel_start, app._slice_sel_end)
        e_line = max(app._slice_sel_start, app._slice_sel_end)
        from src.fuzzy_anchor import FuzzyAnchor
        slice_data        = FuzzyAnchor.create_slice(app.text_viewer_content, s_line, e_line)
        slice_data['tag'] = ""; slice_data['comment'] = ""
        f_item.custom_slices.append(slice_data)
        app._slice_sel_start = -1
        app._slice_sel_end   = -1
      imgui.same_line()
      if imgui.button("Clear Selection"): 
       app._slice_sel_start = -1 
       app._slice_sel_end   = -1
      imgui.same_line()
      if imgui.button("Auto-Populate"): app._populate_auto_slices(f_item)
     
      to_remove = -1
      tags = app.controller.project.get("context_tags", ["auto-ast", "bug", "feature", "important"])
      for idx, slc in enumerate(f_item.custom_slices):
       imgui.push_id(f"slc_row_{idx}"); imgui.text(f"#{idx+1}: L{slc['start_line']}-{slc['end_line']}"); imgui.same_line()
       current_tag = slc.get('tag', '')
       if current_tag not in tags and current_tag: tags.append(current_tag)
       tag_idx = tags.index(current_tag) if current_tag in tags else 0
       imgui.set_next_item_width(100)
       ch_tag, new_tag_idx = imgui.combo("##Tag", tag_idx, tags)
       if ch_tag: slc['tag'] = tags[new_tag_idx]
       imgui.same_line(); imgui.set_next_item_width(-30); changed_comm, new_comm = imgui.input_text("##Note", slc.get('comment', ''))
       if changed_comm: slc['comment'] = new_comm
       imgui.same_line()
       if imgui.button("X"): to_remove = idx
       imgui.pop_id()
      if to_remove != -1: f_item.custom_slices.pop(to_remove)
     imgui.end_child()
     
     imgui.table_next_column()
     
     # --- RIGHT COLUMN: Content Preview with Highlights ---
     with imscope.child("ast_content_scroll", imgui.ImVec2(0, 0), True):
      if not getattr(app, '_cached_ast_file_lines', None):
       imgui.text("No file content loaded.")
      else:
       draw_list = imgui.get_window_draw_list()
       for i, line_text in enumerate(app._cached_ast_file_lines):
        line_num = i + 1
        pos         = imgui.get_cursor_screen_pos()
        line_height = imgui.get_text_line_height()
        avail_width = imgui.get_content_region_avail().x
        
        # 1. AST Highlight
        deepest_node = None
        for node in app._cached_ast_nodes:
         if node['start_line'] <= line_num <= node['end_line']:
          if deepest_node is None or node['indent'] > deepest_node['indent']: deepest_node = node
        mode = 'hide'
        if deepest_node: mode = getattr(f_item, 'ast_mask', {}).get(deepest_node['full_path'], 'hide')
        if   mode == 'def': draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_auto",      alpha=0.15)))
        elif mode == 'sig': draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_selection", alpha=0.15)))
        elif deepest_node and deepest_node['full_path'] == getattr(app, '_hovered_ast_node', None):
         draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("status_warning", alpha=0.2)))
        
        # 2. Slice Highlight
        if hasattr(f_item, 'custom_slices'):
         is_auto = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in f_item.custom_slices if slc.get('tag') == 'auto-ast')
         is_man  = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in f_item.custom_slices if slc.get('tag') != 'auto-ast')
         if is_man: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_manual", alpha=0.2)))
         elif is_auto and mode == 'hide': draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_auto", alpha=0.1)))
        
        # 3. Active Selection Highlight
        if getattr(app, '_slice_sel_start', -1) != -1 and getattr(app, '_slice_sel_end', -1) != -1:
         s, e = min(app._slice_sel_start, app._slice_sel_end), max(app._slice_sel_start, app._slice_sel_end)
         if s <= line_num <= e: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_selection", alpha=0.3)))
        
        imgui.selectable(f"{line_num:4} | {line_text}##ln{line_num}", False)
        if imgui.is_item_clicked(): app._slice_sel_start = line_num; app._slice_sel_end = line_num
        if imgui.is_item_hovered(imgui.HoveredFlags_.allow_when_blocked_by_active_item) and imgui.is_mouse_down(0): app._slice_sel_end = line_num
     
     imgui.end_table()
    
    imgui.separator()
    if imgui.button("Close", imgui.ImVec2(120, 0)):
     app.ui_editing_slices_file = None
     app.ui_inspecting_ast_file = None
     imgui.close_current_popup()
  imgui.end_popup()

 if not opened:
  app.ui_editing_slices_file = None
  app.ui_inspecting_ast_file = None

def render_save_workspace_profile_modal(app: App) -> None:
 """Renders a popup modal for saving the current workspace profile (projects, layout, configuration).

 SSDL: `[I:name_input] -> [B:scope_radio] -> [B:save_button] => [B:cancel_button]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Save Workspace Profile                              [X] |
   +---------------------------------------------------------+
   | Name:  [                              ]                 |
   | Scope: (o) Project  ( ) Global                          |
   | [Save] [Cancel]                                         |
   +---------------------------------------------------------+
 """
 if app._show_save_workspace_profile_modal:
  imgui.open_popup("Save Workspace Profile")
 
 if imgui.begin_popup_modal("Save Workspace Profile", True, imgui.WindowFlags_.always_auto_resize)[0]:
  imgui.text("Name:")
  _, app._new_workspace_profile_name = imgui.input_text("##profile_name", app._new_workspace_profile_name)
  
  imgui.text("Scope:")
  if imgui.radio_button("Project", app._new_workspace_profile_scope == "project"): app._new_workspace_profile_scope = "project"
  imgui.same_line()
  if imgui.radio_button("Global", app._new_workspace_profile_scope  == "global"):  app._new_workspace_profile_scope = "global"
  
  imgui.separator()
  if imgui.button("Save", (120, 0)):
   if app._new_workspace_profile_name.strip():
    app.controller._cb_save_workspace_profile(app._new_workspace_profile_name, app._new_workspace_profile_scope)
    app._show_save_workspace_profile_modal = False
    imgui.close_current_popup()
  
  imgui.same_line()
  if imgui.button("Cancel", (120, 0)):
   app._show_save_workspace_profile_modal = False
   imgui.close_current_popup()
  
  imgui.end_popup()

def render_context_presets_panel(app: App) -> None:
 """Renders the Context Presets configuration panel. Enables naming, saving, loading,
 and deleting context presets.

 SSDL: `[I:presets_list] -> [B:save_new_preset] => [B:presets_action_buttons]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Context Presets                                         |
   | Preset Name: [                    ] [Save Current]      |
   | ------------------------------------------------------- |
   | my-preset (2 files, 0 shots)             [Load] [Delete]|
   +---------------------------------------------------------+
 """
 imgui.text_colored(C_IN(), "Context Presets")
 imgui.separator()
 changed, new_name = imgui.input_text("Preset Name##new_ctx", app.ui_new_context_preset_name)
 if changed: app.ui_new_context_preset_name = new_name
 imgui.same_line()
 if imgui.button("Save Current"):
  if app.ui_new_context_preset_name.strip():
   app.save_context_preset(app.ui_new_context_preset_name.strip())
 
 imgui.separator()
 presets = app.controller.project.get('context_presets', {})
 for name in sorted(presets.keys()):
  preset  = presets[name]
  n_files = len(preset.get('files', []))
  n_shots = len(preset.get('screenshots', []))
  imgui.text(f"{name} ({n_files} files, {n_shots} shots)")
  imgui.same_line()
  if imgui.button(f"Load##{name}"): app.load_context_preset(name)
  imgui.same_line()
  if imgui.button(f"Delete##{name}"): app.delete_context_preset(name)

def render_context_screenshots(app: App) -> None:
 """Renders a list of the currently attached screenshot image files.

 SSDL: `[I:screenshots]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | /path/to/screenshot_1.png                               |
   | /path/to/screenshot_2.png                               |
   +---------------------------------------------------------+
 """
 for i, s in enumerate(app.screenshots): imgui.text(s)

def render_context_files_table(app: App) -> None:
 """Renders a two-column table mapping active context files to their AST view modes
 (Definition, Signature, Hide) and slice indicators. Grouped by directories.

 SSDL: `[I:group_by_dir] -> o-> [I:dir_node] -> o-> [I:file_row]`

 ASCII Layout Map:
   +-------------------------------------------------------------+
   | File                                 | Flags                |
   +--------------------------------------+----------------------+
   | [-] src/                             |                      |
   |   [x] gui_2.py                       | [Sig v] [Inspect] [X] |
   |   [x] models.py                      | [Def v] [Inspect] [X] |
   +--------------------------------------+----------------------+
 """
 imgui.dummy(imgui.ImVec2(0, 4))
 grouped_files = aggregate.group_files_by_dir(app.context_files)

 with imscope.table("ctx_comp_table", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders) as active:
  if active:
   imgui.table_setup_column("File",  imgui.TableColumnFlags_.width_stretch)
   imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 200)
   imgui.table_headers_row()

   file_indices = {id(f): idx for idx, f in enumerate(app.context_files)}

   for dir_name, g_files in grouped_files.items():
    imgui.table_next_row()
    imgui.table_set_column_index(0)
    with imscope.tree_node_ex(f"{dir_name}##dir_{dir_name}", imgui.TreeNodeFlags_.default_open) as is_open:
     imgui.table_set_column_index(1)
     if is_open:
      for f_item in g_files:
       i = file_indices[id(f_item)]
       imgui.table_next_row()
       imgui.table_set_column_index(0)

       f_path = f_item.path if hasattr(f_item, "path") else str(f_item)
       is_sel = f_path in app.ui_selected_context_files
       f_item.auto_aggregate = is_sel
       changed_sel, is_sel = imgui.checkbox(f"##sel{i}", is_sel)
       if changed_sel:
        f_item.auto_aggregate = is_sel
        if imgui.get_io().key_shift and app._last_selected_context_index != -1:
         start = min(app._last_selected_context_index, i)
         end = max(app._last_selected_context_index, i)
         for idx in range(start, end + 1):
          item = app.context_files[idx]
          item_path = item.path if hasattr(item, "path") else str(item)
          if is_sel: app.ui_selected_context_files.add(item_path)
          else:      app.ui_selected_context_files.discard(item_path)
        else:
         if is_sel: app.ui_selected_context_files.add(f_path)
         else:      app.ui_selected_context_files.discard(f_path)
        app._last_selected_context_index = i
       imgui.same_line()

       _abs_p    = f_path if os.path.isabs(f_path) else os.path.join(app.controller.active_project_root, f_path)
       _exists   = os.path.exists(_abs_p)
       mtime     = os.path.getmtime(_abs_p) if _exists else 0
       cache_key = f"{f_path}_{mtime}"
       stats     = app._file_stats_cache.get(cache_key, {"lines": 0, "ast_elements": 0})
       f_name    = os.path.basename(f_path)
       imgui.text(f"{f_name} (L: {stats.get('lines', 0)}, AST: {stats.get('ast_elements', 0)})")
       if not _exists:
        imgui.same_line()
        imgui.text_colored(imgui.ImVec4(1.0, 0.0, 0.0, 1.0), "[MISSING]")

       if f_path.lower().endswith(('.py', '.c', '.cpp', '.h', '.hpp', '.cxx', '.cc')):
        imgui.same_line()
        if imgui.button(f"[Structure]##{i}"):
         app.ui_editing_slices_file = f_item
         app.ui_inspecting_ast_file = f_item
         app.show_structural_editor_modal = True

       imgui.table_set_column_index(1)
       if not hasattr(f_item, "view_mode"): f_item.view_mode = "summary"
       view_modes = ["full", "summary", "skeleton", "outline", "masked", "none"]
       try:
        current_idx = view_modes.index(f_item.view_mode)
       except ValueError:
        current_idx = 1
        f_item.view_mode = "summary"
       imgui.set_next_item_width(120)
       changed_vm, new_idx = imgui.combo(f"##vm{i}", current_idx, view_modes)
       if changed_vm: f_item.view_mode = view_modes[new_idx]

       imgui.same_line()
       if imgui.button(f"[Save]##vpsave{i}"): imgui.open_popup(f"save_vp_popup{i}")

       if imgui.begin_popup(f"save_vp_popup{i}"):
        imgui.text("Preset Name:")
        changed_pname, app.ui_new_vp_name = imgui.input_text(f"##pname{i}", app.ui_new_vp_name)
        if imgui.button("OK"):
         if app.ui_new_vp_name.strip():
          app.controller._cb_save_view_preset(app.ui_new_vp_name.strip(), f_item)
          app.ui_new_vp_name = ""
          imgui.close_current_popup()
        imgui.end_popup()

       imgui.same_line()
       if imgui.button(f"[Load]##vpload{i}"): imgui.open_popup(f"load_vp_popup{i}")

       if imgui.begin_popup(f"load_vp_popup{i}"):
        vp_names = sorted([vp.name for vp in app.controller.view_presets])
        if not vp_names: imgui.text("No presets saved.")
        for vp_name in vp_names:
         if imgui.selectable(vp_name):
          app.controller._cb_apply_view_preset(vp_name, f_item)
          imgui.close_current_popup()
        imgui.end_popup()
       if hasattr(f_item, "custom_slices") and f_item.custom_slices:
        imgui.same_line()
        imgui.text_colored(theme.get_color("status_warning"), "[Slices Active]")

def render_context_presets(app: App) -> None:
 """Renders context preset management controls (combobox selector, update button, save-as, and delete active).

 SSDL: `[I:presets] -> [B:presets_combo] -> [B:save_as_button] => [B:delete_active]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [Presets Combo        v] | [Update]                     |
   | [New Preset Input      ] | [Save As]                    |
   |                          | [Delete Active]              |
   +---------------------------------------------------------+
 """
 presets      = app.controller.project.get('context_presets', {})
 preset_names = [""] + sorted(presets.keys())
 active = getattr(app, "ui_active_context_preset", "")
 if active not in preset_names: active = ""
 try:
  idx = preset_names.index(active)
 except ValueError:
  idx = 0
 
 with imscope.table("ctx_presets_layout", 2, imgui.TableFlags_.none):
  imgui.table_next_column()
  imgui.set_next_item_width(-1)
  ch, new_idx = imgui.combo("##ctx_preset", idx, preset_names)
  if ch:
   app.ui_active_context_preset = preset_names[new_idx]
   if preset_names[new_idx]:
    app.load_context_preset(preset_names[new_idx])
  
  imgui.table_next_column()
  if active:
   if imgui.button("Update##override", imgui.ImVec2(-1, 0)):
    preset_files = []
    for f in app.context_files:
     import copy
     from src import models
     p   = f.path                         if hasattr(f, 'path')            else str(f)
     vm  = f.view_mode                    if hasattr(f, 'view_mode')       else 'summary'
     slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices')   else []
     msk = copy.deepcopy(f.ast_mask)      if hasattr(f, 'ast_mask')        else {}
     sig = f.ast_signatures               if hasattr(f, 'ast_signatures')  else False
     dfn = f.ast_definitions              if hasattr(f, 'ast_definitions') else False
     preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn))
    preset = models.ContextPreset(name=active, files=preset_files, screenshots=list(app.screenshots))
    app.controller.save_context_preset(preset)
  else:
   imgui.text_disabled("No active preset")

  imgui.table_next_row()
  imgui.table_next_column()
  imgui.set_next_item_width(-1)
  changed, new_name = imgui.input_text("##new_preset", getattr(app, "ui_new_context_preset_name", "") or "")
  if changed: app.ui_new_context_preset_name = new_name
  
  imgui.table_next_column()
  if imgui.button("Save As##ctx", imgui.ImVec2(-1, 0)) or getattr(app, "_pending_save_ctx_click", False):
   app._pending_save_ctx_click = False
   name = getattr(app, "ui_new_context_preset_name", "").strip()
   if name:
    missing = []
    root = app.controller.active_project_root
    for f in app.context_files:
     path = f.path if hasattr(f, "path") else str(f)
     if not os.path.isabs(path): full_path = os.path.join(root, path)
     else:                       full_path = path
     if not os.path.exists(full_path): missing.append(path)

    if missing:
     app.missing_context_files = missing
     app.show_missing_files_modal = True
     app.target_context_preset_name = name
    else:
     preset_files = []
     for f in app.context_files:
      import copy
      from src import models
      p   = f.path                         if hasattr(f, 'path')            else str(f)
      vm  = f.view_mode                    if hasattr(f, 'view_mode')       else 'summary'
      slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices')   else []
      msk = copy.deepcopy(f.ast_mask)      if hasattr(f, 'ast_mask')        else {}
      sig = f.ast_signatures               if hasattr(f, 'ast_signatures')  else False
      dfn = f.ast_definitions              if hasattr(f, 'ast_definitions') else False
      preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn))
     preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(app.screenshots))
     app.controller.save_context_preset(preset)
     app.ui_new_context_preset_name = ""

  if active:
   imgui.table_next_row()
   imgui.table_next_column()
   imgui.table_next_column()
   if imgui.button("Delete Active", imgui.ImVec2(-1, 0)):
    app.delete_context_preset(active)
    app.ui_active_context_preset = ""

def render_snapshot_tab(app: App) -> None:
 """Renders the Snapshot tab-bar, containing the resolved aggregate markdown and active system prompts.

 SSDL: `[I:aggregate_text] -> [B:copy_buttons] => [I:preview_panes]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [Aggregate MD] [System Prompt]                          |
   | ------------------------------------------------------- |
   | [Copy]                                                  |
   | +-----------------------------------------------------+ |
   | | Resolved Markdown Content...                        | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+
 """
 with imscope.tab_bar("snapshot_tabs"):
  with imscope.tab_item("Aggregate MD") as (exp, _):
   if exp:
    display_md = app.last_aggregate_markdown
    if app.ui_focus_agent:
     tier_usage = app.mma_tier_usage.get(app.ui_focus_agent)
     if tier_usage:
      persona_name = tier_usage.get("persona")
      if persona_name:
       persona = app.controller.personas.get(persona_name)
       if persona and persona.context_preset:
        cp_name = persona.context_preset
        if cp_name in app._focus_md_cache:
         display_md = app._focus_md_cache[cp_name]
        else:
         flat = src.project_manager.flat_config(app.controller.project, app.active_discussion)
         cp   = app.controller.project.get('context_presets', {}).get(cp_name)
         if cp:
          flat["files"]["paths"]       = cp.get("files", [])
          flat["screenshots"]["paths"] = cp.get("screenshots", [])
          full_md, _, _                = src.aggregate.run(flat)
          app._focus_md_cache[cp_name] = full_md
          display_md                   = full_md
    if imgui.button("Copy"): imgui.set_clipboard_text(display_md)
    with imscope.child("last_agg_md", 0, 0, True):
     markdown_helper.render(display_md, context_id="snapshot_agg")
  with imscope.tab_item("System Prompt") as (exp, _):
   if exp:
    if imgui.button("Copy"): imgui.set_clipboard_text(app.last_resolved_system_prompt)
    with imscope.child("last_sys_prompt", 0, 0, True):
     markdown_helper.render(app.last_resolved_system_prompt, context_id="snapshot_sys")

def render_empty_context_modal(app: App) -> None:
 """Renders a popup modal warning the user when the context composition is empty.

 SSDL: `[I:warning_text] -> [B:proceed_button] => [B:cancel_button]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Empty Context Warning                               [X] |
   +---------------------------------------------------------+
   | WARNING: Empty Context Composition                      |
   | You are attempting to generate a response...            |
   | [Proceed Anyway] [Cancel]                               |
   +---------------------------------------------------------+
 """
 if app.show_empty_context_modal:
  imgui.open_popup("Empty Context Warning")
  app.show_empty_context_modal = False

 if imgui.begin_popup_modal("Empty Context Warning", True, imgui.WindowFlags_.always_auto_resize)[0]:
  imgui.text_colored(theme.get_color("status_warning"), "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._pending_generation_action == 'generate': app.controller._handle_generate_send()
   elif app._pending_generation_action == 'md_only':  app.controller._handle_md_only()
   app._pending_generation_action = None
   imgui.close_current_popup()
  imgui.same_line()
  if imgui.button("Cancel", imgui.ImVec2(120, 0)):
   imgui.close_current_popup()
  imgui.end_popup()

def render_context_modals(app: App) -> None:
 """Dispatches rendering calls to sub-modals relating to context management
 (empty context warning, file picker, missing files warning, and AST inspector).

 SSDL: `[I] -> [I:modals_dispatch]`

 ASCII Layout Map:
   Conditionally opens (in order):
     [Empty Context Warning modal]
     [Select Context Files modal]
     [Missing Files Warning modal]
     [Structural File Editor modal]
 """
 render_empty_context_modal(app)
 render_add_context_files_modal(app)

 if app.show_missing_files_modal:
  imgui.open_popup("Missing Files Warning")
  app.show_missing_files_modal = False
 
 if imgui.begin_popup_modal("Missing Files Warning", True, imgui.WindowFlags_.always_auto_resize)[0]:
  imgui.text("The following files are missing from disk:")
  imgui.separator()
  imgui.begin_child("missing_files_list", imgui.ImVec2(0, 150), True)
  for f in app.missing_context_files:
   imgui.text_colored(theme.get_color("status_error"), f)
  imgui.end_child()
  imgui.separator()
  
  if imgui.button("Save Anyway") or getattr(app, "_pending_save_anyway_click", False):
   app._pending_save_anyway_click = False
   name = app.target_context_preset_name
   preset_files = []
   for f in app.context_files:
    import copy
    from src import models
    p   = f.path                         if hasattr(f, 'path')            else str(f)
    vm  = f.view_mode                    if hasattr(f, 'view_mode')       else 'summary'
    slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices')   else []
    msk = copy.deepcopy(f.ast_mask)      if hasattr(f, 'ast_mask')        else {}
    sig = f.ast_signatures               if hasattr(f, 'ast_signatures')  else False
    dfn = f.ast_definitions              if hasattr(f, 'ast_definitions') else False
    preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn))
   preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(app.screenshots))
   app.controller.save_context_preset(preset)
   app.ui_new_context_preset_name = ""
   imgui.close_current_popup()
  
  imgui.same_line()
  if imgui.button("Cancel"):
   imgui.close_current_popup()
  
  imgui.end_popup()

 render_ast_inspector_modal(app)

def _get_context_composition_state(app: App) -> tuple:
 files_state = []
 for f in app.context_files:
  p    = f.path           if hasattr(f, 'path')           else str(f)
  vm   = f.view_mode      if hasattr(f, 'view_mode')      else 'summary'
  agg  = f.auto_aggregate if hasattr(f, 'auto_aggregate') else False
  slc  = tuple((s.get('start_line'), s.get('end_line'), s.get('tag'), s.get('comment')) for s in getattr(f, 'custom_slices', []))
  mask = tuple(sorted(getattr(f, 'ast_mask', {}).items()))
  files_state.append((p, vm, agg, slc, mask))
 screenshots_state = tuple(app.screenshots)
 return (tuple(files_state), screenshots_state)

def _check_auto_refresh_context_preview(app: App) -> None:
 current_state = _get_context_composition_state(app)
 if not hasattr(app, "_last_context_preview_state") or app._last_context_preview_state != current_state:
  app._last_context_preview_state = current_state
  if not any(getattr(f, 'auto_aggregate', False) for f in app.context_files) and not app.screenshots:
   app.context_preview_text = "# Context Composition Empty\n\nNo files or screenshots have been selected for aggregation."
   return
  
  if getattr(app, "_is_generating_preview", False):
   app._pending_preview_refresh = True
   return
   
  app._is_generating_preview = True
  def worker():
   try:
    app.controller.context_files = app.context_files
    res                          = app.controller._do_generate()
    app.context_preview_text     = res[0]
   except Exception:
    app.context_preview_text = "Error generating context preview."
   finally:
    app._is_generating_preview = False
    if getattr(app, "_pending_preview_refresh", False):
     app._pending_preview_refresh = False
     # This will trigger again on next GUI frame because _last_context_preview_state 
     # will be slightly behind if another change happened during the thread.
     # Or we just clear the state so it re-triggers.
     app._last_context_preview_state = None
  
  app.controller.submit_io(worker)

def render_context_preview_window(app: App) -> None:
 """Renders the Context Preview window. Automatically refreshes aggregated context previews in the background
 and renders formatted markdown text.

 SSDL: `[I:preview_text] -> [B:clipboard_button] => [I:preview_box]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Context Preview                                         |
   +---------------------------------------------------------+
   | [Close] [Copy to Clipboard]                             |
   | +-----------------------------------------------------+ |
   | | # Context Composition                               | |
   | | ## src/gui_2.py                                     | |
   | | ...aggregated markdown...                           | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+
 """
 _check_auto_refresh_context_preview(app)
 with imscope.window("Context Preview", app.show_windows["Context Preview"]) as (exp, opened):
  app.show_windows["Context Preview"] = bool(opened)
  if not opened:
   app.ui_separate_context_preview = False
  if exp:
   if imgui.button("Close"):
    app.show_windows["Context Preview"] = False
    app.ui_separate_context_preview     = False
   imgui.same_line()
   if imgui.button("Copy to Clipboard"):
    imgui.set_clipboard_text(app.context_preview_text)
   with imscope.child("ctx_preview_scroll", 0, 0, True):
    markdown_helper.render(app.context_preview_text, context_id="ctx_preview")

#endregion: Context Management

#region: Discussions

def render_discussion_hub(app: App) -> None:
 """Top-level hub for the Discussion panel. Houses tabs for Discussion, Context Composition,
 Context Preview (inline or detached), Snapshot, and Takes.

 SSDL: `[B:pop_out_toggle] -> [I:tab_bar] => [I:active_tab_content]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [ ] Pop Out Context Preview                             |
   | [Discussion] [Context Composition] [Context Preview]   |
   | [Snapshot]   [Takes]                                    |
   | +-----------------------------------------------------+ |
   | | <active tab content>                                | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+
 """
 _check_auto_refresh_context_preview(app)
 ch, popped = imgui.checkbox("Pop Out Context Preview", getattr(app, "ui_separate_context_preview", False))
 if ch:
  app.ui_separate_context_preview = popped
  app.show_windows["Context Preview"] = popped
 with imscope.tab_bar("discussion_hub_tabs"):
  with imscope.tab_item("Discussion") as (exp, opened):
   if exp: render_discussion_tab(app)
  with imscope.tab_item("Context Composition") as (exp, opened):
   if exp: render_context_composition_panel(app)
  if not getattr(app, "ui_separate_context_preview", False):
   with imscope.tab_item("Context Preview") as (exp, opened):
    if exp:
     if imgui.button("Copy to Clipboard"):
      imgui.set_clipboard_text(app.context_preview_text)
     with imscope.child("ctx_preview_scroll_tab", 0, 0, True):
      markdown_helper.render(app.context_preview_text, context_id="ctx_preview_tab")
  with imscope.tab_item("Snapshot") as (exp, opened):
   if exp: render_snapshot_tab(app)
  with imscope.tab_item("Takes") as (exp, opened):
   if exp: render_takes_panel(app)
 return

def render_thinking_trace(app: App, entry: dict, segments: list[dict], entry_index: int, is_standalone: bool = False) -> None:
 """Renders a collapsible thinking-trace section within a discussion entry bubble.
 Displays each reasoning segment labeled by its marker tag (e.g. [thinking]).

 SSDL: `[I:segment_list] -> [B:read_pure_toggle] => [I:trace_child]`

 ASCII Layout Map:
   +----------------------------------------------------------+
   | > Monologue (3 traces)                                   |
   |   [Pure] / [Read]  Selectable toggle                     |
   |   +----------------------------------------------------+ |
   |   | [thinking] ...reasoning text...                    | |
   |   | ---                                                | |
   |   | [thinking] ...continued...                         | |
   |   +----------------------------------------------------+ |
   +----------------------------------------------------------+
 """
 if not segments:
  return
 # Tint thinking trace background slightly differently
 with imscope.style_color(imgui.Col_.child_bg, theme.get_color("bubble_vendor", alpha=0.7)), \
      theme.ai_text_style():
  with imscope.indent():
   show_content = True
   if not is_standalone:
    header_label = f"Monologue ({len(segments)} traces)###thinking_header_{entry_index}"
    show_content = imgui.collapsing_header(header_label)
   if show_content:
    thinking_read_mode = entry.get("thinking_read_mode", True)
    if imgui.button(f"[Pure]##think_pure_{entry_index}" if thinking_read_mode else f"[Read]##think_read_{entry_index}"):
     entry["thinking_read_mode"] = not thinking_read_mode
    imgui.same_line()
    imgui.text_colored(C_TC(), "Selectable toggle")
    h = 150 if is_standalone else 100
    with imscope.child(f"thinking_content_{entry_index}", 0, h, True):
     for idx, seg in enumerate(segments):
      content = seg.get("content", "")
      marker  = seg.get("marker", "thinking")
      with imscope.id(f"think_{entry_index}_{idx}"):
       imgui.text_colored(C_TC(), f"[{marker}]")
       if thinking_read_mode:
        if app.ui_word_wrap:
         with imscope.text_wrap(imgui.get_content_region_avail().x): imgui.text(content)
        else:                                                        imgui.text(content)
       else:
        render_selectable_label(app, f"think_text_{entry_index}_{idx}", content, multiline=True, height=-1)
      imgui.separator()

def render_discussion_entry(app: App, entry: dict, index: int) -> None:
 """Renders a single discussion message entry as a colored visual bubble.
 Supports collapsible states, read/edit mode toggles, role selection,
 input/output token metrics, and action footers.

 SSDL: `[I:header] -> [B:collapsed] => [I:preview] | [I:body] -> [I:footer]`

 ASCII Layout Map:
   +-------------------------------------------------------------+
   | [-] Entry #3   [Role: AI v]  [Read]   @2026-06-12   in:12   |
   +-------------------------------------------------------------+
   |                                                             |
   |  [thinking trace: <click to expand>]                        |
   |                                                             |
   |  "I think the right approach is..."                         |
   |                                                             |
   +-------------------------------------------------------------+
   | [Ins]  [Del]  [Branch]                                      |
   +-------------------------------------------------------------+
 """
 with imscope.id(f"disc_{index}"):
  role   = entry.get("role", "User")
  bg_col = theme.get_role_tint(role)
  
  draw_list  = imgui.get_window_draw_list()
  p_min      = imgui.get_cursor_screen_pos()
  full_width = imgui.get_content_region_avail().x
  
  # Start Background Layer (Channel 0: Background, Channel 1: Foreground)
  draw_list.channels_split(2)
  draw_list.channels_set_current(1)
  
  imgui.begin_group()
  # FORCE GROUP TO FULL WIDTH to prevent Markdown table squashing
  imgui.dummy(imgui.ImVec2(full_width, 0))
  
  # Header controls
  collapsed, read_mode = entry.get("collapsed", False), entry.get("read_mode", False)
  if imgui.button("+" if collapsed else "-"): entry["collapsed"] = not collapsed
  imgui.same_line()
  render_text_viewer(app, f"Entry #{index+1}", entry["content"], id_suffix=f"disc_btn_{index}")
  imgui.same_line(); imgui.set_next_item_width(120)
  if imgui.begin_combo("##role", entry["role"]):
   for r in app.disc_roles:
    if imgui.selectable(r, r == entry["role"])[0]: entry["role"] = r
   imgui.end_combo()
  if not collapsed:
   imgui.same_line()
   if imgui.button("[Edit]" if read_mode else "[Read]"): entry["read_mode"] = not read_mode
  
  ts_str = entry.get("ts", "")
  usage  = entry.get("usage", {})
  if ts_str or usage:
   imgui.same_line()
   if ts_str: imgui.text_colored(C_SUB(), str(ts_str))
   if usage:
    inp, out, cache = usage.get("input_tokens", 0), usage.get("output_tokens", 0), usage.get("cache_read_input_tokens", 0)
    u_str = f" in:{inp} out:{out}" + (f" cache:{cache}" if cache else "")
    imgui.same_line(); imgui.text_colored(theme.get_color("status_info"), u_str)

  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"):
    if entry in app.disc_entries: app.disc_entries.remove(entry)
    imgui.end_group()
    draw_list.channels_merge()
    return
   imgui.same_line()
   if imgui.button("Branch"): app._branch_discussion(index)
   imgui.same_line(); preview = entry["content"].replace("\n", " ")[:60]
   if len(entry["content"]) > 60: preview += "..."
   imgui.text_colored(C_SUB(), preview)
  else:
   # Body content - FORCE START ON NEW LINE to prevent horizontal squashing
   imgui.new_line()
   imgui.spacing()
   
   thinking_segments, has_content = entry.get("thinking_segments", []), bool(entry.get("content", "").strip())
   if thinking_segments:
    render_thinking_trace(app, entry, thinking_segments, index, is_standalone=not has_content)
    imgui.spacing()
    
   if read_mode:
    render_discussion_entry_read_mode(app, entry, index)
   else:
    if not (bool(thinking_segments) and not has_content):
     # Ensure multiline editor uses full width
     imgui.set_next_item_width(-1)
     ch, entry["content"] = imgui.input_text_multiline("##content", entry["content"], imgui.ImVec2(-1, 150))
  
  imgui.end_group()
  
  # Finalize Background Tint
  draw_list.channels_set_current(0)
  p_max   = imgui.get_item_rect_max()
  # Ensure full width coverage of the panel
  p_max.x = p_min.x + full_width + imgui.get_style().window_padding.x
  draw_list.add_rect_filled(p_min, p_max, imgui.get_color_u32(bg_col), 4.0)
  draw_list.channels_merge()
  
  imgui.separator()

def render_discussion_entry_read_mode(app: App, entry: dict, index: int) -> None:
 """Renders a discussion entry in read-only mode.
 Parses the markdown content, isolates retrieved context sections (RAG chunks),
 handles custom definition/AST links, and invokes the markdown syntax highlighter.

 SSDL: `[I:extract_rag] -> [B:definitions?] => [I:markdown]`

 ASCII Layout Map:
   +-------------------------------------------------------------+
   | > Retrieved Context (collapsible)                           |
   |   > Chunk 1: src/gui_2.py (collapsible)                     |
   |     [Source]  ...chunk text...                              |
   | ---                                                         |
   | > [Definition: foo from gui_2.py (line 42)] (collapsible)  |
   |   [Source]  ```python...```                                 |
   | ---                                                         |
   |  ## Markdown heading                                        |
   |  Rendered response text...                                  |
   +-------------------------------------------------------------+
 """
 with imscope.id(f"read_{index}"):
  content = entry["content"]
  if not content.strip(): return
  
  if '## Retrieved Context' in content:
   rag_match = re.search(r'## Retrieved Context\n\n([\s\S]*?)(?=\n\n#|\Z)', content)
   if rag_match:
    rag_section = rag_match.group(1)
    if imgui.collapsing_header('Retrieved Context'):
     chunks = re.finditer(r'### Chunk (\d+) \(Source: (.*?)\)\n([\s\S]*?)(?=\n### Chunk|\Z)', rag_section)
     for chunk_match in chunks:
      idx, path, chunk_content = chunk_match.group(1), chunk_match.group(2), chunk_match.group(3)
      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 = 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]*?```)?")
  matches = list(pattern.finditer(content))
  
  # Provide a stable width by using a group and ensuring it starts on a new line
  imgui.begin_group()
  with theme.ai_text_style():
   if not matches:
    markdown_helper.render(content, context_id=f"disc_{index}")
   else:
    last_idx = 0
    for m_idx, match in enumerate(matches):
     before = content[last_idx:match.start()]
     if before: markdown_helper.render(before, context_id=f"disc_{index}_b_{m_idx}")
     header_text, path, code_block = match.group(0).split("\n")[0].strip(), match.group(2), match.group(4)
     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 = path, res, (Path(path).suffix.lstrip(".") if Path(path).suffix else "text"); app.show_windows["Text Viewer"] = True
      if code_block: markdown_helper.render(code_block, context_id=f"disc_{index}_c_{m_idx}")
     last_idx = match.end()
    after = content[last_idx:]
    if after: markdown_helper.render(after, context_id=f"disc_{index}_a")
  imgui.end_group()

def render_history_window(app: App) -> None:
 """Renders the Undo/Redo History window. Displays past UI snapshots in reverse chronological
 order and allows reverting to prior states.

 SSDL: `[I:history_list] -> [B:undo_redo_buttons] => [B:selectable_snapshots]`
 """
 if not app.show_windows.get('Undo/Redo History', False):
  return
 def iterate_history(history: typing.List[typing.Dict[str, typing.Any]]) -> None:
  for i, entry in enumerate(reversed(history)):
   actual_idx  = len(history) - 1 - i
   desc        = entry.get("description", "UI Change")
   ts          = entry.get("timestamp", 0.0)
   ts_str      = datetime.datetime.fromtimestamp(ts).strftime("%H:%M:%S")
   label       = f"[{ts_str}] {desc}##{actual_idx}"
   _, selected = imgui.selectable(label, False)
   if selected: app._handle_jump_to_history(actual_idx)
 with imscope.window("Undo/Redo History", app.show_windows['Undo/Redo History']) as (exp, opened):
   app.show_windows['Undo/Redo History'] = bool(opened)
   if exp:
    if imgui.button("Undo") and app.history.can_undo: app._handle_undo()
    imgui.same_line()
    if imgui.button("Redo") and app.history.can_redo: app._handle_redo()
    imgui.separator()
    with imscope.child("history_list", 0, 0, True):
     history = app.history.get_history()
     if not history: imgui.text("No history available.")
     else: iterate_history(history)

def render_session_insights_panel(app: App) -> None:
 """Renders session productivity insights, displaying total tokens, API call counts,
 burn rates, total costs, completed ticket counts, and token efficiency.

 SSDL: `[I:insights] -> [I:telemetry_texts]`
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_session_insights_panel")
 imgui.text_colored(C_LBL(), 'Session Insights')
 imgui.separator()
 insights = app.controller.get_session_insights()
 imgui.text(f"Total Tokens: {insights.get('total_tokens', 0):,}")
 imgui.text(f"API Calls: {insights.get('call_count', 0)}")
 imgui.text(f"Burn Rate: {insights.get('burn_rate', 0):.0f} tokens/min")
 imgui.text(f"Session Cost: ${insights.get('session_cost', 0):.4f}")
 completed  = insights.get('completed_tickets', 0)
 efficiency = insights.get('efficiency', 0)
 imgui.text(f"Completed: {completed}")
 imgui.text(f"Tokens/Ticket: {efficiency:.0f}" if efficiency > 0 else "Tokens/Ticket: N/A")
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_session_insights_panel")

def render_prior_session_view(app: App) -> None:
 """Renders a historical read-only view of a loaded prior discussion session, complete
 with collapsing message bubbles and bubble colors.

 SSDL: `[I:prior_entries] -> [B:exit_button] -> [I:scroll_list]`
 """
 with imscope.style_color(imgui.Col_.child_bg, theme.get_color("bubble_vendor")):
  if imgui.button("Exit Prior Session"): app.controller.cb_exit_prior_session(); app._comms_log_dirty = True
  imgui.same_line()
  imgui.text_colored(theme.get_color("status_warning"), f"({len(app.prior_disc_entries)} entries)")
  imgui.separator()
  avail = imgui.get_content_region_avail()
  with imscope.child("prior_scroll", imgui.ImVec2(avail.x, avail.y), imgui.WindowFlags_.horizontal_scrollbar):
   for idx, entry in enumerate(app.prior_disc_entries):
    with imscope.id(f"prior_disc_{idx}"):
     collapsed = entry.get("collapsed", False)
     if imgui.button("+" if collapsed else "-"): entry["collapsed"] = not collapsed
     imgui.same_line(); role, ts = entry.get("role", "??"), entry.get("ts", "")
     imgui.text_colored(C_LBL(), f"[{role}]")
     if ts: imgui.same_line(); imgui.text_colored(theme.get_color("text_disabled"), str(ts))
     content = entry.get("content", "")
     if collapsed:
      imgui.same_line(); preview = content.replace("\n", " ")[:80]
      if len(content) > 80: preview += "..."
      imgui.text_colored(theme.get_color("text_disabled"), preview)
     else:
      with theme.ai_text_style():
       markdown_helper.render(content, context_id=f'prior_disc_{idx}')
     imgui.separator()

def render_thinking_indicator(app: App) -> None:
 """Renders a blinking red status indicator when the AI is currently executing,
 sending, streaming, or running shell commands.

 SSDL: `[I:ai_status] -> [I:blinking_text]`
 """
 is_thinking = app.ai_status in ['sending...', 'streaming...', 'running powershell...']
 if is_thinking:
  val   = math.sin(time.time() * 10 * math.pi)
  alpha = 1.0 if val > 0 else 0.0
  c     = theme.get_color("status_error", alpha=alpha)
  imgui.text_colored(c, "THINKING...");

def _on_warmup_complete_callback(app: App, status: dict) -> None:
 """Thread-safe callback registered with controller.on_warmup_complete()
 during App._post_init. Records the completion timestamp; the
 indicator function uses it to show a brief "ready" tag. Also
 appends a message to a lock-protected list that the indicator
 (or the diagnostics panel) can read.
 Runs on a background _io_pool thread; only sets primitive state.
 """
 try:
  app._warmup_completion_ts = time.time()
  pending   = status.get("pending",   [])
  completed = status.get("completed", [])
  failed    = status.get("failed",    [])
  total     = len(pending) + len(completed) + len(failed)
  if failed: msg = f"Warmup finished with {len(failed)} failures ({total} modules)"
  else:      msg = f"All imports ready ({total} modules)"
  if not hasattr(app, "_warmup_toast_lock"):
   import threading as _threading #TODO(Ed): Review local import
   app._warmup_toast_lock = _threading.Lock()
  with app._warmup_toast_lock:
   if not hasattr(app, "_warmup_toast_messages"): app._warmup_toast_messages = []
   app._warmup_toast_messages.append((time.time(), msg))
 except Exception: pass

def render_warmup_status_indicator(app: App) -> None:
 """Renders a transient warmup status indicator in the main interface frame. Shows progress
 of AppController's background module imports.

 SSDL: `[I:warmup_status] -> [I:status_text]`
 """
 controller = getattr(app, "controller", None)
 if controller is None: return
 if not hasattr(controller, "warmup_status"): return
 try:
  status = controller.warmup_status()
 except Exception: return
 pending   = status.get("pending", [])
 completed = status.get("completed", [])
 failed    = status.get("failed", [])
 if pending:
  total = len(pending)   + len(completed) + len(failed)
  done  = len(completed) + len(failed)
  c     = theme.get_color("status_warning")
  imgui.text_colored(c, f"Warming up... ({done}/{total})")
  return
 if failed:
  c = theme.get_color("status_error")
  imgui.text_colored(c, f"Imports: {len(failed)} failed")
  return
 # Steady state + transient 3s "ready" tag after completion.
 ts = getattr(app, "_warmup_completion_ts", 0.0)
 if ts > 0 and (time.time() - ts) < 3.0:
  total = len(completed) + len(failed)
  c     = theme.get_color("status_success")
  imgui.text_colored(c, f"All imports ready ({total} modules)")
  return
 # No render: warmup done, no failures, transient window expired.

def render_synthesis_panel(app: App) -> None:
 """Renders a panel for synthesizing multiple discussion takes."""
 imgui.text("Select takes to synthesize:")
 discussions = app.project.get('discussion', {}).get('discussions', {})
 if not isinstance(getattr(app, 'ui_synthesis_selected_takes', None), dict): app.ui_synthesis_selected_takes = {name: False for name in discussions}
 if not isinstance(getattr(app, 'ui_synthesis_prompt', None), str):          app.ui_synthesis_prompt         = ""
 for name in discussions: _, app.ui_synthesis_selected_takes[name] = imgui.checkbox(name, app.ui_synthesis_selected_takes.get(name, False))
 imgui.spacing()
 imgui.text("Synthesis Prompt:")
 _, app.ui_synthesis_prompt = imgui.input_text_multiline("##synthesis_prompt", app.ui_synthesis_prompt, imgui.ImVec2(-1, 100))
 if imgui.button("Generate Synthesis"):
  selected = [name for name, sel in app.ui_synthesis_selected_takes.items() if sel]
  if len(selected) > 1:
   discussions_dict = app.project.get('discussion', {}).get('discussions', {})
   takes_dict       = {name: discussions_dict.get(name, {}).get('history', []) for name in selected}
   diff_text        = synthesis_formatter.format_takes_diff(takes_dict)
   prompt           = f"{app.ui_synthesis_prompt}\n\nHere are the variations:\n{diff_text}"
   
   new_name = "synthesis_take"
   counter  = 1
   while new_name in discussions_dict:
    new_name = f"synthesis_take_{counter}"
    counter += 1
    
   app._create_discussion(new_name)
   with app._disc_entries_lock: app.disc_entries.append({"role": "User", "content": prompt, "collapsed": False, "ts": project_manager.now_ts()})
   app._handle_generate_send()

def render_comms_history_panel(app: App) -> None:
 """Renders the communications history log panel. Displays outgoing requests, incoming responses,
 and tool call inputs/outputs in chronological order.

 SSDL: `[I:ai_status] -> [B:clear_exit_buttons] -> [I:direction_colors] -> [I:entries_scroll_list]`
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_comms_history_panel")
 st_col = theme.get_color("text_disabled")
 if theme.is_nerv_active(): st_col = theme.get_color("status_success") # DATA_GREEN for status in NERV
 imgui.text_colored(st_col, f"Status: {app.ai_status}")
 imgui.same_line()
 if imgui.button("Clear##comms"):
  ai_client.clear_comms_log()
  app._comms_log.clear()
  app._comms_log_dirty = True
 if app.is_viewing_prior_session:
  imgui.same_line()
  if imgui.button("Exit Prior Session"):
   app.controller.cb_exit_prior_session()
   app._comms_log_dirty = True
 imgui.separator()
 
 imgui.text_colored(C_OUT(), "OUT");      imgui.same_line()
 imgui.text_colored(C_REQ(), "request");  imgui.same_line()
 imgui.text_colored(C_TC(), "tool_call"); imgui.same_line()
 imgui.text("  ");                        imgui.same_line()
 imgui.text_colored(C_IN(), "IN");        imgui.same_line()
 imgui.text_colored(C_RES(), "response"); imgui.same_line()
 imgui.text_colored(C_TR(), "tool_result")
 imgui.separator()

 avail = imgui.get_content_region_avail()
 with imscope.child("comms_scroll", imgui.ImVec2(avail.x, avail.y), imgui.WindowFlags_.horizontal_scrollbar):
  log_to_render = app._comms_log_cache
  
  for i, entry in enumerate(log_to_render):
   imgui.push_id(f"comms_entry_{i}")
  
   i_display = i + 1
   ts        = entry.get("ts",        "00:00:00")
   direction = entry.get("direction", "??")
   kind      = entry.get("kind", entry.get("type", "??"))
   provider  = entry.get("provider",    "?")
   model     = entry.get("model",       "?")
   tier      = entry.get("source_tier", "main")
   payload   = entry.get("payload",     {})
   if not payload and kind not in ("request", "response", "tool_call", "tool_result"):
    payload = entry # legacy

   # Row 1: #Idx TS DIR KIND Provider/Model [Tier]
   imgui.text_colored(C_LBL(), f"#{i_display}"); imgui.same_line()
   imgui.text_colored(theme.get_color("text_disabled"), ts)
  
   latency = entry.get("latency") or entry.get("metadata", {}).get("latency")
   if latency:
    imgui.same_line()
    imgui.text_colored(C_SUB(), f" ({latency:.2f}s)")

   ticket_id = entry.get("mma_ticket_id")
   if ticket_id:
    imgui.same_line()
    imgui.text_colored(theme.get_color("status_error"), f"[{ticket_id}]")
   imgui.same_line()
   d_col_fn = DIR_COLORS.get(direction, C_VAL)
   k_col_fn = KIND_COLORS.get(kind,     C_VAL)
   imgui.text_colored(d_col_fn(), direction);          imgui.same_line()
   imgui.text_colored(k_col_fn(), kind);               imgui.same_line()
   imgui.text_colored(C_LBL(), f"{provider}/{model}"); imgui.same_line()
   imgui.text_colored(C_SUB(), f"[{tier}]")
  
   # Optimized content rendering using _render_heavy_text logic
   idx_str = str(i)
   if kind == "request":
    usage = payload.get("usage", {})
    if usage:
     inp = usage.get("input_tokens", 0)
     imgui.text_colored(C_LBL(), f" tokens in:{inp}")
    render_heavy_text(app, "message", payload.get("message", ""), idx_str)
    if payload.get("system"):
     render_heavy_text(app, "system", payload.get("system", ""), idx_str)
   elif kind == "response":
    r         = payload.get("round", 0)
    sr        = payload.get("stop_reason", "STOP")
    usage     = payload.get("usage", {})
    usage_str = ""
    if usage:
     inp       = usage.get("input_tokens", 0)
     out       = usage.get("output_tokens", 0)
     cache     = usage.get("cache_read_input_tokens", 0)
     usage_str = f" in:{inp} out:{out}"
     if cache: usage_str += f" cache:{cache}"
    imgui.text_colored(C_LBL(), f"round: {r} stop_reason: {sr}{usage_str}")
   
    text_content              = payload.get("text", "")
    segments, parsed_response = thinking_parser.parse_thinking_trace(text_content)
    if segments:        render_thinking_trace(app, payload, [{"content": s.content, "marker": s.marker} for s in segments], i, is_standalone=not bool(parsed_response.strip()))
    if parsed_response: render_heavy_text(app, "text", parsed_response, idx_str)
   
    tcs = payload.get("tool_calls", [])
    if tcs: render_heavy_text(app, "tool_calls", json.dumps(tcs, indent=1), idx_str)
  
   elif kind == "tool_call":   render_heavy_text(app, payload.get("name", "call"), payload.get("script") or json.dumps(payload.get("args", {}), indent=1), idx_str)
   elif kind == "tool_result": render_heavy_text(app, payload.get("name", "result"), payload.get("output", ""), idx_str)
   else:                       render_heavy_text(app, "data", str(payload), idx_str)
   imgui.separator()
   imgui.pop_id()

  if app._scroll_comms_to_bottom:
   imgui.set_scroll_here_y(1.0)
   app._scroll_comms_to_bottom = False

 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_comms_history_panel")

def render_takes_panel(app: App) -> None:
 """Renders the Takes & Synthesis panel. Lists all discussion takes in a table,
 allows switching or deleting them, and provides a multi-take synthesis workflow.

 SSDL: `[I:takes_table] -> [B:switch/delete] -> [I:synthesis_config] => [B:generate_synthesis]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Takes & Synthesis                                       |
   | +----------+----------+-----------------------------+   |
   | | Name     | Entries  | Actions                     |   |
   | +----------+----------+-----------------------------+   |
   | | main     | 14       | [Switch] [Delete]           |   |
   | | take_2   | 3        | [Switch] [Delete]           |   |
   | +----------+----------+-----------------------------+   |
   | Synthesis                                               |
   | [ ] main  [ ] take_2                                    |
   | Synthesis Prompt:                                       |
   | +-----------------------------------------------------+ |
   | | Compare and reconcile these takes...                | |
   | +-----------------------------------------------------+ |
   | [Generate Synthesis]                                    |
   +---------------------------------------------------------+
 """
 imgui.text("Takes & Synthesis")
 imgui.separator()
 discussions = app.project.get('discussion', {}).get('discussions', {})
 if not isinstance(getattr(app, 'ui_synthesis_selected_takes', None), dict): app.ui_synthesis_selected_takes = {name: False for name in discussions}
 if not isinstance(getattr(app, 'ui_synthesis_prompt',         None), str):  app.ui_synthesis_prompt         = ""
 if imgui.begin_table("takes_table", 3, imgui.TableFlags_.resizable | imgui.TableFlags_.borders):
  imgui.table_setup_column("Name",    imgui.TableColumnFlags_.width_stretch)
  imgui.table_setup_column("Entries", imgui.TableColumnFlags_.width_fixed, 80)
  imgui.table_setup_column("Actions", imgui.TableColumnFlags_.width_fixed, 150)
  imgui.table_headers_row()
  for name, disc in list(discussions.items()):
   imgui.table_next_row()
   imgui.table_set_column_index(0)
   is_active = name == app.active_discussion
   if is_active: 
    imgui.text_colored(C_IN(), name)
   else:
    imgui.text(name)
   imgui.table_set_column_index(1)
   history = disc.get('history', [])
   imgui.text(f"{len(history)}")
   imgui.table_set_column_index(2)
   if imgui.button(f"Switch##{name}"):
    app._switch_discussion(name)
   imgui.same_line()
   if name != "main" and imgui.button(f"Delete##{name}"):
    del discussions[name]
  imgui.end_table()
 imgui.separator()
 imgui.text("Synthesis")
 imgui.text("Select takes to synthesize:")
 for name in discussions:
  _, app.ui_synthesis_selected_takes[name] = imgui.checkbox(name, app.ui_synthesis_selected_takes.get(name, False))
 imgui.spacing()
 imgui.text("Synthesis Prompt:")
 _, app.ui_synthesis_prompt = imgui.input_text_multiline("##synthesis_prompt", app.ui_synthesis_prompt, imgui.ImVec2(-1, 100))
 if imgui.button("Generate Synthesis"):
  selected = [name for name, sel in app.ui_synthesis_selected_takes.items() if sel]
  if len(selected) > 1:
   from src import synthesis_formatter
   takes_dict = {name: discussions.get(name, {}).get('history', []) for name in selected}
   diff_text  = synthesis_formatter.format_takes_diff(takes_dict)
   prompt     = f"{app.ui_synthesis_prompt}\n\nHere are the variations:\n{diff_text}"
   new_name   = "synthesis_take"
   counter    = 1
   while new_name in discussions:
    new_name = f"synthesis_take_{counter}"
    counter += 1
   app._create_discussion(new_name)
   with app._disc_entries_lock:
    app.disc_entries.append({"role": "user", "content": prompt, "collapsed": False, "ts": project_manager.now_ts()})
   app._handle_generate_send()

def render_discussion_entries(app: App) -> None:
 """Renders the scrollable list of all discussion entry bubbles. When a focus agent is
 active (ui_focus_agent), only entries matching that agent's persona (or User) are shown.

 SSDL: `[I:filter_by_agent?] -> [I:scroll_child] => [I:entry_bubbles]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | +-----------------------------------------------------+ |
   | | Entry #1 [User] ...                                 | |
   | | Entry #2 [AI]   ...                                 | |
   | | Entry #3 [User] ...                                 | |
   | |                              <- scroll-to-bottom -> | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+
 """
 with imscope.child("disc_scroll"):
  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:
    persona_name = tier_usage.get("persona")
    if persona_name: display_entries = [e for e in app.disc_entries if e.get("role") == persona_name or e.get("role") == "User"]
  for i, entry in enumerate(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:
 """Renders the action buttons at the bottom of the discussion panel.
 Handles adding entries, expanding/collapsing all bubbles, clearing, saving,
 compressing logs, and configuring auto-history checkpoints.

 SSDL: `[I:buttons] -> [B:clicks] => [S:entries_or_config]`

 ASCII Layout Map:
   +-------------------------------------------------------------+
   | [+ Entry] [-All] [+All] [Clear All] [Save] [Compress]       |
   | [ ] Auto-add message & response to history                  |
   | Keep Pairs: [15     ] [Truncate]                            |
   +-------------------------------------------------------------+
 """
 if imgui.button("+ Entry"): app.disc_entries.append({"role": app.disc_roles[0] if app.disc_roles else "User", "content": "", "collapsed": True, "ts": project_manager.now_ts()})
 imgui.same_line()
 if imgui.button("-All"):
  for e in app.disc_entries: e["collapsed"] = True
 imgui.same_line()
 if imgui.button("+All"):
  for e in app.disc_entries: e["collapsed"] = False
 imgui.same_line()
 if imgui.button("Clear All"): app.disc_entries.clear()
 imgui.same_line()
 if imgui.button("Save"): app._flush_to_project(); app._flush_to_config(); app.save_config(); app.ai_status = "discussion saved"
 imgui.same_line()
 if imgui.button("Compress"): app.controller._handle_compress_discussion()
 _, app.ui_auto_add_history = imgui.checkbox("Auto-add message & response to history", app.ui_auto_add_history)
 imgui.text("Keep Pairs:"); imgui.same_line(); imgui.set_next_item_width(140)
 ch, app.ui_disc_truncate_pairs = imgui.drag_int("##trunc_pairs", app.ui_disc_truncate_pairs, 1, 1, 999)
 if  app.ui_disc_truncate_pairs < 1: app.ui_disc_truncate_pairs = 1
 imgui.same_line()
 if imgui.button("Truncate"):
  with app._disc_entries_lock: app.disc_entries = truncate_entries(app.disc_entries, app.ui_disc_truncate_pairs)
  app.ai_status = f"history truncated to {app.ui_disc_truncate_pairs} pairs"

def render_discussion_metadata(app: App) -> None:
 """Renders per-discussion metadata: cumulative token counts, git commit association,
 last updated timestamp, and controls for creating, renaming, and deleting discussions.

 SSDL: `[I:token_totals] -> [I:commit_row] -> [I:timestamp] -> [B:create/rename/delete]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Discussion Tokens: 1200 In | 800 Out | 0 Cache          |
   | commit: a1b2c3d4   [Update Commit]                       |
   | updated: 2026-06-12T22:00                                |
   | [new-name________] [Create] [Rename] [Delete]            |
   +---------------------------------------------------------+
 """
 disc_data                = app.project.get("discussion", {}).get("discussions", {}).get(app.active_discussion, {})
 git_commit, last_updated = disc_data.get("git_commit", ""), disc_data.get("last_updated", "")
 
 total_in, total_out, total_cache = 0, 0, 0
 for entry in app.disc_entries:
  if "usage" in entry:
   total_in    += entry["usage"].get("input_tokens", 0)
   total_out   += entry["usage"].get("output_tokens", 0)
   total_cache += entry["usage"].get("cache_read_input_tokens", 0)
 if total_in > 0 or total_out > 0:
  imgui.text_colored(theme.get_color("status_info"), f"Discussion Tokens: {total_in} In | {total_out} Out | {total_cache} Cache")
  imgui.separator()

 imgui.text_colored(C_LBL(), "commit:"); imgui.same_line()
 render_selectable_label(app, 'git_commit_val', git_commit[:12] if git_commit else '(none)', width=100, color=(C_IN() if git_commit else C_LBL()))
 imgui.same_line()
 if imgui.button("Update Commit"):
  if app.ui_project_git_dir:
   cmt = project_manager.get_git_commit(app.ui_project_git_dir)
   if cmt: disc_data["git_commit"], disc_data["last_updated"], app.ai_status = cmt, project_manager.now_ts(), f"commit: {cmt[:12]}"
 imgui.text_colored(C_LBL(), "updated:"); imgui.same_line(); imgui.text_colored(C_SUB(), last_updated if last_updated else "(never)")
 ch, app.ui_disc_new_name_input = imgui.input_text("##new_disc", app.ui_disc_new_name_input); imgui.same_line()
 if imgui.button("Create"):
  nm = app.ui_disc_new_name_input.strip()
  if nm: app._create_discussion(nm); app.ui_disc_new_name_input = ""
 imgui.same_line()
 if imgui.button("Rename"):
  nm = app.ui_disc_new_name_input.strip()
  if nm: app._rename_discussion(app.active_discussion, nm); app.ui_disc_new_name_input = ""
 imgui.same_line()
 if imgui.button("Delete"): app._delete_discussion(app.active_discussion)

def render_discussion_panel(app: App) -> None:
 """Top-level discussion panel compositor. Renders the thinking indicator,
 prior-session read-only view (if active), discussion selector, entry controls,
 role list, and scrollable entry bubbles.

 SSDL: `[I:thinking_indicator] -> [I:prior_view?] | [I:selector] -> [I:controls] -> [I:roles] -> [I:entries]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [● GENERATING...]                    (thinking spinner) |
   |                                                         |
   | > Discussions   [main v] [Original] [take_2] [Synth]    |
   |   commit: a1b2c3  [Update] | updated: 2026-06-12        |
   |   [new-name] [Create] [Rename] [Delete]                 |
   | ---                                                     |
   | [+ Entry] [-All] [+All] [Clear All] [Save] [Compress]   |
   | ---                                                     |
   | > Roles (collapsible)                                   |
   | ---                                                     |
   | Entry #1 [User] ...                                     |
   | Entry #2 [AI]   ...                                     |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_discussion_panel")
 render_thinking_indicator(app)
 
 if app.is_viewing_prior_session:
  render_prior_session_view(app)
  if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_discussion_panel")
  return
 
 render_discussion_selector(app)
 
 if not app.is_viewing_prior_session:
  imgui.separator(); render_discussion_entry_controls(app)
  imgui.separator(); render_discussion_roles(app)
  imgui.separator(); render_discussion_entries(app)
 
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_discussion_panel")
 return

def render_discussion_roles(app: App) -> None:
 """Renders the collapsible Roles section. Lists current roles with an [X] delete button
 per entry, and exposes an input field for adding new roles.

 SSDL: `[B:collapsing_header] => [I:roles_list] -> [B:add_role]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | > Roles                                                 |
   |   +---------------------------------------------------+ |
   |   | [X] User   [X] AI   [X] System                    | |
   |   +---------------------------------------------------+ |
   |   [new-role___________] [Add]                           |
   +---------------------------------------------------------+
 """
 if imgui.collapsing_header("Roles"):
  with imscope.child("roles_scroll", size_y=100, flags=True):
   for i, r in enumerate(list(app.disc_roles)):
    with imscope.id(f"role_{i}"):
     if imgui.button("X"): app.disc_roles.pop(i); break
     imgui.same_line(); imgui.text(r)
  ch, app.ui_disc_new_role_input = imgui.input_text("##new_role", app.ui_disc_new_role_input); imgui.same_line()
  if imgui.button("Add"):
   r = app.ui_disc_new_role_input.strip()
   if r and r not in app.disc_roles: app.disc_roles.append(r); app.ui_disc_new_role_input = ""
 return

def render_discussion_selector(app: App) -> None:
 """Renders the collapsible Discussions selector. Shows a combo-box for choosing
 the active base discussion, a tab-bar for takes, a Synthesis tab, promote/track
 controls, and the discussion metadata row.

 SSDL: `[B:collapsing_header] => [I:combo_base] -> [I:takes_tabs] -> [B:promote?] -> [B:track?] -> [I:metadata]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | > Discussions                                           |
   |   [main v]                                              |
   |   [Original] [take_2] [Synthesis]                       |
   |   [Promote Take]  [ ] Track Discussion                  |
   |   commit: a1b2c3  updated: 2026-06-12                   |
   |   [new-name] [Create] [Rename] [Delete]                 |
   +---------------------------------------------------------+
 """
 if not imgui.collapsing_header("Discussions", imgui.TreeNodeFlags_.default_open): return
 names = app._get_discussion_names(); grouped = {}
 for name in names: base = name.split("_take_")[0]; grouped.setdefault(base, []).append(name)
 active_base = app.active_discussion.split("_take_")[0]
 if active_base not in grouped: active_base = names[0] if names else ""
 base_names = sorted(grouped.keys())
 if imgui.begin_combo("##disc_sel", active_base):
  for bname in base_names:
   is_selected = (bname == active_base)
   if imgui.selectable(bname, is_selected)[0]:
    target = bname if bname in names else grouped[bname][0]
    if target != app.active_discussion: app._switch_discussion(target)
   if is_selected: imgui.set_item_default_focus()
  imgui.end_combo()
 active_base = app.active_discussion.split("_take_")[0]; current_takes = grouped.get(active_base, [])
 if imgui.begin_tab_bar("discussion_takes_tabs"):
  for take_name in current_takes:
   label = "Original" if take_name == active_base else take_name.replace(f"{active_base}_", "").replace("_", " ").title()
   force_flag = imgui.TabItemFlags_.set_selected if take_name == app.active_discussion and getattr(app, '_force_tab_selection', False) else 0
   with imscope.tab_item(f"{label}###{take_name}", force_flag) as (exp, _):
    if exp and take_name != app.active_discussion:
     app._switch_discussion(take_name)
     app._force_tab_selection = False
  app._force_tab_selection = False
  with imscope.tab_item("Synthesis###Synthesis") as (exp, _): 
   if exp: render_synthesis_panel(app)
  imgui.end_tab_bar()
 if "_take_" in app.active_discussion:
  if imgui.button("Promote Take"):
   base_name = app.active_discussion.split("_take_")[0]; new_name = f"{base_name}_promoted"; counter = 1
   while new_name in names: new_name = f"{base_name}_promoted_{counter}"; counter += 1
   project_manager.promote_take(app.project, app.active_discussion, new_name); app._switch_discussion(new_name)
  imgui.same_line()
 if app.active_track:
  imgui.same_line(); ch, app._track_discussion_active = imgui.checkbox("Track Discussion", app._track_discussion_active)
  if ch:
   if app._track_discussion_active:
    app._flush_disc_entries_to_project()
    history_strings = project_manager.load_track_history(app.active_track.id, app.active_project_root)
    with app._disc_entries_lock: app.disc_entries = models.parse_history_entries(history_strings, app.disc_roles)
    app.ai_status = f"track discussion: {app.active_track.id}"
   else: app._flush_disc_entries_to_project(); app._switch_discussion(app.active_discussion); app.ai_status = "track discussion disabled"
 render_discussion_metadata(app)
 return

def render_discussion_tab(app: App) -> None:
 """Renders the Discussion tab content. Comprises a resizable top pane (history/entries)
 and a bottom pane with a draggable splitter bar, pop-out checkboxes, and
 inline Message/Response tabs.

 SSDL Shape:
   `[I:history_child] -> [B:splitter] -> [B:popout_toggles] -> [I:message_response_tabs]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | +-----------------------------------------------------+ |
   | | Discussion entries (scrollable)...                  | |
   | | ...                                                 | |
   | +-----------------------------------------------------+ |
   | [=== drag splitter ===========================]         |
   | [ ] Pop Out Message   [ ] Pop Out Response              |
   | [Message] [Response]                                    |
   | +-----------------------------------------------------+ |
   | | <active tab panel>                                  | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+
 """
 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))
 ch1, app.ui_separate_message_panel  = imgui.checkbox("Pop Out Message", app.ui_separate_message_panel); imgui.same_line()
 ch2, app.ui_separate_response_panel = imgui.checkbox("Pop Out Response", app.ui_separate_response_panel)
 if ch1: app.show_windows["Message"]  = app.ui_separate_message_panel
 if ch2: app.show_windows["Response"] = app.ui_separate_response_panel
 imgui.pop_style_var()
 show_message_tab  = not app.ui_separate_message_panel
 show_response_tab = not app.ui_separate_response_panel
 if show_message_tab or show_response_tab:
  if imgui.begin_tab_bar("discussion_tabs"):
   tab_flags = imgui.TabItemFlags_.none
   if app._autofocus_response_tab:
    tab_flags = imgui.TabItemFlags_.set_selected
    app._autofocus_response_tab            = False
    app.controller._autofocus_response_tab = False
   if show_message_tab:
    if imgui.begin_tab_item("Message", None)[0]:
     render_message_panel(app)
     imgui.end_tab_item()
   if show_response_tab:
    if imgui.begin_tab_item("Response", None, tab_flags)[0]:
     render_response_panel(app)
     imgui.end_tab_item()
   imgui.end_tab_bar()
 else:
  imgui.text_disabled("Message & Response panels are detached.")

#endregion: Discussions

#region: Operations Monitor

def render_operations_hub(app: App) -> None:
 """Top-level Operations Monitor hub. Houses pop-out checkboxes for Tool Calls, Usage Analytics,
 and External Tools, then a tab-bar for all ops sub-panels.

 SSDL: `[B:popout_toggles] -> [I:tab_bar] => [I:active_tab_content]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [ ] Pop Out Tool Calls  [ ] Pop Out Usage  [ ] Ext Tools|
   | [Comms History] [Tool Calls] [Usage Analytics]          |
   | [External Tools] [Workspace Layouts] [Vendor State]     |
   | +-----------------------------------------------------+ |
   | | <active tab content>                                | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+
 """
 imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
 ch1, app.ui_separate_tool_calls_panel = imgui.checkbox("Pop Out Tool Calls", app.ui_separate_tool_calls_panel)
 if ch1: app.show_windows["Tool Calls"] = app.ui_separate_tool_calls_panel
 imgui.same_line()
 ch2, app.ui_separate_usage_analytics = imgui.checkbox("Pop Out Usage Analytics", app.ui_separate_usage_analytics)
 if ch2: app.show_windows["Usage Analytics"] = app.ui_separate_usage_analytics
 imgui.same_line()
 ch3, app.ui_separate_external_tools = imgui.checkbox('Pop Out External Tools', app.ui_separate_external_tools)
 if ch3: app.show_windows['External Tools'] = app.ui_separate_external_tools
 imgui.pop_style_var()
 show_tc_tab, show_usage_tab = not app.ui_separate_tool_calls_panel, not app.ui_separate_usage_analytics
 with imscope.tab_bar("ops_tabs"):
  with imscope.tab_item("Comms History") as (exp, _):
   if exp: render_comms_history_panel(app)
  if show_tc_tab:
   with imscope.tab_item("Tool Calls") as (exp, _):
    if exp: render_tool_calls_panel(app)
  if show_usage_tab:
   with imscope.tab_item("Usage Analytics") as (exp, _):
    if exp: render_usage_analytics_panel(app)
  if not app.ui_separate_external_tools:
   with imscope.tab_item("External Tools") as (exp, _):
    if exp:
     render_external_tools_panel(app)
     imgui.separator(); imgui.text("")
     try: render_external_editor_panel(app)
     except Exception as e: imgui.text_colored(theme.get_color("status_error"), f"Error: {str(e)}")
  with imscope.tab_item("Workspace Layouts") as (exp, _):
   if exp:
    imgui.text("Experimental: Auto-switch layout by Tier")
    ch, app.controller.ui_auto_switch_layout = imgui.checkbox("Enable Auto-Switch", app.controller.ui_auto_switch_layout)
    if  app.controller.ui_auto_switch_layout:
     imgui.separator(); imgui.text("Tier Bindings (select profile for each tier)")
     profiles = [""] + [p.name for p in app.controller.workspace_profiles.values()]
     for t in ["Tier 1", "Tier 2", "Tier 3", "Tier 4"]:
      curr              = app.controller.ui_tier_layout_bindings.get(t, ""); idx = profiles.index(curr) if curr in profiles else 0
      ch_combo, new_idx = imgui.combo(t, idx, profiles)
      if ch_combo: app.controller.ui_tier_layout_bindings[t] = profiles[new_idx]
  with imscope.tab_item("Vendor State") as (exp, _):
   if exp: render_vendor_state(app)

def render_vendor_state(app: App) -> None:
 """Renders the Operations Hub > Vendor State panel. Displays per-vendor health metrics
 (model name, cache state, token budget, connection status) in a colour-coded table.

 SSDL: `[I:metrics_table] => [I:state_column]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Metric                   | Value           | State      |
   |--------------------------|-----------------|------------|
   | Provider                 | gemini          | ok         |
   | Model                    | gemini-2.0-flash| ok         |
   | Cache Token Budget       | 32000           | warn       |
   | Last Error               | (none)          | info       |
   +---------------------------------------------------------+
 """
 from src.vendor_state import get_vendor_state
 metrics = get_vendor_state(app)
 if imgui.begin_table("vendor_state", 3, imgui.TableFlags_.row_bg | imgui.TableFlags_.borders):
  imgui.table_setup_column("Metric", imgui.TableColumnFlags_.width_fixed, 180)
  imgui.table_setup_column("Value",  imgui.TableColumnFlags_.width_stretch)
  imgui.table_setup_column("State",  imgui.TableColumnFlags_.width_fixed, 60)
  imgui.table_headers_row()
  state_colors = {"ok": theme.get_color("status_success"), "warn": theme.get_color("status_warning"), "error": theme.get_color("status_error"), "info": theme.get_color("text_disabled")}
  for m in metrics:
   imgui.table_next_row()
   imgui.table_next_column(); imgui.text(m.label)
   imgui.table_next_column()
   imgui.text(m.value)
   if imgui.is_item_hovered(): imgui.set_tooltip(m.tooltip)
   imgui.table_next_column()
   imgui.text_colored(state_colors.get(m.state, theme.get_color("text_disabled")), m.state)
  imgui.end_table()

def render_message_panel(app: App) -> None:
 """Renders user message text input area, exposing buttons to generate assistant responses,
 inject contextual files, or reset active conversation sessions.

 SSDL: `[I:live_indicator] -> [I:input_textbox] -> [B:gen_send_buttons] -> [B:inject_reset]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | LIVE (blinking green when tool is executing)            |
   | +-----------------------------------------------------+ |
   | | Type your message here...               (Ctrl+Enter)| |
   | |                                                     | |
   | +-----------------------------------------------------+ |
   | [Gen + Send] [MD Only] [Inject File] [-> History]       |
   | [Reset]                                                 |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_message_panel")
# LIVE indicator
 is_live = app.ai_status in ["running powershell...", "fetching url...", "searching web...", "powershell done, awaiting AI..."]
 if is_live:
  val   = math.sin(time.time() * 10 * math.pi)
  alpha = 1.0 if val > 0 else 0.0
  c     = theme.get_color("status_success", alpha=alpha)
  imgui.text_colored(c, "LIVE")
  imgui.separator()
 ch, app.ui_ai_input = imgui.input_text_multiline("##ai_in", app.ui_ai_input, imgui.ImVec2(-1, -40))
 # Keyboard shortcuts
 io = imgui.get_io()
 ctrl_l = io.key_ctrl and imgui.is_key_pressed(imgui.Key.l)
 if ctrl_l: app.ui_ai_input = ""
 imgui.separator()
 is_busy = app.ai_status in ['sending...', 'streaming...']
 send_busy = False
 with app._send_thread_lock:
  if app.send_thread and app.send_thread.is_alive(): send_busy = True
 if is_busy: send_busy = True

 imgui.begin_disabled(send_busy)
 ctrl_enter = io.key_ctrl and imgui.is_key_pressed(imgui.Key.enter)
 label = "Gen + Send (Busy)" if send_busy else "Gen + Send"
 if (imgui.button(label) or ctrl_enter) and not send_busy: app._handle_generate_send()
 imgui.end_disabled()
 imgui.same_line()
 if imgui.button("MD Only"): app._handle_md_only()
 imgui.same_line()
 if imgui.button("Inject File"): app.show_inject_modal = True
 imgui.same_line()
 if imgui.button("-> History"): 
  if app.ui_ai_input: app.disc_entries.append({"role": "User", "content": app.ui_ai_input, "collapsed": False, "ts": project_manager.now_ts()})
 imgui.same_line()
 if imgui.button("Reset"): app._handle_reset_session()
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_message_panel")

def render_response_panel(app: App) -> None:
 """Renders assistant stream output panel. Renders thinking traces, markdown segments,
 and exports active responses to history entries.

 SSDL: `[I:response_text] -> [I:thinking_trace] -> [I:markdown_view] => [B:export_to_history]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | +-----------------------------------------------------+ |
   | | > Monologue (collapsible thinking trace)            | |
   | |                                                     | |
   | | ## AI Response Heading                              | |
   | | Rendered markdown text...                           | |
   | +-----------------------------------------------------+ |
   | [-> History]                                            |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_response_panel")
 if app._trigger_blink:
  app._trigger_blink    = False
  app._is_blinking      = True
  app._blink_start_time = time.time()
  try:
   imgui.set_window_focus("Response") # type: ignore[call-arg]
  except:
   pass
 is_blinking = False
 blink_color = imgui.ImVec4(0, 0, 0, 0)
 if app._is_blinking:
  elapsed = time.time() - app._blink_start_time
  if elapsed > 1.5:
   app._is_blinking = False
  else:
   is_blinking = True
   val = math.sin(elapsed * 8 * math.pi)
   alpha = 50/255 if val > 0 else 0
   blink_color = theme.get_color("status_success", alpha=alpha)

 with imscope.style_color(imgui.Col_.frame_bg, blink_color) if is_blinking else nullcontext():
  with imscope.style_color(imgui.Col_.child_bg, blink_color) if is_blinking else nullcontext():
   with imscope.child("response_scroll_area", 0, -40, True):
    with theme.ai_text_style():
     segments, parsed_response = thinking_parser.parse_thinking_trace(app.ai_response)
     if segments:
      render_thinking_trace(app, {}, [{"content": s.content, "marker": s.marker} for s in segments], 9999)
     markdown_helper.render(parsed_response, context_id="response")

 imgui.separator()
 if imgui.button("-> History"):
  if app.ai_response:
   segments, response = thinking_parser.parse_thinking_trace(app.ai_response)
   entry = {"role": "AI", "content": response, "collapsed": True, "ts": project_manager.now_ts()}
   if segments: entry["thinking_segments"] = [{"content": s.content, "marker": s.marker} for s in segments]
   app.disc_entries.append(entry)
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_response_panel")

def render_tool_calls_panel(app: App) -> None:
 """Renders tool call execution log. Displays script execution details, status codes,
 and outputs in a scrollable table. Clicking a row opens the full call in the Text Viewer.

 SSDL: `[I:tool_log] -> [B:clear_button] => [I:calls_table]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Tool call history  [Clear]                              |
   | +----+------+---------------------------+-----------+   |
   | | #  | Tier | Script                    | Result    |   |
   | +----+------+---------------------------+-----------+   |
   | | #1 | main | uv run pytest tests/...   | passed... |   |
   | | #2 | T3   | git commit -m "fix: ..." | done      |   |
   | +----+------+---------------------------+-----------+   |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_tool_calls_panel")
 imgui.text("Tool call history")
 imgui.same_line()
 if imgui.button("Clear##tc"):
  app._tool_log.clear()
  app._tool_log_dirty = True
 imgui.separator()
 
 log_to_render = app._tool_log_cache
 flags = imgui.TableFlags_.resizable | imgui.TableFlags_.hideable | imgui.TableFlags_.borders_inner_v | imgui.TableFlags_.row_bg | imgui.TableFlags_.scroll_y
 
 if imgui.begin_table("tool_calls_table", 4, flags, imgui.ImVec2(0, 0)):
  imgui.table_setup_column("#", imgui.TableColumnFlags_.width_fixed, 40)
  imgui.table_setup_column("Tier", imgui.TableColumnFlags_.width_fixed, 60)
  imgui.table_setup_column("Script", imgui.TableColumnFlags_.width_stretch)
  imgui.table_setup_column("Result", imgui.TableColumnFlags_.width_fixed, 100)

  imgui.table_headers_row()

  for i, entry in enumerate(log_to_render):
   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()
   # 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_preview = script.replace("\n", " ")[:150]
   if len(script) > 150: script_preview += "..."
   render_selectable_label(app, f'tc_script_{i}', script_preview, width=-1)

   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)

 imgui.end_table()

 if app._scroll_tool_calls_to_bottom:
  imgui.set_scroll_here_y(1.0)
  app._scroll_tool_calls_to_bottom = False
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_tool_calls_panel")

def render_external_tools_panel(app: App) -> None:
 """Renders the External MCPs panel. Shows server health indicators, a [Refresh] button,
 and a table of all registered external MCP tool names, servers, and descriptions.

 SSDL Shape: `[B:refresh] -> [I:server_status_badges] -> [I:tools_table]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [Refresh External MCPs]                                 |
   | Servers: [●] my-mcp-server  [●] another-server          |
   | +----------------------+------------+-----------------+ |
   | | Name                 | Server     | Description     | |
   | +----------------------+------------+-----------------+ |
   | | py_get_definition    | manual-slop| Get Python def  | |
   | +----------------------+------------+-----------------+ |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled:            app.perf_monitor.start_component("_render_external_tools_panel")
 if imgui.button("Refresh External MCPs"): app.event_queue.put("refresh_external_mcps", None)
 
 imgui.separator()
 
 # Server status indicators
 manager  = mcp_client.get_external_mcp_manager()
 statuses = manager.get_servers_status()
 if statuses:
  imgui.text("Servers:")
  for sname, status in statuses.items():
   imgui.same_line()
   # Green for running, Yellow for starting, Red for error, Gray for idle
   col = (0.5, 0.5, 0.5, 1.0)
   if   status == 'running':  col = (0.0, 1.0, 0.0, 1.0)
   elif status == 'starting': col = (1.0, 1.0, 0.0, 1.0) 
   elif status == 'error':    col = (1.0, 0.0, 0.0, 1.0)
   imgui.color_button(f"##status_{sname}", col)
   imgui.same_line()
   imgui.text(sname)
  imgui.separator()
 
 tools = manager.get_all_tools()
 if not tools:
  imgui.text_disabled("No external tools found.")
 else:
  if imgui.begin_table("external_tools_table", 3, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable):
   imgui.table_setup_column("Name")
   imgui.table_setup_column("Server")
   imgui.table_setup_column("Description")
   imgui.table_headers_row()
   
   for tname, tinfo in tools.items():
    imgui.table_next_row()
    imgui.table_next_column()
    imgui.text(tname)
    imgui.table_next_column()
    imgui.text(tinfo.get('server', 'unknown'))
    imgui.table_next_column()
    imgui.text(tinfo.get('description', ''))
   imgui.end_table()
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_external_tools_panel")

def render_text_viewer_window(app: App) -> None:
 """Renders the standalone text/code/markdown viewer window. Supports four rendering modes
 based on content type: markdown (rendered), slice editor (line-click range selection),
 syntax-highlighted code (CodeEditor widget), and plain text (scrollable).

 SSDL: `[I:mode_dispatch] => [I:markdown] | [I:slice_editor] | [I:code_editor] | [I:plain_text]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | src/gui_2.py - Text Viewer                 [x]          |
   | --- (slice mode only) ---                               |
   | [Add Selection as Slice] [Clear Selection]              |
   | [Auto-Populate AST Slices] [Edit Tags]                  |
   | Slice 1: L10-L42  [tag v] [comment________] [X]         |
   | ---                                                     |
   | [Copy]  [ ] Word Wrap                                   |
   | +-----------------------------------------------------+ |
   | | <markdown / slice-editor / CodeEditor / plain text> | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+
 """
 if not app.show_windows.get("Text Viewer", False): return
 imgui.set_next_window_size(imgui.ImVec2(900, 700), imgui.Cond_.first_use_ever)
 # Force a unique ID to clear legacy docking corruption
 expanded, opened                = imgui.begin(f"{app.text_viewer_title or 'Text Viewer'}###Text_Viewer_Unified", 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
  app._slice_sel_end         = -1
 if expanded:
  if app.ui_editing_slices_file is not None:
   imgui.text_colored(C_IN(), "Slice Management (Click-drag lines to select range)")
   if imgui.button("Add Selection as Slice"):
    if app._slice_sel_start != -1 and app._slice_sel_end != -1:
     s_line = min(app._slice_sel_start, app._slice_sel_end)
     e_line = max(app._slice_sel_start, app._slice_sel_end)
     from src.fuzzy_anchor import FuzzyAnchor
     slice_data = FuzzyAnchor.create_slice(app.text_viewer_content, s_line, e_line)
     slice_data['tag'] = ""; slice_data['comment'] = ""
     app.ui_editing_slices_file.custom_slices.append(slice_data)
     app._slice_sel_start = -1; app._slice_sel_end = -1
   imgui.same_line()
   if imgui.button("Clear Selection"): app._slice_sel_start = -1; app._slice_sel_end = -1
   imgui.same_line()
   if imgui.button("Auto-Populate AST Slices"): app._populate_auto_slices(app.ui_editing_slices_file)
   imgui.same_line()
   if imgui.button("Edit Tags"): imgui.open_popup("Edit Context Tags")
   
   if imgui.begin_popup("Edit Context Tags"):
    tags = app.controller.project.setdefault("context_tags", ["auto-ast", "bug", "feature", "important"])
    imgui.text("Context Tags")
    imgui.separator()
    to_remove_tag = -1
    for i, t in enumerate(tags):
     imgui.push_id(f"tag_{i}")
     imgui.set_next_item_width(150)
     ch, new_t = imgui.input_text("##t", t)
     if ch: tags[i] = new_t
     imgui.same_line()
     if imgui.button("X"): to_remove_tag = i
     imgui.pop_id()
    if to_remove_tag != -1: tags.pop(to_remove_tag)
    if imgui.button("+ Add Tag"): tags.append("new-tag")
    if imgui.button("Close"): imgui.close_current_popup()
    imgui.end_popup()
   
   to_remove = -1
   tags = app.controller.project.get("context_tags", ["auto-ast", "bug", "feature", "important"])
   for idx, slc in enumerate(app.ui_editing_slices_file.custom_slices):
    imgui.push_id(f"slc_row_{idx}"); imgui.text(f"Slice {idx+1}: {slc['start_line']}-{slc['end_line']}"); imgui.same_line()
    current_tag = slc.get('tag', '')
    if current_tag not in tags and current_tag: tags.append(current_tag)
    tag_idx = tags.index(current_tag) if current_tag in tags else 0
    imgui.set_next_item_width(100)
    ch_tag, new_tag_idx = imgui.combo("##Tag", tag_idx, tags)
    if ch_tag: slc['tag'] = tags[new_tag_idx]
    imgui.same_line(); imgui.set_next_item_width(-30); changed_comm, new_comm = imgui.input_text("##Note", slc.get('comment', ''))
    if changed_comm: slc['comment'] = new_comm
    imgui.same_line()
    if imgui.button("X"): to_remove = idx
    imgui.pop_id()
   if to_remove != -1: app.ui_editing_slices_file.custom_slices.pop(to_remove)
   imgui.separator() 
   if imgui.button("Copy"): imgui.set_clipboard_text(app.text_viewer_content)
  imgui.same_line(); _, app.text_viewer_wrap = imgui.checkbox("Word Wrap", app.text_viewer_wrap)
  
  imgui.separator()
  
  renderer = markdown_helper.get_renderer(); tv_type = getattr(app, "text_viewer_type", "text")
  if tv_type == 'markdown':
   with imscope.child("tv_md_scroll", -1, -1, True): markdown_helper.render(app.text_viewer_content, context_id='text_viewer')
  elif app.ui_editing_slices_file is not None:
   with imscope.child("slice_editor_content", -1, -1, True):
    lines = app.text_viewer_content.splitlines(); draw_list = imgui.get_window_draw_list()
    for i, line_text in enumerate(lines):
     line_num = i + 1; pos = imgui.get_cursor_screen_pos(); line_height = imgui.get_text_line_height()
     
     is_auto_sliced   = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in app.ui_editing_slices_file.custom_slices if slc.get('tag') == 'auto-ast')
     is_manual_sliced = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in app.ui_editing_slices_file.custom_slices if slc.get('tag') != 'auto-ast')
     
     if is_manual_sliced: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_manual", alpha=0.2)))
     elif is_auto_sliced: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_auto", alpha=0.15)))
     
     if app._slice_sel_start != -1 and app._slice_sel_end != -1:
      s, e = min(app._slice_sel_start, app._slice_sel_end), max(app._slice_sel_start, app._slice_sel_end)
      if s <= line_num <= e: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_selection", alpha=0.3)))
     imgui.selectable(f"{line_num:4} | {line_text}##ln{line_num}", False)
     if imgui.is_item_clicked(): app._slice_sel_start = line_num; app._slice_sel_end = line_num
     if imgui.is_item_hovered(imgui.HoveredFlags_.allow_when_blocked_by_active_item) and imgui.is_mouse_down(0): app._slice_sel_end = line_num
  elif tv_type in renderer._lang_map:
   if app._text_viewer_editor is None:
    app._text_viewer_editor = ced.TextEditor(); app._text_viewer_editor.set_read_only_enabled(True); app._text_viewer_editor.set_show_line_numbers_enabled(True)
   try:
    app._text_viewer_editor.set_text(app.text_viewer_content)
    if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_text_viewer_ced")
    app._text_viewer_editor.render(f"##ced_{app.text_viewer_title}", imgui.ImVec2(-1, -1))
    if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_text_viewer_ced")
   except Exception as e: imgui.text_colored(theme.get_color("status_error"), f"CED Error: {e}"); imgui.text_unformatted(app.text_viewer_content)
  else:
   with imscope.child("tv_scroll", -1, -1, True):
    if app.text_viewer_wrap: imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
    imgui.text_unformatted(app.text_viewer_content)
    if app.text_viewer_wrap: imgui.pop_text_wrap_pos()
 imgui.end()

def render_patch_modal(app: App) -> None:
 """Renders the Apply Patch? modal. Shows files to be modified, a syntax-highlighted
 diff preview, and action buttons to open in an external editor, apply, or reject.

 SSDL: `[I:files_list] -> [I:diff_preview] => [B:open_external | B:apply | B:reject]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | ⚠ Tier 4 QA Generated a Patch                           |
   | Files to modify:                                        |
   |   - src/gui_2.py                                        |
   | ---                                                     |
   | Diff Preview:                                           |
   | +-----------------------------------------------------+ |
   | | --- a/src/gui_2.py                                  | |
   | | +++ b/src/gui_2.py                                  | |
   | | @@ -100,7 +100,7 @@                                 | |
   | | -old line                                           | |
   | | +new line                                           | |
   | +-----------------------------------------------------+ |
   | [Open in External Editor] [Apply Patch] [Reject]        |
   +---------------------------------------------------------+
 """
 if not app._show_patch_modal:
  return
 imgui.open_popup("Apply Patch?")
 with imscope.popup_modal("Apply Patch?", True, imgui.WindowFlags_.always_auto_resize) as (opened, _):
  if opened:
   p_min = imgui.get_window_pos()
   p_max = imgui.ImVec2(p_min.x + imgui.get_window_size().x, p_min.y + imgui.get_window_size().y)
   shaders.draw_soft_shadow(imgui.get_background_draw_list(), p_min, p_max, imgui.ImVec4(0, 0, 0, 0.6), 25.0, 6.0)
   
   imgui.text_colored(theme.get_color("status_warning"), "Tier 4 QA Generated a Patch")
   imgui.separator()
   if app._pending_patch_files:
    imgui.text("Files to modify:")
    for f in app._pending_patch_files: imgui.text(f"  - {f}")
   imgui.separator()
   if app._patch_error_message:
    imgui.text_colored(theme.get_color("status_error"), f"Error: {app._patch_error_message}")
    imgui.separator()
   imgui.text("Diff Preview:")
   imgui.begin_child("patch_diff_scroll", imgui.ImVec2(-1, 280), True)
   if app._pending_patch_text:
    diff_lines = app._pending_patch_text.split("\n")
    for line in diff_lines:
     if   line.startswith("+++") or line.startswith("---") or line.startswith("@@"): imgui.text_colored(theme.get_color("diff_header"), line)
     elif line.startswith("+"):                                                      imgui.text_colored(theme.get_color("diff_added"), line)
     elif line.startswith("-"):                                                      imgui.text_colored(theme.get_color("diff_removed"), line)
     else: imgui.text(line)
   imgui.end_child()
   imgui.separator()
   if imgui.button("Open in External Editor"): app._open_patch_in_external_editor()
   imgui.same_line()
   if imgui.button("Apply Patch"):
    app._apply_pending_patch()
    app._close_vscode_diff()
   imgui.same_line()
   if imgui.button("Reject"):
    app._close_vscode_diff()
    app._show_patch_modal    = False
    app._pending_patch_text  = None
    app._pending_patch_files = []
    app._patch_error_message = None
    imgui.close_current_popup()

def render_external_editor_panel(app: App) -> None:
 """Renders the External Editor configuration panel. Lists configured editors
 from config.toml, shows the current default, and allows setting a new default.

 SSDL: `[I:editors_list] -> [B:set_default] => [I:config_hint?]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | External Editor for Diff Viewing                        |
   | (no editors configured)                                 |
   | Add editors in config.toml:                             |
   |   [tools.text_editors.vscode]                           |
   |   path = "C:\\path\\to\\code.exe"                       |
   | ---  OR when editors exist ---                          |
   | Default: vscode                                         |
   | [vscode] [notepad++]                [Set as Default]    |
   +---------------------------------------------------------+
 """
 from src.external_editor import get_default_launcher
 imgui.text("External Editor for Diff Viewing")
 imgui.separator()
 try:
  launcher = get_default_launcher(app.config)
  editors = launcher.config.editors
  default_name = launcher.config.default_editor
  if not editors:
   imgui.text_colored(C_REQ(), "  No editors configured")
   imgui.text("")
   imgui.text("Add editors in config.toml:")
   imgui.text("  [tools.text_editors.vscode]")
   imgui.text('  path = "C:\\\\path\\\\to\\\\code.exe"')
   imgui.text('  diff_args = ["--diff"]')
   imgui.text("")
   imgui.text("  [tools.text_editors.notepadpp]")
   imgui.text('  path = "C:\\\\path\\\\to\\\\notepad++.exe"')
   imgui.text('  diff_args = ["-multiInst", "-nosession"]')
   imgui.text("")
   imgui.text("Then set default in [tools.default_editor]")
  else:
   imgui.text("Default Editor:")
   editor_names = sorted(list(editors.keys()))
   if default_name and default_name in editor_names: current_idx = editor_names.index(default_name)
   else:                                             current_idx = 0
   changed, new_idx = imgui.combo("##editor_combo", current_idx, editor_names)
   if changed: app._set_external_editor_default(editor_names[new_idx])
   imgui.text("")
   imgui.text("Configured Editors:")
   imgui.separator()
   for name in editor_names:
    editor = editors.get(name)
    if not editor: continue
    is_default = name == default_name
    marker = " (default)" if is_default else ""
    if is_default: imgui.text_colored(C_IN(), f"  {name}{marker}")
    else:          imgui.text(f"  {name}{marker}")
    imgui.text(f"    {editor.path}")
    if editor.diff_args: imgui.textDisabled(f"    diff: {editor.diff_args}")
   imgui.text("")
   imgui.text("Config: config.toml [tools.text_editors]")
   imgui.text("Override: manual_slop.toml default_editor")
 except Exception as e:
  imgui.text_colored(C_TC(), f"Error: {str(e)}")

def render_approve_script_modal(app: App) -> None:
 """
 Renders the modal dialog for approving AI-generated PowerShell scripts.
 Supports full script editing or markdown code blocks preview.

 SSDL Shape:
   `[I:command_details] -> [B:preview_checkbox] -> [I:preview_box_or_editor] => [B:approve_reject]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Approve PowerShell Command                              |
   | The AI wants to run the following PowerShell script:    |
   | base_dir: C:\projects\manual_slop                       |
   | [ ] Read-only Full Preview                              |
   | +-----------------------------------------------------+ |
   | | Get-Content .\src\gui_2.py | Select-String 'def'   | |
   | | ...                                                 | |
   | +-----------------------------------------------------+ |
   | [Approve & Run]  [Reject]                               |
   +---------------------------------------------------------+
 """
 with app._pending_dialog_lock:
  dlg = app._pending_dialog
 if dlg:
  if not app._pending_dialog_open:
   imgui.open_popup("Approve PowerShell Command")
   app._pending_dialog_open = True
 else:
  app._pending_dialog_open = False
 
 imgui.set_next_window_size(imgui.ImVec2(800, 600), imgui.Cond_.first_use_ever)
 if imgui.begin_popup_modal("Approve PowerShell Command", None, 0)[0]:
  if not dlg: imgui.close_current_popup()
  else:
   imgui.text("The AI wants to run the following PowerShell script:")
   imgui.text_colored(theme.get_color("status_warning"), f"base_dir: {dlg._base_dir}")
   imgui.separator()
   # Checkbox to toggle full preview inside modal
   if not hasattr(app, 'ui_approve_modal_preview'): app.ui_approve_modal_preview = False
   _, app.ui_approve_modal_preview = imgui.checkbox("Read-only Full Preview", app.ui_approve_modal_preview)
   
   avail_y = imgui.get_content_region_avail().y - 40 # reserve space for buttons
   if app.ui_approve_modal_preview:
    with imscope.child("preview_child", -1, avail_y, True):
     markdown_helper.render(f"```powershell\n{dlg._script}\n```", context_id="approve_script_preview")
   else:
    ch, dlg._script = imgui.input_text_multiline("##confirm_script", dlg._script, imgui.ImVec2(-1, avail_y))
   imgui.separator()
   if imgui.button("Approve & Run", imgui.ImVec2(120, 0)):
    with dlg._condition:
     dlg._approved = True
     dlg._done     = True
     dlg._condition.notify_all()
    with app._pending_dialog_lock: app._pending_dialog = None
    imgui.close_current_popup()
   imgui.same_line()
   if imgui.button("Reject", imgui.ImVec2(120, 0)):
    with dlg._condition:
     dlg._approved = False
     dlg._done     = True
     dlg._condition.notify_all()
    with app._pending_dialog_lock: app._pending_dialog = None
    imgui.close_current_popup()
  imgui.end_popup()

#endregion: Operations Monitor

#region: Misc Tools

def render_theme_panel(app: App) -> None:
 """Renders the Theme configuration window. Covers palette selection, panel pop-out toggles,
 font path/size, DPI scale, transparency sliders, background shader, CRT filter, and
 per-palette tone mapping (brightness/contrast/gamma).

 SSDL: `[I:palette_combo] -> [B:panel_popout_toggles] -> [I:font_config] -> [I:scale_sliders] -> [I:tone_mapping]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Theme                                              [x]  |
   | Palette: [Nerv v]                                       |
   | [ ] Separate Message  [ ] Separate Response             |
   | [ ] Separate Tool Calls                                 |
   | Font: [path/to/font.ttf___________] [Browse]            |
   | Size (px): [14.0] [Apply Font (Requires Restart)]       |
   | UI Scale (DPI): [====|====] 1.00                        |
   | Panel Transparency: [======|===] 0.90                   |
   | Panel Item Transparency: [=====|====] 0.85              |
   | [x] Animated Background Shader  [ ] CRT Filter          |
   | Tone Mapping (Per-Palette)                              |
   | Brightness: [====|=] 1.00                               |
   | Contrast:   [====|=] 1.00                               |
   | Gamma:      [====|=] 1.00                               |
   | [Reset Tone Mapping]                                    |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_theme_panel")
 exp, opened = imgui.begin("Theme", app.show_windows["Theme"])
 app.show_windows["Theme"] = bool(opened)
 if exp:
  imgui.text("Palette")
  cp = theme.get_current_palette()
  if imgui.begin_combo("##pal", cp):
   for p in theme.get_palette_names():
    if imgui.selectable(p, p == cp)[0]:
     theme.apply(p)
     app._flush_to_config()
     app.save_config()
   imgui.end_combo()

  imgui.separator()
  ch1, app.ui_separate_message_panel    = imgui.checkbox("Separate Message Panel",    app.ui_separate_message_panel)
  ch2, app.ui_separate_response_panel   = imgui.checkbox("Separate Response Panel",   app.ui_separate_response_panel)
  ch3, app.ui_separate_tool_calls_panel = imgui.checkbox("Separate Tool Calls Panel", app.ui_separate_tool_calls_panel)
  if ch1: app.show_windows["Message"]    = app.ui_separate_message_panel
  if ch2: app.show_windows["Response"]   = app.ui_separate_response_panel
  if ch3: app.show_windows["Tool Calls"] = app.ui_separate_tool_calls_panel
  imgui.separator()
  imgui.text("Font")
  imgui.push_item_width(-150)
  ch, path = imgui.input_text("##fontp", theme.get_current_font_path())
  imgui.pop_item_width()
  if ch: theme._current_font_path = path
  imgui.same_line()
  if imgui.button("Browse##font"):
   r = hide_tk_root()
   p = filedialog.askopenfilename(filetypes=[("Fonts", "*.ttf *.otf"), ("All", "*.*")])
   r.destroy()
   if p: theme._current_font_path = p
  imgui.text("Size (px)")
  imgui.same_line()
  imgui.push_item_width(100)
  ch, size = imgui.input_float("##fonts", theme.get_current_font_size(), 1.0, 1.0, "%.0f")
  if ch: theme._current_font_size = size
  imgui.pop_item_width()
  imgui.same_line()
  if imgui.button("Apply Font (Requires Restart)"):
   app._flush_to_config()
   app.save_config()
   app.ai_status = "Font settings saved. Restart required."
  imgui.separator()
  imgui.text("UI Scale (DPI)")
  ch, scale = imgui.slider_float("##scale", theme.get_current_scale(), 0.5, 3.0, "%.2f")
  if ch:
   theme.set_scale(scale)
   app._flush_to_config()
   app.save_config()

  imgui.text("Panel Transparency")
  ch_t, trans = imgui.slider_float("##trans", theme.get_transparency(), 0.1, 1.0, "%.2f")
  if ch_t:
   theme.set_transparency(trans)
   app._flush_to_config()
   app.save_config()

  imgui.text("Panel Item Transparency")
  ch_ct, ctrans = imgui.slider_float("##ctrans", theme.get_child_transparency(), 0.1, 1.0, "%.2f")
  if ch_ct:
   theme.set_child_transparency(ctrans)
  bg                = bg_shader.get_bg()
  ch_bg, bg.enabled = imgui.checkbox("Animated Background Shader", bg.enabled)
  if ch_bg:
   gui_cfg = app.config.setdefault("gui", {})
   gui_cfg["bg_shader_enabled"] = bg.enabled
   app._flush_to_config()
   app.save_config()

  ch_crt, app.ui_crt_filter = imgui.checkbox("CRT Filter", app.ui_crt_filter)
  if ch_crt:
   gui_cfg = app.config.setdefault("gui", {})
   gui_cfg["crt_filter_enabled"] = app.ui_crt_filter
   app._flush_to_config()
   app.save_config()
  
  imgui.separator()
  imgui.text("Tone Mapping (Per-Palette)")
  curr_p = theme.get_current_palette()
  
  imgui.text("Brightness")
  ch_b, b = imgui.slider_float("##tm_b", theme.get_brightness(curr_p), 0.1, 2.0, "%.2f")
  if ch_b: theme.set_brightness(curr_p, b); app._flush_to_config(); app.save_config()
  
  imgui.text("Contrast")
  ch_c, c = imgui.slider_float("##tm_c", theme.get_contrast(curr_p), 0.1, 2.0, "%.2f")
  if ch_c: theme.set_contrast(curr_p, c); app._flush_to_config(); app.save_config()
  
  imgui.text("Gamma")
  ch_g, g = imgui.slider_float("##tm_g", theme.get_gamma(curr_p), 0.1, 3.0, "%.2f")
  if ch_g: theme.set_gamma(curr_p, g); app._flush_to_config(); app.save_config()
  
  if imgui.button("Reset Tone Mapping"):
   theme.reset_tone_mapping(curr_p)
   app._flush_to_config()
   app.save_config()
  
 imgui.end()
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_theme_panel")

#NOTE(Ed): This was part of an experiment to see what the ai could generate and if it could figure how to add
# A post-process mechanism to the gui (it did not).
def render_shader_live_editor(app: App) -> None:
 """Renders the Shader Live Editor window. Exposes real-time sliders for CRT curvature,
 scanline intensity, and bloom threshold.

 SSDL: `[I:shader_uniforms] => [I:slider_controls]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Shader Editor                                      [x]  |
   | CRT Curvature:       [======|=====] 0.80                |
   | Scanline Intensity:  [==|=========] 0.20                |
   | Bloom Threshold:     [===|========] 0.30                |
   +---------------------------------------------------------+
 """
 if app.show_windows.get('Shader Editor', False):
  with imscope.window('Shader Editor', app.show_windows['Shader Editor']) as (exp, opened):
   app.show_windows['Shader Editor'] = bool(opened)
   if exp:
    changed_crt,   app.shader_uniforms['crt']      = imgui.slider_float('CRT Curvature',      app.shader_uniforms['crt'],      0.0, 2.0)
    changed_scan,  app.shader_uniforms['scanline'] = imgui.slider_float('Scanline Intensity', app.shader_uniforms['scanline'], 0.0, 1.0)
    changed_bloom, app.shader_uniforms['bloom']    = imgui.slider_float('Bloom Threshold',    app.shader_uniforms['bloom'],    0.0, 1.0)

#TODO(Ed): This shouldn't be here, it needs to be within the test that uitlizes it.
def render_markdown_test(app: App) -> None:
 """Renders a static markdown test panel used to validate the markdown renderer.
 Displays headers, bold/italic text, lists, links, and a code block.

 SSDL: `[I:static_md_sample] => [I:rendered_markdown]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Markdown Test Panel                                     |
   | # Header 1                                              |
   | ## Header 2                                             |
   | This is **bold** text and *italic* text...              |
   | * List item 1                                           |
   |   * Sub-item                                            |
   | [Link to Google](https://google.com)                    |
   | ```python                                               |
   | def hello(): ...                                        |
   | ```                                                     |
   +---------------------------------------------------------+
 """
 imgui.text("Markdown Test Panel")
 imgui.separator()
 md = """
# Header 1
## Header 2
### Header 3
This is **bold** text and *italic* text.
And ***bold italic*** text.

* List item 1
* List item 2
 * Sub-item

[Link to Google](https://google.com)

```python

def hello():
   print("Markdown works!")
```
"""
 markdown_helper.render(md)

def render_error_tint(app: App) -> None:
 """Renders a red tint overlay if hot reload failed."""
 if not HotReloader.is_error_state: return
 draw_list    = imgui.get_background_draw_list()
 display_size = imgui.get_io().display_size
 # Translucent red
 tint_col = imgui.get_color_u32(theme.get_color("status_error", alpha=0.2))
 draw_list.add_rect_filled(imgui.ImVec2(0, 0), display_size, tint_col)
 if app.perf_profiling_enabled:
  imgui.set_next_window_pos(imgui.ImVec2(10, 50))
  with imscope.window("Hot Reload Error", None, imgui.WindowFlags_.always_auto_resize | imgui.WindowFlags_.no_title_bar):
   imgui.text_colored(theme.get_color("status_error"), "HOT RELOAD ERROR")
   imgui.text_wrapped(HotReloader.last_error or "Unknown error")

def render_project_stale_tint(app: App) -> None:
 """Renders a yellow/amber tint overlay when the project is mid-switch.

 UI remains responsive (scroll, tab browse) but AI / MD ops are gated
 on the controller's is_project_stale() returning False.
 """
 if not app.controller.is_project_stale(): return
 draw_list    = imgui.get_background_draw_list()
 display_size = imgui.get_io().display_size
 tint_col     = imgui.get_color_u32(theme.get_color("status_warning", alpha=0.15))
 draw_list.add_rect_filled(imgui.ImVec2(0, 0), display_size, tint_col)
 pending = app.controller._project_switch_pending_path or app.controller.active_project_path
 imgui.set_next_window_pos(imgui.ImVec2(10, 50))
 with imscope.window("Project Stale", None,
  imgui.WindowFlags_.always_auto_resize | imgui.WindowFlags_.no_title_bar |
  imgui.WindowFlags_.no_resize          | imgui.WindowFlags_.no_move):
  imgui.text_colored(theme.get_color("status_warning"), "PROJECT SWITCHING")
  imgui.text_wrapped(f"Loading: {Path(pending).stem if pending else '?'}")
  imgui.text_wrapped("UI is read-only until the switch completes. You can still browse tabs.")

def render_heavy_text(app: App, label: str, content: str, id_suffix: str = "") -> None:
 """Renders a labelled heavy-text field: a [+] button to pop content into the Text Viewer,
 a truncated inline preview, and a scrollable child panel showing the full content
 (markdown-rendered for message/text/content/system labels; plain text otherwise).

 SSDL: `[B:pop_out_viewer] -> [I:label_preview] -> [I:content_child]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [+] message: "You are a helpful assistant that..."      |
   | +-----------------------------------------------------+ |
   | | You are a helpful assistant that specializes in...  | |
   | | ...full rendered content...                         | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+
 """
 if imgui.button(f"[+]##{label}{id_suffix}"):
  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
  app.text_viewer_content         = content
  app.show_windows["Text Viewer"] = True
 imgui.same_line()
 imgui.text_colored(C_LBL(), f"{label}:"); imgui.same_line()
 render_selectable_label(app, f"heavy_label_{label}_{id_suffix}", content[:60].replace("\n", " ") + ("..." if len(content)>60 else ""), color=C_VAL())
 
 if content:
  ctx_id = f"{label}_{id_suffix}"
  is_md  = label in ('message', 'text', 'content', 'system')
  with imscope.indent():
   with imscope.style_color(imgui.Col_.child_bg, theme.get_table_color(is_alt=True)):
    with imscope.child(f"heavy_text_child_{label}_{id_suffix}", imgui.ImVec2(0, 300), imgui.WindowFlags_.always_vertical_scrollbar):
     if is_md:
      markdown_helper.render(content, context_id=ctx_id)
     else:
      if app.ui_word_wrap:
       with imscope.text_wrap(imgui.get_content_region_avail().x):
        imgui.text_colored(theme.get_color("text"), content)
      else:
       imgui.text_colored(theme.get_color("text"), content)

#endregion: Misc Tools

#region: MMA

def render_mma_dashboard(app: App) -> None:
 """Main MMA dashboard interface.

 SSDL: `[I:focus_selector] -> [I:track_summary] -> [B:epic_planner] -> [I:track_browser] -> [B:global_controls] -> [I:usage_analytics] -> [I:ticket_queue] -> [I:task_dag] => [I:agent_streams]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [Focus: Tier 1 v] [Agent Streams v]                     |
   | Track: my-track | Status: RUNNING | Cost: $0.0012       |
   | Progress: ============================= 67.2%            |
   | [Plan Epic (Tier 1)]         [Conductor Setup >]        |
   | Track Browser table...                                  |
   | [Step Mode] [Pause/Resume]                              |
   | Usage section...                                        |
   | Ticket queue table...                                   |
   | [Task DAG panel]                                        |
   | [Ticket Editor if selected]                             |
   | Agent Streams tabs...                                   |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_mma_dashboard")
 render_mma_focus_selector(app)
 imgui.separator()
 if app.is_viewing_prior_session:
  c = theme.get_color("status_warning") if theme.is_nerv_active() else theme.get_color("status_warning")
  imgui.text_colored(c, "HISTORICAL VIEW - READ ONLY")
  if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_mma_dashboard")
  return
 render_mma_track_summary(app)
 imgui.separator()
 render_mma_epic_planner(app)
 imgui.separator()
 if imgui.collapsing_header("Conductor Setup"): render_mma_conductor_setup(app)
 imgui.separator()
 render_mma_track_browser(app)
 imgui.separator()
 render_mma_global_controls(app)
 imgui.separator()
 render_mma_usage_section(app)
 imgui.separator()
 render_ticket_queue(app)
 imgui.separator()
 app._render_window_if_open("Task DAG", lambda: render_task_dag_panel(app), not app.ui_separate_task_dag)
 if app.ui_selected_ticket_id: render_mma_ticket_editor(app)
 imgui.separator()
 render_mma_agent_streams(app)
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_mma_dashboard")

def render_mma_modals(app: App) -> None:
 """Renders all MMA-specific approval and info modals: Tool Execution Approval,
 MMA Step Approval (with optional payload edit), MMA Spawn Approval (with
 prompt/context editing), and the Cycle Detected error modal.

 SSDL: `[I:pending_queues] => [I:tool_approval_modal] | [I:step_approval_modal] | [I:spawn_approval_modal] | [I:cycle_modal]`

 ASCII Layout Map:
   Conditionally opens (at most one at a time):

   [Approve Tool Execution modal]
     Tool: py_get_definition | Args: {...} | [Approve] [Deny]

   [MMA Step Approval modal]
     Ticket T-003 waiting... | payload preview or edit
     [Approve] [Edit Payload / Show Original] [Abort Ticket]

   [MMA Spawn Approval modal]
     Spawning Tier 3 for Ticket T-003
     Prompt + Context MD preview or edit
     [Approve] [Edit Mode / Preview Mode] [Abort]

   [Cycle Detected! modal]
     Circular dependency found. [OK]
 """
 is_nerv = theme.is_nerv_active()
 # Tool Execution Approval
 if app._pending_ask_dialog:
  if not app._ask_dialog_open:
   imgui.open_popup("Approve Tool Execution")
   app._ask_dialog_open = True
 else:
  app._ask_dialog_open = False
 if imgui.begin_popup_modal("Approve Tool Execution", None, imgui.WindowFlags_.always_auto_resize)[0]:
  if not app._pending_ask_dialog or app._ask_tool_data is None: imgui.close_current_popup()
  else:
   tool_name = app._ask_tool_data.get("tool", "unknown"); tool_args = app._ask_tool_data.get("args", {})
   imgui.text("The AI wants to execute a tool:"); imgui.text_colored(theme.get_color("status_warning"), f"Tool: {tool_name}"); imgui.separator()
   imgui.text("Arguments:"); imgui.begin_child("ask_args_child", imgui.ImVec2(400, 200), True); imgui.text_unformatted(json.dumps(tool_args, indent=2)); imgui.end_child()
   imgui.separator()
   if imgui.button("Approve", imgui.ImVec2(120, 0)): app._handle_approve_ask(); imgui.close_current_popup()
   imgui.same_line()
   if imgui.button("Deny", imgui.ImVec2(120, 0)): app._handle_reject_ask(); imgui.close_current_popup()
  imgui.end_popup()
 # MMA Step Approval
 if app._pending_mma_approvals:
  if not app._mma_approval_open:
   imgui.open_popup("MMA Step Approval")
   app._mma_approval_open, app._mma_approval_edit_mode = True, False
   app._mma_approval_payload = app._pending_mma_approvals[0].get("payload", "")
 else: app._mma_approval_open = False
 if imgui.begin_popup_modal("MMA Step Approval", None, imgui.WindowFlags_.always_auto_resize)[0]:
  if not app._pending_mma_approvals: imgui.close_current_popup()
  else:
   ticket_id = app._pending_mma_approvals[0].get("ticket_id", "??")
   imgui.text(f"Ticket {ticket_id} is waiting for tool execution approval."); imgui.separator()
   if app._mma_approval_edit_mode:
    imgui.text("Edit Raw Payload (Manual Memory Mutation):"); _, app._mma_approval_payload = imgui.input_text_multiline("##mma_payload", app._mma_approval_payload, imgui.ImVec2(600, 400))
   else:
    imgui.text("Proposed Tool Call:"); imgui.begin_child("mma_preview", imgui.ImVec2(600, 300), True); imgui.text_unformatted(str(app._pending_mma_approvals[0].get("payload", ""))); imgui.end_child()
   imgui.separator()
   if imgui.button("Approve", imgui.ImVec2(120, 0)): app._handle_mma_respond(approved=True, payload=app._mma_approval_payload); imgui.close_current_popup()
   imgui.same_line()
   if imgui.button("Edit Payload" if not app._mma_approval_edit_mode else "Show Original", imgui.ImVec2(120, 0)): app._mma_approval_edit_mode = not app._mma_approval_edit_mode
   imgui.same_line()
   if imgui.button("Abort Ticket", imgui.ImVec2(120, 0)): app._handle_mma_respond(approved=False); imgui.close_current_popup()
  imgui.end_popup()
 # MMA Spawn Approval
 if app._pending_mma_spawns:
  if not app._mma_spawn_open:
   imgui.open_popup("MMA Spawn Approval")
   app._mma_spawn_open, app._mma_spawn_edit_mode = True, False
   app._mma_spawn_prompt, app._mma_spawn_context = app._pending_mma_spawns[0].get("prompt", ""), app._pending_mma_spawns[0].get("context_md", "")
 else: app._mma_spawn_open = False
 if imgui.begin_popup_modal("MMA Spawn Approval", None, imgui.WindowFlags_.always_auto_resize)[0]:
  if not app._pending_mma_spawns: imgui.close_current_popup()
  else:
   role, ticket_id = app._pending_mma_spawns[0].get("role", "??"), app._pending_mma_spawns[0].get("ticket_id", "??")
   imgui.text(f"Spawning {role} for Ticket {ticket_id}"); imgui.separator()
   if app._mma_spawn_edit_mode:
    imgui.text("Edit Prompt:");     _, app._mma_spawn_prompt  = imgui.input_text_multiline("##spawn_prompt",  app._mma_spawn_prompt,  imgui.ImVec2(800, 200))
    imgui.text("Edit Context MD:"); _, app._mma_spawn_context = imgui.input_text_multiline("##spawn_context", app._mma_spawn_context, imgui.ImVec2(800, 300))
   else:
    imgui.text("Proposed Prompt:");     imgui.begin_child("spawn_prompt_preview",  imgui.ImVec2(800, 150), True); imgui.text_unformatted(app._mma_spawn_prompt);  imgui.end_child()
    imgui.text("Proposed Context MD:"); imgui.begin_child("spawn_context_preview", imgui.ImVec2(800, 250), True); imgui.text_unformatted(app._mma_spawn_context); imgui.end_child()
   imgui.separator()
   if imgui.button("Approve", imgui.ImVec2(120, 0)): app._handle_mma_respond(approved=True, prompt=app._mma_spawn_prompt, context_md=app._mma_spawn_context); imgui.close_current_popup()
   imgui.same_line()
   if imgui.button("Edit Mode" if not app._mma_spawn_edit_mode else "Preview Mode", imgui.ImVec2(120, 0)): app._mma_spawn_edit_mode = not app._mma_spawn_edit_mode
   imgui.same_line()
   if imgui.button("Abort", imgui.ImVec2(120, 0)): app._handle_mma_respond(approved=False, abort=True); imgui.close_current_popup()
  imgui.end_popup()
 # Cycle Detection
 if imgui.begin_popup_modal("Cycle Detected!", None, imgui.WindowFlags_.always_auto_resize)[0]:
  imgui.text_colored(theme.get_color("status_error", alpha=1), "The dependency graph contains a cycle!")
  imgui.text("Please remove the circular dependency.")
  if imgui.button("OK"): imgui.close_current_popup()
  imgui.end_popup()

def render_mma_track_summary(app: App) -> None:
 """Renders the active-track summary row: track name, MMA pipeline status, and running
 cost estimate, followed by a colour-coded progress bar and a 4-column breakdown table
 (Completed / In Progress / Blocked / Todo) and ETA estimation.

 SSDL: `[I:track_name] -> [I:status_badge] -> [I:cost] -> [I:progress_bar] -> [I:stats_table] -> [I:eta]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Track: my-feature | Status: RUNNING | Cost: $0.0024     |
   | [=========================         ] 52.3%              |
   | Completed: 6 | In Progress: 1 | Blocked: 0 | Todo: 5    |
   | ETA: ~14m (6 tickets remaining)                         |
   +---------------------------------------------------------+
 """
 is_nerv    = theme.is_nerv_active()
 track_name = app.active_track.description if app.active_track else "None"
 if getattr(app, "ui_project_execution_mode", "native") == "beads": track_name = "Beads Graph"
 track_stats = project_manager.calculate_track_progress(app.active_track.tickets if app.active_track else app.active_tickets)
 total_cost  = sum(cost_tracker.estimate_cost(u.get('model','unknown'), u.get('input',0), u.get('output',0)) for u in app.mma_tier_usage.values())
 imgui.text("Track:"); imgui.same_line(); imgui.text_colored(C_VAL(), track_name); imgui.same_line(); imgui.text(" | Status:"); imgui.same_line()
 if app.mma_status == "paused":
  imgui.text_colored(theme.get_color("status_warning") if is_nerv else theme.get_color("status_warning"), "PIPELINE PAUSED"); imgui.same_line()
 status_col = imgui.ImVec4(1, 1, 1, 1)
 if   app.mma_status == "idle":    status_col = theme.get_color("text_disabled")
 elif app.mma_status == "running": status_col = theme.get_color("status_success") if is_nerv else theme.get_color("status_warning")
 elif app.mma_status == "done":    status_col = theme.get_color("status_success")
 elif app.mma_status == "error":   status_col = theme.get_color("status_error")   if is_nerv else theme.get_color("status_error")
 elif app.mma_status == "paused":  status_col = theme.get_color("status_warning")
 imgui.text_colored(status_col, app.mma_status.upper()); imgui.same_line(); imgui.text(" | Cost:"); imgui.same_line(); imgui.text_colored(theme.get_color("status_success"), f"${total_cost:,.4f}")
 perc    = track_stats["percentage"] / 100.0
 p_color = theme.get_color("status_error") if track_stats["percentage"] < 33 else (theme.get_color("status_warning") if track_stats["percentage"] < 66 else theme.get_color("status_success"))
 imgui.push_style_color(imgui.Col_.plot_histogram, p_color); imgui.progress_bar(perc, imgui.ImVec2(-1, 0), f"{track_stats['percentage']:.1f}%"); imgui.pop_style_color()
 if imgui.begin_table("ticket_stats_breakdown", 4):
  for lbl, val in [("Completed:", track_stats["completed"]), ("In Progress:", track_stats["in_progress"]), ("Blocked:", track_stats["blocked"]), ("Todo:", track_stats["todo"])]:
   imgui.table_next_column(); imgui.text_colored(C_LBL(), lbl); imgui.same_line(); imgui.text_colored(C_VAL(), str(val))
  imgui.end_table()
 if app.active_track:
  remaining = track_stats["total"] - track_stats["completed"]
  eta_mins  = (app._avg_ticket_time * remaining) / 60.0
  imgui.text_colored(C_LBL(), "ETA:"); imgui.same_line(); imgui.text_colored(C_VAL(), f"~{int(eta_mins)}m ({remaining} tickets remaining)")

def render_mma_epic_planner(app: App) -> None:
 """Renders the Epic Planning panel for Tier 1 strategy input and planning button.

 SSDL: `[I:input_textbox] => [B:plan_button]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Epic Planning (Tier 1)                                  |
   | +-----------------------------------------------------+ |
   | | Describe the epic goal...                           | |
   | +-----------------------------------------------------+ |
   | [Plan Epic (Tier 1)                                   ] |
   +---------------------------------------------------------+
 """
 imgui.text_colored(C_LBL(), 'Epic Planning (Tier 1)')
 _, app.ui_epic_input = imgui.input_text_multiline('##epic_input', app.ui_epic_input, imgui.ImVec2(-1, 80))
 if imgui.button('Plan Epic (Tier 1)', imgui.ImVec2(-1, 0)): app._cb_plan_epic()

def render_mma_conductor_setup(app: App) -> None:
 """Renders the Conductor Setup panel. Runs a one-shot project-structure scan and
 shows the summarised output in a read-only text area.

 SSDL: `[B:run_scan] => [I:summary_text]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [Run Setup Scan]                                        |
   | +-----------------------------------------------------+ |
   | | Project: manual_slop                                | |
   | | Tracks found: 3  |  Active: my-feature-20260612     | |
   | | Tickets: 12 todo, 3 done                            | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+
 """
 if imgui.button("Run Setup Scan"): app._cb_run_conductor_setup()
 if app.ui_conductor_setup_summary: imgui.input_text_multiline("##setup_summary", app.ui_conductor_setup_summary, imgui.ImVec2(-1, 120), imgui.InputTextFlags_.read_only)

def render_mma_track_browser(app: App) -> None:
 """Renders the Track Browser: a table listing all known tracks with status, progress bar,
 and a [Load] button per row. Below the table is a form for creating a new track.

 SSDL: `[I:tracks_table] -> [B:load_button] -> [I:create_track_form] => [B:create_track]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Track Browser                                           |
   | +------------------+--------+----------+---------+      |
   | | Title            | Status | Progress | Actions |      |
   | +------------------+--------+----------+---------+      |
   | | my-feature-0612  | ACTIVE | [=====  ] [Load]   |      |
   | | chore-cleanup    | NEW    | [        ] [Load]   |     |
   | +------------------+--------+----------+---------+      |
   | Create New Track                                        |
   | Name: [___________]                                     |
   | Description: [_____________________________]            |
   | Type: [feature v]                                       |
   | [Create Track]                                          |
   +---------------------------------------------------------+
 """
 imgui.text("Track Browser")
 if imgui.begin_table("mma_tracks_table", 4, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable):
  imgui.table_setup_column("Title"); imgui.table_setup_column("Status"); imgui.table_setup_column("Progress"); imgui.table_setup_column("Actions"); imgui.table_headers_row()
  for track in app.tracks:
   imgui.table_next_row(); imgui.table_next_column(); imgui.text(track.get("title", "Untitled")); imgui.table_next_column()
   status = track.get("status", "unknown").lower()
   c      = theme.get_color("text_disabled") if status == "new" else (theme.get_color("status_success") if status == "active" and theme.is_nerv_active() else (theme.get_color("status_warning") if status == "active" else (theme.get_color("status_success") if status == "done" else (theme.get_color("status_error") if status == "blocked" else imgui.ImVec4(1, 1, 1, 1)))))
   imgui.text_colored(c, status.upper()); imgui.table_next_column()
   prog = track.get("progress", 0.0)
   p_c  = theme.get_color("status_error") if prog < 0.33 else (theme.get_color("status_warning") if prog < 0.66 else theme.get_color("status_success"))
   imgui.push_style_color(imgui.Col_.plot_histogram, p_c); imgui.progress_bar(prog, imgui.ImVec2(-1, 0), f"{int(prog*100)}%"); imgui.pop_style_color(); imgui.table_next_column()
   if imgui.button(f"Load##{track.get('id')}"): app._cb_load_track(str(track.get("id") or ""))
  imgui.end_table()
 imgui.text("Create New Track")
 _, app.ui_new_track_name = imgui.input_text("Name##new_track", app.ui_new_track_name)
 _, app.ui_new_track_desc = imgui.input_text_multiline("Description##new_track", app.ui_new_track_desc, imgui.ImVec2(-1, 60))
 imgui.text("Type:"); imgui.same_line()
 if imgui.begin_combo("##track_type", app.ui_new_track_type):
  for ttype in ["feature", "chore", "fix"]:
   if imgui.selectable(ttype, app.ui_new_track_type == ttype)[0]: app.ui_new_track_type = ttype
  imgui.end_combo()
 if imgui.button("Create Track"):
  app._cb_create_track(app.ui_new_track_name, app.ui_new_track_desc, app.ui_new_track_type)
  app.ui_new_track_name = ""; app.ui_new_track_desc = ""

def render_mma_global_controls(app: App) -> None:
 """Renders the MMA global controls bar: Step Mode (HITL) toggle, pipeline status label,
 Pause/Resume engine button, active tier badge, approval-pending blink indicator, and
 the Hot Reload shortcut.

 SSDL: `[B:step_mode] -> [I:status] -> [B:pause_resume] -> [I:active_tier] -> [I:approval_blink] -> [B:hot_reload]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [ ] Step Mode (HITL)  Status: RUNNING  [Pause]          |
   | |  Active: Tier 3                                       |
   | *** APPROVAL PENDING ***  [Go to Approval]              |
   | Hot Reload: [Reload GUI]  (Ctrl+Alt+R)                  |
   +---------------------------------------------------------+
 """
 changed, app.mma_step_mode = imgui.checkbox("Step Mode (HITL)", app.mma_step_mode)
 imgui.same_line(); imgui.text(f"Status: {app.mma_status.upper()}")
 if app.controller and hasattr(app.controller, 'engine') and app.controller.engine and hasattr(app.controller.engine, '_pause_event'):
  imgui.same_line()
  is_paused = app.controller.engine._pause_event.is_set()
  if imgui.button("Resume" if is_paused else "Pause"):
   if is_paused: app.controller.engine.resume()
   else:         app.controller.engine.pause()
 if app.active_tier:
  imgui.same_line(); imgui.text_colored(C_VAL(), f"|  Active: {app.active_tier}")
 any_pending = len(app._pending_mma_spawns) > 0 or len(app._pending_mma_approvals) > 0 or app._pending_ask_dialog
 if any_pending:
  alpha = abs(math.sin(time.time() * 5))
  c     = theme.get_color("status_error", alpha=alpha)
  imgui.same_line(); imgui.text_colored(c, "  APPROVAL PENDING"); imgui.same_line()
  if imgui.button("Go to Approval"): pass
 imgui.separator()
 imgui.text("Hot Reload:")
 imgui.same_line()
 if imgui.button("Reload GUI"):
  success = app._trigger_hot_reload()
  if success: imgui.text_colored(theme.get_color("status_success"), "Reloaded!")
  else:       imgui.text_colored(theme.get_color("status_error"), f"Error: {app._hot_reload_error or 'Unknown'}")
 imgui.same_line(); imgui.text_disabled("(Ctrl+Alt+R)")

def render_mma_usage_section(app: App) -> None:
 """Renders the Tier Usage table (tokens and estimated cost per tier) and, when expanded,
 a collapsible Tier Model Config section with per-tier provider, model, tool-preset,
 and persona overrides.

 SSDL: `[I:usage_table] -> [B:tier_model_config_header] => [I:combos_per_tier]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Tier Usage (Tokens & Cost)                              |
   | +--------+------------------+-------+-------+--------+  |
   | | Tier   | Model            | Input | Output | Cost  |  |
   | +--------+------------------+-------+-------+--------+  |
   | | Tier 1 | gemini-2.5-flash | 1,200 | 400   | $0.001 |  |
   | | TOTAL  |                  |       |       | $0.004 |  |
   | +--------+------------------+-------+-------+--------+  |
   | > Tier Model Config (collapsible)                       |
   |   Tier 1: [gemini v] [gemini-2.5-flash v] [None v]      |
   +---------------------------------------------------------+
 """
 imgui.text("Tier Usage (Tokens & Cost)")
 if imgui.begin_table("mma_usage", 5, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg):
  imgui.table_setup_column("Tier"); imgui.table_setup_column("Model"); imgui.table_setup_column("Input"); imgui.table_setup_column("Output"); imgui.table_setup_column("Est. Cost"); imgui.table_headers_row()
  total_cost = 0.0
  for tier, stats in app.mma_tier_usage.items():
   imgui.table_next_row(); 
   imgui.table_next_column(); 
   imgui.text(tier); imgui.table_next_column(); 
   model       = stats.get('model', 'unknown'); imgui.text(model);        imgui.table_next_column(); 
   in_t        = stats.get('input', 0);         imgui.text(f"{in_t:,}");  imgui.table_next_column(); 
   out_t       = stats.get('output', 0);        imgui.text(f"{out_t:,}"); imgui.table_next_column();
   cost        = cost_tracker.estimate_cost(model, in_t, out_t); 
   total_cost += cost; imgui.text(f"${cost:,.4f}")
  imgui.table_next_row(); 
  imgui.table_set_bg_color(imgui.TableBgTarget_.row_bg0, imgui.get_color_u32(imgui.Col_.plot_lines_hovered)); imgui.table_next_column(); 
  imgui.text("TOTAL"); imgui.table_next_column(); 
  imgui.text("");      imgui.table_next_column(); 
  imgui.text("");      imgui.table_next_column(); 
  imgui.text("");      imgui.table_next_column(); 
  imgui.text(f"${total_cost:,.4f}"); 
  imgui.end_table()

 if imgui.collapsing_header("Tier Model Config"):
  for tier in app.mma_tier_usage.keys():
   imgui.text(f"{tier}:"); imgui.same_line(); curr_model, curr_prov = app.mma_tier_usage[tier].get("model", "unknown"), app.mma_tier_usage[tier].get("provider", "gemini")
   with imscope.id(f"tier_cfg_{tier}"):
    imgui.push_item_width(80)
    if imgui.begin_combo("##prov", curr_prov):
     for p in ai_client.PROVIDERS:
      if imgui.selectable(p, p == curr_prov)[0]:
       app.mma_tier_usage[tier]["provider"] = p
       models_list = app.controller.all_available_models.get(p, [])
       if models_list: app.mma_tier_usage[tier]["model"] = models_list[0]
     imgui.end_combo()
    imgui.pop_item_width(); imgui.same_line(); imgui.push_item_width(150)
    models_list = app.controller.all_available_models.get(curr_prov, [])
    if imgui.begin_combo("##model", curr_model):
     for m in models_list:
      if imgui.selectable(m, curr_model == m)[0]: app.mma_tier_usage[tier]["model"] = m
     imgui.end_combo()
    imgui.pop_item_width(); imgui.same_line(); imgui.push_item_width(-1)
    curr_preset = app.mma_tier_usage[tier].get("tool_preset") or "None"
    p_names = ["None"] + sorted(app.controller.tool_presets.keys())
    if imgui.begin_combo("##preset", curr_preset):
     for pn in p_names:
      if imgui.selectable(pn, curr_preset == pn)[0]: app.mma_tier_usage[tier]["tool_preset"] = None if pn == "None" else pn
     imgui.end_combo()
    imgui.pop_item_width(); imgui.same_line(); imgui.push_item_width(150)
    curr_pers = app.mma_tier_usage[tier].get("persona") or "None"
    personas  = getattr(app.controller, 'personas', {})
    pers_opts = ["None"] + sorted(personas.keys())
    if imgui.begin_combo("##persona", curr_pers):
     for pern in pers_opts:
      if imgui.selectable(pern, curr_pers == pern)[0]: app.mma_tier_usage[tier]["persona"] = None if pern == "None" else pern
     imgui.end_combo()
    imgui.pop_item_width()

def render_mma_ticket_editor(app: App) -> None:
 """Renders the ticket detail editor panel, letting the user modify priority, target,
 and persona override, mark a ticket complete, or delete it.

 SSDL: `[I:ticket_details] -> [B:combo_selectors] => [B:action_buttons]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Editing: ticket-042                                     |
   | Status: in_progress                                     |
   | Priority: [medium v]                                    |
   | Target: src/gui_2.py                                    |
   | Depends on: ticket-041, ticket-040                      |
   | Persona Override: [None v]                              |
   | [Mark Complete] [Delete]                                |
   +---------------------------------------------------------+
 """
 imgui.separator(); imgui.text_colored(C_VAL(), f"Editing: {app.ui_selected_ticket_id}")
 ticket = next((t for t in app.active_tickets if str(t.get('id', '')) == app.ui_selected_ticket_id), None)
 if ticket:
  imgui.text(f"Status: {ticket.get('status', 'todo')}"); prio = ticket.get('priority', 'medium')
  imgui.text("Priority:"); imgui.same_line()
  if imgui.begin_combo(f"##edit_prio_{ticket.get('id')}", prio):
   for p_opt in ['high', 'medium', 'low']:
    if imgui.selectable(p_opt, p_opt == prio)[0]: ticket['priority'] = p_opt; app._push_mma_state_update()
   imgui.end_combo()
  imgui.text(f"Target: {ticket.get('target_file', '')}"); imgui.text(f"Depends on: {', '.join(ticket.get('depends_on', []))}")
  personas = getattr(app.controller, 'personas', {}); curr_pers = ticket.get('persona_id', '')
  imgui.text("Persona Override:"); imgui.same_line()
  pers_opts            = ["None"] + sorted(personas.keys()); 
  curr_idx             = pers_opts.index(curr_pers) + 1 if curr_pers in pers_opts else 0
  _, curr_idx          = imgui.combo(f"##ticket_persona_{ticket.get('id')}", curr_idx, pers_opts)
  ticket['persona_id'] = None if curr_idx == 0 or pers_opts[curr_idx] == "None" else pers_opts[curr_idx]
  if imgui.button(f"Mark Complete##{app.ui_selected_ticket_id}"): ticket['status'] = 'done'; app._push_mma_state_update()
  imgui.same_line()
  if imgui.button(f"Delete##{app.ui_selected_ticket_id}"): 
   app.active_tickets        = [t for t in app.active_tickets if str(t.get('id', '')) != app.ui_selected_ticket_id]
   app.ui_selected_ticket_id = None
   app._push_mma_state_update()

def render_mma_agent_streams(app: App) -> None:
 """Renders the agent execution stream panels in a tabbed view for Tier 1, 2, 3, and 4.

 SSDL: `[I] -> [B:tab_bar] => [I:stream_panels]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | [Tier 1] [Tier 2] [Tier 3] [Tier 4] ([Beads])          |
   | [ ] Pop Out Tier 1                                      |
   | +-----------------------------------------------------+ |
   | | Tier 1: Strategy stream text...                     | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+
 """
 imgui.text("Agent Streams")
 if imgui.begin_tab_bar("mma_streams_tabs"):
  for tier, label, sep_flag_attr in [("Tier 1", "Tier 1", "ui_separate_tier1"), ("Tier 2", "Tier 2 (Tech Lead)", "ui_separate_tier2"), ("Tier 3", None, "ui_separate_tier3"), ("Tier 4", "Tier 4 (QA)", "ui_separate_tier4")]:
   with imscope.tab_item(tier) as (exp, _):
    if exp:
     sep_val     = getattr(app, sep_flag_attr);
     ch, new_val = imgui.checkbox(f"Pop Out {tier}", sep_val)
     if ch:
      setattr(app, sep_flag_attr, new_val)
      app.show_windows[f"{tier}: Strategy" if tier == "Tier 1" else (f"{tier}: Tech Lead" if tier == "Tier 2" else (f"{tier}: Workers" if tier == "Tier 3" else f"{tier}: QA"))] = new_val
     if not new_val: render_tier_stream_panel(app, tier, label)
     else:           imgui.text_disabled(f"{tier} stream is detached.")
  if getattr(app, "ui_project_execution_mode", "native") == "beads":
   with imscope.tab_item("Beads") as (exp, _):
    if exp: render_beads_tab(app)
  imgui.end_tab_bar()

def render_tier_stream_panel(app: App, tier_key: str, stream_key: str | None) -> None:
 """Renders a single tier's live streaming text panel. For Tier 1, 2, and 4 a single scrollable
 selectable text area is shown; for Tier 3 (stream_key=None), each worker ticket gets
 its own labelled, colour-coded sub-child with an auto-scroll behaviour.

 SSDL: `[I:stream_text] => [I:single_scroll] | [I:worker_sub_children]`

 ASCII Layout Map (single-stream, e.g. Tier 1):
   +---------------------------------------------------------+
   | +-----------------------------------------------------+ |
   | | Tier 1 stream output text...                        | |
   | | ...                                                 | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+

 ASCII Layout Map (Tier 3 multi-worker):
   +---------------------------------------------------------+
   | T-001 [running]                                         |
   | +-----------------------------------------------------+ |
   | | Worker output for T-001...                          | |
   | +-----------------------------------------------------+ |
   | T-002 [completed]                                       |
   | +-----------------------------------------------------+ |
   | | Worker output for T-002...                          | |
   | +-----------------------------------------------------+ |
   +---------------------------------------------------------+
 """
 if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_tier_stream_panel")
 if app.is_viewing_prior_session:
  imgui.text_colored(theme.get_color("status_warning"), "HISTORICAL VIEW - READ ONLY")
  if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_tier_stream_panel")
  return
 if stream_key is not None:
  content = app.mma_streams.get(stream_key, "")
  imgui.begin_child(f"##stream_content_{tier_key}", imgui.ImVec2(-1, -1))
  render_selectable_label(app, f'stream_{tier_key}', content, width=-1, multiline=True, height=0)
  try:
   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)
  except (TypeError, AttributeError):
   pass
  finally:
   imgui.end_child()
 else:
  tier3_keys = [k for k in app.mma_streams if "Tier 3" in k]
  if not tier3_keys:
   imgui.text_disabled("No worker output yet.")
  else:
   worker_status = getattr(app, '_worker_status', {})
   for key in tier3_keys:
    ticket_id = key.split(": ", 1)[-1] if ": " in key else key
    status = worker_status.get(key, "unknown")
    if   status == "running":   imgui.text_colored(theme.get_color("status_warning"), f"{ticket_id} [{status}]")
    elif status == "completed": imgui.text_colored(theme.get_color("status_success"), f"{ticket_id} [{status}]")
    elif status == "failed":    imgui.text_colored(theme.get_color("status_error"),   f"{ticket_id} [{status}]")
    else:                       imgui.text(                                           f"{ticket_id} [{status}]")
    imgui.begin_child(f"##tier3_{ticket_id}_scroll", imgui.ImVec2(-1, 150), True)
    render_selectable_label(app, f'stream_t3_{ticket_id}', app.mma_streams[key], width=-1, multiline=True, height=0)
    #NOTE(Ed): Exception(Thirdparty)
    try:
     if len(app.mma_streams[key]) != app._tier_stream_last_len.get(key, -1):
      imgui.set_scroll_here_y(1.0)
     app._tier_stream_last_len[key] = len(app.mma_streams[key])
     imgui.end_child()
    except (TypeError, AttributeError):
     imgui.end_child()
     pass
 if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_tier_stream_panel")

def render_track_proposal_modal(app: App) -> None:
 """Renders the Track Proposal modal. Shows Tier 1's generated implementation tracks
 with editable title and goal fields, and buttons to remove, start individual tracks,
 accept all, or cancel.

 SSDL: `[I:proposed_tracks] -> [B:edit_title/goal] -> [B:remove/start] => [B:accept_all | B:cancel]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Proposed Implementation Tracks                          |
   | Title: [my-feature-core_______________]                 |
   | Goal:  [Implement the rendering pipeline...]            |
   | [Remove]  [Start This Track]                            |
   | ---                                                     |
   | Title: [my-feature-tests______________]                 |
   | Goal:  [Write unit tests for...]                        |
   | [Remove]  [Start This Track]                            |
   | ---                                                     |
   | [Accept]  [Cancel]                                      |
   +---------------------------------------------------------+
 """
 if app._show_track_proposal_modal:
  imgui.open_popup("Track Proposal")
 if imgui.begin_popup_modal("Track Proposal", True, imgui.WindowFlags_.always_auto_resize)[0]:
  from src import shaders #TODO(Ed): Review local import
  p_min = imgui.get_window_pos()
  p_max = imgui.ImVec2(p_min.x + imgui.get_window_size().x, p_min.y + imgui.get_window_size().y)
  # Render soft shadow behind the modal
  shaders.draw_soft_shadow(imgui.get_background_draw_list(), p_min, p_max, imgui.ImVec4(0, 0, 0, 0.6), 25.0, 6.0)
  
  if app._show_track_proposal_modal:
   imgui.text_colored(C_IN(), "Proposed Implementation Tracks")
   imgui.separator()
   if not app.proposed_tracks:
    imgui.text("No tracks generated.")
   else:
    for idx, track in enumerate(app.proposed_tracks):
    # Title Edit
     changed_t, new_t = imgui.input_text(f"Title##{idx}", track.get('title', ''))
     if changed_t:
      track['title'] = new_t
      # Goal Edit
     changed_g, new_g = imgui.input_text_multiline(f"Goal##{idx}", track.get('goal', ''), imgui.ImVec2(-1, 60))
     if changed_g:
      track['goal'] = new_g
      # Buttons
     if imgui.button(f"Remove##{idx}"):
      app.proposed_tracks.pop(idx)
      break
     imgui.same_line()
     if imgui.button(f"Start This Track##{idx}"):
      app._cb_start_track(idx)
     imgui.separator()
   if imgui.button("Accept", imgui.ImVec2(120, 0)):
    app._cb_accept_tracks()
    app._show_track_proposal_modal = False
    imgui.close_current_popup()
   imgui.same_line()
   if imgui.button("Cancel", imgui.ImVec2(120, 0)):
    app._show_track_proposal_modal = False
    imgui.close_current_popup()
  else:
   imgui.close_current_popup()
  imgui.end_popup()

def render_ticket_queue(app: App) -> None:
 """Renders the Ticket Queue Management panel. Provides select-all/none controls,
 bulk action buttons, a 7-column scrollable table of tickets with drag-to-reorder,
 priority/model combos, status labels, and per-row Kill/Block/Unblock actions.

 SSDL: `[B:select_all/none] -> [B:bulk_actions] -> [I:ticket_table] => [I:ticket_editor?]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Ticket Queue Management                                 |
   | [Select All] [Select None]  [Bulk Execute] [Bulk Skip]  |
   | [Bulk Block]                                            |
   | +---+-------+----------+-------+----------+----------+ |
   | |[ ]| ID    | Priority | Model | Status   | Desc ... | |
   | +---+-------+----------+-------+----------+----------+ |
   | |[x]| T-001 | [high v] |[def v]| in_prog  | Impl ... | |
   | |[ ]| T-002 | [med  v] |[def v]| todo     | Write... | |
   | +---+-------+----------+-------+----------+----------+ |
   +---------------------------------------------------------+
 """
 imgui.text("Ticket Queue Management")
 if not app.active_track:
  imgui.text_disabled("No active track.")
  return

 # Select All / None
 if imgui.button("Select All"): app.ui_selected_tickets = {str(t.get('id', '')) for t in app.active_tickets}
 imgui.same_line()
 if imgui.button("Select None"): app.ui_selected_tickets.clear()

 imgui.same_line(); imgui.spacing(); imgui.same_line()

 # Bulk Actions
 if imgui.button("Bulk Execute"): app.bulk_execute()
 imgui.same_line()
 if imgui.button("Bulk Skip"): app.bulk_skip()
 imgui.same_line()
 if imgui.button("Bulk Block"): app.bulk_block()
 # Table
 flags = imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable | imgui.TableFlags_.scroll_y
 if imgui.begin_table("ticket_queue_table", 7, flags, imgui.ImVec2(0, 300)):
  imgui.table_setup_column("Select",      imgui.TableColumnFlags_.width_fixed, 40)
  imgui.table_setup_column("ID",          imgui.TableColumnFlags_.width_fixed, 80)
  imgui.table_setup_column("Priority",    imgui.TableColumnFlags_.width_fixed, 100)
  imgui.table_setup_column("Model",       imgui.TableColumnFlags_.width_fixed, 150)
  imgui.table_setup_column("Status",      imgui.TableColumnFlags_.width_fixed, 100)
  imgui.table_setup_column("Description", imgui.TableColumnFlags_.width_stretch)
  imgui.table_setup_column("Actions",     imgui.TableColumnFlags_.width_fixed, 80)
  imgui.table_headers_row()

  for i, t in enumerate(app.active_tickets):
   tid = str(t.get('id', ''))
   imgui.table_next_row()

   # Select
   imgui.table_next_column()
   is_sel          = tid in app.ui_selected_tickets
   changed, is_sel = imgui.checkbox(f"##sel_{tid}", is_sel)
   if changed:
    if is_sel: app.ui_selected_tickets.add(tid)
    else:      app.ui_selected_tickets.discard(tid)

   # ID
   imgui.table_next_column()
   is_selected = (tid == app.ui_selected_ticket_id)
   opened, _   = imgui.selectable(f"{tid}##drag_{tid}", is_selected)
   if opened: app.ui_selected_ticket_id = tid

   if imgui.begin_drag_drop_source():
    imgui.set_drag_drop_payload("TICKET_REORDER", i)
    imgui.text(f"Moving {tid}")
    imgui.end_drag_drop_source()

   if imgui.begin_drag_drop_target():
    payload = imgui.accept_drag_drop_payload("TICKET_REORDER")
    if payload:
     src_idx = int(payload.data)
     app._reorder_ticket(src_idx, i)
    imgui.end_drag_drop_target()

   # Priority

   imgui.table_next_column()
   prio  = t.get('priority', 'medium')
   p_col = theme.get_color("text_disabled") # gray
   if   prio == 'high':   _col  = theme.get_color("status_error") # red
   elif prio == 'medium': p_col = theme.get_color("status_warning") # yellow
   
   imgui.push_style_color(imgui.Col_.text, p_col)
   if imgui.begin_combo(f"##prio_{tid}", prio, imgui.ComboFlags_.height_small):
    for p_opt in ['high', 'medium', 'low']:
     if imgui.selectable(p_opt, p_opt == prio)[0]:
      t['priority'] = p_opt
      app._push_mma_state_update()
    imgui.end_combo()
   imgui.pop_style_color()

   # Model
   imgui.table_next_column()
   model_override = t.get('model_override')
   current_model = model_override if model_override else "Default"
   if imgui.begin_combo(f"##model_{tid}", current_model, imgui.ComboFlags_.height_small):
    if imgui.selectable("Default", model_override is None)[0]:
     t['model_override'] = None; app._push_mma_state_update()
    for model in ["gemini-2.5-flash-lite", "gemini-2.5-flash", "gemini-3-flash-preview", "gemini-3.1-pro-preview", "deepseek-v3"]:
     if imgui.selectable(model, model_override == model)[0]:
      t['model_override'] = model; app._push_mma_state_update()
    imgui.end_combo()

   # Status
   imgui.table_next_column()
   status = t.get('status', 'todo')
   if t.get('model_override'): imgui.text_colored(theme.get_color("status_warning"), f"{status} [{t.get('model_override')}]")
   else:                       imgui.text(t.get('status', 'todo'))

   # Description
   imgui.table_next_column()
   imgui.text(t.get('description', ''))

   # Actions - Kill button for in_progress tickets
   imgui.table_next_column()
   status = t.get('status', 'todo')
   if status == 'in_progress':  
    if imgui.button(f"Kill##{tid}"): app._cb_kill_ticket(tid)
   elif status == 'todo':
    if imgui.button(f"Block##{tid}"): app._cb_block_ticket(tid)
   elif status == 'blocked' and t.get('manual_block', False):
    if imgui.button(f"Unblock##{tid}"): app._cb_unblock_ticket(tid)

  imgui.end_table()

def render_task_dag_panel(app: App) -> None:  # 4. Task DAG Visualizer
 """Renders the interactive node editor DAG visualizer, mapping ticket relationships
 and allowing creation or deletion of links.

 SSDL: `[I:task_nodes] -> [B:node_editor_canvas] => [I:link_connections]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Task DAG                                                |
   | +-----------------------------------------------------+ |
   | |  [ticket-041]--->[ticket-042]--->[ticket-043]       | |
   | |        |                                            | |
   | |  [ticket-040]                                       | |
   | +-----------------------------------------------------+ |
   | [Add Ticket]                                            |
   +---------------------------------------------------------+
 """
 imgui.text("Task DAG")
 if (app.active_track or app.active_tickets) and app.node_editor_ctx:
  ed.set_current_editor(app.node_editor_ctx)
  ed.begin('Visual DAG')
  # Selection detection
  selected = ed.get_selected_nodes()
  if selected:
   for node_id in selected:
    node_val = node_id.id()
    for t in app.active_tickets:
     if abs(hash(str(t.get('id', '')))) == node_val:
      app.ui_selected_ticket_id = str(t.get('id', ''))
      break
    break
  for t in app.active_tickets:
   tid    = str(t.get('id', '??'))
   int_id = abs(hash(tid))
   ed.begin_node(ed.NodeId(int_id))
   if getattr(app, "ui_project_execution_mode", "native") == "beads":
    imgui.text_colored(theme.get_color("status_info"), "[B] ")
    imgui.same_line()
   imgui.text_colored(C_KEY(), f"Ticket: {tid}")
   status = t.get('status', 'todo')
   s_col  = C_VAL()
   if  status  == 'done'        or status == 'complete': s_col = C_IN()
   elif status == 'in_progress' or status == 'running':  s_col = C_OUT()
   elif status == 'error': s_col = theme.get_color("status_error")
   imgui.text("Status: ")
   imgui.same_line()
   imgui.text_colored(s_col, status)
   imgui.text(f"Target: {t.get('target_file','')}")
   ed.begin_pin(ed.PinId(abs(hash(tid + "_in"))), ed.PinKind.input)
   imgui.text("->")
   ed.end_pin()
   imgui.same_line()
   ed.begin_pin(ed.PinId(abs(hash(tid + "_out"))), ed.PinKind.output)
   imgui.text("->")
   ed.end_pin()
   ed.end_node()
  for t in app.active_tickets:
   tid = str(t.get('id', '??'))
   for dep in t.get('depends_on', []):
    ed.link(ed.LinkId(abs(hash(dep + "_" + tid))), ed.PinId(abs(hash(dep + "_out"))), ed.PinId(abs(hash(tid + "_in"))))
  
  # Handle link creation
  if ed.begin_create():
   start_pin = ed.PinId()
   end_pin = ed.PinId()
   if ed.query_new_link(start_pin, end_pin):
    if ed.accept_new_item():
     s_id = start_pin.id()
     e_id = end_pin.id()
     source_tid = None
     target_tid = None
     for t in app.active_tickets:
      tid = str(t.get('id', ''))
      if abs(hash(tid + "_out")) == s_id: source_tid = tid
      if abs(hash(tid + "_out")) == e_id: source_tid = tid
      if abs(hash(tid + "_in"))  == s_id: target_tid = tid
      if abs(hash(tid + "_in"))  == e_id: target_tid = tid
     if source_tid and target_tid and source_tid != target_tid:
      for t in app.active_tickets:
       if str(t.get('id', '')) == target_tid:
        if source_tid not in t.get('depends_on', []):
         t.setdefault('depends_on', []).append(source_tid)
         app._push_mma_state_update()
        break
    ed.end_create()

  # Handle link deletion
  if ed.begin_delete():
   link_id = ed.LinkId()
   while ed.query_deleted_link(link_id):
    if ed.accept_deleted_item():
     lid_val = link_id.id()
     for t in app.active_tickets:
      tid  = str(t.get('id', ''))
      deps = t.get('depends_on', [])
      if any(abs(hash(d + "_" + tid)) == lid_val for d in deps):
       t['depends_on'] = [dep for dep in deps if abs(hash(dep + "_" + tid)) != lid_val]
       app._push_mma_state_update()
       break
   ed.end_delete()
  
  # Validate DAG after any changes
  #TODO(Ed): Exception(Review)
  try:
   from src.dag_engine import TrackDAG #TODO(Ed) Reivew local import
   ticket_dicts = [{'id': str(t.get('id', '')), 'depends_on': t.get('depends_on', [])} for t in app.active_tickets]
   temp_dag     = TrackDAG(ticket_dicts)
   if temp_dag.has_cycle():
    imgui.open_popup("Cycle Detected!")
  except Exception:
   pass
  
  ed.end()
  # 5. Add Ticket Form
  imgui.separator()
  if imgui.button("Add Ticket"):
   app._show_add_ticket_form = not app._show_add_ticket_form
   if app._show_add_ticket_form:
   # Default Ticket ID
    max_id = 0
    for t in app.active_tickets:
     tid = t.get('id', '')
     if tid.startswith('T-'):
      #TODO(Ed): Exception(Review)
      try: max_id = max(max_id, int(tid[2:]))
      except: pass
    app.ui_new_ticket_id     = f"T-{max_id + 1:03d}"
    app.ui_new_ticket_desc   = ""
    app.ui_new_ticket_target = ""
    app.ui_new_ticket_deps   = ""
  if app._show_add_ticket_form:
   imgui.begin_child("add_ticket_form", imgui.ImVec2(-1, 220), True)
   imgui.text_colored(C_VAL(), "New Ticket Details")
   _, app.ui_new_ticket_id     = imgui.input_text("ID##new_ticket", app.ui_new_ticket_id)
   _, app.ui_new_ticket_desc   = imgui.input_text_multiline("Description##new_ticket", app.ui_new_ticket_desc, imgui.ImVec2(-1, 60))
   _, app.ui_new_ticket_target = imgui.input_text("Target File##new_ticket", app.ui_new_ticket_target)
   _, app.ui_new_ticket_deps   = imgui.input_text("Depends On (IDs, comma-separated)##new_ticket", app.ui_new_ticket_deps)
   imgui.text("Priority:")
   imgui.same_line()
   if imgui.begin_combo("##new_prio", app.ui_new_ticket_priority):
    for p_opt in ['high', 'medium', 'low']:
     if imgui.selectable(p_opt, p_opt == app.ui_new_ticket_priority)[0]:
      app.ui_new_ticket_priority = p_opt
    imgui.end_combo()
   if imgui.button("Create"):
    new_ticket = {
     "id":          app.ui_new_ticket_id,
     "description": app.ui_new_ticket_desc,
     "status":      "todo",
     "priority":    app.ui_new_ticket_priority,
     "assigned_to": "tier3-worker",
     "target_file": app.ui_new_ticket_target,
     "depends_on":  [d.strip() for d in app.ui_new_ticket_deps.split(",") if d.strip()]
    }
    app.active_tickets.append(new_ticket)
    app._show_add_ticket_form = False
    app._push_mma_state_update()
   imgui.same_line()
   if imgui.button("Cancel"): app._show_add_ticket_form = False
   imgui.end_child()
 else:
  imgui.text_disabled("No active MMA track or tickets.")

def render_beads_tab(app: App) -> None:
 """Renders the Beads Graph tab. Checks for `dolt` and `bd` CLI availability,
 shows a warning if missing, then lists beads from the Dolt-backed BeadsClient
 in a 3-column table (ID / Status / Title).

 SSDL Shape: `[I:dep_check] -> [B:refresh] -> [I:beads_table]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Beads Graph (Dolt-backed)   [Refresh Beads]             |
   | ⚠ Warning: 'dolt' not found in PATH. (if missing)      |
   | +------+-----------+---------------------------------+  |
   | | ID   | Status    | Title                           |  |
   | +------+-----------+---------------------------------+  |
   | | bd-1 | todo      | Implement render pipeline       |  |
   | | bd-2 | complete  | Write tests                     |  |
   | +------+-----------+---------------------------------+  |
   +---------------------------------------------------------+
 """
 imgui.text("Beads Graph (Dolt-backed)")
 if imgui.button("Refresh Beads"):
  pass
 imgui.separator()
 
 # Check for dolt/bd dependencies
 dolt_path = shutil.which("dolt")
 bd_path   = shutil.which("bd")
 if not dolt_path or not bd_path:
  missing = []
  if not dolt_path: missing.append("'dolt'")
  if not bd_path:   missing.append("'bd'")
  imgui.text_colored(theme.get_color("status_warning"), f"Warning: {', '.join(missing)} not found in PATH.")
  imgui.text_wrapped("Beads mode requires Dolt and the Beads (bd) CLI tools.")
 
 if getattr(app, "ui_project_execution_mode", "native") == "beads":
  try:
   from src import beads_client
   bclient = beads_client.BeadsClient(Path(app.active_project_root))
   beads   = bclient.list_beads()
   if not beads:
    imgui.text_disabled("No beads found.")
   else:
    if imgui.begin_table("beads_table", 3, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable):
     imgui.table_setup_column("ID")
     imgui.table_setup_column("Status")
     imgui.table_setup_column("Title")
     imgui.table_headers_row()
     for b in beads:
      imgui.table_next_row();    imgui.table_next_column()
      imgui.text(str(b.id));     imgui.table_next_column()
      imgui.text(str(b.status)); imgui.table_next_column(); imgui.text(str(b.title))
     imgui.end_table()
  except Exception as e:
   imgui.text_colored(theme.get_color("status_error"), f"Error loading beads: {e}")

def render_mma_focus_selector(app: App) -> None:
 """Renders the Focus Agent selector. Filters the discussion entries list to show
 only the output of the chosen tier agent (or All). Includes a clear [x] button.

 SSDL: `[I:focus_combo] -> [B:clear_x?]`

 ASCII Layout Map:
   +---------------------------------------------------------+
   | Focus Agent: [All v]  (or [Tier 2 v] [x])              |
   +---------------------------------------------------------+
 """
 imgui.text("Focus Agent:"); imgui.same_line()
 focus_label = app.ui_focus_agent or "All"
 if imgui.begin_combo("##focus_agent", focus_label, imgui.ComboFlags_.width_fit_preview):
  if imgui.selectable("All", app.ui_focus_agent is None)[0]: app.ui_focus_agent = None
  for tier in ["Tier 2", "Tier 3", "Tier 4"]:
   if imgui.selectable(tier, app.ui_focus_agent == tier)[0]: app.ui_focus_agent = tier
  imgui.end_combo()
 imgui.same_line()
 if app.ui_focus_agent and imgui.button("x##clear_focus"): app.ui_focus_agent = None

#endregion: MMA
