# gui_2.py
from __future__ import annotations
import tomli_w
import time
import math
import json
import sys
import os
import shutil
import copy
from pathlib import Path
from tkinter import filedialog, Tk
from typing import Optional, Any
from src import ai_client
from src import cost_tracker
from src import session_logger
from src import project_manager
from src import paths
from src import presets
from src import theme_2 as theme
from src import theme_nerv_fx as theme_fx
from src import api_hooks
import numpy as np
from src import log_registry
from src import log_pruner
from src import models
from src import app_controller
from src import workspace_manager
from src import mcp_client
from src import aggregate
from src import markdown_helper
from src import bg_shader
from src import thinking_parser
from src import thinking_parser
import re
import difflib
import subprocess
if sys.platform == "win32":
 import win32gui
 import win32con
else:
 win32gui = None
 win32con = None

from pydantic import BaseModel
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced

PROVIDERS: list[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax"]
COMMS_CLAMP_CHARS: int = 300

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

def vec4(r: float, g: float, b: float, a: float = 1.0) -> imgui.ImVec4: return imgui.ImVec4(r/255, g/255, b/255, a)

C_OUT: imgui.ImVec4 = vec4(100, 200, 255)
C_IN: imgui.ImVec4 = vec4(140, 255, 160)
C_REQ: imgui.ImVec4 = vec4(255, 220, 100)
C_RES: imgui.ImVec4 = vec4(180, 255, 180)
C_TC: imgui.ImVec4 = vec4(255, 180, 80)
C_TR: imgui.ImVec4 = vec4(180, 220, 255)
C_TRS: imgui.ImVec4 = vec4(200, 180, 255)
C_LBL: imgui.ImVec4 = vec4(180, 180, 180)
C_VAL: imgui.ImVec4 = vec4(220, 220, 220)
C_KEY: imgui.ImVec4 = vec4(140, 200, 255)
C_NUM: imgui.ImVec4 = vec4(180, 255, 180)
C_SUB: imgui.ImVec4 = vec4(220, 200, 120)

DIR_COLORS: dict[str, imgui.ImVec4] = {"OUT": C_OUT, "IN": C_IN}
KIND_COLORS: dict[str, 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 truncate_entries(entries: list[dict[str, Any]], max_pairs: int) -> list[dict[str, Any]]:
 if max_pairs <= 0:
  return []
 count = 0
 target = max_pairs * 2
 for i in range(len(entries) - 1, -1, -1):
  role = entries[i].get("role", "")
  if role in ("User", "AI"):
   count += 1
  if count == target:
   return entries[i:]
 return entries

class GenerateRequest(BaseModel):
 prompt: str
 auto_add_history: bool = True
 temperature: float | None = None
 top_p: float | None = None
 max_tokens: int | None = None

class ConfirmRequest(BaseModel):
 approved: bool
 script: Optional[str] = None

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

 def __init__(self) -> None:
  # Initialize controller and delegate state
  self.controller = app_controller.AppController()
  self.controller._app = self
  from src import history
  self.history = history.HistoryManager(max_capacity=100)
  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
  
  # Restore legacy PROVIDERS to controller if needed (it already has it via delegation if set on class level, but let's be explicit)
  if not hasattr(self.controller, 'PROVIDERS'):
   self.controller.PROVIDERS = PROVIDERS
  self.controller.init_state()
  self.workspace_manager = workspace_manager.WorkspaceManager(project_root=self.active_project_root)
  self.workspace_profiles = self.workspace_manager.load_all_profiles()
  self.show_windows.setdefault("Diagnostics", False)
  self.controller.start_services(self)
  self.controller._predefined_callbacks['_render_text_viewer'] = self._render_text_viewer
  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['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._clickable_actions.update({
   'btn_undo': self._handle_undo,
   'btn_redo': self._handle_redo
  })
  def simulate_save_preset(name: str):
   from src import models
   self.files = [models.FileItem(path='test.py')]
   self.screenshots = ['test.png']
   self.save_context_preset(name)
  self.controller._predefined_callbacks['simulate_save_preset'] = simulate_save_preset
  self.show_preset_manager_window = False
  self.show_tool_preset_manager_window = False
  self.show_persona_editor_window = False
  self.show_text_viewer = False
  self.text_viewer_title = ''
  self.text_viewer_content = ''
  self.text_viewer_type = 'text'
  self.text_viewer_wrap = True
  self._text_viewer_editor: Optional[ced.TextEditor] = None
  self.ui_active_tool_preset = ""
  self.ui_active_bias_profile = ""
  self.ui_active_context_preset = ""
  self.ui_active_persona = ""
  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] = {}
  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._editing_tool_preset_name = ''
  self._editing_tool_preset_categories = {}
  self._editing_tool_preset_scope = 'project'
  self._selected_preset_idx = -1
  self._selected_tool_preset_idx = -1
  self._selected_bias_profile_idx = -1
  self._new_bias_tool_name = "run_powershell"
  self._new_bias_category_name = "General"
  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] = {}
  # 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
  # Aliases for controller-owned locks
  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
  self._discussion_names_cache: list[str] = []
  self._discussion_names_dirty: bool = True
  # Initialize node editor context
  self.node_editor_config = ed.Config()
  self.node_editor_ctx = ed.create_editor(self.node_editor_config)
  self.ui_selected_ticket_id: Optional[str] = None
  self.ui_selected_tickets: set[str] = set()
  self.ui_new_ticket_priority: str = 'medium'
  self._autofocus_response_tab = False
  gui_cfg = self.config.get("gui", {})
  self.ui_separate_message_panel = gui_cfg.get("separate_message_panel", False)
  self.ui_separate_response_panel = gui_cfg.get("separate_response_panel", False)
  self.ui_separate_tool_calls_panel = gui_cfg.get("separate_tool_calls_panel", False)
  self.ui_separate_task_dag = gui_cfg.get("separate_task_dag", False)
  self.ui_separate_usage_analytics = gui_cfg.get("separate_usage_analytics", False)
  self.ui_separate_tier1 = gui_cfg.get("separate_tier1", False)
  self.ui_separate_tier2 = gui_cfg.get("separate_tier2", False)
  self.ui_separate_tier3 = gui_cfg.get("separate_tier3", False)
  self.ui_separate_tier4 = gui_cfg.get("separate_tier4", False)
  self.ui_separate_external_tools = gui_cfg.get('separate_external_tools', False)
  self.show_windows.setdefault("Usage Analytics", 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)
  self.ui_multi_viewport = gui_cfg.get("multi_viewport", False)
  self.layout_presets = self.config.get("layout_presets", {})
  self._new_preset_name = ""
  self._show_save_preset_modal = False
  self._show_save_workspace_profile_modal = False
  self._new_workspace_profile_name = ""
  self._new_workspace_profile_scope = "project"
  self._comms_log_cache: list[dict[str, Any]] = []
  self._tool_log_cache: list[dict[str, Any]] = []
  self._last_ui_focus_agent: Optional[str] = None
  self._log_registry: Optional[log_registry.LogRegistry] = None
  self.perf_profiling_enabled = False
  self.perf_show_graphs: dict[str, bool] = {}
  self._token_stats: dict[str, Any] = {}
  self.perf_history: dict[str, list] = {"frame_time": [0.0] * 100, "fps": [0.0] * 100}
  self._nerv_crt = theme_fx.CRTFilter()
  self.ui_crt_filter = True
  self._nerv_alert = theme_fx.AlertPulsing()
  self._nerv_flicker = theme_fx.StatusFlicker()
  self.ui_tool_filter_category = "All"
  self.ui_discussion_split_h = 300.0
  self.shader_uniforms = {'crt': 1.0, 'scanline': 0.5, 'bloom': 0.8}
  self.shader_uniforms = {'crt': 1.0, 'scanline': 0.5, 'bloom': 0.8}
  self.ui_new_context_preset_name = ""
  self._focus_md_cache: dict[str, str] = {}
  """UI-level wrapper for approving a pending tool execution ask."""
  self._handle_approve_ask()

 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)

 def __getattr__(self, name: str) -> Any:
  if name != 'controller' and hasattr(self, 'controller') and hasattr(self.controller, name):
   return getattr(self.controller, name)
  raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")

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

 @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

 @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

 def _take_snapshot(self) -> history.UISnapshot:
  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],
   screenshots=list(self.screenshots)
  )

 def _apply_snapshot(self, snapshot: history.UISnapshot) -> None:
  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.screenshots = list(snapshot.screenshots)
   self._last_ui_snapshot = snapshot # Update last snapshot to avoid immediate re-push
  finally:
   self._is_applying_snapshot = False

 def _capture_workspace_profile(self, name: str) -> models.WorkspaceProfile:
  ini = imgui.save_ini_settings_to_memory()
  panel_states = {
   "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):
  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:
  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:
  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 and saves state."""
  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 save_context_preset(self, name: str) -> None:
  sys.stderr.write(f"[DEBUG] save_context_preset called with: {name}\n")
  sys.stderr.flush()
  if 'context_presets' not in self.controller.project:
   self.controller.project['context_presets'] = {}
  self.controller.project['context_presets'][name] = {
   'files': [f.to_dict() if hasattr(f, 'to_dict') else {'path': str(f)} for f in self.files],
   'screenshots': list(self.screenshots)
  }
  self.controller._save_active_project()
  sys.stderr.write(f"[DEBUG] save_context_preset finished. Project keys: {list(self.controller.project.keys())}\n")
  sys.stderr.flush()

 def load_context_preset(self, name: str) -> None:
  presets = self.controller.project.get('context_presets', {})
  if name in presets:
   preset = presets[name]
   self.files = [models.FileItem.from_dict(f) if isinstance(f, dict) else models.FileItem(path=str(f)) for f in preset.get('files', [])]
   self.screenshots = list(preset.get('screenshots', []))

 def delete_context_preset(self, name: str) -> None:
  if 'context_presets' in self.controller.project:
   self.controller.project['context_presets'].pop(name, None)
   self.controller._save_active_project()
 @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 _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)
   # ---------------------------------------------------------------- helpers

 def _render_text_viewer(self, label: str, content: str, text_type: str = 'text', force_open: bool = False) -> None:
  self.text_viewer_type = text_type
  if imgui.button("[+]##" + str(id(content))) or force_open:
   self.show_text_viewer = True
   self.text_viewer_title = label
   self.text_viewer_content = content

 def _render_heavy_text(self, label: str, content: str, id_suffix: str = "") -> None:
  imgui.text_colored(C_LBL, f"{label}:")
  imgui.same_line()
  if imgui.button("[+]##" + label + id_suffix):
   self.show_text_viewer = True
   self.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'
   self.text_viewer_title = label
   self.text_viewer_content = content
  
  if not content:
   imgui.text_disabled("(empty)")
   return

  is_md = label in ("message", "text", "content")
  ctx_id = f"heavy_{label}_{id_suffix}"

  is_nerv = theme.is_nerv_active()
  if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))

  if len(content) > COMMS_CLAMP_CHARS:
   if is_md:
    imgui.begin_child(f"heavy_text_child_{label}_{id_suffix}", imgui.ImVec2(0, 180), True, imgui.WindowFlags_.always_vertical_scrollbar)
    markdown_helper.render(content, context_id=ctx_id)
    imgui.end_child()
   else:
    imgui.input_text_multiline(f"##heavy_text_input_{label}_{id_suffix}", content, imgui.ImVec2(-1, 180), imgui.InputTextFlags_.read_only)
  else:
   if is_md:
    markdown_helper.render(content, context_id=ctx_id)
   else:
    if self.ui_word_wrap:
     imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
     imgui.text(content)
     imgui.pop_text_wrap_pos()
    else:
     imgui.text(content)
  
  if is_nerv: imgui.pop_style_color()
    # ---------------------------------------------------------------- gui

 def _render_thinking_trace(self, segments: list[dict], entry_index: int, is_standalone: bool = False) -> None:
  if not segments:
   return
  imgui.push_style_color(imgui.Col_.child_bg, vec4(40, 35, 25, 180))
  imgui.push_style_color(imgui.Col_.text, vec4(200, 200, 150))
  imgui.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:
   h = 150 if is_standalone else 100
   imgui.begin_child(f"thinking_content_{entry_index}", imgui.ImVec2(0, h), True)
   for idx, seg in enumerate(segments):
    content = seg.get("content", "")
    marker = seg.get("marker", "thinking")
    imgui.push_id(f"think_{entry_index}_{idx}")
    imgui.text_colored(vec4(180, 150, 80), f"[{marker}]")
    if self.ui_word_wrap:
     imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
     imgui.text_colored(vec4(200, 200, 150), content)
     imgui.pop_text_wrap_pos()
    else:
     imgui.text_colored(vec4(200, 200, 150), content)
    imgui.pop_id()
    imgui.separator()
   imgui.end_child()
  imgui.unindent()
  imgui.pop_style_color(2)


 def _render_selectable_label(self, label: str, value: str, width: float = 0.0, multiline: bool = False, height: float = 0.0, color: Optional[imgui.ImVec4] = None) -> None:
  imgui.push_id(label + str(hash(value)))
  pops = 4
  imgui.push_style_color(imgui.Col_.frame_bg, vec4(0, 0, 0, 0))
  imgui.push_style_color(imgui.Col_.frame_bg_hovered, vec4(0, 0, 0, 0))
  imgui.push_style_color(imgui.Col_.frame_bg_active, vec4(0, 0, 0, 0))
  imgui.push_style_color(imgui.Col_.border, vec4(0, 0, 0, 0))
  if color:
   imgui.push_style_color(imgui.Col_.text, color)
   pops += 1
  imgui.push_style_var(imgui.StyleVar_.frame_border_size, 0.0)
  imgui.push_style_var(imgui.StyleVar_.frame_padding, imgui.ImVec2(0, 0))
  if multiline:
   imgui.input_text_multiline("##" + label, value, imgui.ImVec2(width, height), imgui.InputTextFlags_.read_only)
  else:
   if width > 0: imgui.set_next_item_width(width)
   imgui.input_text("##" + label, value, imgui.InputTextFlags_.read_only)
  imgui.pop_style_color(pops)
  imgui.pop_style_var(2)
  imgui.pop_id()

 def _show_menus(self) -> None:
  if imgui.begin_menu("manual slop"):
   if imgui.menu_item("Quit", "Ctrl+Q", False)[0]:
    self.runner_params.app_shall_exit = True
   imgui.end_menu()
  if imgui.begin_menu("Windows"):
   for w in self.show_windows.keys():
    _, self.show_windows[w] = imgui.menu_item(w, "", self.show_windows[w])
   imgui.end_menu()
  if imgui.begin_menu("Project"):
   if imgui.menu_item("Save All", "", False)[0]:
    self._flush_to_project()
    self._flush_to_config()
    models.save_config(self.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}"
   imgui.end_menu()
  if imgui.begin_menu("Layout"):
   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()
   if imgui.begin_menu("Delete Profile"):
    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)
    imgui.end_menu()
   imgui.end_menu()

  # RAG status indicator
  if self.controller.rag_config and self.controller.rag_config.enabled:
   imgui.same_line()
   status = self.controller.rag_status
   if status == "indexing...":
    color = vec4(100, 255, 100)
   elif status == "error":
    color = vec4(255, 100, 100)
   else:
    color = vec4(180, 180, 180)
   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():
    self.controller.event_queue.put('click', 'btn_rebuild_rag_index')

  # 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
    display_w = imgui.get_io().display_size.x
    right_x = display_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:
     # Use a small positive height to satisfy IM_ASSERT(size_arg.y != 0.0f)
     # The menu bar naturally constrains the hit box height anyway.
     imgui.invisible_button("##drag_area", (drag_w, 20.0))
     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, vec4(0, 0, 0, 0))
    
    try:
     is_max = win32gui.GetWindowPlacement(hwnd)[1] == win32con.SW_SHOWMAXIMIZED
    except Exception:
     is_max = False
    
    imgui.set_cursor_pos_x(right_x)
    if imgui.button("_", (btn_w, 0)):
     win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
     
    imgui.set_cursor_pos_x(right_x + btn_w)
    if imgui.button("[=]" if is_max else "[]", (btn_w, 0)):
     win32gui.ShowWindow(hwnd, win32con.SW_RESTORE if is_max else win32con.SW_MAXIMIZE)
    
    imgui.set_cursor_pos_x(right_x + btn_w * 2)
    imgui.push_style_color(imgui.Col_.button_hovered, vec4(200, 50, 50, 255))
    if imgui.button("X", (btn_w, 0)):
     win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
    imgui.pop_style_color()
    
    imgui.pop_style_color()

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

 def _render_shader_live_editor(self) -> None:
  if self.show_windows.get('Shader Editor', False):
   exp, opened = imgui.begin('Shader Editor', self.show_windows['Shader Editor'])
   self.show_windows['Shader Editor'] = bool(opened)
   if exp:
    changed_crt, self.shader_uniforms['crt'] = imgui.slider_float('CRT Curvature', self.shader_uniforms['crt'], 0.0, 2.0)
    changed_scan, self.shader_uniforms['scanline'] = imgui.slider_float('Scanline Intensity', self.shader_uniforms['scanline'], 0.0, 1.0)
    changed_bloom, self.shader_uniforms['bloom'] = imgui.slider_float('Bloom Threshold', self.shader_uniforms['bloom'], 0.0, 1.0)
   imgui.end()

 def _render_history_window(self) -> None:
  if not self.show_windows.get('Undo/Redo History', False):
   return
  
  exp, opened = imgui.begin("Undo/Redo History", self.show_windows['Undo/Redo History'])
  self.show_windows['Undo/Redo History'] = bool(opened)
  if exp:
   if imgui.button("Undo") and self.history.can_undo:
    self._handle_undo()
   imgui.same_line()
   if imgui.button("Redo") and self.history.can_redo:
    self._handle_redo()
   
   imgui.separator()
   imgui.begin_child("history_list", imgui.ImVec2(0, 0), True)
   history = self.history.get_history()
   if not history:
    imgui.text("No history available.")
   else:
    for i, entry in enumerate(reversed(history)):
     # Actual index in undo stack
     actual_idx = len(history) - 1 - i
     desc = entry.get("description", "UI Change")
     ts = entry.get("timestamp", 0.0)
     import datetime
     ts_str = datetime.datetime.fromtimestamp(ts).strftime("%H:%M:%S")
     
     label = f"[{ts_str}] {desc}##{actual_idx}"
     if imgui.selectable(label)[0]:
      self._handle_jump_to_history(actual_idx)
   imgui.end_child()
  imgui.end()

 def _gui_func(self) -> None:
  self._render_custom_title_bar()
  self._render_shader_live_editor()
  self._render_history_window()
  pushed_prior_tint = False
  # Render background shader
  bg = bg_shader.get_bg()
  if bg.enabled:
   ws = imgui.get_io().display_size
   bg.render(ws.x, ws.y)

  if theme.is_nerv_active():
   ws = imgui.get_io().display_size
   self._nerv_alert.update(self.ai_status)
   self._nerv_alert.render(ws.x, ws.y)
   self._nerv_crt.enabled = self.ui_crt_filter
   self._nerv_crt.render(ws.x, ws.y)
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func")
  if self.is_viewing_prior_session:
   imgui.push_style_color(imgui.Col_.window_bg, vec4(50, 40, 20))
   pushed_prior_tint = True
  try:
   self.perf_monitor.start_frame()
   self._autofocus_response_tab = self.controller._autofocus_response_tab
   # Process GUI task queue
   # DEBUG: Check if tasks exist before processing
   if hasattr(self, 'controller') and hasattr(self.controller, '_pending_gui_tasks'):
    pending_count = len(self.controller._pending_gui_tasks)
    if pending_count > 0:
     sys.stderr.write(f"[DEBUG gui_2] _gui_func: found {pending_count} pending tasks\n")
     sys.stderr.flush()
   self._process_pending_gui_tasks()
   self._process_pending_history_adds()
   if self.controller._process_pending_tool_calls():
    self._tool_log_dirty = True
   self._render_track_proposal_modal()
   self._render_patch_modal()
   self._render_base_prompt_diff_modal()
   self._render_save_preset_modal()
   self._render_save_workspace_profile_modal()
   self._render_preset_manager_window()
   self._render_tool_preset_manager_window()
   self._render_persona_editor_window()
   # Auto-save (every 60s)
   now = time.time()
   if now - self._last_autosave >= self._autosave_interval:
    self._last_autosave = now
    try:
     self._flush_to_project()
     self._flush_to_config()
     models.save_config(self.config)
    except Exception:
     pass  # silent â€” don't disrupt the GUI loop
     # Sync pending comms
   with self._pending_comms_lock:
    if self._pending_comms:
     if self.ui_auto_scroll_comms:
      self._scroll_comms_to_bottom = True
     self._comms_log_dirty = True
     for c in self._pending_comms:
      self._comms_log.append(c)
     self._pending_comms.clear()
   
   if self.ui_focus_agent != self._last_ui_focus_agent:
    self._comms_log_dirty = True
    self._tool_log_dirty = True
    self._last_ui_focus_agent = self.ui_focus_agent

   if self._comms_log_dirty:
    if self.is_viewing_prior_session:
     self._comms_log_cache = self.prior_session_entries
    else:
     log_raw = list(self._comms_log)
     if self.ui_focus_agent:
      self._comms_log_cache = [e for e in log_raw if e.get("source_tier", "").startswith(self.ui_focus_agent)]
     else:
      self._comms_log_cache = log_raw
    self._comms_log_dirty = False

   if self._tool_log_dirty:
    if self.is_viewing_prior_session:
     self._tool_log_cache = self.prior_tool_calls
    else:
     log_raw = list(self._tool_log)
     if self.ui_focus_agent:
      self._tool_log_cache = [e for e in log_raw if e.get("source_tier", "").startswith(self.ui_focus_agent)]
     else:
      self._tool_log_cache = log_raw
    self._tool_log_dirty = False
   
   if self.show_windows.get("Project Settings", False):
    exp, opened = imgui.begin("Project Settings", self.show_windows["Project Settings"])
    self.show_windows["Project Settings"] = bool(opened)
    if exp:
     if imgui.begin_tab_bar('context_hub_tabs'):
      if imgui.begin_tab_item('Projects')[0]:
       self._render_projects_panel()
       imgui.end_tab_item()
      if imgui.begin_tab_item('Paths')[0]:
       self._render_paths_panel()
       imgui.end_tab_item()
      imgui.end_tab_bar()
    imgui.end()
   if self.show_windows.get("Files & Media", False):
    exp, opened = imgui.begin("Files & Media", self.show_windows["Files & Media"])
    self.show_windows["Files & Media"] = bool(opened)
    if exp:
     if imgui.collapsing_header("Files"):
      self._render_files_panel()
     if imgui.collapsing_header("Screenshots"):
      self._render_screenshots_panel()
    imgui.end()
   if self.show_windows.get("AI Settings", False):
    exp, opened = imgui.begin("AI Settings", self.show_windows["AI Settings"])
    self.show_windows["AI Settings"] = bool(opened)
    if exp:
     self._render_persona_selector_panel()
     if imgui.collapsing_header("Provider & Model"):
      self._render_provider_panel()
     if imgui.collapsing_header("System Prompts"):
      self._render_system_prompts_panel()
     if imgui.collapsing_header("RAG Settings"):
      self._render_rag_panel()
     self._render_agent_tools_panel()
    imgui.end()
   if self.ui_separate_usage_analytics and self.show_windows.get("Usage Analytics", False):
    exp, opened = imgui.begin("Usage Analytics", self.show_windows["Usage Analytics"])
    self.show_windows["Usage Analytics"] = bool(opened)
    if exp:
     self._render_usage_analytics_panel()
    imgui.end()
   if self.show_windows.get("MMA Dashboard", False):
    exp, opened = imgui.begin("MMA Dashboard", self.show_windows["MMA Dashboard"])
    self.show_windows["MMA Dashboard"] = bool(opened)
    if exp:
     if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
     self._render_mma_dashboard()
     if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard")
    imgui.end()

   if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False):
    exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"])
    self.show_windows["Task DAG"] = bool(opened)
    if exp:
     self._render_task_dag_panel()
    imgui.end()
   if self.ui_separate_tier1 and self.show_windows.get("Tier 1: Strategy", False):
    exp, opened = imgui.begin("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"])
    self.show_windows["Tier 1: Strategy"] = bool(opened)
    if exp:
     self._render_tier_stream_panel("Tier 1", "Tier 1")
    imgui.end()
   if self.ui_separate_tier2 and self.show_windows.get("Tier 2: Tech Lead", False):
    exp, opened = imgui.begin("Tier 2: Tech Lead", self.show_windows["Tier 2: Tech Lead"])
    self.show_windows["Tier 2: Tech Lead"] = bool(opened)
    if exp:
     self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)")
    imgui.end()
   if self.ui_separate_tier3 and self.show_windows.get("Tier 3: Workers", False):
    exp, opened = imgui.begin("Tier 3: Workers", self.show_windows["Tier 3: Workers"])
    self.show_windows["Tier 3: Workers"] = bool(opened)
    if exp:
     self._render_tier_stream_panel("Tier 3", None)
    imgui.end()
   if self.ui_separate_tier4 and self.show_windows.get("Tier 4: QA", False):
    exp, opened = imgui.begin("Tier 4: QA", self.show_windows["Tier 4: QA"])
    self.show_windows["Tier 4: QA"] = bool(opened)
    if exp:
     self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)")
    imgui.end()
   if self.show_windows.get("Theme", False):
    self._render_theme_panel()
   if self.show_windows.get("Discussion Hub", False):
    exp, opened = imgui.begin("Discussion Hub", self.show_windows["Discussion Hub"])
    self.show_windows["Discussion Hub"] = bool(opened)
    if exp:
     if imgui.begin_tab_bar("discussion_hub_tabs"):
      if imgui.begin_tab_item("Discussion")[0]:
       self._render_discussion_tab()
       imgui.end_tab_item()
      if imgui.begin_tab_item("Context Composition")[0]:
       self._render_context_composition_panel()
       imgui.end_tab_item()
      if imgui.begin_tab_item("Snapshot")[0]:
       self._render_snapshot_tab()
       imgui.end_tab_item()
      if imgui.begin_tab_item("Takes")[0]:
       self._render_takes_panel()
       imgui.end_tab_item()
      imgui.end_tab_bar()
    imgui.end()
   if self.show_windows.get("Operations Hub", False):
    exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"])
    self.show_windows["Operations Hub"] = bool(opened)
    if exp:
     imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
     ch1, self.ui_separate_tool_calls_panel = imgui.checkbox("Pop Out Tool Calls", self.ui_separate_tool_calls_panel)
     if ch1: self.show_windows["Tool Calls"] = self.ui_separate_tool_calls_panel
     imgui.same_line()
     ch2, self.ui_separate_usage_analytics = imgui.checkbox("Pop Out Usage Analytics", self.ui_separate_usage_analytics)
     if ch2: self.show_windows["Usage Analytics"] = self.ui_separate_usage_analytics
     imgui.same_line()
     ch3, self.ui_separate_external_tools = imgui.checkbox('Pop Out External Tools', self.ui_separate_external_tools)
     if ch3: self.show_windows['External Tools'] = self.ui_separate_external_tools
     imgui.pop_style_var()

     show_tc_tab = not self.ui_separate_tool_calls_panel
     show_usage_tab = not self.ui_separate_usage_analytics

     if imgui.begin_tab_bar("ops_tabs"):
      if imgui.begin_tab_item("Comms History")[0]:
       self._render_comms_history_panel()
       imgui.end_tab_item()
      if show_tc_tab:
       if imgui.begin_tab_item("Tool Calls")[0]:
        self._render_tool_calls_panel()
        imgui.end_tab_item()
      if show_usage_tab:
       if imgui.begin_tab_item("Usage Analytics")[0]:
        self._render_usage_analytics_panel()
        imgui.end_tab_item()
      if not self.ui_separate_external_tools:
       if imgui.begin_tab_item("External Tools")[0]:
        self._render_external_tools_panel()
        imgui.end_tab_item()
      if imgui.begin_tab_item("Workspace Layouts")[0]:
       imgui.text("Experimental: Auto-switch layout by Tier")
       ch, self.controller.ui_auto_switch_layout = imgui.checkbox("Enable Auto-Switch", self.controller.ui_auto_switch_layout)
       if self.controller.ui_auto_switch_layout:
        imgui.separator()
        imgui.text("Tier Bindings (select profile for each tier)")
        profiles = [""] + [p.name for p in self.controller.workspace_profiles.values()]
        for t in ["Tier 1", "Tier 2", "Tier 3", "Tier 4"]:
         curr = self.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:
          self.controller.ui_tier_layout_bindings[t] = profiles[new_idx]
       imgui.end_tab_item()
      imgui.end_tab_bar()
    imgui.end()
   
   if self.ui_separate_message_panel and self.show_windows.get("Message", False):
    exp, opened = imgui.begin("Message", self.show_windows["Message"])
    self.show_windows["Message"] = bool(opened)
    if exp:
     self._render_message_panel()
    imgui.end()

   if self.ui_separate_response_panel and self.show_windows.get("Response", False):
    exp, opened = imgui.begin("Response", self.show_windows["Response"])
    self.show_windows["Response"] = bool(opened)
    if exp:
     self._render_response_panel()
    imgui.end()

   if self.ui_separate_tool_calls_panel and self.show_windows.get("Tool Calls", False):
    exp, opened = imgui.begin("Tool Calls", self.show_windows["Tool Calls"])
    self.show_windows["Tool Calls"] = bool(opened)
    if exp:
     self._render_tool_calls_panel()
    imgui.end()

   if self.ui_separate_external_tools and self.show_windows.get('External Tools', False):
    exp, opened = imgui.begin('External Tools', self.show_windows['External Tools'])
    self.show_windows['External Tools'] = bool(opened)
    if exp:
     self._render_external_tools_panel()
    imgui.end()

   if self.show_windows.get("Log Management", False):
    if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management")
    self._render_log_management()
    if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_log_management")

   if self.show_windows.get("Diagnostics", False):
    self._render_diagnostics_panel()

   self.perf_monitor.end_frame()
   # ---- Modals / Popups
   with self._pending_dialog_lock:
    dlg = self._pending_dialog
   if dlg:
    if not self._pending_dialog_open:
     imgui.open_popup("Approve PowerShell Command")
     self._pending_dialog_open = True
   else:
    self._pending_dialog_open = False
   if imgui.begin_popup_modal("Approve PowerShell Command", None, imgui.WindowFlags_.always_auto_resize)[0]:
    if not dlg:
     imgui.close_current_popup()
    else:
     imgui.text("The AI wants to run the following PowerShell script:")
     imgui.text_colored(vec4(200, 200, 100), f"base_dir: {dlg._base_dir}")
     imgui.separator()
     # Checkbox to toggle full preview inside modal
     _, self.show_text_viewer = imgui.checkbox("Show Full Preview", self.show_text_viewer)
     if self.show_text_viewer:
      imgui.begin_child("preview_child", imgui.ImVec2(600, 300), True)
      imgui.text_unformatted(dlg._script)
      imgui.end_child()
     else:
      ch, dlg._script = imgui.input_text_multiline("##confirm_script", dlg._script, imgui.ImVec2(-1, 200))
     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 self._pending_dialog_lock:
       self._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 self._pending_dialog_lock:
       self._pending_dialog = None
      imgui.close_current_popup()
    imgui.end_popup()
   if self._pending_ask_dialog:
    if not self._ask_dialog_open:
     imgui.open_popup("Approve Tool Execution")
     self._ask_dialog_open = True
   else:
    self._ask_dialog_open = False
   if imgui.begin_popup_modal("Approve Tool Execution", None, imgui.WindowFlags_.always_auto_resize)[0]:
    if not self._pending_ask_dialog or self._ask_tool_data is None:
     imgui.close_current_popup()
    else:
     tool_name = self._ask_tool_data.get("tool", "unknown")
     tool_args = self._ask_tool_data.get("args", {})
     imgui.text("The AI wants to execute a tool:")
     imgui.text_colored(vec4(200, 200, 100), 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)):
      self._handle_approve_ask()
      imgui.close_current_popup()
     imgui.same_line()
     if imgui.button("Deny", imgui.ImVec2(120, 0)):
      self._handle_reject_ask()
      imgui.close_current_popup()
    imgui.end_popup()
    # MMA Step Approval Modal
   if self._pending_mma_approval:
    if not self._mma_approval_open:
     imgui.open_popup("MMA Step Approval")
     self._mma_approval_open = True
     self._mma_approval_edit_mode = False
     self._mma_approval_payload = self._pending_mma_approval.get("payload", "")
   else:
    self._mma_approval_open = False
   if imgui.begin_popup_modal("MMA Step Approval", None, imgui.WindowFlags_.always_auto_resize)[0]:
    if not self._pending_mma_approval:
     imgui.close_current_popup()
    else:
     ticket_id = self._pending_mma_approval.get("ticket_id", "??")
     imgui.text(f"Ticket {ticket_id} is waiting for tool execution approval.")
     imgui.separator()
     if self._mma_approval_edit_mode:
      imgui.text("Edit Raw Payload (Manual Memory Mutation):")
      _, self._mma_approval_payload = imgui.input_text_multiline("##mma_payload", self._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(self._pending_mma_approval.get("payload", "")))
      imgui.end_child()
     imgui.separator()
     if imgui.button("Approve", imgui.ImVec2(120, 0)):
      self._handle_mma_respond(approved=True, payload=self._mma_approval_payload)
      imgui.close_current_popup()
     imgui.same_line()
     if imgui.button("Edit Payload" if not self._mma_approval_edit_mode else "Show Original", imgui.ImVec2(120, 0)):
      self._mma_approval_edit_mode = not self._mma_approval_edit_mode
     imgui.same_line()
     if imgui.button("Abort Ticket", imgui.ImVec2(120, 0)):
      self._handle_mma_respond(approved=False)
      imgui.close_current_popup()
    imgui.end_popup()
    # MMA Spawn Approval Modal
   if self._pending_mma_spawn:
    if not self._mma_spawn_open:
     imgui.open_popup("MMA Spawn Approval")
     self._mma_spawn_open = True
     self._mma_spawn_edit_mode = False
     self._mma_spawn_prompt = self._pending_mma_spawn.get("prompt", "")
     self._mma_spawn_context = self._pending_mma_spawn.get("context_md", "")
   else:
    self._mma_spawn_open = False
   if imgui.begin_popup_modal("MMA Spawn Approval", None, imgui.WindowFlags_.always_auto_resize)[0]:
    if not self._pending_mma_spawn:
     imgui.close_current_popup()
    else:
     role = self._pending_mma_spawn.get("role", "??")
     ticket_id = self._pending_mma_spawn.get("ticket_id", "??")
     imgui.text(f"Spawning {role} for Ticket {ticket_id}")
     imgui.separator()
     if self._mma_spawn_edit_mode:
      imgui.text("Edit Prompt:")
      _, self._mma_spawn_prompt = imgui.input_text_multiline("##spawn_prompt", self._mma_spawn_prompt, imgui.ImVec2(800, 200))
      imgui.text("Edit Context MD:")
      _, self._mma_spawn_context = imgui.input_text_multiline("##spawn_context", self._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(self._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(self._mma_spawn_context)
      imgui.end_child()
     imgui.separator()
     if imgui.button("Approve", imgui.ImVec2(120, 0)):
      self._handle_mma_respond(approved=True, prompt=self._mma_spawn_prompt, context_md=self._mma_spawn_context)
      imgui.close_current_popup()
     imgui.same_line()
     if imgui.button("Edit Mode" if not self._mma_spawn_edit_mode else "Preview Mode", imgui.ImVec2(120, 0)):
      self._mma_spawn_edit_mode = not self._mma_spawn_edit_mode
     imgui.same_line()
     if imgui.button("Abort", imgui.ImVec2(120, 0)):
      self._handle_mma_respond(approved=False, abort=True)
      imgui.close_current_popup()
    imgui.end_popup()
   # Cycle Detected Popup
   if imgui.begin_popup_modal("Cycle Detected!", None, imgui.WindowFlags_.always_auto_resize)[0]:
    imgui.text_colored(imgui.ImVec4(1, 0.3, 0.3, 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()
   if self.show_script_output:
    if self._trigger_script_blink:
     self._trigger_script_blink = False
     self._is_script_blinking = True
     self._script_blink_start_time = time.time()
     try:
      imgui.set_window_focus("Last Script Output") # type: ignore[call-arg]
     except Exception:
      pass
    if self._is_script_blinking:
     elapsed = time.time() - self._script_blink_start_time
     if elapsed > 1.5:
      self._is_script_blinking = False
     else:
      val = math.sin(elapsed * 8 * math.pi)
      alpha = 60/255 if val > 0 else 0
      imgui.push_style_color(imgui.Col_.frame_bg, vec4(0, 100, 255, alpha))
      imgui.push_style_color(imgui.Col_.child_bg, vec4(0, 100, 255, alpha))
    imgui.set_next_window_size(imgui.ImVec2(800, 600), imgui.Cond_.first_use_ever)
    expanded, opened = imgui.begin("Last Script Output", self.show_script_output)
    self.show_script_output = bool(opened)
    if expanded:
     imgui.text("Script:")
     imgui.same_line()
     self._render_text_viewer("Last Script", self.ui_last_script_text)
     if self.ui_word_wrap:
      imgui.begin_child("lso_s_wrap", imgui.ImVec2(-1, 200), True)
      imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
      imgui.text(self.ui_last_script_text)
      imgui.pop_text_wrap_pos()
      imgui.end_child()
     else:
      imgui.input_text_multiline("##lso_s", self.ui_last_script_text, imgui.ImVec2(-1, 200), imgui.InputTextFlags_.read_only)
     imgui.separator()
     imgui.text("Output:")
     imgui.same_line()
     self._render_text_viewer("Last Output", self.ui_last_script_output)
     if self.ui_word_wrap:
      imgui.begin_child("lso_o_wrap", imgui.ImVec2(-1, -1), True)
      imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
      imgui.text(self.ui_last_script_output)
      imgui.pop_text_wrap_pos()
      imgui.end_child()
     else:
      imgui.input_text_multiline("##lso_o", self.ui_last_script_output, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
    if self._is_script_blinking:
     imgui.pop_style_color(2)
    imgui.end()
   if self.show_text_viewer:
    imgui.set_next_window_size(imgui.ImVec2(900, 700), imgui.Cond_.first_use_ever)
    expanded, opened = imgui.begin(f"Text Viewer - {self.text_viewer_title}", self.show_text_viewer)
    self.show_text_viewer = bool(opened)
    if expanded:
     # Toolbar
     if imgui.button("Copy"):
      imgui.set_clipboard_text(self.text_viewer_content)
     imgui.same_line()
     _, self.text_viewer_wrap = imgui.checkbox("Word Wrap", self.text_viewer_wrap)
     imgui.separator()

     renderer = markdown_helper.get_renderer()
     tv_type = getattr(self, "text_viewer_type", "text")
     
     if tv_type == 'markdown':
      imgui.begin_child("tv_md_scroll", imgui.ImVec2(-1, -1), True)
      markdown_helper.render(self.text_viewer_content, context_id='text_viewer')
      imgui.end_child()
     elif tv_type in renderer._lang_map:
      if self._text_viewer_editor is None:
       self._text_viewer_editor = ced.TextEditor()
       self._text_viewer_editor.set_read_only_enabled(True)
       self._text_viewer_editor.set_show_line_numbers_enabled(True)
      
      # Sync text and language
      lang_id = renderer._lang_map[tv_type]
      if self._text_viewer_editor.get_text().strip() != self.text_viewer_content.strip():
       self._text_viewer_editor.set_text(self.text_viewer_content)
      self._text_viewer_editor.set_language_definition(lang_id)
      
      self._text_viewer_editor.render('##tv_editor', a_size=imgui.ImVec2(-1, -1))
     else:
      if self.text_viewer_wrap:
       imgui.begin_child("tv_wrap", imgui.ImVec2(-1, -1), False)
       imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
       imgui.text(self.text_viewer_content)
       imgui.pop_text_wrap_pos()
       imgui.end_child()
      else:
       imgui.input_text_multiline("##tv_c", self.text_viewer_content, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
    imgui.end()
   # Inject File Modal
   if getattr(self, "show_inject_modal", False):
    imgui.open_popup("Inject File")
    self.show_inject_modal = False
   if imgui.begin_popup_modal("Inject File", None, imgui.WindowFlags_.always_auto_resize)[0]:
    files = self.project.get('files', {}).get('paths', [])
    imgui.text("Select File to Inject:")
    imgui.begin_child("inject_file_list", imgui.ImVec2(0, 200), True)
    for f_path in files:
     is_selected = (self._inject_file_path == f_path)
     if imgui.selectable(f_path, is_selected)[0]:
      self._inject_file_path = f_path
      self.controller._update_inject_preview()
    imgui.end_child()
    imgui.separator()
    if imgui.radio_button("Skeleton", self._inject_mode == "skeleton"):
     self._inject_mode = "skeleton"
     self.controller._update_inject_preview()
    imgui.same_line()
    if imgui.radio_button("Full", self._inject_mode == "full"):
     self._inject_mode = "full"
     self.controller._update_inject_preview()
    imgui.separator()
    imgui.text("Preview:")
    imgui.begin_child("inject_preview_area", imgui.ImVec2(600, 300), True)
    imgui.text_unformatted(self._inject_preview)
    imgui.end_child()
    imgui.separator()
    if imgui.button("Inject", imgui.ImVec2(120, 0)):
     formatted = f"## File: {self._inject_file_path}\n```python\n{self._inject_preview}\n```\n"
     with self._disc_entries_lock:
      self.disc_entries.append({
       "role": "Context",
       "content": formatted,
       "collapsed": True,
       "ts": project_manager.now_ts()
      })
     self._scroll_disc_to_bottom = True
     imgui.close_current_popup()
    imgui.same_line()
    if imgui.button("Cancel", imgui.ImVec2(120, 0)):
     imgui.close_current_popup()
    imgui.end_popup()
  except Exception as e:
   print(f"ERROR in _gui_func: {e}")
   import traceback
   traceback.print_exc()
  
  if pushed_prior_tint:
   imgui.pop_style_color()
  
  self._handle_history_logic()
  
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_gui_func")

 def _handle_history_logic(self) -> None:
  # Skip history tracking only during active AI thinking/tool execution
  is_thinking = self.ai_status in ["sending...", "streaming...", "running powershell...", "fetching url...", "searching web..."]
  if self._is_applying_snapshot or is_thinking:
   return  
  io = imgui.get_io()
  
  # 1. Hotkey handling (Undo: Ctrl+Z, Redo: Ctrl+Y or Ctrl+Shift+Z)
  ctrl = io.key_ctrl
  shift = io.key_shift
  
  if ctrl:
   if imgui.is_key_pressed(imgui.Key.z):
    if shift:
     self._handle_redo()
    else:
     self._handle_undo()
   elif imgui.is_key_pressed(imgui.Key.y):
    self._handle_redo()

  # 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.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 State Change")
   self._pending_snapshot = False

 def _render_base_prompt_diff_modal(self) -> None:
  if not getattr(self.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 = self.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(vec4(77, 178, 255), line.rstrip())
     elif line.startswith("+"):
      imgui.text_colored(vec4(51, 230, 51), line.rstrip())
     elif line.startswith("-"):
      imgui.text_colored(vec4(230, 51, 51), line.rstrip())
     else:
      imgui.text(line.rstrip())
    imgui.end_child()
   
   imgui.separator()
   if imgui.button("Close", imgui.ImVec2(120, 0)):
    self.controller._show_base_prompt_diff_modal = False
    imgui.close_current_popup()
   imgui.end_popup()

 def _render_save_preset_modal(self) -> None:
  if not self._show_save_preset_modal: return
  imgui.open_popup("Save Layout Preset")
  if imgui.begin_popup_modal("Save Layout Preset", True, imgui.WindowFlags_.always_auto_resize)[0]:
   imgui.text("Preset Name:")
   _, self._new_preset_name = imgui.input_text("##preset_name", self._new_preset_name)
   if imgui.button("Save", imgui.ImVec2(120, 0)):
    if self._new_preset_name.strip():
     ini_data = imgui.save_ini_settings_to_memory()
     self.layout_presets[self._new_preset_name.strip()] = {
      "ini": ini_data,
      "multi_viewport": self.ui_multi_viewport
     }
     self.config["layout_presets"] = self.layout_presets
     models.save_config(self.config)
     self._show_save_preset_modal = False
     self._new_preset_name = ""
    imgui.close_current_popup()
   imgui.same_line()
   if imgui.button("Cancel", imgui.ImVec2(120, 0)):
    self._show_save_preset_modal = False
    imgui.close_current_popup()
   imgui.end_popup()

 def _render_save_workspace_profile_modal(self) -> None:
  if self._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:")
   _, self._new_workspace_profile_name = imgui.input_text("##profile_name", self._new_workspace_profile_name)
   
   imgui.text("Scope:")
   if imgui.radio_button("Project", self._new_workspace_profile_scope == "project"):
    self._new_workspace_profile_scope = "project"
   imgui.same_line()
   if imgui.radio_button("Global", self._new_workspace_profile_scope == "global"):
    self._new_workspace_profile_scope = "global"
   
   imgui.separator()
   if imgui.button("Save", (120, 0)):
    if self._new_workspace_profile_name.strip():
     self.controller._cb_save_workspace_profile(self._new_workspace_profile_name, self._new_workspace_profile_scope)
     self._show_save_workspace_profile_modal = False
     imgui.close_current_popup()
   
   imgui.same_line()
   if imgui.button("Cancel", (120, 0)):
    self._show_save_workspace_profile_modal = False
    imgui.close_current_popup()
   
   imgui.end_popup()

 def _render_preset_manager_content(self, is_embedded: bool = False) -> None:
  avail = imgui.get_content_region_avail()
  if not hasattr(self, "_prompt_md_preview"): self._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()
   if imgui.begin_child("prompt_list_pane", imgui.ImVec2(0, 0), False):
    if imgui.button("New Preset", imgui.ImVec2(-1, 0)):
     self._editing_preset_name = ""
     self._editing_preset_system_prompt = ""
     self._editing_preset_scope = "project"
     self._selected_preset_idx = -1
    imgui.separator()
    preset_names = sorted(self.controller.presets.keys())
    for i, name in enumerate(preset_names):
     if name and imgui.selectable(f"{name}##p_{i}", self._selected_preset_idx == i)[0]:
      self._selected_preset_idx = i
      self._editing_preset_name = name
      p = self.controller.presets[name]
      self._editing_preset_system_prompt = p.system_prompt
      self._editing_preset_scope = self.controller.preset_manager.get_preset_scope(name)
    imgui.end_child()

   # Right Editor
   imgui.table_next_column()
   avail_r = imgui.get_content_region_avail()
   if imgui.begin_child("prompt_edit_pane", imgui.ImVec2(0, avail_r.y - 45), False):
    p_disp = self._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)
     _, self._editing_preset_name = imgui.input_text("##epn", self._editing_preset_name)
     imgui.table_next_row()
     imgui.table_next_column(); imgui.text("Scope:")
     imgui.table_next_column()
     if imgui.radio_button("Global##ps", self._editing_preset_scope == "global"): self._editing_preset_scope = "global"
     imgui.same_line()
     if imgui.radio_button("Project##ps", self._editing_preset_scope == "project"): self._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"):
     self.text_viewer_title = f"Preset: {self._editing_preset_name}"
     self.text_viewer_content = self._editing_preset_system_prompt
     self.text_viewer_type = "markdown"
     self.show_text_viewer = True
    
    rem_y = imgui.get_content_region_avail().y
    _, self._editing_preset_system_prompt = imgui.input_text_multiline("##pcont", self._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 self._editing_preset_name.strip():
     self.controller._cb_save_preset(
      self._editing_preset_name.strip(),
      self._editing_preset_system_prompt,
      self._editing_preset_scope
     )
     self.ai_status = f"Saved: {self._editing_preset_name}"
   imgui.same_line()
   if imgui.button("Delete##p", imgui.ImVec2(100, 0)):
    if self._editing_preset_name:
     self.controller._cb_delete_preset(self._editing_preset_name, self._editing_preset_scope)
     self._editing_preset_name = ""
     self._selected_preset_idx = -1
   if not is_embedded:
    imgui.same_line()
    if imgui.button("Close##p", imgui.ImVec2(100, 0)):
     self.show_preset_manager_window = False
   imgui.end_table()

 def _render_preset_manager_window(self, is_embedded: bool = False) -> None:
  if not self.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)
   opened, self.show_preset_manager_window = imgui.begin("Prompt Presets Manager", self.show_preset_manager_window)
   if not opened:
    imgui.end(); return
  self._render_preset_manager_content(is_embedded=is_embedded)
  if not is_embedded: imgui.end()

 def _render_tool_preset_manager_content(self, is_embedded: bool = False) -> None:
  avail = imgui.get_content_region_avail()
  if not hasattr(self, "_tool_split_v"): self._tool_split_v = 0.4
  if not hasattr(self, "_bias_split_v"): self._bias_split_v = 0.6
  if not hasattr(self, "_tool_list_open"): self._tool_list_open = True
  if not hasattr(self, "_bias_list_open"): self._bias_list_open = True
  if not hasattr(self, "_bias_weights_open"): self._bias_weights_open = True
  if not hasattr(self, "_bias_cats_open"): self._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()
   if imgui.begin_child("tp_list_pane", imgui.ImVec2(0, 0), False):
    if imgui.button("New Preset", imgui.ImVec2(-1, 0)):
     self._editing_tool_preset_name = ""; self._editing_tool_preset_categories = {cat: {} for cat in models.DEFAULT_TOOL_CATEGORIES}
     self._editing_tool_preset_scope = "project"; self._selected_tool_preset_idx = -1
    imgui.separator()
    preset_names = sorted(self.controller.tool_presets.keys())
    for i, name in enumerate(preset_names):
     if name and imgui.selectable(f"{name}##tp_{i}", self._selected_tool_preset_idx == i)[0]:
      self._selected_tool_preset_idx = i; self._editing_tool_preset_name = name
      preset = self.controller.tool_presets[name]
      self._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()
   if imgui.begin_child("tp_editor_content", imgui.ImVec2(0, avail_r.y - 45), False):
    p_name = self._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); _, self._editing_tool_preset_name = imgui.input_text("##etpn", self._editing_tool_preset_name)
     imgui.table_next_row(); imgui.table_next_column(); imgui.text("Scope:"); imgui.table_next_column()
     if imgui.radio_button("Global", self._editing_tool_preset_scope == "global"): self._editing_tool_preset_scope = "global"
     imgui.same_line(); 
     if imgui.radio_button("Project", self._editing_tool_preset_scope == "project"): self._editing_tool_preset_scope = "project"
     imgui.end_table()

    rem_y = imgui.get_content_region_avail().y - 80
    if self._tool_list_open and self._bias_list_open: h1, h2 = rem_y * self._tool_split_v, rem_y - (rem_y * self._tool_split_v) - 10
    elif self._tool_list_open: h1, h2 = rem_y, 0
    elif self._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 != self._tool_list_open: self._tool_list_open = opened_t
    if self._tool_list_open:
     imgui.text("Filter:"); imgui.same_line()
     cat_opts = ["All"] + sorted(list(models.DEFAULT_TOOL_CATEGORIES.keys()))
     f_idx = cat_opts.index(self.ui_tool_filter_category) if self.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: self.ui_tool_filter_category = cat_opts[next_f_idx]
     if imgui.begin_child("tp_scroll", imgui.ImVec2(0, h1), True):
      for cat_name, default_tools in models.DEFAULT_TOOL_CATEGORIES.items():
       if self.ui_tool_filter_category != "All" and self.ui_tool_filter_category != cat_name: continue
       if imgui.tree_node(cat_name):
        if cat_name not in self._editing_tool_preset_categories: self._editing_tool_preset_categories[cat_name] = []
        curr_cat_tools = self._editing_tool_preset_categories[cat_name]
        if imgui.begin_table(f"tt_{cat_name}", 2, imgui.TableFlags_.borders_inner_v):
         imgui.table_setup_column("Tool", imgui.TableColumnFlags_.width_fixed, 250); imgui.table_setup_column("Ctrls", imgui.TableColumnFlags_.width_stretch)
         for tool_name in default_tools:
          tool = next((t for t in curr_cat_tools if t.name == tool_name), None)
          mode = "disabled" if tool is None else tool.approval
          imgui.table_next_row(); imgui.table_next_column(); imgui.text(tool_name); imgui.table_next_column()
          if imgui.radio_button(f"Off##{cat_name}_{tool_name}", mode == "disabled"):
           if tool: curr_cat_tools.remove(tool)
          imgui.same_line(); 
          if imgui.radio_button(f"Auto##{cat_name}_{tool_name}", mode == "auto"):
           if not tool: tool = models.Tool(name=tool_name, approval="auto"); current_cat_tools.append(tool)
           else: tool.approval = "auto"
          imgui.same_line(); 
          if imgui.radio_button(f"Ask##{cat_name}_{tool_name}", mode == "ask"):
           if not tool: tool = models.Tool(name=tool_name, approval="ask"); current_cat_tools.append(tool)
           else: tool.approval = "ask"
         imgui.end_table()
        imgui.tree_pop()
      imgui.end_child()
     if self._bias_list_open:
      imgui.button("###tool_splitter", imgui.ImVec2(-1, 4))
      if imgui.is_item_active(): self._tool_split_v = max(0.1, min(0.9, self._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 != self._bias_list_open: self._bias_list_open = opened_b
    if self._bias_list_open:
     if imgui.begin_child("bias_area", imgui.ImVec2(0, h2), 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()
       if imgui.begin_child("blist_pane", imgui.ImVec2(0, 0), False):
        if imgui.button("New Profile", imgui.ImVec2(-1, 0)):
         self._editing_bias_profile_name = ""; self._editing_bias_profile_tool_weights = {}
         self._editing_bias_profile_category_multipliers = {}; self._selected_bias_profile_idx = -1
        imgui.separator(); bnames = sorted(self.bias_profiles.keys())
        for i, bname in enumerate(bnames):
         if bname and imgui.selectable(f"{bname}##b_{i}", self._selected_bias_profile_idx == i)[0]:
          self._selected_bias_profile_idx = i; self._editing_bias_profile_name = bname; prof = self.bias_profiles[bname]
          self._editing_bias_profile_tool_weights = copy.deepcopy(prof.tool_weights); self._editing_bias_profile_category_multipliers = copy.deepcopy(prof.category_multipliers)
        imgui.end_child()
       
       imgui.table_next_column()
       if imgui.begin_child("bedit_pane", imgui.ImVec2(0, 0), False):
        imgui.text("Name:"); imgui.same_line(); imgui.set_next_item_width(-1); _, self._editing_bias_profile_name = imgui.input_text("##bname", self._editing_bias_profile_name)
        rem_bias_y = imgui.get_content_region_avail().y - 45 
        if self._bias_weights_open and self._bias_cats_open: bh1, bh2 = rem_bias_y * self._bias_split_v, rem_bias_y - (rem_bias_y * self._bias_split_v) - 10
        elif self._bias_weights_open: bh1, bh2 = rem_bias_y, 0
        elif self._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 != self._bias_weights_open: self._bias_weights_open = opened_bw
        if self._bias_weights_open:
         if imgui.begin_child("btool_scroll", imgui.ImVec2(0, bh1), 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 = self._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: self._editing_bias_profile_tool_weights[tn] = n_w
             imgui.end_table()
            imgui.tree_pop()
          imgui.end_child()
          if self._bias_cats_open:
           imgui.button("###bias_splitter", imgui.ImVec2(-1, 4))
           if imgui.is_item_active(): self._bias_split_v = max(0.1, min(0.9, self._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 != self._bias_cats_open: self._bias_cats_open = opened_bc
        if self._bias_cats_open:
         if imgui.begin_child("bcat_scroll", imgui.ImVec2(0, bh2), 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 = self._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: self._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=self._editing_bias_profile_name, tool_weights=self._editing_bias_profile_tool_weights, category_multipliers=self._editing_bias_profile_category_multipliers)
          self.controller._cb_save_bias_profile(p, self._editing_tool_preset_scope); self.ai_status = f"Saved: {p.name}"
         except Exception as e: self.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 self._editing_tool_preset_name.strip(): self.controller._cb_save_tool_preset(self._editing_tool_preset_name.strip(), self._editing_tool_preset_categories, self._editing_tool_preset_scope); self.ai_status = f"Saved: {self._editing_tool_preset_name}"
   imgui.same_line()
   if imgui.button("Delete##tp", imgui.ImVec2(100, 0)):
    if self._editing_tool_preset_name: self.controller._cb_delete_tool_preset(self._editing_tool_preset_name, self._editing_tool_preset_scope); self._editing_tool_preset_name = ""; self._selected_tool_preset_idx = -1
   imgui.same_line()
   if not is_embedded:
    if imgui.button("Close##tp", imgui.ImVec2(100, 0)): self.show_tool_preset_manager_window = False
   imgui.end_table()

 def _render_tool_preset_manager_window(self, is_embedded: bool = False) -> None:
  if not self.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)
   opened, self.show_tool_preset_manager_window = imgui.begin("Tool Preset Manager", self.show_tool_preset_manager_window)
   if not opened:
    imgui.end(); return
  self._render_tool_preset_manager_content(is_embedded=is_embedded)
  if not is_embedded: imgui.end()

 def _render_persona_editor_window(self, is_embedded: bool = False) -> None:
  if not self.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, self.show_persona_editor_window = imgui.begin("Persona Editor", self.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()
   if imgui.begin_child("persona_list_pane", imgui.ImVec2(0, 0), False):
    if imgui.button("New Persona", imgui.ImVec2(-1, 0)):
     self._editing_persona_name = ""; self._editing_persona_system_prompt = ""
     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 = [{"provider": self.current_provider, "model": self.current_model, "temperature": 0.7, "top_p": 1.0, "max_output_tokens": 4096, "history_trunc_limit": 900000}]
     self._editing_persona_scope = "project"; self._editing_persona_is_new = True
    imgui.separator()
    personas = getattr(self.controller, 'personas', {})
    for name in sorted(personas.keys()):
     if name and imgui.selectable(f"{name}##p_list", name == self._editing_persona_name and not getattr(self, '_editing_persona_is_new', False))[0]:
      p = personas[name]; self._editing_persona_name = p.name; self._editing_persona_system_prompt = p.system_prompt or ""
      self._editing_persona_tool_preset_id = p.tool_preset or ""; self._editing_persona_bias_profile_id = p.bias_profile or ""
      self._editing_persona_context_preset_id = getattr(p, 'context_preset', '') or ""
      self._editing_persona_aggregation_strategy = getattr(p, 'aggregation_strategy', '') or ""
      import copy; self._editing_persona_preferred_models_list = copy.deepcopy(p.preferred_models) if p.preferred_models else []
      self._editing_persona_scope = self.controller.persona_manager.get_persona_scope(p.name); self._editing_persona_is_new = False
    imgui.end_child()

   # --- Right Editor ---
   imgui.table_next_column()
   avail = imgui.get_content_region_avail()
   if imgui.begin_child("persona_editor_content", imgui.ImVec2(0, avail.y - 45), False):
    header_text = "New Persona" if getattr(self, '_editing_persona_is_new', True) else f"Editing Persona: {self._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); _, self._editing_persona_name = imgui.input_text("##pname", self._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(self, '_editing_persona_scope', 'project') == "global"): self._editing_persona_scope = "global"
     imgui.same_line(); 
     if imgui.radio_button("Project##pscope", getattr(self, '_editing_persona_scope', 'project') == "project"): self._editing_persona_scope = "project"
     imgui.end_table()
    
    rem_y = imgui.get_content_region_avail().y - 100
    if self._persona_models_open and self._persona_prompt_open: h1, h2 = rem_y * self._persona_split_v, rem_y - (rem_y * self._persona_split_v) - 10
    elif self._persona_models_open: h1, h2 = rem_y, 0
    elif self._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 != self._persona_models_open: self._persona_models_open = opened_models
    
    if self._persona_models_open:
     if imgui.begin_child("pref_models_scroll", imgui.ImVec2(0, h1), True):
      to_remove = []
      providers = self.controller.PROVIDERS
      if not hasattr(self, '_persona_pref_models_expanded'): self._persona_pref_models_expanded = {}
      for i, entry in enumerate(self._editing_persona_preferred_models_list):
       imgui.push_id(f"pref_model_{i}")
       prov, mod, is_expanded = entry.get("provider", "Unknown"), entry.get("model", "Unknown"), self._persona_pref_models_expanded.get(i, False)
       if imgui.button("-" if is_expanded else "+"): self._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:
        imgui.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 = self.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.unindent(20)
       imgui.pop_id()
      for i in reversed(to_remove): self._editing_persona_preferred_models_list.pop(i)
      imgui.end_child()
     if self._persona_prompt_open:
      imgui.button("###persona_splitter", imgui.ImVec2(-1, 4))
      if imgui.is_item_active(): self._persona_split_v = max(0.1, min(0.9, self._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)): self._editing_persona_preferred_models_list.append({"provider": self.current_provider, "model": self.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(self.controller.tool_presets.keys())
     t_idx = tn.index(self._editing_persona_tool_preset_id) if getattr(self, '_editing_persona_tool_preset_id', '') in tn else 0
     imgui.set_next_item_width(-1); _, t_idx = imgui.combo("##ptp", t_idx, tn); self._editing_persona_tool_preset_id = tn[t_idx] if t_idx > 0 else ""
     imgui.table_next_column(); imgui.text("Bias Profile:"); bn = ["None"] + sorted(self.controller.bias_profiles.keys())
     b_idx = bn.index(self._editing_persona_bias_profile_id) if getattr(self, '_editing_persona_bias_profile_id', '') in bn else 0
     imgui.set_next_item_width(-1); _, b_idx = imgui.combo("##pbp", b_idx, bn); self._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(self.controller.project.get("context_presets", {}).keys())
     c_idx = cn.index(self._editing_persona_context_preset_id) if getattr(self, '_editing_persona_context_preset_id', '') in cn else 0
     imgui.set_next_item_width(-1); _, c_idx = imgui.combo("##pcp", c_idx, cn); self._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(self._editing_persona_aggregation_strategy) if getattr(self, '_editing_persona_aggregation_strategy', '') in sn else 0
     imgui.set_next_item_width(-1); _, s_idx = imgui.combo("##pas", s_idx, sn); self._editing_persona_aggregation_strategy = sn[s_idx]
     imgui.end_table()
    
    if imgui.button("Manage Tools & Biases", imgui.ImVec2(-1, 0)): self.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 != self._persona_prompt_open: self._persona_prompt_open = opened_prompt
    
    if self._persona_prompt_open:
     if imgui.begin_child("p_prompt_header_pane", imgui.ImVec2(0, 30), False):
      imgui.text("Template:"); imgui.same_line(); p_pre = ["Select..."] + sorted(self.controller.presets.keys())
      if not hasattr(self, "_load_preset_idx"): self._load_preset_idx = 0
      imgui.set_next_item_width(200); _, self._load_preset_idx = imgui.combo("##load_p", self._load_preset_idx, p_pre)
      imgui.same_line(); 
      if imgui.button("Apply"):
       if self._load_preset_idx > 0: self._editing_persona_system_prompt = self.controller.presets[p_pre[self._load_preset_idx]].system_prompt
      imgui.same_line(); 
      if imgui.button("Manage"): self.show_preset_manager_window = True
      imgui.end_child()
     _, self._editing_persona_system_prompt = imgui.input_text_multiline("##pprompt", self._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 self._editing_persona_name.strip():
     try:
      import copy; persona = models.Persona(name=self._editing_persona_name.strip(), system_prompt=self._editing_persona_system_prompt, tool_preset=self._editing_persona_tool_preset_id or None, bias_profile=self._editing_persona_bias_profile_id or None, context_preset=self._editing_persona_context_preset_id or None, aggregation_strategy=self._editing_persona_aggregation_strategy or None, preferred_models=copy.deepcopy(self._editing_persona_preferred_models_list))
      self.controller._cb_save_persona(persona, getattr(self, '_editing_persona_scope', 'project')); self.ai_status = f"Saved: {persona.name}"
     except Exception as e: self.ai_status = f"Error: {e}"
    else: self.ai_status = "Name required"
   imgui.same_line(); 
   if imgui.button("Delete##pers", imgui.ImVec2(100, 0)):
    if not getattr(self, '_editing_persona_is_new', True) and self._editing_persona_name:
     self.controller._cb_delete_persona(self._editing_persona_name, getattr(self, '_editing_persona_scope', 'project'))
     self._editing_persona_name = ""; self._editing_persona_is_new = True
   if not is_embedded:
    imgui.same_line()
    if imgui.button("Close##pers", imgui.ImVec2(100, 0)):
     self.show_persona_editor_window = False
   imgui.end_table()
  if not is_embedded: imgui.end()

 def _render_projects_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_projects_panel")
  proj_name = self.project.get("project", {}).get("name", Path(self.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(self.ui_project_execution_mode) if self.ui_project_execution_mode in modes else 0
  ch, new_idx = imgui.combo("##exec_mode", current_idx, modes)
  if ch:
   self.ui_project_execution_mode = modes[new_idx]
  imgui.separator()
  imgui.text("Git Directory")
  ch, self.ui_project_git_dir = imgui.input_text("##git_dir", self.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: self.ui_project_git_dir = d
  imgui.separator()
  imgui.text("Main Context File")
  ch, self.ui_project_main_context = imgui.input_text("##main_ctx", self.ui_project_main_context)
  imgui.same_line()
  if imgui.button("Browse##ctx"):
   r = hide_tk_root()
   p = filedialog.askopenfilename(title="Select Main Context File")
   r.destroy()
   if p: self.ui_project_main_context = p
  imgui.separator()
  imgui.text("Output Dir")
  ch, self.ui_output_dir = imgui.input_text("##out_dir", self.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: self.ui_output_dir = d
  imgui.separator()
  imgui.text("Conductor Directory")
  ch, self.ui_project_conductor_dir = imgui.input_text("##cond_dir", self.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: self.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(self.project_paths):
   is_active = (pp == self.active_project_path)
   if imgui.button(f"x##p{i}"):
    removed = self.project_paths.pop(i)
    if removed == self.active_project_path and self.project_paths:
     self._switch_project(self.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}"):
    self._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 self.project_paths:
    self.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 self.project_paths:
     self.project_paths.append(p)
    self._switch_project(p)
  imgui.same_line()
  if imgui.button("Save All"):
   self._flush_to_project()
   self._flush_to_config()
   models.save_config(self.config)
   self.ai_status = "config saved"
  ch, self.ui_word_wrap = imgui.checkbox("Word-Wrap (Read-only panels)", self.ui_word_wrap)
  ch, self.ui_auto_scroll_comms = imgui.checkbox("Auto-scroll Comms History", self.ui_auto_scroll_comms)
  ch, self.ui_auto_scroll_tool_calls = imgui.checkbox("Auto-scroll Tool History", self.ui_auto_scroll_tool_calls)
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_projects_panel")

 def _save_paths(self):
  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")
  models.save_config(self.config)
  paths.reset_resolved()
  self.init_state()
  self.ai_status = 'paths applied and session reset'

 def _render_paths_panel(self) -> None:
  if self.perf_profiling_enabled: self.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(self, attr)
   changed, new_val = imgui.input_text(f"##{key}", val)
   if imgui.is_item_hovered(): imgui.set_tooltip(tooltip)
   if changed: setattr(self, 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(self, 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)):
   self._save_paths()
  imgui.same_line()
  if imgui.button("Reset", imgui.ImVec2(120, 0)):
   self.init_state()
   self.ai_status = "paths reset to defaults"

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

 def _render_context_presets_panel(self) -> None:
  imgui.text_colored(C_IN, "Context Presets")
  imgui.separator()
  changed, new_name = imgui.input_text("Preset Name##new_ctx", self.ui_new_context_preset_name)
  if changed: self.ui_new_context_preset_name = new_name
  imgui.same_line()
  if imgui.button("Save Current"):
   if self.ui_new_context_preset_name.strip():
    self.save_context_preset(self.ui_new_context_preset_name.strip())
  
  imgui.separator()
  presets = self.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}"):
    self.load_context_preset(name)
   imgui.same_line()
   if imgui.button(f"Delete##{name}"):
    self.delete_context_preset(name)
 def _render_track_proposal_modal(self) -> None:
  if self._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
   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 not self._show_track_proposal_modal:
    imgui.close_current_popup()
    imgui.end_popup()
    return
   imgui.text_colored(C_IN, "Proposed Implementation Tracks")
   imgui.separator()
   if not self.proposed_tracks:
    imgui.text("No tracks generated.")
   else:
    for idx, track in enumerate(self.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}"):
      self.proposed_tracks.pop(idx)
      break
     imgui.same_line()
     if imgui.button(f"Start This Track##{idx}"):
      self._cb_start_track(idx)
     imgui.separator()
   if imgui.button("Accept", imgui.ImVec2(120, 0)):
    self._cb_accept_tracks()
    self._show_track_proposal_modal = False
    imgui.close_current_popup()
   imgui.same_line()
   if imgui.button("Cancel", imgui.ImVec2(120, 0)):
    self._show_track_proposal_modal = False
    imgui.close_current_popup()
   imgui.end_popup()

 def _render_patch_modal(self) -> None:
  if not self._show_patch_modal:
   return
  imgui.open_popup("Apply Patch?")
  if imgui.begin_popup_modal("Apply Patch?", True, imgui.WindowFlags_.always_auto_resize)[0]:
   from src import shaders
   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(vec4(255, 230, 77), "Tier 4 QA Generated a Patch")
   imgui.separator()
   if self._pending_patch_files:
    imgui.text("Files to modify:")
    for f in self._pending_patch_files:
     imgui.text(f"  - {f}")
   imgui.separator()
   if self._patch_error_message:
    imgui.text_colored(vec4(255, 77, 77), f"Error: {self._patch_error_message}")
    imgui.separator()
   imgui.text("Diff Preview:")
   imgui.begin_child("patch_diff_scroll", imgui.ImVec2(-1, 280), True)
   if self._pending_patch_text:
    diff_lines = self._pending_patch_text.split("\n")
    for line in diff_lines:
     if line.startswith("+++") or line.startswith("---") or line.startswith("@@"):
      imgui.text_colored(vec4(77, 178, 255), line)
     elif line.startswith("+"):
      imgui.text_colored(vec4(51, 230, 51), line)
     elif line.startswith("-"):
      imgui.text_colored(vec4(230, 51, 51), line)
     else:
      imgui.text(line)
   imgui.end_child()
   imgui.separator()
   if imgui.button("Apply Patch"):
    self._apply_pending_patch()
   imgui.same_line()
   if imgui.button("Reject"):
    self._show_patch_modal = False
    self._pending_patch_text = None
    self._pending_patch_files = []
    self._patch_error_message = None
    imgui.close_current_popup()
   imgui.end_popup()

 def _apply_pending_patch(self) -> None:
  if not self._pending_patch_text:
   self._patch_error_message = "No patch to apply"
   return
  try:
   from src.diff_viewer import apply_patch_to_file
   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 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 _render_log_management(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management")
  exp, opened = imgui.begin("Log Management", self.show_windows["Log Management"])
  self.show_windows["Log Management"] = bool(opened)
  if not exp:
   imgui.end()
   return

  if self._log_registry is None:
   self._log_registry = log_registry.LogRegistry(str(paths.get_logs_dir() / "log_registry.toml"))
  else:
   if imgui.button("Refresh Registry"):
    self._log_registry = log_registry.LogRegistry(str(paths.get_logs_dir() / "log_registry.toml"))
   imgui.same_line()
  if imgui.button("Load Log"):
   self.cb_load_prior_log()
  imgui.same_line()
  if imgui.button("Force Prune Logs"):
   self.controller.event_queue.put("gui_task", {"action": "click", "item": "btn_prune_logs"})

  registry = self._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(vec4(255, 215, 0), "YES")
    else:
     imgui.text("NO")
    metadata = s_data.get("metadata") or {}
    imgui.table_next_column()
    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}"):
     self.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 self.perf_profiling_enabled: self.perf_monitor.end_component("_render_log_management")
  imgui.end()

 def _render_diagnostics_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_diagnostics_panel")
  exp, opened = imgui.begin("Diagnostics", self.show_windows.get("Diagnostics", False))
  self.show_windows["Diagnostics"] = bool(opened)
  if not exp:
   imgui.end()
   if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_diagnostics_panel")
   return

  metrics = self.perf_monitor.get_metrics()
  imgui.text("Performance Telemetry")
  imgui.same_line()
  _, self.perf_profiling_enabled = imgui.checkbox("Enable Profiling", self.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()
    self.perf_show_graphs.setdefault(key, False)
    _, self.perf_show_graphs[key] = imgui.checkbox(f"##g_{key}", self.perf_show_graphs[key])
   imgui.end_table()

  if self.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(imgui.ImVec4(1.0, 0.2, 0.2, 1.0), 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()
      self.perf_show_graphs.setdefault(comp_name, False)
      _, self.perf_show_graphs[comp_name] = imgui.checkbox(f"##g_{comp_name}", self.perf_show_graphs[comp_name])
    imgui.end_table()

  imgui.separator()
  imgui.text("Performance Graphs")
  for key, show in self.perf_show_graphs.items():
   if show:
    imgui.text(f"History: {key}")
    hist_data = self.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(self.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 self.perf_profiling_enabled: self.perf_monitor.end_component("_render_diagnostics_panel")
  imgui.end()

 def _render_discussion_tab(self) -> None:
  imgui.begin_child("HistoryChild", size=(0, -self.ui_discussion_split_h))
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
  self._render_discussion_panel()
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_discussion_panel")
  imgui.end_child()
  imgui.button("###discussion_splitter", imgui.ImVec2(-1, 4))
  if imgui.is_item_active():
   self.ui_discussion_split_h = max(150.0, min(imgui.get_window_height() - 150.0, self.ui_discussion_split_h - imgui.get_io().mouse_delta.y))
  imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
  ch1, self.ui_separate_message_panel = imgui.checkbox("Pop Out Message", self.ui_separate_message_panel)
  imgui.same_line()
  ch2, self.ui_separate_response_panel = imgui.checkbox("Pop Out Response", self.ui_separate_response_panel)
  if ch1: self.show_windows["Message"] = self.ui_separate_message_panel
  if ch2: self.show_windows["Response"] = self.ui_separate_response_panel
  imgui.pop_style_var()
  show_message_tab = not self.ui_separate_message_panel
  show_response_tab = not self.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 self._autofocus_response_tab:
     tab_flags = imgui.TabItemFlags_.set_selected
     self._autofocus_response_tab = False
     self.controller._autofocus_response_tab = False
    if show_message_tab:
     if imgui.begin_tab_item("Message", None)[0]:
      self._render_message_panel()
      imgui.end_tab_item()
    if show_response_tab:
     if imgui.begin_tab_item("Response", None, tab_flags)[0]:
      self._render_response_panel()
      imgui.end_tab_item()
    imgui.end_tab_bar()
  else:
   imgui.text_disabled("Message & Response panels are detached.")

 def _render_context_composition_panel(self) -> None:
  imgui.text("Context Composition")
  imgui.separator()
  if imgui.begin_table("ctx_comp_table", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders):
   imgui.table_setup_column("File", imgui.TableColumnFlags_.width_stretch)
   imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 120)
   imgui.table_headers_row()
   for i, f_item in enumerate(self.files):
    imgui.table_next_row()
    imgui.table_set_column_index(0)
    imgui.text(f_item.path if hasattr(f_item, "path") else str(f_item))
    imgui.table_set_column_index(1)
    if hasattr(f_item, "auto_aggregate"):
     changed_agg, f_item.auto_aggregate = imgui.checkbox(f"Agg##cc{i}", f_item.auto_aggregate)
     imgui.same_line()
     changed_full, f_item.force_full = imgui.checkbox(f"Full##cc{i}", f_item.force_full)
   imgui.end_table()
  imgui.separator()
  imgui.text("Screenshots")
  for i, s in enumerate(self.screenshots):
   imgui.text(s)
  imgui.separator()
  imgui.text("Presets")
  presets = self.controller.project.get('context_presets', {})
  preset_names = [""] + sorted(presets.keys())
  active = getattr(self, "ui_active_context_preset", "")
  if active not in preset_names:
   active = ""
  try:
   idx = preset_names.index(active)
  except ValueError:
   idx = 0
  ch, new_idx = imgui.combo("##ctx_preset", idx, preset_names)
  if ch:
   self.ui_active_context_preset = preset_names[new_idx]
   if preset_names[new_idx]:
    self.load_context_preset(preset_names[new_idx])
  imgui.same_line()
  changed, new_name = imgui.input_text("##new_preset", getattr(self, "ui_new_context_preset_name", ""))
  if changed:
   self.ui_new_context_preset_name = new_name
  imgui.same_line()
  if imgui.button("Save##ctx"):
   if getattr(self, "ui_new_context_preset_name", "").strip():
    self.save_context_preset(self.ui_new_context_preset_name.strip())
    self.ui_new_context_preset_name = ""
  imgui.same_line()
  if imgui.button("Delete##ctx"):
   if getattr(self, "ui_active_context_preset", ""):
    self.delete_context_preset(self.ui_active_context_preset)
    self.ui_active_context_preset = ""

 def _render_snapshot_tab(self) -> None:
  if imgui.begin_tab_bar("snapshot_tabs"):
   if imgui.begin_tab_item("Aggregate MD")[0]:
    display_md = self.last_aggregate_markdown
    if self.ui_focus_agent:
     tier_usage = self.mma_tier_usage.get(self.ui_focus_agent)
     if tier_usage:
      persona_name = tier_usage.get("persona")
      if persona_name:
       persona = self.controller.personas.get(persona_name)
       if persona and persona.context_preset:
        cp_name = persona.context_preset
        if cp_name in self._focus_md_cache:
         display_md = self._focus_md_cache[cp_name]
        else:
         flat = src.project_manager.flat_config(self.controller.project, self.active_discussion)
         cp = self.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)
          self._focus_md_cache[cp_name] = full_md
          display_md = full_md
    if imgui.button("Copy"):
     imgui.set_clipboard_text(display_md)
    imgui.begin_child("last_agg_md", imgui.ImVec2(0, 0), True)
    markdown_helper.render(display_md, context_id="snapshot_agg")
    imgui.end_child()
    imgui.end_tab_item()
   if imgui.begin_tab_item("System Prompt")[0]:
    if imgui.button("Copy"):
     imgui.set_clipboard_text(self.last_resolved_system_prompt)
    imgui.begin_child("last_sys_prompt", imgui.ImVec2(0, 0), True)
    markdown_helper.render(self.last_resolved_system_prompt, context_id="snapshot_sys")
    imgui.end_child()
    imgui.end_tab_item()
   imgui.end_tab_bar()

 def _render_takes_panel(self) -> None:
  imgui.text("Takes & Synthesis")
  imgui.separator()
  discussions = self.project.get('discussion', {}).get('discussions', {})
  if not hasattr(self, 'ui_synthesis_selected_takes'):
   self.ui_synthesis_selected_takes = {name: False for name in discussions}
  if not hasattr(self, 'ui_synthesis_prompt'):
   self.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 discussions.items():
    imgui.table_next_row()
    imgui.table_set_column_index(0)
    is_active = name == self.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}"):
     self._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:
   _, self.ui_synthesis_selected_takes[name] = imgui.checkbox(name, self.ui_synthesis_selected_takes.get(name, False))
  imgui.spacing()
  imgui.text("Synthesis Prompt:")
  _, self.ui_synthesis_prompt = imgui.input_text_multiline("##synthesis_prompt", self.ui_synthesis_prompt, imgui.ImVec2(-1, 100))
  if imgui.button("Generate Synthesis"):
   selected = [name for name, sel in self.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"{self.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
    self._create_discussion(new_name)
    with self._disc_entries_lock:
     self.disc_entries.append({"role": "user", "content": prompt, "collapsed": False, "ts": project_manager.now_ts()})
    self._handle_generate_send()
 def _render_markdown_test(self) -> None:
  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_files_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_files_panel")
  imgui.text("Paths")
  imgui.same_line()
  imgui.text("| Base Dir:")
  imgui.same_line()
  imgui.set_next_item_width(-100)
  ch, self.ui_files_base_dir = imgui.input_text("##f_base", self.ui_files_base_dir)
  imgui.same_line()
  if imgui.button("Browse##fb"):
   r = hide_tk_root()
   d = filedialog.askdirectory()
   r.destroy()
   if d: self.ui_files_base_dir = d
  imgui.separator()
  imgui.begin_child("f_paths", imgui.ImVec2(0, -40), True)
  if imgui.begin_table("files_table", 4, imgui.TableFlags_.resizable | imgui.TableFlags_.borders):
   imgui.table_setup_column("Actions", imgui.TableColumnFlags_.width_fixed, 40)
   imgui.table_setup_column("File Path", imgui.TableColumnFlags_.width_stretch)
   imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 150)
   imgui.table_setup_column("Cache", imgui.TableColumnFlags_.width_fixed, 40)
   imgui.table_headers_row()
   
   for i, f_item in enumerate(self.files):
    imgui.table_next_row()
    # Actions
    imgui.table_set_column_index(0)
    if imgui.button(f"x##f{i}"):
     self.files.pop(i)
     break
    # File Path
    imgui.table_set_column_index(1)
    imgui.text(f_item.path if hasattr(f_item, "path") else str(f_item))
    # Flags
    imgui.table_set_column_index(2)
    if hasattr(f_item, "auto_aggregate"):
     changed_agg, f_item.auto_aggregate = imgui.checkbox(f"Agg##a{i}", f_item.auto_aggregate)
     imgui.same_line()
     changed_full, f_item.force_full = imgui.checkbox(f"Full##f{i}", f_item.force_full)
    # Cache
    imgui.table_set_column_index(3)
    path = f_item.path if hasattr(f_item, "path") else str(f_item)
    is_cached = any(path in c for c in getattr(self, "_cached_files", []))
    if is_cached:
     imgui.text_colored("●", imgui.ImVec4(0, 1, 0, 1)) # Green dot
    else:
     imgui.text_disabled("○")
   imgui.end_table()
  imgui.end_child()
  if imgui.button("Add File(s)"):
   r = hide_tk_root()
   paths = filedialog.askopenfilenames()
   r.destroy()
   for p in paths:
    if p not in [f.path if hasattr(f, "path") else f for f in self.files]:
     self.files.append(models.FileItem(path=p))
  imgui.same_line()
  if imgui.button("Add Wildcard"):
   r = hide_tk_root()
   d = filedialog.askdirectory()
   r.destroy()
   if d: self.files.append(models.FileItem(path=str(Path(d) / "**" / "*")))
  
  imgui.separator()
  from src import summarize
  stats = summarize._summary_cache.get_stats()
  imgui.text_disabled(f"Summary Cache: {stats['entries']} entries ({stats['size_bytes']} bytes)")
  imgui.same_line()
  if imgui.button("Clear Summary Cache##btn_clear_summary_cache"):
   self.controller._cb_clear_summary_cache()

  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_files_panel")

 def _render_screenshots_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_screenshots_panel")
  imgui.text("Paths")
  imgui.same_line()
  imgui.text("| Base Dir:")
  imgui.same_line()
  imgui.set_next_item_width(-100)
  ch, self.ui_shots_base_dir = imgui.input_text("##s_base", self.ui_shots_base_dir)
  imgui.same_line()
  if imgui.button("Browse##sb"):
   r = hide_tk_root()
   d = filedialog.askdirectory()
   r.destroy()
   if d: self.ui_shots_base_dir = d
  imgui.separator()
  imgui.begin_child("s_paths", imgui.ImVec2(0, -40), True)
  for i, s in enumerate(self.screenshots):
   if imgui.button(f"x##s{i}"):
    self.screenshots.pop(i)
    break
   imgui.same_line()
   imgui.text(s)
  imgui.end_child()
  if imgui.button("Add Screenshot(s)"):
   r = hide_tk_root()
   paths = filedialog.askopenfilenames(
    title="Select Screenshots",
    filetypes=[("Images", "*.png *.jpg *.jpeg *.gif *.bmp *.webp"), ("All", "*.*")],
   )
   r.destroy()
   for p in paths:
    if p not in self.screenshots: self.screenshots.append(p)
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_screenshots_panel")

 def _render_discussion_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
  # THINKING indicator
  is_thinking = self.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 = vec4(255, 100, 100, alpha)
   if theme.is_nerv_active():
    c = vec4(255, 50, 50, alpha) # More vibrant for NERV
   imgui.text_colored(c, "THINKING...")
   imgui.same_line()

  if self.is_viewing_prior_session:
   imgui.push_style_color(imgui.Col_.child_bg, vec4(50, 40, 20))
   if imgui.button("Exit Prior Session"):
    self.controller.cb_exit_prior_session()
    self._comms_log_dirty = True
   imgui.separator()
   imgui.begin_child("prior_scroll", imgui.ImVec2(0, 0), False)
   clipper = imgui.ListClipper()
   clipper.begin(len(self.prior_disc_entries))
   while clipper.step():
    for idx in range(clipper.display_start, clipper.display_end):
     entry = self.prior_disc_entries[idx]
     imgui.push_id(f"prior_disc_{idx}")
     collapsed = entry.get("collapsed", False)
     if imgui.button("+" if collapsed else "-"):
      entry["collapsed"] = not collapsed
     imgui.same_line()
     role = entry.get("role", "??")
     ts = entry.get("ts", "")
     imgui.text_colored(C_LBL, f"[{role}]")
     if ts:
      imgui.same_line()
      imgui.text_colored(vec4(160, 160, 160), str(ts))

     content = entry.get("content", "")
     if collapsed:
      imgui.same_line()
      preview = content.replace("\n", " ")[:80]
      if len(content) > 80: preview += "..."
      imgui.text_colored(vec4(180, 180, 180), preview)
     else:
      is_nerv = theme.is_nerv_active()
      if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
      markdown_helper.render(content, context_id=f'prior_disc_{idx}')
      if is_nerv: imgui.pop_style_color()

     imgui.separator()
     imgui.pop_id()
   imgui.end_child()
   imgui.pop_style_color()
   if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_discussion_panel")
   return

  if not self.is_viewing_prior_session and imgui.collapsing_header("Discussions", imgui.TreeNodeFlags_.default_open):
   names = self._get_discussion_names()
   grouped_discussions = {}
   for name in names:
    base = name.split("_take_")[0]
    grouped_discussions.setdefault(base, []).append(name)
    
   active_base = self.active_discussion.split("_take_")[0]
   if active_base not in grouped_discussions:
    active_base = names[0] if names else ""

   base_names = sorted(grouped_discussions.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_discussions[bname][0]
      if target != self.active_discussion:
       self._switch_discussion(target)
     if is_selected:
      imgui.set_item_default_focus()
    imgui.end_combo()

   # Sync variables in case combo selection changed self.active_discussion
   active_base = self.active_discussion.split("_take_")[0]
   current_takes = grouped_discussions.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()
     flags = imgui.TabItemFlags_.set_selected if take_name == self.active_discussion else 0
     res = imgui.begin_tab_item(f"{label}###{take_name}", None, flags)
     if res[0]:
      if take_name != self.active_discussion:
       self._switch_discussion(take_name)
      imgui.end_tab_item()
    
    res_s = imgui.begin_tab_item("Synthesis###Synthesis")
    if res_s[0]:
     self._render_synthesis_panel()
     imgui.end_tab_item()

    imgui.end_tab_bar()

   if "_take_" in self.active_discussion:
    if imgui.button("Promote Take"):
     base_name = self.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(self.project, self.active_discussion, new_name)
     self._switch_discussion(new_name)
    imgui.same_line()

   if self.active_track:
    imgui.same_line()
    changed, self._track_discussion_active = imgui.checkbox("Track Discussion", self._track_discussion_active)
    if changed:
     if self._track_discussion_active:
      self._flush_disc_entries_to_project()
      history_strings = project_manager.load_track_history(self.active_track.id, self.active_project_root)
      with self._disc_entries_lock:
       self.disc_entries = models.parse_history_entries(history_strings, self.disc_roles)
      self.ai_status = f"track discussion: {self.active_track.id}"
     else:
      self._flush_disc_entries_to_project()
      # Restore project discussion
      self._switch_discussion(self.active_discussion)
      self.ai_status = "track discussion disabled"

   disc_sec = self.project.get("discussion", {})
   disc_data = disc_sec.get("discussions", {}).get(self.active_discussion, {})
   git_commit = disc_data.get("git_commit", "")
   last_updated = disc_data.get("last_updated", "")
   
   imgui.text_colored(C_LBL, "commit:")
   imgui.same_line()
   self._render_selectable_label('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"):
    git_dir = self.ui_project_git_dir
    if git_dir:
     cmt = project_manager.get_git_commit(git_dir)
     if cmt:
      disc_data["git_commit"] = cmt
      disc_data["last_updated"] = project_manager.now_ts()
      self.ai_status = 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, self.ui_disc_new_name_input = imgui.input_text("##new_disc", self.ui_disc_new_name_input)
   imgui.same_line()
   if imgui.button("Create"):
    nm = self.ui_disc_new_name_input.strip()
    if nm: self._create_discussion(nm); self.ui_disc_new_name_input = ""
   imgui.same_line()
   if imgui.button("Rename"):
    nm = self.ui_disc_new_name_input.strip()
    if nm: self._rename_discussion(self.active_discussion, nm); self.ui_disc_new_name_input = ""
   imgui.same_line()
   if imgui.button("Delete"):
    self._delete_discussion(self.active_discussion)

  if not self.is_viewing_prior_session:
   imgui.separator()
   if imgui.button("+ Entry"):
    self.disc_entries.append({"role": self.disc_roles[0] if self.disc_roles else "User", "content": "", "collapsed": True, "ts": project_manager.now_ts()})
   imgui.same_line()
   if imgui.button("-All"):
    for e in self.disc_entries: e["collapsed"] = True
   imgui.same_line()
   if imgui.button("+All"):
    for e in self.disc_entries: e["collapsed"] = False
   imgui.same_line()
   if imgui.button("Clear All"):
    self.disc_entries.clear()
   imgui.same_line()
   if imgui.button("Save"):
    self._flush_to_project()
    self._flush_to_config()
    models.save_config(self.config)
    self.ai_status = "discussion saved"
   
   ch, self.ui_auto_add_history = imgui.checkbox("Auto-add message & response to history", self.ui_auto_add_history)
   # Truncation controls
   imgui.text("Keep Pairs:")
   imgui.same_line()
   imgui.set_next_item_width(80)
   ch, self.ui_disc_truncate_pairs = imgui.input_int("##trunc_pairs", self.ui_disc_truncate_pairs, 1)
   if self.ui_disc_truncate_pairs < 1: self.ui_disc_truncate_pairs = 1
   imgui.same_line()
   if imgui.button("Truncate"):
    with self._disc_entries_lock:
     self.disc_entries = truncate_entries(self.disc_entries, self.ui_disc_truncate_pairs)
    self.ai_status = f"history truncated to {self.ui_disc_truncate_pairs} pairs"
   
   imgui.separator()
   if imgui.collapsing_header("Roles"):
    imgui.begin_child("roles_scroll", imgui.ImVec2(0, 100), True)
    for i, r in enumerate(self.disc_roles):
     imgui.push_id(f"role_{i}")
     if imgui.button("X"):
      self.disc_roles.pop(i)
      imgui.pop_id()
      break
     imgui.same_line()
     imgui.text(r)
     imgui.pop_id()
    imgui.end_child()
    ch, self.ui_disc_new_role_input = imgui.input_text("##new_role", self.ui_disc_new_role_input)
    imgui.same_line()
    if imgui.button("Add"):
     r = self.ui_disc_new_role_input.strip()
     if r and r not in self.disc_roles:
      self.disc_roles.append(r)
      self.ui_disc_new_role_input = ""
   
   imgui.separator()
   imgui.begin_child("disc_scroll", imgui.ImVec2(0, 0), False)

   # Filter entries based on focused agent persona
   display_entries = self.disc_entries
   if self.ui_focus_agent:
    tier_usage = self.mma_tier_usage.get(self.ui_focus_agent)
    if tier_usage:
     persona_name = tier_usage.get("persona")
     if persona_name:
      # Show User messages and the focused agent's responses
      display_entries = [e for e in self.disc_entries if e.get("role") == persona_name or e.get("role") == "User"]

   clipper = imgui.ListClipper()
   clipper.begin(len(display_entries))
   while clipper.step():
    for i in range(clipper.display_start, clipper.display_end):
     entry = display_entries[i]
     # Use the index in the original list for ID if possible, but here i is index in display_entries
     imgui.push_id(f"disc_{i}") 
     collapsed = entry.get("collapsed", False)
     read_mode = entry.get("read_mode", False)
     if imgui.button("+" if collapsed else "-"):
      entry["collapsed"] = not collapsed
     imgui.same_line()
     self._render_text_viewer(f"Entry #{i+1}", entry["content"])
     imgui.same_line()
     imgui.set_next_item_width(120)
     if imgui.begin_combo("##role", entry["role"]):
      for r in self.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", "")
     if ts_str:
      imgui.same_line()
      imgui.text_colored(vec4(120, 120, 100), str(ts_str))
      # Visual indicator for file injections
      e_dt = project_manager.parse_ts(ts_str)
      if e_dt:
       e_unix = e_dt.timestamp()
       next_unix = float('inf')
       if i + 1 < len(self.disc_entries):
        n_ts = self.disc_entries[i+1].get("ts", "")
        n_dt = project_manager.parse_ts(n_ts)
        if n_dt: next_unix = n_dt.timestamp()
       injected_here = [f for f in self.files if hasattr(f, 'injected_at') and f.injected_at and e_unix <= f.injected_at < next_unix]
       if injected_here:
        imgui.same_line()
        imgui.text_colored(vec4(100, 255, 100), f"[{len(injected_here)}+]")
        if imgui.is_item_hovered():
         tooltip = "Files injected at this point:\n" + "\n".join([f.path for f in injected_here])
         imgui.set_tooltip(tooltip)

     if collapsed:
      imgui.same_line()
      if imgui.button("Ins"):
       self.disc_entries.insert(i, {"role": "User", "content": "", "collapsed": True, "ts": project_manager.now_ts()})
      imgui.same_line()
      if imgui.button("Del"):
       self.disc_entries.pop(i)
       imgui.pop_id()
       break # Break from inner loop, clipper will re-step
      imgui.same_line()
      if imgui.button("Branch"):
       self._branch_discussion(i)
      imgui.same_line()
      preview = entry["content"].replace("\n", " ")[:60]
      if len(entry["content"]) > 60: preview += "..."
      if not preview.strip() and entry.get("thinking_segments"):
       preview = entry["thinking_segments"][0]["content"].replace("\n", " ")[:60]
       if len(entry["thinking_segments"][0]["content"]) > 60: preview += "..."
      imgui.text_colored(vec4(160, 160, 150), preview)
     if not collapsed:
      thinking_segments = entry.get("thinking_segments", [])
      has_content = bool(entry.get("content", "").strip())
      is_standalone = bool(thinking_segments) and not has_content
      if thinking_segments:
       self._render_thinking_trace(thinking_segments, i, is_standalone=is_standalone)
      if read_mode:
       content = entry["content"]
       if content.strip():
        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 = chunk_match.group(1)
            path = chunk_match.group(2)
            chunk_content = chunk_match.group(3)
            if imgui.collapsing_header(f'Chunk {idx}: {path}'):
             if imgui.button(f'[Source]##rag_{i}_{idx}'):
              res = mcp_client.read_file(path)
              if res:
               self.text_viewer_title = path
               self.text_viewer_content = res
               self.text_viewer_type = Path(path).suffix.lstrip('.') if Path(path).suffix else 'text'
               self.show_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))
        is_nerv = theme.is_nerv_active()
        if not matches:
         if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
         markdown_helper.render(content, context_id=f'disc_{i}')
         if is_nerv: imgui.pop_style_color()
        else:
         imgui.begin_child(f"read_content_{i}", imgui.ImVec2(0, 150), True)
         if self.ui_word_wrap: imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
         last_idx = 0
         for m_idx, match in enumerate(matches):
          before = content[last_idx:match.start()]
          if before:
           if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
           markdown_helper.render(before, context_id=f'disc_{i}_b_{m_idx}')
           if is_nerv: imgui.pop_style_color()
          header_text = match.group(0).split("\n")[0].strip()
          path = match.group(2)
          code_block = match.group(4)
          if imgui.collapsing_header(header_text):
           if imgui.button(f"[Source]##{i}_{match.start()}"):
            res = mcp_client.read_file(path)
            if res:
             self.text_viewer_title = path
             self.text_viewer_content = res
             self.text_viewer_type = Path(path).suffix.lstrip('.') if Path(path).suffix else 'text'
             self.show_text_viewer = True
           if code_block:
            if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
            markdown_helper.render(code_block, context_id=f'disc_{i}_c_{m_idx}')
            if is_nerv: imgui.pop_style_color()
          last_idx = match.end()
         after = content[last_idx:]
         if after:
          if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
          markdown_helper.render(after, context_id=f'disc_{i}_a')
          if is_nerv: imgui.pop_style_color()
         if self.ui_word_wrap: imgui.pop_text_wrap_pos()
         imgui.end_child()
      else:
       if not is_standalone:
        ch, entry["content"] = imgui.input_text_multiline("##content", entry["content"], imgui.ImVec2(-1, 150))
     imgui.separator()
     imgui.pop_id()
   
   if self._scroll_disc_to_bottom:
    imgui.set_scroll_here_y(1.0)
    self._scroll_disc_to_bottom = False
   
   imgui.end_child()
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_discussion_panel")

 def _render_synthesis_panel(self) -> None:
  """Renders a panel for synthesizing multiple discussion takes."""
  imgui.text("Select takes to synthesize:")
  discussions = self.project.get('discussion', {}).get('discussions', {})
  if not hasattr(self, 'ui_synthesis_selected_takes'):
   self.ui_synthesis_selected_takes = {name: False for name in discussions}
  if not hasattr(self, 'ui_synthesis_prompt'):
   self.ui_synthesis_prompt = ""
  for name in discussions:
   _, self.ui_synthesis_selected_takes[name] = imgui.checkbox(name, self.ui_synthesis_selected_takes.get(name, False))
  imgui.spacing()
  imgui.text("Synthesis Prompt:")
  _, self.ui_synthesis_prompt = imgui.input_text_multiline("##synthesis_prompt", self.ui_synthesis_prompt, imgui.ImVec2(-1, 100))
  if imgui.button("Generate Synthesis"):
   selected = [name for name, sel in self.ui_synthesis_selected_takes.items() if sel]
   if len(selected) > 1:
    from src import synthesis_formatter
    discussions_dict = self.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"{self.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
     
    self._create_discussion(new_name)
    with self._disc_entries_lock:
     self.disc_entries.append({"role": "User", "content": prompt, "collapsed": False, "ts": project_manager.now_ts()})
    self._handle_generate_send()

 def _render_persona_selector_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_persona_selector_panel")
  imgui.text("Persona")
  if not hasattr(self, 'ui_active_persona'):
   self.ui_active_persona = ""
  personas = getattr(self.controller, 'personas', {})
  if imgui.begin_combo("##persona", self.ui_active_persona or "None"):
   if imgui.selectable("None", not self.ui_active_persona)[0]:
    self.ui_active_persona = ""
   for pname in sorted(personas.keys()):
    if not pname:
     continue
    if imgui.selectable(pname, pname == self.ui_active_persona)[0]:
     self.ui_active_persona = pname
     if pname in personas:
      persona = personas[pname]
      self._editing_persona_name = persona.name
      self._editing_persona_system_prompt = persona.system_prompt or ""
      self._editing_persona_tool_preset_id = persona.tool_preset or ""
      self._editing_persona_bias_profile_id = persona.bias_profile or ""
      self._editing_persona_context_preset_id = getattr(persona, 'context_preset', '') or ""
      self._editing_persona_aggregation_strategy = getattr(persona, 'aggregation_strategy', '') or ""
      import copy
      self._editing_persona_preferred_models_list = copy.deepcopy(persona.preferred_models) if persona.preferred_models else []
      self._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"):
        self.current_provider = first_model.get("provider")
       if first_model.get("model"):
        self.current_model = first_model.get("model")
       if first_model.get("temperature") is not None:
        ai_client.temperature = first_model.get("temperature")
        self.temperature = first_model.get("temperature")
       if first_model.get("max_output_tokens"):
        ai_client.max_output_tokens = first_model.get("max_output_tokens")
        self.max_tokens = first_model.get("max_output_tokens")
       if first_model.get("history_trunc_limit"):
        self.history_trunc_limit = first_model.get("history_trunc_limit")
      
      if persona.system_prompt:
       self.ui_project_system_prompt = persona.system_prompt
      if persona.tool_preset:
       self.ui_active_tool_preset = persona.tool_preset
       ai_client.set_tool_preset(persona.tool_preset)
      if persona.bias_profile:
       self.ui_active_bias_profile = persona.bias_profile
       ai_client.set_bias_profile(persona.bias_profile)
      if getattr(persona, 'context_preset', None):
       self.ui_active_context_preset = persona.context_preset
       self.load_context_preset(persona.context_preset)
   imgui.end_combo()
  imgui.same_line()
  if imgui.button("Manage Personas"):
   self.show_persona_editor_window = True
   if self.ui_active_persona and self.ui_active_persona in personas:
    persona = personas[self.ui_active_persona]
    self._editing_persona_name = persona.name
    self._editing_persona_system_prompt = persona.system_prompt or ""
    self._editing_persona_tool_preset_id = persona.tool_preset or ""
    self._editing_persona_bias_profile_id = persona.bias_profile or ""
    self._editing_persona_context_preset_id = getattr(persona, 'context_preset', '') or ""
    self._editing_persona_aggregation_strategy = getattr(persona, 'aggregation_strategy', '') or ""
    import copy
    self._editing_persona_preferred_models_list = copy.deepcopy(persona.preferred_models) if persona.preferred_models else []
    self._editing_persona_scope = self.controller.persona_manager.get_persona_scope(persona.name)
    self._editing_persona_is_new = False
   else:
    self._editing_persona_name = ""
    self._editing_persona_system_prompt = ""
    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 = [{
     "provider": self.current_provider,
     "model": self.current_model,
     "temperature": getattr(self, "temperature", 0.7),
     "max_output_tokens": getattr(self, "max_tokens", 4096),
     "history_trunc_limit": getattr(self, "history_trunc_limit", 900000)
    }]
    self._editing_persona_scope = "project"
    self._editing_persona_is_new = True
  imgui.separator()
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_persona_selector_panel")

 def _render_provider_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_provider_panel")
  imgui.text("Provider")
  if imgui.begin_combo("##prov", self.current_provider):
   for p in PROVIDERS:
    if imgui.selectable(p, p == self.current_provider)[0]:
     self.current_provider = p
   imgui.end_combo()
  imgui.separator()
  imgui.text("Model")
  if imgui.begin_list_box("##models", imgui.ImVec2(-1, 120)):
   for m in self.available_models:
    if imgui.selectable(m, m == self.current_model)[0]:
     self.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)
  _, self.temperature = imgui.slider_float("##slider", self.temperature, 0.0, 2.0, "%.2f")
  imgui.same_line()
  imgui.set_next_item_width(-1)
  _, self.temperature = imgui.input_float("Temp", self.temperature, 0.0, 0.0, "%.2f")
  imgui.pop_id()

  # Top-P
  imgui.push_id("top_p")
  imgui.set_next_item_width(imgui.get_content_region_avail().x * 0.6)
  _, self.top_p = imgui.slider_float("##slider", self.top_p, 0.0, 1.0, "%.2f")
  imgui.same_line()
  imgui.set_next_item_width(-1)
  _, self.top_p = imgui.input_float("Top-P", self.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)
  _, self.max_tokens = imgui.slider_int("##slider", self.max_tokens, 1, 32768)
  imgui.same_line()
  imgui.set_next_item_width(-1)
  _, self.max_tokens = imgui.input_int("MaxTok", self.max_tokens)
  imgui.pop_id()

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



  if self.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(); self._render_selectable_label("gemini_cli_sid", sid, width=200)
   if imgui.button("Reset CLI Session"):
    ai_client.reset_session()
   imgui.text("Binary Path")
   ch, self.ui_gemini_cli_path = imgui.input_text("##gcli_path", self.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:
     self.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 = self.ui_gemini_cli_path
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_provider_panel")

 def _render_token_budget_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_token_budget_panel")
  imgui.text_colored(C_LBL, 'Prompt Utilization')
  usage = self.session_usage
  total = usage["input_tokens"] + usage["output_tokens"]
  if total == 0 and usage.get("total_tokens", 0) > 0:
   total = usage["total_tokens"]
  self._render_selectable_label("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 self._gemini_cache_text:
   imgui.text_colored(C_SUB, self._gemini_cache_text)
  imgui.separator()

  if self._token_stats_dirty:
   self._token_stats_dirty = False
   # Offload to background thread via event queue
   self.controller.event_queue.put("refresh_api_metrics", {"md_content": self._last_stable_md or ""})
  stats = self._token_stats
  if not stats:
   imgui.text_disabled("Token stats unavailable")
   if self.perf_profiling_enabled: self.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 = imgui.ImVec4(0.2, 0.8, 0.2, 1.0)
  elif pct < 80.0:
   color = imgui.ImVec4(1.0, 0.8, 0.0, 1.0)
  else:
   color = imgui.ImVec4(1.0, 0.2, 0.2, 1.0)
  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(self, 'mma_tier_usage') and self.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 self.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); self._render_selectable_label(f"tier_{tier}", tier, width=-1)
     imgui.table_set_column_index(1); self._render_selectable_label(f"model_{tier}", model.split("-")[0], width=-1)
     imgui.table_set_column_index(2); self._render_selectable_label(f"tokens_{tier}", f"{tokens:,}", width=-1)
     imgui.table_set_column_index(3); self._render_selectable_label(f"cost_{tier}", f"${cost:.4f}", width=-1, color=imgui.ImVec4(0.2, 0.9, 0.2, 1))
    imgui.end_table()
    tier_total = sum(cost_tracker.estimate_cost(stats.get('model', ''), stats.get('input', 0), stats.get('output', 0)) for stats in self.mma_tier_usage.values())
    self._render_selectable_label("session_total_cost", f"Session Total: ${tier_total:.4f}", width=-1, color=imgui.ImVec4(0, 1, 0, 1))
  else:
   imgui.text_disabled("No MMA tier usage data")
  if stats.get("would_trim"):
   imgui.text_colored(imgui.ImVec4(1.0, 0.3, 0.0, 1.0), "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()
  cache_stats = getattr(self.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 self.perf_profiling_enabled: self.perf_monitor.end_component("_render_token_budget_panel")

 def _render_cache_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_cache_panel")
  if self.current_provider != "gemini":
   if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_cache_panel")
   return
  imgui.text_colored(C_LBL, 'Cache Analytics')
  stats = getattr(self.controller, '_cached_cache_stats', {})
  if not stats.get("cache_exists"):
   imgui.text_disabled("No active cache")
   if self.perf_profiling_enabled: self.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 = imgui.ImVec4(0.2, 0.8, 0.2, 1.0)
  if ttl_pct < 20:
   color = imgui.ImVec4(1.0, 0.2, 0.2, 1.0)
  elif ttl_pct < 50:
   color = imgui.ImVec4(1.0, 0.8, 0.0, 1.0)
  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"):
   self.controller.clear_cache()
   self._cache_cleared_timestamp = time.time()
  if hasattr(self, '_cache_cleared_timestamp') and time.time() - self._cache_cleared_timestamp < 5:
   imgui.text_colored(imgui.ImVec4(0.2, 1.0, 0.2, 1.0), "Cache cleared - will rebuild on next request")
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_cache_panel")

 def _render_tool_analytics_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_tool_analytics_panel")
  imgui.text_colored(C_LBL, 'Tool Usage')
  imgui.separator()
  now = time.time()
  if not hasattr(self, '_tool_stats_cache_time') or now - self._tool_stats_cache_time > 1.0:
   self._cached_tool_stats = getattr(self.controller, '_tool_stats', {})
  tool_stats = getattr(self.controller, '_cached_tool_stats', {})
  if not tool_stats:
   imgui.text_disabled("No tool usage data")
   if self.perf_profiling_enabled: self.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(imgui.ImVec4(1.0, 0.2, 0.2, 1.0), f"{fail_pct:.0f}%")
    else:
     imgui.text("0%")
   imgui.end_table()
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_tool_analytics_panel")

 def _render_session_insights_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_session_insights_panel")
  imgui.text_colored(C_LBL, 'Session Insights')
  imgui.separator()
  insights = self.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 self.perf_profiling_enabled: self.perf_monitor.end_component("_render_session_insights_panel")

 def _render_usage_analytics_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_usage_analytics_panel")
  self._render_token_budget_panel()
  imgui.separator()
  self._render_cache_panel()
  imgui.separator()
  self._render_tool_analytics_panel()
  imgui.separator()
  self._render_session_insights_panel()
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_usage_analytics_panel")

 def _render_message_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_message_panel")
 # LIVE indicator
  is_live = self.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 = imgui.ImVec4(0.39, 1.0, 0.39, alpha)
   if theme.is_nerv_active():
    c = vec4(80, 255, 80, alpha) # DATA_GREEN for LIVE in NERV
   imgui.text_colored(c, "LIVE")
   imgui.separator()
  ch, self.ui_ai_input = imgui.input_text_multiline("##ai_in", self.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:
   self.ui_ai_input = ""
  imgui.separator()
  is_busy = self.ai_status in ['sending...', 'streaming...']
  send_busy = False
  with self._send_thread_lock:
   if self.send_thread and self.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:
   self._handle_generate_send()
  imgui.end_disabled()
  imgui.same_line()
  if imgui.button("MD Only"):
   self._handle_md_only()
  imgui.same_line()
  if imgui.button("Inject File"):
   self.show_inject_modal = True
  imgui.same_line()
  if imgui.button("-> History"):
   if self.ui_ai_input:
    self.disc_entries.append({"role": "User", "content": self.ui_ai_input, "collapsed": False, "ts": project_manager.now_ts()})
  imgui.same_line()
  if imgui.button("Reset"):
   self._handle_reset_session()
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_message_panel")

 def _render_response_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_response_panel")
  if self._trigger_blink:
   self._trigger_blink = False
   self._is_blinking = True
   self._blink_start_time = time.time()
   try:
    imgui.set_window_focus("Response") # type: ignore[call-arg]
   except:
    pass
  is_blinking = False
  if self._is_blinking:
   elapsed = time.time() - self._blink_start_time
   if elapsed > 1.5:
    self._is_blinking = False
   else:
    is_blinking = True
    val = math.sin(elapsed * 8 * math.pi)
    alpha = 50/255 if val > 0 else 0
    imgui.push_style_color(imgui.Col_.frame_bg, vec4(0, 255, 0, alpha))
    imgui.push_style_color(imgui.Col_.child_bg, vec4(0, 255, 0, alpha))
    # --- Always Render Content ---
  
  imgui.begin_child("response_scroll_area", imgui.ImVec2(0, -40), True)
  is_nerv = theme.is_nerv_active()
  if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
  
  segments, parsed_response = thinking_parser.parse_thinking_trace(self.ai_response)
  if segments:
   self._render_thinking_trace([{"content": s.content, "marker": s.marker} for s in segments], 9999)
  
  markdown_helper.render(parsed_response, context_id="response")
  
  if is_nerv: imgui.pop_style_color()
  imgui.end_child()
  
  imgui.separator()
  if imgui.button("-> History"):
   if self.ai_response:
    segments, response = thinking_parser.parse_thinking_trace(self.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]
    self.disc_entries.append(entry)
  if is_blinking:
   imgui.pop_style_color(2)
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_response_panel")

 def _render_external_tools_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_external_tools_panel")
  if imgui.button("Refresh External MCPs"):
   self.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 self.perf_profiling_enabled: self.perf_monitor.end_component("_render_external_tools_panel")

 def _render_comms_history_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_comms_history_panel")
  st_col = vec4(200, 220, 160)
  if theme.is_nerv_active():
   st_col = vec4(80, 255, 80) # DATA_GREEN for status in NERV
  imgui.text_colored(st_col, f"Status: {self.ai_status}")
  imgui.same_line()
  if imgui.button("Clear##comms"):
   ai_client.clear_comms_log()
   self._comms_log.clear()
   self._comms_log_dirty = True
  if self.is_viewing_prior_session:
   imgui.same_line()
   if imgui.button("Exit Prior Session"):
    self.controller.cb_exit_prior_session()
    self._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()
  
  # Use tinted background for prior session
  if self.is_viewing_prior_session:
   imgui.push_style_color(imgui.Col_.child_bg, vec4(40, 30, 20))
  
  imgui.begin_child("comms_scroll", imgui.ImVec2(0, 0), False, imgui.WindowFlags_.horizontal_scrollbar)
  
  log_to_render = self._comms_log_cache
  
  clipper = imgui.ListClipper()
  clipper.begin(len(log_to_render))
  while clipper.step():
   for i in range(clipper.display_start, clipper.display_end):
    entry = log_to_render[i]
    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(vec4(160, 160, 160), 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(vec4(255, 120, 120), f"[{ticket_id}]")
    imgui.same_line()
    d_col = DIR_COLORS.get(direction, C_VAL)
    imgui.text_colored(d_col, direction)
    imgui.same_line()
    k_col = KIND_COLORS.get(kind, C_VAL)
    imgui.text_colored(k_col, 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}")
     self._render_heavy_text("message", payload.get("message", ""), idx_str)
     if payload.get("system"):
      self._render_heavy_text("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:
      self._render_thinking_trace([{"content": s.content, "marker": s.marker} for s in segments], i, is_standalone=not bool(parsed_response.strip()))
     if parsed_response:
      self._render_heavy_text("text", parsed_response, idx_str)
     
     tcs = payload.get("tool_calls", [])
     if tcs:
      self._render_heavy_text("tool_calls", json.dumps(tcs, indent=1), idx_str)
    elif kind == "tool_call":
     self._render_heavy_text(payload.get("name", "call"), payload.get("script") or json.dumps(payload.get("args", {}), indent=1), idx_str)
    elif kind == "tool_result":
     self._render_heavy_text(payload.get("name", "result"), payload.get("output", ""), idx_str)
    else:
     self._render_heavy_text("data", str(payload), idx_str)

    imgui.separator()
    imgui.pop_id()

  if self._scroll_comms_to_bottom:
   imgui.set_scroll_here_y(1.0)
   self._scroll_comms_to_bottom = False
   
  imgui.end_child()
  if self.is_viewing_prior_session:
   imgui.pop_style_color()
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_comms_history_panel")

 def _render_tool_calls_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_tool_calls_panel")
  imgui.text("Tool call history")
  imgui.same_line()
  if imgui.button("Clear##tc"):
   self._tool_log.clear()
   self._tool_log_dirty = True
  imgui.separator()
  
  log_to_render = self._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()
   
   clipper = imgui.ListClipper()
   clipper.begin(len(log_to_render))
   while clipper.step():
    for i in range(clipper.display_start, clipper.display_end):
     entry = log_to_render[i]
     imgui.table_next_row()
     
     imgui.table_next_column()
     imgui.text_colored(C_LBL, f"#{i+1}")
     
     imgui.table_next_column()
     imgui.text_colored(C_SUB, f"[{entry.get('source_tier', 'main')}]")
     
     imgui.table_next_column()
     script = entry.get("script", "")
     res = entry.get("result", "")
     # Use a clear, formatted combined view for the detail window
     combined = f"**COMMAND:**\n```powershell\n{script}\n```\n\n---\n**OUTPUT:**\n```text\n{res}\n```"
     
     script_preview = script.replace("\n", " ")[:150]
     if len(script) > 150: script_preview += "..."
     self._render_selectable_label(f'tc_script_{i}', script_preview, width=-1)
     if imgui.is_item_clicked():
      self.text_viewer_title = f"Tool Call #{i+1} Details"
      self.text_viewer_content = combined
      self.text_viewer_type = 'markdown'
      self.show_text_viewer = True
     
     imgui.table_next_column()
     res_preview = res.replace("\n", " ")[:30]
     if len(res) > 30: res_preview += "..."
     self._render_selectable_label(f'tc_res_{i}', res_preview, width=-1)
     if imgui.is_item_clicked():
      self.text_viewer_title = f"Tool Call #{i+1} Details"
      self.text_viewer_content = combined
      self.text_viewer_type = 'markdown'
      self.show_text_viewer = True
      
   imgui.end_table()

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

 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 _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 _render_ticket_queue(self) -> None:
  imgui.text("Ticket Queue Management")
  if not self.active_track:
   imgui.text_disabled("No active track.")
   return

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

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

  # Bulk Actions
  if imgui.button("Bulk Execute"):
   self.bulk_execute()
  imgui.same_line()
  if imgui.button("Bulk Skip"):
   self.bulk_skip()
  imgui.same_line()
  if imgui.button("Bulk Block"):
   self.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(self.active_tickets):
    tid = str(t.get('id', ''))
    imgui.table_next_row()

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

    # ID
    imgui.table_next_column()
    is_selected = (tid == self.ui_selected_ticket_id)
    opened, _ = imgui.selectable(f"{tid}##drag_{tid}", is_selected)
    if opened:
     self.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)
      self._reorder_ticket(src_idx, i)
     imgui.end_drag_drop_target()

    # Priority

    imgui.table_next_column()
    prio = t.get('priority', 'medium')
    p_col = vec4(180, 180, 180) # gray
    if prio == 'high': p_col = vec4(255, 100, 100) # red
    elif prio == 'medium': p_col = vec4(255, 255, 100) # 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
       self._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
      self._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
       self._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(imgui.ImVec4(1, 0.5, 0, 1), 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}"):
      self._cb_kill_ticket(tid)
    elif status == 'todo':
     if imgui.button(f"Block##{tid}"):
      self._cb_block_ticket(tid)
    elif status == 'blocked' and t.get('manual_block', False):
     if imgui.button(f"Unblock##{tid}"):
      self._cb_unblock_ticket(tid)

   imgui.end_table()

 def _render_mma_dashboard(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
  
  # Focus Agent dropdown
  imgui.text("Focus Agent:")
  imgui.same_line()
  focus_label = self.ui_focus_agent or "All"
  if imgui.begin_combo("##focus_agent", focus_label, imgui.ComboFlags_.width_fit_preview):
   if imgui.selectable("All", self.ui_focus_agent is None)[0]:
    self.ui_focus_agent = None
   for tier in ["Tier 2", "Tier 3", "Tier 4"]:
    if imgui.selectable(tier, self.ui_focus_agent == tier)[0]:
     self.ui_focus_agent = tier
   imgui.end_combo()
  imgui.same_line()
  if self.ui_focus_agent:
   if imgui.button("x##clear_focus"):
    self.ui_focus_agent = None
  imgui.separator()

  is_nerv = theme.is_nerv_active()
  if self.is_viewing_prior_session:
   c = vec4(255, 200, 100)
   if is_nerv: c = vec4(255, 152, 48) # NERV_ORANGE
   imgui.text_colored(c, "HISTORICAL VIEW - READ ONLY")
   if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard")
   return
  # Task 5.3: Dense Summary Line
  track_name = self.active_track.description if self.active_track else "None"
  if getattr(self, "ui_project_execution_mode", "native") == "beads":
   track_name = "Beads Graph"
  track_stats = {"percentage": 0.0, "completed": 0, "total": 0, "in_progress": 0, "blocked": 0, "todo": 0}
  if self.active_track:
   track_stats = project_manager.calculate_track_progress(self.active_track.tickets)
  elif self.active_tickets:
   track_stats = project_manager.calculate_track_progress(self.active_tickets)
  total_cost = 0.0
  for usage in self.mma_tier_usage.values():
   model = usage.get('model', 'unknown')
   in_t = usage.get('input', 0)
   out_t = usage.get('output', 0)
   total_cost += cost_tracker.estimate_cost(model, in_t, out_t)

  imgui.text("Track:")
  imgui.same_line()
  imgui.text_colored(C_VAL, track_name)
  imgui.same_line()
  imgui.text(" | Status:")
  imgui.same_line()
  if self.mma_status == "paused":
   c = imgui.ImVec4(1, 0.5, 0, 1)
   if is_nerv: c = vec4(255, 152, 48)
   imgui.text_colored(c, "PIPELINE PAUSED")
   imgui.same_line()
  status_col = imgui.ImVec4(1, 1, 1, 1)
  if self.mma_status == "idle": status_col = imgui.ImVec4(0.7, 0.7, 0.7, 1)
  elif self.mma_status == "running": status_col = imgui.ImVec4(1, 1, 0, 1)
  elif self.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1)
  elif self.mma_status == "error": status_col = imgui.ImVec4(1, 0, 0, 1)
  elif self.mma_status == "paused": status_col = imgui.ImVec4(1, 0.5, 0, 1)
  
  if is_nerv:
   if self.mma_status == "running": status_col = vec4(80, 255, 80) # DATA_GREEN
   elif self.mma_status == "error": status_col = vec4(255, 72, 64) # ALERT_RED
  
  imgui.text_colored(status_col, self.mma_status.upper())
  imgui.same_line()
  imgui.text(" | Cost:")
  imgui.same_line()
  imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), f"${total_cost:,.4f}")

  # Progress Bar
  perc = track_stats["percentage"] / 100.0
  p_color = imgui.ImVec2(0.0, 1.0) # WAIT WRONG TYPE

  p_color = imgui.ImVec4(0.0, 1.0, 0.0, 1.0)
  if track_stats["percentage"] < 33:
   p_color = imgui.ImVec4(1.0, 0.0, 0.0, 1.0)
  elif track_stats["percentage"] < 66:
   p_color = imgui.ImVec4(1.0, 1.0, 0.0, 1.0)

  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()

  # Detailed breakdown
  if imgui.begin_table("ticket_stats_breakdown", 4):
   imgui.table_next_column()
   imgui.text_colored(C_LBL, "Completed:")
   imgui.same_line()
   imgui.text_colored(C_VAL, str(track_stats["completed"]))

   imgui.table_next_column()
   imgui.text_colored(C_LBL, "In Progress:")
   imgui.same_line()
   imgui.text_colored(C_VAL, str(track_stats["in_progress"]))

   imgui.table_next_column()
   imgui.text_colored(C_LBL, "Blocked:")
   imgui.same_line()
   imgui.text_colored(C_VAL, str(track_stats["blocked"]))

   imgui.table_next_column()
   imgui.text_colored(C_LBL, "Todo:")
   imgui.same_line()
   imgui.text_colored(C_VAL, str(track_stats["todo"]))

   imgui.end_table()

  if self.active_track:
   remaining = track_stats["total"] - track_stats["completed"]
   eta_mins = (self._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)")

  imgui.separator()
  imgui.text_colored(C_LBL, 'Epic Planning (Tier 1)')
  _, self.ui_epic_input = imgui.input_text_multiline('##epic_input', self.ui_epic_input, imgui.ImVec2(-1, 80))
  if imgui.button('Plan Epic (Tier 1)', imgui.ImVec2(-1, 0)):
   self._cb_plan_epic()

  imgui.separator()
  # 0. Conductor Setup
  if imgui.collapsing_header("Conductor Setup"):
   if imgui.button("Run Setup Scan"):
    self._cb_run_conductor_setup()
   if self.ui_conductor_setup_summary:
    imgui.input_text_multiline("##setup_summary", self.ui_conductor_setup_summary, imgui.ImVec2(-1, 120), imgui.InputTextFlags_.read_only)
  imgui.separator()
  # 1. Track Browser
  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 self.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()
    if status == "new":
     imgui.text_colored(imgui.ImVec4(0.7, 0.7, 0.7, 1.0), "NEW")
    elif status == "active":
     c = imgui.ImVec4(1.0, 1.0, 0.0, 1.0)
     if is_nerv: c = vec4(80, 255, 80)
     imgui.text_colored(c, "ACTIVE")
    elif status == "done":
     imgui.text_colored(imgui.ImVec4(0.0, 1.0, 0.0, 1.0), "DONE")
    elif status == "blocked":
     imgui.text_colored(imgui.ImVec4(1.0, 0.0, 0.0, 1.0), "BLOCKED")
    else:
     imgui.text(status)
    imgui.table_next_column()
    progress = track.get("progress", 0.0)
    if progress < 0.33:
     p_color = imgui.ImVec4(1.0, 0.0, 0.0, 1.0)
    elif progress < 0.66:
     p_color = imgui.ImVec4(1.0, 1.0, 0.0, 1.0)
    else:
     p_color = imgui.ImVec4(0.0, 1.0, 0.0, 1.0)
    imgui.push_style_color(imgui.Col_.plot_histogram, p_color)
    imgui.progress_bar(progress, imgui.ImVec2(-1, 0), f"{int(progress*100)}%")
    imgui.pop_style_color()
    imgui.table_next_column()
    if imgui.button(f"Load##{track.get('id')}"):
     self._cb_load_track(str(track.get("id") or ""))
   imgui.end_table()
   # 1b. New Track Form
  imgui.text("Create New Track")
  changed_n, self.ui_new_track_name = imgui.input_text("Name##new_track", self.ui_new_track_name)
  changed_d, self.ui_new_track_desc = imgui.input_text_multiline("Description##new_track", self.ui_new_track_desc, imgui.ImVec2(-1, 60))
  imgui.text("Type:")
  imgui.same_line()
  if imgui.begin_combo("##track_type", self.ui_new_track_type):
   for ttype in ["feature", "chore", "fix"]:
    if imgui.selectable(ttype, self.ui_new_track_type == ttype)[0]:
     self.ui_new_track_type = ttype
   imgui.end_combo()
  if imgui.button("Create Track"):
   self._cb_create_track(self.ui_new_track_name, self.ui_new_track_desc, self.ui_new_track_type)
   self.ui_new_track_name = ""
   self.ui_new_track_desc = ""
  imgui.separator()
  # 2. Global Controls
  changed, self.mma_step_mode = imgui.checkbox("Step Mode (HITL)", self.mma_step_mode)
  if changed:
  # We could push an event here if the engine needs to know immediately
   pass
  imgui.same_line()
  imgui.text(f"Status: {self.mma_status.upper()}")
  if self.controller and hasattr(self.controller, 'engine') and self.controller.engine and hasattr(self.controller.engine, '_pause_event'):
   imgui.same_line()
   is_paused = self.controller.engine._pause_event.is_set()
   label = "Resume" if is_paused else "Pause"
   if imgui.button(label):
    if is_paused:
     self.controller.engine.resume()
    else:
     self.controller.engine.pause()
  if self.active_tier:
   imgui.same_line()
   imgui.text_colored(C_VAL, f"|  Active: {self.active_tier}")
   # Approval pending indicator
  any_pending = (
   self._pending_mma_spawn is not None or
   self._pending_mma_approval is not None or
   self._pending_ask_dialog
  )
  if any_pending:
   alpha = abs(math.sin(time.time() * 5))
   imgui.same_line()
   c = imgui.ImVec4(1.0, 0.3, 0.3, alpha)
   if is_nerv: c = vec4(255, 72, 64, alpha) # ALERT_RED
   imgui.text_colored(c, "  APPROVAL PENDING")
   imgui.same_line()
   if imgui.button("Go to Approval"):
    pass  # scroll/focus handled by existing dialog rendering
  imgui.separator()
  # 3. Token Usage Table
  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()
   usage = self.mma_tier_usage
   total_cost = 0.0
   for tier, stats in 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}")
    # Total Row
   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()
  imgui.separator()
  # 3b. Tier Model Config
  if imgui.collapsing_header("Tier Model Config"):
   for tier in self.mma_tier_usage.keys():
    imgui.text(f"{tier}:")
    imgui.same_line()
    current_model = self.mma_tier_usage[tier].get("model", "unknown")
    current_provider = self.mma_tier_usage[tier].get("provider", "gemini")
    
    imgui.push_id(f"tier_cfg_{tier}")
    
    # Provider selection
    imgui.push_item_width(80)
    if imgui.begin_combo("##prov", current_provider):
     for p in PROVIDERS:
      if imgui.selectable(p, p == current_provider)[0]:
       self.mma_tier_usage[tier]["provider"] = p
       # Reset model to default for provider
       models_list = self.controller.all_available_models.get(p, [])
       if models_list:
        self.mma_tier_usage[tier]["model"] = models_list[0]
     imgui.end_combo()
    imgui.pop_item_width()
    
    imgui.same_line()
    
    # Model selection
    imgui.push_item_width(150)
    models_list = self.controller.all_available_models.get(current_provider, [])
    if imgui.begin_combo("##model", current_model):
     for model in models_list:
      if imgui.selectable(model, current_model == model)[0]:
       self.mma_tier_usage[tier]["model"] = model
     imgui.end_combo()
    imgui.pop_item_width()

    imgui.same_line()

    # Tool Preset selection
    imgui.push_item_width(-1)
    current_preset = self.mma_tier_usage[tier].get("tool_preset") or "None"
    preset_names = ["None"] + sorted(self.controller.tool_presets.keys())
    if imgui.begin_combo("##preset", current_preset):
     for preset_name in preset_names:
      if imgui.selectable(preset_name, current_preset == preset_name)[0]:
       self.mma_tier_usage[tier]["tool_preset"] = None if preset_name == "None" else preset_name
     imgui.end_combo()
    imgui.pop_item_width()
    
    imgui.same_line()

    # Persona selection
    imgui.push_item_width(150)
    current_persona = self.mma_tier_usage[tier].get("persona") or "None"
    personas = getattr(self.controller, 'personas', {})
    persona_options = ["None"] + sorted(personas.keys())
    if imgui.begin_combo("##persona", current_persona):
     for persona_name in persona_options:
      if imgui.selectable(persona_name, current_persona == persona_name)[0]:
       self.mma_tier_usage[tier]["persona"] = None if persona_name == "None" else persona_name
     imgui.end_combo()
    imgui.pop_item_width()
    
    imgui.pop_id()
  imgui.separator()
  self._render_ticket_queue()
  imgui.separator()
  ch, self.ui_separate_task_dag = imgui.checkbox("Pop Out Task DAG", self.ui_separate_task_dag)
  if ch:
   self.show_windows["Task DAG"] = self.ui_separate_task_dag
  
  if not self.ui_separate_task_dag:
   self._render_task_dag_panel()

  # 6. Edit Selected Ticket
  if self.ui_selected_ticket_id:
   imgui.separator()
   imgui.text_colored(C_VAL, f"Editing: {self.ui_selected_ticket_id}")
   ticket = next((t for t in self.active_tickets if str(t.get('id', '')) == self.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
       self._push_mma_state_update()
     imgui.end_combo()
    imgui.text(f"Target: {ticket.get('target_file', '')}")
    deps = ticket.get('depends_on', [])
    imgui.text(f"Depends on: {', '.join(deps)}")
    personas = getattr(self.controller, 'personas', {})
    current_persona = ticket.get('persona_id', '')
    imgui.text("Persona Override:")
    imgui.same_line()
    persona_options = ["None"] + sorted(personas.keys())
    current_idx = persona_options.index(current_persona) + 1 if current_persona in persona_options else 0
    _, current_idx = imgui.combo(f"##ticket_persona_{ticket.get('id')}", current_idx, persona_options)
    if current_idx > 0:
     ticket['persona_id'] = None if persona_options[current_idx] == "None" else persona_options[current_idx]
    else:
     ticket['persona_id'] = ""
    if imgui.button(f"Mark Complete##{self.ui_selected_ticket_id}"):
     ticket['status'] = 'done'
     self._push_mma_state_update()
    imgui.same_line()
    if imgui.button(f"Delete##{self.ui_selected_ticket_id}"):
     self.active_tickets = [t for t in self.active_tickets if str(t.get('id', '')) != self.ui_selected_ticket_id]
     self.ui_selected_ticket_id = None
     self._push_mma_state_update()

  imgui.separator()
  imgui.text("Agent Streams")
  if imgui.begin_tab_bar("mma_streams_tabs"):
   # Tier 1
   if imgui.begin_tab_item("Tier 1")[0]:
    ch, self.ui_separate_tier1 = imgui.checkbox("Pop Out Tier 1", self.ui_separate_tier1)
    if ch: self.show_windows["Tier 1: Strategy"] = self.ui_separate_tier1
    if not self.ui_separate_tier1:
     self._render_tier_stream_panel("Tier 1", "Tier 1")
    else:
     imgui.text_disabled("Tier 1 stream is detached.")
    imgui.end_tab_item()
   # Tier 2
   if imgui.begin_tab_item("Tier 2")[0]:
    ch, self.ui_separate_tier2 = imgui.checkbox("Pop Out Tier 2", self.ui_separate_tier2)
    if ch: self.show_windows["Tier 2: Tech Lead"] = self.ui_separate_tier2
    if not self.ui_separate_tier2:
     self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)")
    else:
     imgui.text_disabled("Tier 2 stream is detached.")
    imgui.end_tab_item()
   # Tier 3
   if imgui.begin_tab_item("Tier 3")[0]:
    ch, self.ui_separate_tier3 = imgui.checkbox("Pop Out Tier 3", self.ui_separate_tier3)
    if ch: self.show_windows["Tier 3: Workers"] = self.ui_separate_tier3
    if not self.ui_separate_tier3:
     self._render_tier_stream_panel("Tier 3", None)
    else:
     imgui.text_disabled("Tier 3 stream is detached.")
    imgui.end_tab_item()
   # Tier 4
   if imgui.begin_tab_item("Tier 4")[0]:
    ch, self.ui_separate_tier4 = imgui.checkbox("Pop Out Tier 4", self.ui_separate_tier4)
    if ch: self.show_windows["Tier 4: QA"] = self.ui_separate_tier4
    if not self.ui_separate_tier4:
     self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)")
    else:
     imgui.text_disabled("Tier 4 stream is detached.")
    imgui.end_tab_item()
   if getattr(self, "ui_project_execution_mode", "native") == "beads":
    if imgui.begin_tab_item("Beads")[0]:
     self._render_beads_tab()
     imgui.end_tab_item()
   imgui.end_tab_bar()

 def _render_task_dag_panel(self) -> None:
  # 4. Task DAG Visualizer
  imgui.text("Task DAG")
  if (self.active_track or self.active_tickets) and self.node_editor_ctx:
   ed.set_current_editor(self.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 self.active_tickets:
      if abs(hash(str(t.get('id', '')))) == node_val:
       self.ui_selected_ticket_id = str(t.get('id', ''))
       break
     break
   for t in self.active_tickets:
    tid = str(t.get('id', '??'))
    int_id = abs(hash(tid))
    ed.begin_node(ed.NodeId(int_id))
    if getattr(self, "ui_project_execution_mode", "native") == "beads":
     imgui.text_colored(imgui.ImVec4(0, 1, 1, 1), "[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 = imgui.ImVec4(1, 0.4, 0.4, 1)
    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 self.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 self.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 self.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)
          self._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 self.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]
        self._push_mma_state_update()
        break
    ed.end_delete()
   # Validate DAG after any changes
   try:
    from src.dag_engine import TrackDAG
    ticket_dicts = [{'id': str(t.get('id', '')), 'depends_on': t.get('depends_on', [])} for t in self.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"):
    self._show_add_ticket_form = not self._show_add_ticket_form
    if self._show_add_ticket_form:
    # Default Ticket ID
     max_id = 0
     for t in self.active_tickets:
      tid = t.get('id', '')
      if tid.startswith('T-'):
       try: max_id = max(max_id, int(tid[2:]))
       except: pass
     self.ui_new_ticket_id = f"T-{max_id + 1:03d}"
     self.ui_new_ticket_desc = ""
     self.ui_new_ticket_target = ""
     self.ui_new_ticket_deps = ""
   if self._show_add_ticket_form:
    imgui.begin_child("add_ticket_form", imgui.ImVec2(-1, 220), True)
    imgui.text_colored(C_VAL, "New Ticket Details")
    _, self.ui_new_ticket_id = imgui.input_text("ID##new_ticket", self.ui_new_ticket_id)
    _, self.ui_new_ticket_desc = imgui.input_text_multiline("Description##new_ticket", self.ui_new_ticket_desc, imgui.ImVec2(-1, 60))
    _, self.ui_new_ticket_target = imgui.input_text("Target File##new_ticket", self.ui_new_ticket_target)
    _, self.ui_new_ticket_deps = imgui.input_text("Depends On (IDs, comma-separated)##new_ticket", self.ui_new_ticket_deps)
    imgui.text("Priority:")
    imgui.same_line()
    if imgui.begin_combo("##new_prio", self.ui_new_ticket_priority):
     for p_opt in ['high', 'medium', 'low']:
      if imgui.selectable(p_opt, p_opt == self.ui_new_ticket_priority)[0]:
       self.ui_new_ticket_priority = p_opt
     imgui.end_combo()
    if imgui.button("Create"):
     new_ticket = {
      "id": self.ui_new_ticket_id,
      "description": self.ui_new_ticket_desc,
      "status": "todo",
      "priority": self.ui_new_ticket_priority,
      "assigned_to": "tier3-worker",
      "target_file": self.ui_new_ticket_target,
      "depends_on": [d.strip() for d in self.ui_new_ticket_deps.split(",") if d.strip()]
     }
     self.active_tickets.append(new_ticket)
     self._show_add_ticket_form = False
     self._push_mma_state_update()
    imgui.same_line()
    if imgui.button("Cancel"):
     self._show_add_ticket_form = False
    imgui.end_child()
  else:
   imgui.text_disabled("No active MMA track or tickets.")

 def _render_beads_tab(self) -> None:
  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(imgui.ImVec4(1, 0.5, 0, 1), f"Warning: {', '.join(missing)} not found in PATH.")
   imgui.text_wrapped("Beads mode requires Dolt and the Beads (bd) CLI tools.")

  if getattr(self, "ui_project_execution_mode", "native") == "beads":
   try:
    from src import beads_client
    bclient = beads_client.BeadsClient(Path(self.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(imgui.ImVec4(1, 0, 0, 1), f"Error loading beads: {e}")

 def _render_tier_stream_panel(self, tier_key: str, stream_key: str | None) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_tier_stream_panel")
  if self.is_viewing_prior_session:
   imgui.text_colored(vec4(255, 200, 100), "HISTORICAL VIEW - READ ONLY")
   if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_tier_stream_panel")
   return
  if stream_key is not None:
   content = self.mma_streams.get(stream_key, "")
   imgui.begin_child(f"##stream_content_{tier_key}", imgui.ImVec2(-1, -1))
   self._render_selectable_label(f'stream_{tier_key}', content, width=-1, multiline=True, height=0)
   try:
    if len(content) != self._tier_stream_last_len.get(stream_key, -1):
     imgui.set_scroll_here_y(1.0)
    self._tier_stream_last_len[stream_key] = len(content)
   except (TypeError, AttributeError):
    pass
   imgui.end_child()
  else:
   tier3_keys = [k for k in self.mma_streams if "Tier 3" in k]
   if not tier3_keys:
    imgui.text_disabled("No worker output yet.")
   else:
    worker_status = getattr(self, '_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(imgui.ImVec4(1, 1, 0, 1), f"{ticket_id} [{status}]")
     elif status == "completed":
      imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), f"{ticket_id} [{status}]")
     elif status == "failed":
      imgui.text_colored(imgui.ImVec4(1, 0, 0, 1), f"{ticket_id} [{status}]")
     else:
      imgui.text(f"{ticket_id} [{status}]")
     imgui.begin_child(f"##tier3_{ticket_id}_scroll", imgui.ImVec2(-1, 150), True)
     self._render_selectable_label(f'stream_t3_{ticket_id}', self.mma_streams[key], width=-1, multiline=True, height=0)
     try:
      if len(self.mma_streams[key]) != self._tier_stream_last_len.get(key, -1):
       imgui.set_scroll_here_y(1.0)
      self._tier_stream_last_len[key] = len(self.mma_streams[key])
     except (TypeError, AttributeError):
      pass
     imgui.end_child()
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_tier_stream_panel")

 def _render_system_prompts_panel(self) -> None:
  imgui.text("Global System Prompt (all projects)")
  preset_names = sorted(self.controller.presets.keys())
  current_global = self.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]:
     self.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"):
   self.show_preset_manager_window = True
  imgui.set_item_tooltip("Open preset management modal")
  ch, self.ui_global_system_prompt = imgui.input_text_multiline("##gsp", self.ui_global_system_prompt, imgui.ImVec2(-1, 100))
  imgui.separator()
  _, self.ui_use_default_base_prompt = imgui.checkbox("Use Default Base System Prompt", self.ui_use_default_base_prompt)
  imgui.same_line()
  if imgui.button("Reset to Default##btn_reset_base_prompt"):
   self.controller._cb_reset_base_prompt()
  imgui.same_line()
  if imgui.button("Show Diff##btn_show_base_prompt_diff"):
   self.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 self.ui_use_default_base_prompt else 0
  if imgui.collapsing_header("Base System Prompt (foundational instructions)", header_flags):
   if self.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, self.ui_base_system_prompt = imgui.input_text_multiline("##base_prompt", self.ui_base_system_prompt, imgui.ImVec2(-1, 150))
    imgui.text_disabled(f"Characters: {len(self.ui_base_system_prompt)}")
  imgui.separator()
  imgui.text("Project System Prompt")
  current_project = self.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]:
     self.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"):
   self.show_preset_manager_window = True
  imgui.set_item_tooltip("Open preset management modal")
  ch, self.ui_project_system_prompt = imgui.input_text_multiline("##psp", self.ui_project_system_prompt, imgui.ImVec2(-1, 100))
 def _render_rag_panel(self) -> None:
  conf = self.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']
  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']
  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: {self.controller.rag_status}")
  
  if imgui.button("Rebuild Index"):
   self.controller.event_queue.put('click', 'btn_rebuild_rag_index')

 def _render_agent_tools_panel(self) -> None:
  if imgui.collapsing_header("Active Tool Presets & Biases", imgui.TreeNodeFlags_.default_open):
   imgui.text("Tool Preset")
   presets = self.controller.tool_presets
   preset_names = [""] + sorted(list(presets.keys()))
   
   active = getattr(self, "ui_active_tool_preset", "")
   if active is None: active = ""
   try:
    idx = preset_names.index(active)
   except ValueError:
    idx = 0
   
   ch, new_idx = imgui.combo("##tool_preset_select", idx, preset_names)
   if ch:
    self.ui_active_tool_preset = preset_names[new_idx]
   
   imgui.same_line()
   if imgui.button("Manage Presets##tools"):
    self.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(self, 'ui_active_bias_profile', "") or "None"):
    if imgui.selectable("None", not getattr(self, 'ui_active_bias_profile', ""))[0]:
     self.ui_active_bias_profile = ""
     from src import ai_client
     ai_client.set_bias_profile(None)
    for bname in sorted(self.controller.bias_profiles.keys()):
     if not bname:
      continue
     if imgui.selectable(bname, bname == getattr(self, 'ui_active_bias_profile', ""))[0]:
      self.ui_active_bias_profile = bname
      from src import ai_client
      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()))
   try:
    f_idx = cat_options.index(self.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:
    self.ui_tool_filter_category = cat_options[next_f_idx]

   imgui.dummy(imgui.ImVec2(0, 8))
   active_name = self.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 self.ui_tool_filter_category != "All" and self.ui_tool_filter_category != cat_name:
      continue
     if imgui.tree_node(cat_name):
      for tool in tools:
       if tool.weight >= 5:
        imgui.text_colored(vec4(255, 100, 100), "[HIGH]")
        imgui.same_line()
       elif tool.weight == 4:
        imgui.text_colored(vec4(255, 255, 100), "[PREF]")
        imgui.same_line()
       elif tool.weight == 2:
        imgui.text_colored(vec4(255, 150, 50), "[REJECT]")
        imgui.same_line()
       elif tool.weight <= 1:
        imgui.text_colored(vec4(180, 180, 180), "[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_theme_panel(self) -> None:
  if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_theme_panel")
  exp, opened = imgui.begin("Theme", self.show_windows["Theme"])
  self.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)
      self._flush_to_config()
      models.save_config(self.config)
    imgui.end_combo()

   imgui.separator()
   ch1, self.ui_separate_message_panel = imgui.checkbox("Separate Message Panel", self.ui_separate_message_panel)
   ch2, self.ui_separate_response_panel = imgui.checkbox("Separate Response Panel", self.ui_separate_response_panel)
   ch3, self.ui_separate_tool_calls_panel = imgui.checkbox("Separate Tool Calls Panel", self.ui_separate_tool_calls_panel)
   if ch1: self.show_windows["Message"] = self.ui_separate_message_panel
   if ch2: self.show_windows["Response"] = self.ui_separate_response_panel
   if ch3: self.show_windows["Tool Calls"] = self.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)"):
    self._flush_to_config()
    models.save_config(self.config)
    self.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)
    self._flush_to_config()
    models.save_config(self.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)
    self._flush_to_config()
    models.save_config(self.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 = self.config.setdefault("gui", {})
    gui_cfg["bg_shader_enabled"] = bg.enabled
    self._flush_to_config()
    models.save_config(self.config)
   
   ch_crt, self.ui_crt_filter = imgui.checkbox("CRT Filter", self.ui_crt_filter)
   if ch_crt:
    gui_cfg = self.config.setdefault("gui", {})
    gui_cfg["crt_filter_enabled"] = self.ui_crt_filter
    self._flush_to_config()
    models.save_config(self.config)
    self._flush_to_config()
    models.save_config(self.config)
  imgui.end()
  if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_theme_panel")

 def _load_fonts(self) -> None:
  # 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:
   p = Path(font_path)
   if p.is_absolute():
    try:
     if p.is_relative_to(assets_dir):
      font_path = str(p.relative_to(assets_dir)).replace("\\", "/")
    except (ValueError, AttributeError):
     pass # Fallback to original font_path if relative_to fails or on old Python

   # Just try loading it directly; hello_imgui will look in the assets folder
   try:
    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

  try:
   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 _post_init(self) -> None:
  theme.apply_current()

 def run(self) -> None:
  """Initializes the ImGui runner and starts the main application 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:
   theme.load_from_config(self.config)
   self.runner_params = hello_imgui.RunnerParams()
   self.runner_params.app_window_params.window_title = "manual slop"
   
   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)
   fps_cap = 60.0
   if sys.platform == "win32":
    try:
     # Use PowerShell to get max refresh rate across all controllers
     cmd = "powershell -NoProfile -Command \"Get-CimInstance -ClassName Win32_VideoController | Select-Object -ExpandProperty CurrentRefreshRate\""
     out = subprocess.check_output(cmd, shell=True).decode().splitlines()
     rates = [float(r.strip()) for r in out if r.strip().isdigit()]
     if rates: fps_cap = max(rates)
    except Exception: pass

   # 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"
   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 = theme.apply_current
   self.runner_params.callbacks.post_init = self._post_init
   self._fetch_models(self.current_provider)
   md_options = markdown_helper.get_renderer().options
   immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options))
   # On exit
   self.shutdown()
   session_logger.close_session()

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

if __name__ == "__main__":
 main()

