diff --git a/src/ai_client.py b/src/ai_client.py index 4e58f2c5..810d693d 100644 --- a/src/ai_client.py +++ b/src/ai_client.py @@ -79,10 +79,7 @@ events: EventEmitter = EventEmitter() #region: Provider Configuration def set_model_params(temp: float, max_tok: int, trunc_limit: int = 8000, top_p: float = 1.0) -> None: - """ - Sets global generation parameters like temperature and max tokens. - [C: src/app_controller.py:AppController._handle_request_event, src/app_controller.py:_api_generate] - """ + """Sets global generation parameters like temperature and max tokens.""" global _temperature, _max_tokens, _history_trunc_limit, _top_p _temperature = temp _max_tokens = max_tok @@ -150,17 +147,11 @@ _local_storage = threading.local() _tool_approval_modes: dict[str, str] = {} def get_current_tier() -> Optional[str]: - """ - Returns the current tier from thread-local storage. - [C: src/app_controller.py:AppController._on_tool_log, tests/test_ai_client_concurrency.py:intercepted_append] - """ + """Returns the current tier from thread-local storage.""" return getattr(_local_storage, "current_tier", None) def set_current_tier(tier: Optional[str]) -> None: - """ - Sets the current tier in thread-local storage. - [C: src/app_controller.py:AppController._handle_request_event, src/conductor_tech_lead.py:generate_tickets, src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_ai_client_concurrency.py:run_t1, tests/test_ai_client_concurrency.py:run_t2, tests/test_mma_agent_focus_phase1.py:reset_tier, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2] - """ + """Sets the current tier in thread-local storage.""" _local_storage.current_tier = tier # Increased to allow thorough code exploration before forcing a summary @@ -198,10 +189,7 @@ _project_context_marker: str = "" #region: System Prompt Management def set_custom_system_prompt(prompt: str) -> None: - """ - Sets a custom system prompt to be combined with the default instructions. - [C: simulation/user_agent.py:UserSimAgent.generate_response, src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event, src/app_controller.py:_api_generate, src/conductor_tech_lead.py:generate_tickets, src/multi_agent_conductor.py:run_worker_lifecycle, src/orchestrator_pm.py:generate_tracks, tests/test_system_prompt_exposure.py:TestSystemPromptExposure.setUp] - """ + """Sets a custom system prompt to be combined with the default instructions.""" global _custom_system_prompt _custom_system_prompt = prompt @@ -236,9 +224,6 @@ def _get_combined_system_prompt(preset: Optional[ToolPreset] = None, bias: Optio return base def get_combined_system_prompt(preset: Optional[ToolPreset] = None, bias: Optional[BiasProfile] = None) -> str: - """ - [C: src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._handle_request_event] - """ return _get_combined_system_prompt(preset, bias) _comms_log: deque[dict[str, Any]] = deque(maxlen=1000) @@ -250,27 +235,16 @@ COMMS_CLAMP_CHARS: int = 300 #region: Comms Log def get_comms_log_callback() -> Optional[Callable[[dict[str, Any]], None]]: - """ - Returns the comms log callback (thread-local with global fallback). - [C: src/multi_agent_conductor.py:run_worker_lifecycle] - """ tl_cb = getattr(_local_storage, "comms_log_callback", None) if tl_cb: return tl_cb return comms_log_callback def set_comms_log_callback(cb: Optional[Callable[[dict[str, Any]], None]]) -> None: - """ - Sets the comms log callback (both global and thread-local). - [C: src/app_controller.py:AppController._init_ai_and_hooks, src/multi_agent_conductor.py:run_worker_lifecycle] - """ global comms_log_callback comms_log_callback = cb _local_storage.comms_log_callback = cb def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None: - """ - [C: tests/test_ai_client_concurrency.py:run_t1, tests/test_ai_client_concurrency.py:run_t2, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2] - """ entry: dict[str, Any] = { "ts": datetime.datetime.now().strftime("%H:%M:%S"), "direction": direction, @@ -287,27 +261,15 @@ def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None: _cb(entry) def get_comms_log() -> list[dict[str, Any]]: - """ - [C: src/app_controller.py:AppController._bg_task, src/app_controller.py:AppController._recalculate_session_usage, src/app_controller.py:AppController._start_track_logic, src/multi_agent_conductor.py:run_worker_lifecycle, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2, tests/test_token_usage.py:test_token_usage_tracking] - """ return list(_comms_log) def clear_comms_log() -> None: - """ - [C: src/app_controller.py:AppController._handle_reset_session, src/gui_2.py:App._render_comms_history_panel, src/gui_2.py:App._show_menus, tests/test_ai_client_concurrency.py:test_ai_client_tier_isolation, tests/test_token_usage.py:test_token_usage_tracking] - """ _comms_log.clear() def get_credentials_path() -> Path: - """ - [C: src/mcp_client.py:_is_allowed] - """ return Path(os.environ.get("SLOP_CREDENTIALS", str(Path(__file__).parent.parent / "credentials.toml"))) def _load_credentials() -> dict[str, Any]: - """ - [C: src/ai_server.py:_send_anthropic, src/ai_server.py:_send_deepseek, src/ai_server.py:_send_gemini, src/ai_server.py:_send_minimax, src/ai_server.py:handle_command, tests/test_deepseek_infra.py:test_credentials_error_mentions_deepseek, tests/test_minimax_provider.py:test_minimax_credentials_template] - """ cred_path = get_credentials_path() try: with open(cred_path, "rb") as f: @@ -407,15 +369,13 @@ def _classify_minimax_error(exc: Exception, source: str = "ai_client.minimax") - return ErrorInfo(kind=ErrorKind.UNKNOWN, message=body, source=source, original=exc) def set_provider(provider: str, model: str, validate: bool = True) -> None: - """ - Updates the active LLM provider and model name. + """Updates the active LLM provider and model name. When validate is True (default), the model is checked against the provider's LIVE model list, which for gemini_cli/minimax means a blocking subprocess / network call (and importing the provider SDK). Pass validate=False during startup so the GUI's first frame is not blocked — AppController._fetch_models corrects the model against the live list shortly after, off the main thread. - [C: src/app_controller.py:AppController._handle_reset_session, src/app_controller.py:AppController._init_ai_and_hooks, src/app_controller.py:AppController.current_model, src/app_controller.py:AppController.current_provider, src/app_controller.py:AppController.do_fetch, src/multi_agent_conductor.py:run_worker_lifecycle, src/orchestrator_pm.py:generate_tracks, tests/conftest.py:reset_ai_client, tests/test_ai_cache_tracking.py:test_gemini_cache_tracking, tests/test_ai_client_cli.py:test_ai_client_send_gemini_cli, tests/test_api_events.py:test_send_emits_events_proper, tests/test_api_events.py:test_send_emits_tool_events, tests/test_deepseek_provider.py:test_deepseek_completion_logic, tests/test_deepseek_provider.py:test_deepseek_model_selection, tests/test_deepseek_provider.py:test_deepseek_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoner_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoning_logic, tests/test_deepseek_provider.py:test_deepseek_streaming, tests/test_deepseek_provider.py:test_deepseek_tool_calling, tests/test_gemini_cli_edge_cases.py:test_gemini_cli_loop_termination, tests/test_gemini_cli_integration.py:test_gemini_cli_full_integration, tests/test_gemini_cli_integration.py:test_gemini_cli_rejection_and_history, tests/test_gemini_cli_parity_regression.py:test_send_invokes_adapter_send, tests/test_gui2_mcp.py:test_mcp_tool_call_is_dispatched, tests/test_minimax_provider.py:test_minimax_default_model, tests/test_minimax_provider.py:test_minimax_model_selection, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_rag_integration.py:test_rag_integration, tests/test_tier4_interceptor.py:test_ai_client_passes_qa_callback, tests/test_tier4_interceptor.py:test_gemini_provider_passes_qa_callback_to_run_script, tests/test_token_usage.py:test_token_usage_tracking] """ global _provider, _model _provider = provider @@ -442,17 +402,11 @@ def set_provider(provider: str, model: str, validate: bool = True) -> None: _model = model def get_provider() -> str: - """ - Returns the current active provider name. - [C: src/multi_agent_conductor.py:run_worker_lifecycle] - """ + """Returns the current active provider name.""" return _provider def cleanup() -> None: - """ - Performs cleanup operations like deleting server-side Gemini caches. - [C: src/app_controller.py:AppController.clear_cache, src/app_controller.py:AppController.shutdown, tests/test_ai_cache_tracking.py:test_gemini_cache_tracking_cleanup, tests/test_log_registry.py:TestLogRegistry.tearDown, tests/test_project_serialization.py:TestProjectSerialization.tearDown] - """ + """Performs cleanup operations like deleting server-side Gemini caches.""" global _gemini_client, _gemini_cache, _gemini_cached_file_paths if _gemini_client and _gemini_cache: try: @@ -462,10 +416,7 @@ def cleanup() -> None: _gemini_cached_file_paths = [] def reset_session() -> None: - """ - Clears conversation history and resets provider-specific session state. - [C: src/app_controller.py:AppController._handle_reset_session, src/app_controller.py:AppController.current_model, src/app_controller.py:AppController.current_provider, src/app_controller.py:AppController.init_state, src/gui_2.py:App._render_provider_panel, src/gui_2.py:App._show_menus, src/multi_agent_conductor.py:run_worker_lifecycle, tests/conftest.py:live_gui, tests/conftest.py:reset_ai_client, tests/test_ai_cache_tracking.py:test_gemini_cache_tracking, tests/test_ai_client_cli.py:test_ai_client_send_gemini_cli, tests/test_api_events.py:test_send_emits_events_proper, tests/test_api_events.py:test_send_emits_tool_events, tests/test_deepseek_provider.py:test_deepseek_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoner_payload_verification, tests/test_gemini_cli_integration.py:test_gemini_cli_full_integration, tests/test_gemini_cli_integration.py:test_gemini_cli_rejection_and_history, tests/test_gemini_metrics.py:test_get_gemini_cache_stats_with_mock_client, tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2, tests/test_session_logger_reset.py:test_reset_session, tests/test_token_usage.py:test_token_usage_tracking] - """ + """Clears conversation history and resets provider-specific session state.""" global _gemini_client, _gemini_chat, _gemini_cache global _gemini_cache_md_hash, _gemini_cache_created_at, _gemini_cached_file_paths global _anthropic_client, _anthropic_history @@ -2208,7 +2159,7 @@ def _ensure_grok_client() -> Any: _grok_client = openai.OpenAI(api_key=api_key, base_url="https://api.x.ai/v1") return _grok_client -def _send_grok_result(md_content: str, user_message: str, base_dir: str, +def _send_grok(md_content: str, user_message: str, base_dir: str, file_items: list[dict[str, Any]] | None = None, discussion_history: str = "", stream: bool = False, @@ -2260,7 +2211,7 @@ def _list_grok_models() -> list[str]: from src.vendor_capabilities import list_models_for_vendor return list_models_for_vendor("grok") -def _send_minimax_result(md_content: str, user_message: str, base_dir: str, +def _send_minimax(md_content: str, user_message: str, base_dir: str, file_items: list[dict[str, Any]] | None = None, discussion_history: str = "", stream: bool = False, @@ -2432,7 +2383,7 @@ def _ensure_llama_client() -> Any: _llama_client = openai.OpenAI(api_key=_llama_api_key, base_url=_llama_base_url) return _llama_client -def _send_llama_result(md_content: str, user_message: str, base_dir: str, +def _send_llama(md_content: str, user_message: str, base_dir: str, file_items: list[dict[str, Any]] | None = None, discussion_history: str = "", stream: bool = False, @@ -2497,7 +2448,7 @@ def ollama_chat( resp = requests.post(f"{base_url}/api/chat", json=payload, timeout=120) return resp.json() -def _send_llama_native_result(md_content: str, user_message: str, base_dir: str, +def _send_llama_native(md_content: str, user_message: str, base_dir: str, file_items: list[dict[str, Any]] | None = None, discussion_history: str = "", stream: bool = False, @@ -2751,7 +2702,7 @@ def send_result( stream, pre_tool_callback, qa_callback, stream_callback, patch_callback ) elif p == "minimax": - res = _send_minimax_result( + res = _send_minimax( md_content, user_message, base_dir, file_items, discussion_history, stream, pre_tool_callback, qa_callback, stream_callback, patch_callback ) @@ -2771,7 +2722,7 @@ def send_result( stream, pre_tool_callback, qa_callback, stream_callback, patch_callback ) elif p == "llama_native": - res = _send_llama_native_result( + res = _send_llama_native( md_content, user_message, base_dir, file_items, discussion_history, stream, pre_tool_callback, qa_callback, stream_callback, patch_callback ) diff --git a/src/gui_2.py b/src/gui_2.py index ff787e7d..46ffeac5 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -4429,11 +4429,11 @@ def render_discussion_entry(app: App, entry: dict, index: int) -> None: +-------------------------------------------------------------+ """ with imscope.id(f"disc_{index}"): - role = entry.get("role", "User") + role = entry.get("role", "User") bg_col = theme.get_role_tint(role) - draw_list = imgui.get_window_draw_list() - p_min = imgui.get_cursor_screen_pos() + draw_list = imgui.get_window_draw_list() + p_min = imgui.get_cursor_screen_pos() full_width = imgui.get_content_region_avail().x # Start Background Layer (Channel 0: Background, Channel 1: Foreground) @@ -4459,7 +4459,7 @@ def render_discussion_entry(app: App, entry: dict, index: int) -> None: if imgui.button("[Edit]" if read_mode else "[Read]"): entry["read_mode"] = not read_mode ts_str = entry.get("ts", "") - usage = entry.get("usage", {}) + usage = entry.get("usage", {}) if ts_str or usage: imgui.same_line() if ts_str: imgui.text_colored(C_SUB(), str(ts_str)) @@ -4504,7 +4504,7 @@ def render_discussion_entry(app: App, entry: dict, index: int) -> None: # Finalize Background Tint draw_list.channels_set_current(0) - p_max = imgui.get_item_rect_max() + p_max = imgui.get_item_rect_max() # Ensure full width coverage of the panel p_max.x = p_min.x + full_width + imgui.get_style().window_padding.x draw_list.add_rect_filled(p_min, p_max, imgui.get_color_u32(bg_col), 4.0) @@ -4513,13 +4513,11 @@ def render_discussion_entry(app: App, entry: dict, index: int) -> None: imgui.separator() def render_discussion_entry_read_mode(app: App, entry: dict, index: int) -> None: - """ - Renders a discussion entry in read-only mode. + """Renders a discussion entry in read-only mode. Parses the markdown content, isolates retrieved context sections (RAG chunks), handles custom definition/AST links, and invokes the markdown syntax highlighter. - SSDL Shape: - `[I:extract_rag] -> [B:definitions?] => [I:markdown]` + SSDL: `[I:extract_rag] -> [B:definitions?] => [I:markdown]` ASCII Layout Map: +-------------------------------------------------------------+ @@ -4578,16 +4576,10 @@ def render_discussion_entry_read_mode(app: App, entry: dict, index: int) -> None imgui.end_group() def render_history_window(app: App) -> None: - """ - Renders the Undo/Redo History window. Displays past UI snapshots in reverse chronological + """Renders the Undo/Redo History window. Displays past UI snapshots in reverse chronological order and allows reverting to prior states. - State Mutations: - app.show_windows['Undo/Redo History'] (updates visibility) - app.history (jumping/traversing history) - - SSDL Shape: - `[I:history_list] -> [B:undo_redo_buttons] => [B:selectable_snapshots]` + SSDL: `[I:history_list] -> [B:undo_redo_buttons] => [B:selectable_snapshots]` """ if not app.show_windows.get('Undo/Redo History', False): return @@ -4613,15 +4605,10 @@ def render_history_window(app: App) -> None: else: iterate_history(history) def render_session_insights_panel(app: App) -> None: - """ - Renders session productivity insights, displaying total tokens, API call counts, + """Renders session productivity insights, displaying total tokens, API call counts, burn rates, total costs, completed ticket counts, and token efficiency. - State Mutations: - None directly. - - SSDL Shape: - `[I:insights] -> [I:telemetry_texts]` + SSDL: `[I:insights] -> [I:telemetry_texts]` """ if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_session_insights_panel") imgui.text_colored(C_LBL(), 'Session Insights') @@ -4638,16 +4625,10 @@ def render_session_insights_panel(app: App) -> None: if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_session_insights_panel") def render_prior_session_view(app: App) -> None: - """ - Renders a historical read-only view of a loaded prior discussion session, complete + """Renders a historical read-only view of a loaded prior discussion session, complete with collapsing message bubbles and bubble colors. - State Mutations: - app.prior_disc_entries (collapsing/expanding bubbles) - app._comms_log_dirty (triggers log reload on exit) - - SSDL Shape: - `[I:prior_entries] -> [B:exit_button] -> [I:scroll_list]` + SSDL: `[I:prior_entries] -> [B:exit_button] -> [I:scroll_list]` """ with imscope.style_color(imgui.Col_.child_bg, theme.get_color("bubble_vendor")): if imgui.button("Exit Prior Session"): app.controller.cb_exit_prior_session(); app._comms_log_dirty = True @@ -4674,26 +4655,20 @@ def render_prior_session_view(app: App) -> None: imgui.separator() def render_thinking_indicator(app: App) -> None: - """ - Renders a blinking red status indicator when the AI is currently executing, + """Renders a blinking red status indicator when the AI is currently executing, sending, streaming, or running shell commands. - State Mutations: - None. - - SSDL Shape: - `[I:ai_status] -> [I:blinking_text]` + SSDL: `[I:ai_status] -> [I:blinking_text]` """ is_thinking = app.ai_status in ['sending...', 'streaming...', 'running powershell...'] if is_thinking: val = math.sin(time.time() * 10 * math.pi) alpha = 1.0 if val > 0 else 0.0 c = theme.get_color("status_error", alpha=alpha) - imgui.text_colored(c, "THINKING..."); imgui.same_line() + imgui.text_colored(c, "THINKING..."); def _on_warmup_complete_callback(app: App, status: dict) -> None: - """ - Thread-safe callback registered with controller.on_warmup_complete() + """Thread-safe callback registered with controller.on_warmup_complete() during App._post_init. Records the completion timestamp; the indicator function uses it to show a brief "ready" tag. Also appends a message to a lock-protected list that the indicator @@ -4702,14 +4677,14 @@ def _on_warmup_complete_callback(app: App, status: dict) -> None: """ try: app._warmup_completion_ts = time.time() - pending = status.get("pending", []) + pending = status.get("pending", []) completed = status.get("completed", []) - failed = status.get("failed", []) - total = len(pending) + len(completed) + len(failed) + failed = status.get("failed", []) + total = len(pending) + len(completed) + len(failed) if failed: msg = f"Warmup finished with {len(failed)} failures ({total} modules)" else: msg = f"All imports ready ({total} modules)" if not hasattr(app, "_warmup_toast_lock"): - import threading as _threading + import threading as _threading #TODO(Ed): Review local import app._warmup_toast_lock = _threading.Lock() with app._warmup_toast_lock: if not hasattr(app, "_warmup_toast_messages"): app._warmup_toast_messages = [] @@ -4717,15 +4692,10 @@ def _on_warmup_complete_callback(app: App, status: dict) -> None: except Exception: pass def render_warmup_status_indicator(app: App) -> None: - """ - Renders a transient warmup status indicator in the main interface frame. Shows progress + """Renders a transient warmup status indicator in the main interface frame. Shows progress of AppController's background module imports. - State Mutations: - None directly (reads controller warmup state). - - SSDL Shape: - `[I:warmup_status] -> [I:status_text]` + SSDL: `[I:warmup_status] -> [I:status_text]` """ controller = getattr(app, "controller", None) if controller is None: return @@ -4733,13 +4703,13 @@ def render_warmup_status_indicator(app: App) -> None: try: status = controller.warmup_status() except Exception: return - pending = status.get("pending", []) + pending = status.get("pending", []) completed = status.get("completed", []) - failed = status.get("failed", []) + failed = status.get("failed", []) if pending: - total = len(pending) + len(completed) + len(failed) - done = len(completed) + len(failed) - c = theme.get_color("status_warning") + total = len(pending) + len(completed) + len(failed) + done = len(completed) + len(failed) + c = theme.get_color("status_warning") imgui.text_colored(c, f"Warming up... ({done}/{total})") return if failed: @@ -4750,7 +4720,7 @@ def render_warmup_status_indicator(app: App) -> None: ts = getattr(app, "_warmup_completion_ts", 0.0) if ts > 0 and (time.time() - ts) < 3.0: total = len(completed) + len(failed) - c = theme.get_color("status_success") + c = theme.get_color("status_success") imgui.text_colored(c, f"All imports ready ({total} modules)") return # No render: warmup done, no failures, transient window expired. @@ -4760,7 +4730,7 @@ def render_synthesis_panel(app: App) -> None: imgui.text("Select takes to synthesize:") discussions = app.project.get('discussion', {}).get('discussions', {}) if not isinstance(getattr(app, 'ui_synthesis_selected_takes', None), dict): app.ui_synthesis_selected_takes = {name: False for name in discussions} - if not isinstance(getattr(app, 'ui_synthesis_prompt', None), str): app.ui_synthesis_prompt = "" + if not isinstance(getattr(app, 'ui_synthesis_prompt', None), str): app.ui_synthesis_prompt = "" for name in discussions: _, app.ui_synthesis_selected_takes[name] = imgui.checkbox(name, app.ui_synthesis_selected_takes.get(name, False)) imgui.spacing() imgui.text("Synthesis Prompt:") @@ -4784,16 +4754,10 @@ def render_synthesis_panel(app: App) -> None: app._handle_generate_send() def render_comms_history_panel(app: App) -> None: - """ - Renders the communications history log panel. Displays outgoing requests, incoming responses, + """Renders the communications history log panel. Displays outgoing requests, incoming responses, and tool call inputs/outputs in chronological order. - State Mutations: - app._comms_log_dirty (marks log for reload) - app._comms_log (clears log on user clear request) - - SSDL Shape: - `[I:ai_status] -> [B:clear_exit_buttons] -> [I:direction_colors] -> [I:entries_scroll_list]` + SSDL: `[I:ai_status] -> [B:clear_exit_buttons] -> [I:direction_colors] -> [I:entries_scroll_list]` """ if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_comms_history_panel") st_col = theme.get_color("text_disabled") @@ -4814,7 +4778,7 @@ def render_comms_history_panel(app: App) -> None: 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(" "); 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") @@ -4828,13 +4792,13 @@ def render_comms_history_panel(app: App) -> None: imgui.push_id(f"comms_entry_{i}") i_display = i + 1 - ts = entry.get("ts", "00:00:00") + 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", "?") + provider = entry.get("provider", "?") + model = entry.get("model", "?") tier = entry.get("source_tier", "main") - payload = entry.get("payload", {}) + payload = entry.get("payload", {}) if not payload and kind not in ("request", "response", "tool_call", "tool_result"): payload = entry # legacy @@ -4853,9 +4817,9 @@ def render_comms_history_panel(app: App) -> None: imgui.text_colored(theme.get_color("status_error"), f"[{ticket_id}]") imgui.same_line() d_col_fn = DIR_COLORS.get(direction, C_VAL) - imgui.text_colored(d_col_fn(), direction); imgui.same_line() - k_col_fn = KIND_COLORS.get(kind, C_VAL) - imgui.text_colored(k_col_fn(), kind); imgui.same_line() + k_col_fn = KIND_COLORS.get(kind, C_VAL) + imgui.text_colored(d_col_fn(), direction); imgui.same_line() + imgui.text_colored(k_col_fn(), kind); imgui.same_line() imgui.text_colored(C_LBL(), f"{provider}/{model}"); imgui.same_line() imgui.text_colored(C_SUB(), f"[{tier}]") @@ -4903,12 +4867,10 @@ def render_comms_history_panel(app: App) -> None: if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_comms_history_panel") def render_takes_panel(app: App) -> None: - """ - Renders the Takes & Synthesis panel. Lists all discussion takes in a table, + """Renders the Takes & Synthesis panel. Lists all discussion takes in a table, allows switching or deleting them, and provides a multi-take synthesis workflow. - SSDL Shape: - `[I:takes_table] -> [B:switch/delete] -> [I:synthesis_config] => [B:generate_synthesis]` + SSDL: `[I:takes_table] -> [B:switch/delete] -> [I:synthesis_config] => [B:generate_synthesis]` ASCII Layout Map: +---------------------------------------------------------+ @@ -4931,12 +4893,10 @@ def render_takes_panel(app: App) -> None: imgui.text("Takes & Synthesis") imgui.separator() discussions = app.project.get('discussion', {}).get('discussions', {}) - if not isinstance(getattr(app, 'ui_synthesis_selected_takes', None), dict): - app.ui_synthesis_selected_takes = {name: False for name in discussions} - if not isinstance(getattr(app, 'ui_synthesis_prompt', None), str): - app.ui_synthesis_prompt = "" + if not isinstance(getattr(app, 'ui_synthesis_selected_takes', None), dict): app.ui_synthesis_selected_takes = {name: False for name in discussions} + if not isinstance(getattr(app, 'ui_synthesis_prompt', None), str): app.ui_synthesis_prompt = "" if imgui.begin_table("takes_table", 3, imgui.TableFlags_.resizable | imgui.TableFlags_.borders): - imgui.table_setup_column("Name", imgui.TableColumnFlags_.width_stretch) + imgui.table_setup_column("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() @@ -4944,7 +4904,7 @@ def render_takes_panel(app: App) -> None: imgui.table_next_row() imgui.table_set_column_index(0) is_active = name == app.active_discussion - if is_active: + if is_active: imgui.text_colored(C_IN(), name) else: imgui.text(name) @@ -4971,10 +4931,10 @@ def render_takes_panel(app: App) -> None: if len(selected) > 1: from src import synthesis_formatter takes_dict = {name: discussions.get(name, {}).get('history', []) for name in selected} - diff_text = synthesis_formatter.format_takes_diff(takes_dict) - prompt = f"{app.ui_synthesis_prompt}\n\nHere are the variations:\n{diff_text}" - new_name = "synthesis_take" - counter = 1 + diff_text = synthesis_formatter.format_takes_diff(takes_dict) + prompt = f"{app.ui_synthesis_prompt}\n\nHere are the variations:\n{diff_text}" + new_name = "synthesis_take" + counter = 1 while new_name in discussions: new_name = f"synthesis_take_{counter}" counter += 1 @@ -4984,12 +4944,10 @@ def render_takes_panel(app: App) -> None: app._handle_generate_send() def render_discussion_entries(app: App) -> None: - """ - Renders the scrollable list of all discussion entry bubbles. When a focus agent is + """Renders the scrollable list of all discussion entry bubbles. When a focus agent is active (ui_focus_agent), only entries matching that agent's persona (or User) are shown. - SSDL Shape: - `[I:filter_by_agent?] -> [I:scroll_child] => [I:entry_bubbles]` + SSDL: `[I:filter_by_agent?] -> [I:scroll_child] => [I:entry_bubbles]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5013,13 +4971,11 @@ def render_discussion_entries(app: App) -> None: if app._scroll_disc_to_bottom: imgui.set_scroll_here_y(1.0); app._scroll_disc_to_bottom = False def render_discussion_entry_controls(app: App) -> None: - """ - Renders the action buttons at the bottom of the discussion panel. + """Renders the action buttons at the bottom of the discussion panel. Handles adding entries, expanding/collapsing all bubbles, clearing, saving, compressing logs, and configuring auto-history checkpoints. - SSDL Shape: - `[I:buttons] -> [B:clicks] => [S:entries_or_config]` + SSDL: `[I:buttons] -> [B:clicks] => [S:entries_or_config]` ASCII Layout Map: +-------------------------------------------------------------+ @@ -5044,19 +5000,17 @@ def render_discussion_entry_controls(app: App) -> None: _, app.ui_auto_add_history = imgui.checkbox("Auto-add message & response to history", app.ui_auto_add_history) imgui.text("Keep Pairs:"); imgui.same_line(); imgui.set_next_item_width(140) ch, app.ui_disc_truncate_pairs = imgui.drag_int("##trunc_pairs", app.ui_disc_truncate_pairs, 1, 1, 999) - if app.ui_disc_truncate_pairs < 1: app.ui_disc_truncate_pairs = 1 + if app.ui_disc_truncate_pairs < 1: app.ui_disc_truncate_pairs = 1 imgui.same_line() if imgui.button("Truncate"): with app._disc_entries_lock: app.disc_entries = truncate_entries(app.disc_entries, app.ui_disc_truncate_pairs) app.ai_status = f"history truncated to {app.ui_disc_truncate_pairs} pairs" def render_discussion_metadata(app: App) -> None: - """ - Renders per-discussion metadata: cumulative token counts, git commit association, + """Renders per-discussion metadata: cumulative token counts, git commit association, last updated timestamp, and controls for creating, renaming, and deleting discussions. - SSDL Shape: - `[I:token_totals] -> [I:commit_row] -> [I:timestamp] -> [B:create/rename/delete]` + SSDL: `[I:token_totals] -> [I:commit_row] -> [I:timestamp] -> [B:create/rename/delete]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5066,14 +5020,14 @@ def render_discussion_metadata(app: App) -> None: | [new-name________] [Create] [Rename] [Delete] | +---------------------------------------------------------+ """ - disc_data = app.project.get("discussion", {}).get("discussions", {}).get(app.active_discussion, {}) + disc_data = app.project.get("discussion", {}).get("discussions", {}).get(app.active_discussion, {}) git_commit, last_updated = disc_data.get("git_commit", ""), disc_data.get("last_updated", "") total_in, total_out, total_cache = 0, 0, 0 for entry in app.disc_entries: if "usage" in entry: - total_in += entry["usage"].get("input_tokens", 0) - total_out += entry["usage"].get("output_tokens", 0) + total_in += entry["usage"].get("input_tokens", 0) + total_out += entry["usage"].get("output_tokens", 0) total_cache += entry["usage"].get("cache_read_input_tokens", 0) if total_in > 0 or total_out > 0: imgui.text_colored(theme.get_color("status_info"), f"Discussion Tokens: {total_in} In | {total_out} Out | {total_cache} Cache") @@ -5099,13 +5053,11 @@ def render_discussion_metadata(app: App) -> None: if imgui.button("Delete"): app._delete_discussion(app.active_discussion) def render_discussion_panel(app: App) -> None: - """ - Top-level discussion panel compositor. Renders the thinking indicator, + """Top-level discussion panel compositor. Renders the thinking indicator, prior-session read-only view (if active), discussion selector, entry controls, role list, and scrollable entry bubbles. - SSDL Shape: - `[I:thinking_indicator] -> [I:prior_view?] | [I:selector] -> [I:controls] -> [I:roles] -> [I:entries]` + SSDL: `[I:thinking_indicator] -> [I:prior_view?] | [I:selector] -> [I:controls] -> [I:roles] -> [I:entries]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5142,12 +5094,10 @@ def render_discussion_panel(app: App) -> None: return def render_discussion_roles(app: App) -> None: - """ - Renders the collapsible Roles section. Lists current roles with an [X] delete button + """Renders the collapsible Roles section. Lists current roles with an [X] delete button per entry, and exposes an input field for adding new roles. - SSDL Shape: - `[B:collapsing_header] => [I:roles_list] -> [B:add_role]` + SSDL: `[B:collapsing_header] => [I:roles_list] -> [B:add_role]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5171,13 +5121,11 @@ def render_discussion_roles(app: App) -> None: return def render_discussion_selector(app: App) -> None: - """ - Renders the collapsible Discussions selector. Shows a combo-box for choosing + """Renders the collapsible Discussions selector. Shows a combo-box for choosing the active base discussion, a tab-bar for takes, a Synthesis tab, promote/track controls, and the discussion metadata row. - SSDL Shape: - `[B:collapsing_header] => [I:combo_base] -> [I:takes_tabs] -> [B:promote?] -> [B:track?] -> [I:metadata]` + SSDL: `[B:collapsing_header] => [I:combo_base] -> [I:takes_tabs] -> [B:promote?] -> [B:track?] -> [I:metadata]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5189,11 +5137,9 @@ def render_discussion_selector(app: App) -> None: | [new-name] [Create] [Rename] [Delete] | +---------------------------------------------------------+ """ - if not imgui.collapsing_header("Discussions", imgui.TreeNodeFlags_.default_open): - return + if not imgui.collapsing_header("Discussions", imgui.TreeNodeFlags_.default_open): return names = app._get_discussion_names(); grouped = {} - for name in names: - base = name.split("_take_")[0]; grouped.setdefault(base, []).append(name) + for name in names: base = name.split("_take_")[0]; grouped.setdefault(base, []).append(name) active_base = app.active_discussion.split("_take_")[0] if active_base not in grouped: active_base = names[0] if names else "" base_names = sorted(grouped.keys()) @@ -5215,7 +5161,7 @@ def render_discussion_selector(app: App) -> None: app._switch_discussion(take_name) app._force_tab_selection = False app._force_tab_selection = False - with imscope.tab_item("Synthesis###Synthesis") as (exp, _): + with imscope.tab_item("Synthesis###Synthesis") as (exp, _): if exp: render_synthesis_panel(app) imgui.end_tab_bar() if "_take_" in app.active_discussion: @@ -5237,8 +5183,7 @@ def render_discussion_selector(app: App) -> None: return def render_discussion_tab(app: App) -> None: - """ - Renders the Discussion tab content. Comprises a resizable top pane (history/entries) + """Renders the Discussion tab content. Comprises a resizable top pane (history/entries) and a bottom pane with a draggable splitter bar, pop-out checkboxes, and inline Message/Response tabs. @@ -5297,12 +5242,10 @@ def render_discussion_tab(app: App) -> None: #region: Operations Monitor def render_operations_hub(app: App) -> None: - """ - Top-level Operations Monitor hub. Houses pop-out checkboxes for Tool Calls, Usage Analytics, + """Top-level Operations Monitor hub. Houses pop-out checkboxes for Tool Calls, Usage Analytics, and External Tools, then a tab-bar for all ops sub-panels. - SSDL Shape: - `[B:popout_toggles] -> [I:tab_bar] => [I:active_tab_content]` + SSDL: `[B:popout_toggles] -> [I:tab_bar] => [I:active_tab_content]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5345,23 +5288,21 @@ def render_operations_hub(app: App) -> None: if exp: imgui.text("Experimental: Auto-switch layout by Tier") ch, app.controller.ui_auto_switch_layout = imgui.checkbox("Enable Auto-Switch", app.controller.ui_auto_switch_layout) - if app.controller.ui_auto_switch_layout: + if app.controller.ui_auto_switch_layout: imgui.separator(); imgui.text("Tier Bindings (select profile for each tier)") profiles = [""] + [p.name for p in app.controller.workspace_profiles.values()] for t in ["Tier 1", "Tier 2", "Tier 3", "Tier 4"]: - curr = app.controller.ui_tier_layout_bindings.get(t, ""); idx = profiles.index(curr) if curr in profiles else 0 + curr = app.controller.ui_tier_layout_bindings.get(t, ""); idx = profiles.index(curr) if curr in profiles else 0 ch_combo, new_idx = imgui.combo(t, idx, profiles) if ch_combo: app.controller.ui_tier_layout_bindings[t] = profiles[new_idx] with imscope.tab_item("Vendor State") as (exp, _): if exp: render_vendor_state(app) def render_vendor_state(app: App) -> None: - """ - Renders the Operations Hub > Vendor State panel. Displays per-vendor health metrics + """Renders the Operations Hub > Vendor State panel. Displays per-vendor health metrics (model name, cache state, token budget, connection status) in a colour-coded table. - SSDL Shape: - `[I:metrics_table] => [I:state_column]` + SSDL: `[I:metrics_table] => [I:state_column]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5377,8 +5318,8 @@ def render_vendor_state(app: App) -> None: metrics = get_vendor_state(app) if imgui.begin_table("vendor_state", 3, imgui.TableFlags_.row_bg | imgui.TableFlags_.borders): imgui.table_setup_column("Metric", imgui.TableColumnFlags_.width_fixed, 180) - imgui.table_setup_column("Value", imgui.TableColumnFlags_.width_stretch) - imgui.table_setup_column("State", imgui.TableColumnFlags_.width_fixed, 60) + imgui.table_setup_column("Value", imgui.TableColumnFlags_.width_stretch) + imgui.table_setup_column("State", imgui.TableColumnFlags_.width_fixed, 60) imgui.table_headers_row() state_colors = {"ok": theme.get_color("status_success"), "warn": theme.get_color("status_warning"), "error": theme.get_color("status_error"), "info": theme.get_color("text_disabled")} for m in metrics: @@ -5392,12 +5333,10 @@ def render_vendor_state(app: App) -> None: imgui.end_table() def render_message_panel(app: App) -> None: - """ - Renders user message text input area, exposing buttons to generate assistant responses, + """Renders user message text input area, exposing buttons to generate assistant responses, inject contextual files, or reset active conversation sessions. - SSDL Shape: - `[I:live_indicator] -> [I:input_textbox] -> [B:gen_send_buttons] -> [B:inject_reset]` + SSDL: `[I:live_indicator] -> [I:input_textbox] -> [B:gen_send_buttons] -> [B:inject_reset]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5448,12 +5387,10 @@ def render_message_panel(app: App) -> None: if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_message_panel") def render_response_panel(app: App) -> None: - """ - Renders assistant stream output panel. Renders thinking traces, markdown segments, + """Renders assistant stream output panel. Renders thinking traces, markdown segments, and exports active responses to history entries. - SSDL Shape: - `[I:response_text] -> [I:thinking_trace] -> [I:markdown_view] => [B:export_to_history]` + SSDL: `[I:response_text] -> [I:thinking_trace] -> [I:markdown_view] => [B:export_to_history]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5468,8 +5405,8 @@ def render_response_panel(app: App) -> None: """ if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_response_panel") if app._trigger_blink: - app._trigger_blink = False - app._is_blinking = True + app._trigger_blink = False + app._is_blinking = True app._blink_start_time = time.time() try: imgui.set_window_focus("Response") # type: ignore[call-arg] @@ -5506,12 +5443,10 @@ def render_response_panel(app: App) -> None: if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_response_panel") def render_tool_calls_panel(app: App) -> None: - """ - Renders tool call execution log. Displays script execution details, status codes, + """Renders tool call execution log. Displays script execution details, status codes, and outputs in a scrollable table. Clicking a row opens the full call in the Text Viewer. - SSDL Shape: - `[I:tool_log] -> [B:clear_button] => [I:calls_table]` + SSDL: `[I:tool_log] -> [B:clear_button] => [I:calls_table]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5580,12 +5515,10 @@ def render_tool_calls_panel(app: App) -> None: if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_tool_calls_panel") def render_external_tools_panel(app: App) -> None: - """ - Renders the External MCPs panel. Shows server health indicators, a [Refresh] button, + """Renders the External MCPs panel. Shows server health indicators, a [Refresh] button, and a table of all registered external MCP tool names, servers, and descriptions. - SSDL Shape: - `[B:refresh] -> [I:server_status_badges] -> [I:tools_table]` + SSDL Shape: `[B:refresh] -> [I:server_status_badges] -> [I:tools_table]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5598,7 +5531,7 @@ def render_external_tools_panel(app: App) -> None: | +----------------------+------------+-----------------+ | +---------------------------------------------------------+ """ - if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_external_tools_panel") + if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_external_tools_panel") if imgui.button("Refresh External MCPs"): app.event_queue.put("refresh_external_mcps", None) imgui.separator() @@ -5642,13 +5575,11 @@ def render_external_tools_panel(app: App) -> None: if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_external_tools_panel") def render_text_viewer_window(app: App) -> None: - """ - Renders the standalone text/code/markdown viewer window. Supports four rendering modes + """Renders the standalone text/code/markdown viewer window. Supports four rendering modes based on content type: markdown (rendered), slice editor (line-click range selection), syntax-highlighted code (CodeEditor widget), and plain text (scrollable). - SSDL Shape: - `[I:mode_dispatch] => [I:markdown] | [I:slice_editor] | [I:code_editor] | [I:plain_text]` + SSDL: `[I:mode_dispatch] => [I:markdown] | [I:slice_editor] | [I:code_editor] | [I:plain_text]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5667,12 +5598,12 @@ def render_text_viewer_window(app: App) -> None: if not app.show_windows.get("Text Viewer", False): return imgui.set_next_window_size(imgui.ImVec2(900, 700), imgui.Cond_.first_use_ever) # Force a unique ID to clear legacy docking corruption - expanded, opened = imgui.begin(f"{app.text_viewer_title or 'Text Viewer'}###Text_Viewer_Unified", True, imgui.WindowFlags_.no_collapse) + expanded, opened = imgui.begin(f"{app.text_viewer_title or 'Text Viewer'}###Text_Viewer_Unified", True, imgui.WindowFlags_.no_collapse) app.show_windows["Text Viewer"] = bool(opened) if not opened: app.ui_editing_slices_file = None - app._slice_sel_start = -1 - app._slice_sel_end = -1 + app._slice_sel_start = -1 + app._slice_sel_end = -1 if expanded: if app.ui_editing_slices_file is not None: imgui.text_colored(C_IN(), "Slice Management (Click-drag lines to select range)") @@ -5691,7 +5622,7 @@ def render_text_viewer_window(app: App) -> None: if imgui.button("Auto-Populate AST Slices"): app._populate_auto_slices(app.ui_editing_slices_file) imgui.same_line() if imgui.button("Edit Tags"): imgui.open_popup("Edit Context Tags") - + if imgui.begin_popup("Edit Context Tags"): tags = app.controller.project.setdefault("context_tags", ["auto-ast", "bug", "feature", "important"]) imgui.text("Context Tags") @@ -5709,7 +5640,7 @@ def render_text_viewer_window(app: App) -> None: if imgui.button("+ Add Tag"): tags.append("new-tag") if imgui.button("Close"): imgui.close_current_popup() imgui.end_popup() - + to_remove = -1 tags = app.controller.project.get("context_tags", ["auto-ast", "bug", "feature", "important"]) for idx, slc in enumerate(app.ui_editing_slices_file.custom_slices): @@ -5729,7 +5660,9 @@ def render_text_viewer_window(app: App) -> None: imgui.separator() if imgui.button("Copy"): imgui.set_clipboard_text(app.text_viewer_content) imgui.same_line(); _, app.text_viewer_wrap = imgui.checkbox("Word Wrap", app.text_viewer_wrap) + imgui.separator() + renderer = markdown_helper.get_renderer(); tv_type = getattr(app, "text_viewer_type", "text") if tv_type == 'markdown': with imscope.child("tv_md_scroll", -1, -1, True): markdown_helper.render(app.text_viewer_content, context_id='text_viewer') @@ -5739,7 +5672,7 @@ def render_text_viewer_window(app: App) -> None: for i, line_text in enumerate(lines): line_num = i + 1; pos = imgui.get_cursor_screen_pos(); line_height = imgui.get_text_line_height() - is_auto_sliced = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in app.ui_editing_slices_file.custom_slices if slc.get('tag') == 'auto-ast') + is_auto_sliced = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in app.ui_editing_slices_file.custom_slices if slc.get('tag') == 'auto-ast') is_manual_sliced = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in app.ui_editing_slices_file.custom_slices if slc.get('tag') != 'auto-ast') if is_manual_sliced: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_manual", alpha=0.2))) @@ -5768,12 +5701,10 @@ def render_text_viewer_window(app: App) -> None: imgui.end() def render_patch_modal(app: App) -> None: - """ - Renders the Apply Patch? modal. Shows files to be modified, a syntax-highlighted + """Renders the Apply Patch? modal. Shows files to be modified, a syntax-highlighted diff preview, and action buttons to open in an external editor, apply, or reject. - SSDL Shape: - `[I:files_list] -> [I:diff_preview] => [B:open_external | B:apply | B:reject]` + SSDL: `[I:files_list] -> [I:diff_preview] => [B:open_external | B:apply | B:reject]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5800,7 +5731,7 @@ def render_patch_modal(app: App) -> None: p_min = imgui.get_window_pos() p_max = imgui.ImVec2(p_min.x + imgui.get_window_size().x, p_min.y + imgui.get_window_size().y) shaders.draw_soft_shadow(imgui.get_background_draw_list(), p_min, p_max, imgui.ImVec4(0, 0, 0, 0.6), 25.0, 6.0) - + imgui.text_colored(theme.get_color("status_warning"), "Tier 4 QA Generated a Patch") imgui.separator() if app._pending_patch_files: @@ -5829,19 +5760,17 @@ def render_patch_modal(app: App) -> None: imgui.same_line() if imgui.button("Reject"): app._close_vscode_diff() - app._show_patch_modal = False - app._pending_patch_text = None + app._show_patch_modal = False + app._pending_patch_text = None app._pending_patch_files = [] app._patch_error_message = None imgui.close_current_popup() def render_external_editor_panel(app: App) -> None: - """ - Renders the External Editor configuration panel. Lists configured editors + """Renders the External Editor configuration panel. Lists configured editors from config.toml, shows the current default, and allows setting a new default. - SSDL Shape: - `[I:editors_list] -> [B:set_default] => [I:config_hint?]` + SSDL: `[I:editors_list] -> [B:set_default] => [I:config_hint?]` ASCII Layout Map: +---------------------------------------------------------+ @@ -5951,7 +5880,7 @@ def render_approve_script_modal(app: App) -> None: if imgui.button("Approve & Run", imgui.ImVec2(120, 0)): with dlg._condition: dlg._approved = True - dlg._done = True + dlg._done = True dlg._condition.notify_all() with app._pending_dialog_lock: app._pending_dialog = None imgui.close_current_popup() @@ -5959,7 +5888,7 @@ def render_approve_script_modal(app: App) -> None: if imgui.button("Reject", imgui.ImVec2(120, 0)): with dlg._condition: dlg._approved = False - dlg._done = True + dlg._done = True dlg._condition.notify_all() with app._pending_dialog_lock: app._pending_dialog = None imgui.close_current_popup() @@ -5970,13 +5899,11 @@ def render_approve_script_modal(app: App) -> None: #region: Misc Tools def render_theme_panel(app: App) -> None: - """ - Renders the Theme configuration window. Covers palette selection, panel pop-out toggles, + """Renders the Theme configuration window. Covers palette selection, panel pop-out toggles, font path/size, DPI scale, transparency sliders, background shader, CRT filter, and per-palette tone mapping (brightness/contrast/gamma). - SSDL Shape: - `[I:palette_combo] -> [B:panel_popout_toggles] -> [I:font_config] -> [I:scale_sliders] -> [I:tone_mapping]` + SSDL: `[I:palette_combo] -> [B:panel_popout_toggles] -> [I:font_config] -> [I:scale_sliders] -> [I:tone_mapping]` ASCII Layout Map: +---------------------------------------------------------+ @@ -6060,7 +5987,7 @@ def render_theme_panel(app: App) -> None: 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() + bg = bg_shader.get_bg() ch_bg, bg.enabled = imgui.checkbox("Animated Background Shader", bg.enabled) if ch_bg: gui_cfg = app.config.setdefault("gui", {}) @@ -6099,13 +6026,13 @@ def render_theme_panel(app: App) -> None: imgui.end() if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_theme_panel") +#NOTE(Ed): This was part of an experiment to see what the ai could generate and if it could figure how to add +# A post-process mechanism to the gui (it did not). def render_shader_live_editor(app: App) -> None: - """ - Renders the Shader Live Editor window. Exposes real-time sliders for CRT curvature, + """Renders the Shader Live Editor window. Exposes real-time sliders for CRT curvature, scanline intensity, and bloom threshold. - SSDL Shape: - `[I:shader_uniforms] => [I:slider_controls]` + SSDL: `[I:shader_uniforms] => [I:slider_controls]` ASCII Layout Map: +---------------------------------------------------------+ @@ -6114,24 +6041,21 @@ def render_shader_live_editor(app: App) -> None: | Scanline Intensity: [==|=========] 0.20 | | Bloom Threshold: [===|========] 0.30 | +---------------------------------------------------------+ - - [C: tests/test_shader_live_editor.py:test_shader_live_editor_renders] """ if app.show_windows.get('Shader Editor', False): with imscope.window('Shader Editor', app.show_windows['Shader Editor']) as (exp, opened): app.show_windows['Shader Editor'] = bool(opened) if exp: - changed_crt, app.shader_uniforms['crt'] = imgui.slider_float('CRT Curvature', app.shader_uniforms['crt'], 0.0, 2.0) - changed_scan, app.shader_uniforms['scanline'] = imgui.slider_float('Scanline Intensity', app.shader_uniforms['scanline'], 0.0, 1.0) - changed_bloom, app.shader_uniforms['bloom'] = imgui.slider_float('Bloom Threshold', app.shader_uniforms['bloom'], 0.0, 1.0) + changed_crt, app.shader_uniforms['crt'] = imgui.slider_float('CRT Curvature', app.shader_uniforms['crt'], 0.0, 2.0) + changed_scan, app.shader_uniforms['scanline'] = imgui.slider_float('Scanline Intensity', app.shader_uniforms['scanline'], 0.0, 1.0) + changed_bloom, app.shader_uniforms['bloom'] = imgui.slider_float('Bloom Threshold', app.shader_uniforms['bloom'], 0.0, 1.0) +#TODO(Ed): This shouldn't be here, it needs to be within the test that uitlizes it. def render_markdown_test(app: App) -> None: - """ - Renders a static markdown test panel used to validate the markdown renderer. + """Renders a static markdown test panel used to validate the markdown renderer. Displays headers, bold/italic text, lists, links, and a code block. - SSDL Shape: - `[I:static_md_sample] => [I:rendered_markdown]` + SSDL: `[I:static_md_sample] => [I:rendered_markdown]` ASCII Layout Map: +---------------------------------------------------------+ @@ -6173,7 +6097,7 @@ def hello(): def render_error_tint(app: App) -> None: """Renders a red tint overlay if hot reload failed.""" if not HotReloader.is_error_state: return - draw_list = imgui.get_background_draw_list() + draw_list = imgui.get_background_draw_list() display_size = imgui.get_io().display_size # Translucent red tint_col = imgui.get_color_u32(theme.get_color("status_error", alpha=0.2)) @@ -6184,7 +6108,6 @@ def render_error_tint(app: App) -> None: imgui.text_colored(theme.get_color("status_error"), "HOT RELOAD ERROR") imgui.text_wrapped(HotReloader.last_error or "Unknown error") - def render_project_stale_tint(app: App) -> None: """Renders a yellow/amber tint overlay when the project is mid-switch. @@ -6192,32 +6115,29 @@ def render_project_stale_tint(app: App) -> None: on the controller's is_project_stale() returning False. """ if not app.controller.is_project_stale(): return - draw_list = imgui.get_background_draw_list() + draw_list = imgui.get_background_draw_list() display_size = imgui.get_io().display_size - tint_col = imgui.get_color_u32(theme.get_color("status_warning", alpha=0.15)) + tint_col = imgui.get_color_u32(theme.get_color("status_warning", alpha=0.15)) draw_list.add_rect_filled(imgui.ImVec2(0, 0), display_size, tint_col) pending = app.controller._project_switch_pending_path or app.controller.active_project_path imgui.set_next_window_pos(imgui.ImVec2(10, 50)) with imscope.window("Project Stale", None, imgui.WindowFlags_.always_auto_resize | imgui.WindowFlags_.no_title_bar | - imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_move): + imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_move): imgui.text_colored(theme.get_color("status_warning"), "PROJECT SWITCHING") imgui.text_wrapped(f"Loading: {Path(pending).stem if pending else '?'}") imgui.text_wrapped("UI is read-only until the switch completes. You can still browse tabs.") - def render_heavy_text(app: App, label: str, content: str, id_suffix: str = "") -> None: - """ - Renders a labelled heavy-text field: a [+] button to pop content into the Text Viewer, + """Renders a labelled heavy-text field: a [+] button to pop content into the Text Viewer, a truncated inline preview, and a scrollable child panel showing the full content (markdown-rendered for message/text/content/system labels; plain text otherwise). - SSDL Shape: - `[B:pop_out_viewer] -> [I:label_preview] -> [I:content_child]` + SSDL: `[B:pop_out_viewer] -> [I:label_preview] -> [I:content_child]` ASCII Layout Map: +---------------------------------------------------------+ - | [+] message: "You are a helpful assistant that..." | + | [+] message: "You are a helpful assistant that..." | | +-----------------------------------------------------+ | | | You are a helpful assistant that specializes in... | | | | ...full rendered content... | | @@ -6225,9 +6145,9 @@ def render_heavy_text(app: App, label: str, content: str, id_suffix: str = "") - +---------------------------------------------------------+ """ if imgui.button(f"[+]##{label}{id_suffix}"): - app.text_viewer_type = 'markdown' if label in ('message', 'text', 'content', 'system') else 'json' if label in ('tool_calls', 'data') else 'powershell' if label == 'script' else 'text' - app.text_viewer_title = label - app.text_viewer_content = content + app.text_viewer_type = 'markdown' if label in ('message', 'text', 'content', 'system') else 'json' if label in ('tool_calls', 'data') else 'powershell' if label == 'script' else 'text' + app.text_viewer_title = label + app.text_viewer_content = content app.show_windows["Text Viewer"] = True imgui.same_line() imgui.text_colored(C_LBL(), f"{label}:"); imgui.same_line() @@ -6248,7 +6168,6 @@ def render_heavy_text(app: App, label: str, content: str, id_suffix: str = "") - else: imgui.text_colored(theme.get_color("text"), content) - #endregion: Misc Tools #region: MMA @@ -6381,10 +6300,10 @@ def render_mma_modals(app: App) -> None: role, ticket_id = app._pending_mma_spawns[0].get("role", "??"), app._pending_mma_spawns[0].get("ticket_id", "??") imgui.text(f"Spawning {role} for Ticket {ticket_id}"); imgui.separator() if app._mma_spawn_edit_mode: - imgui.text("Edit Prompt:"); _, app._mma_spawn_prompt = imgui.input_text_multiline("##spawn_prompt", app._mma_spawn_prompt, imgui.ImVec2(800, 200)) + imgui.text("Edit Prompt:"); _, app._mma_spawn_prompt = imgui.input_text_multiline("##spawn_prompt", app._mma_spawn_prompt, imgui.ImVec2(800, 200)) imgui.text("Edit Context MD:"); _, app._mma_spawn_context = imgui.input_text_multiline("##spawn_context", app._mma_spawn_context, imgui.ImVec2(800, 300)) else: - imgui.text("Proposed Prompt:"); imgui.begin_child("spawn_prompt_preview", imgui.ImVec2(800, 150), True); imgui.text_unformatted(app._mma_spawn_prompt); imgui.end_child() + imgui.text("Proposed Prompt:"); imgui.begin_child("spawn_prompt_preview", imgui.ImVec2(800, 150), True); imgui.text_unformatted(app._mma_spawn_prompt); imgui.end_child() imgui.text("Proposed Context MD:"); imgui.begin_child("spawn_context_preview", imgui.ImVec2(800, 250), True); imgui.text_unformatted(app._mma_spawn_context); imgui.end_child() imgui.separator() if imgui.button("Approve", imgui.ImVec2(120, 0)): app._handle_mma_respond(approved=True, prompt=app._mma_spawn_prompt, context_md=app._mma_spawn_context); imgui.close_current_popup() @@ -6419,18 +6338,18 @@ def render_mma_track_summary(app: App) -> None: track_name = app.active_track.description if app.active_track else "None" if getattr(app, "ui_project_execution_mode", "native") == "beads": track_name = "Beads Graph" track_stats = project_manager.calculate_track_progress(app.active_track.tickets if app.active_track else app.active_tickets) - total_cost = sum(cost_tracker.estimate_cost(u.get('model','unknown'), u.get('input',0), u.get('output',0)) for u in app.mma_tier_usage.values()) + total_cost = sum(cost_tracker.estimate_cost(u.get('model','unknown'), u.get('input',0), u.get('output',0)) for u in app.mma_tier_usage.values()) imgui.text("Track:"); imgui.same_line(); imgui.text_colored(C_VAL(), track_name); imgui.same_line(); imgui.text(" | Status:"); imgui.same_line() if app.mma_status == "paused": imgui.text_colored(theme.get_color("status_warning") if is_nerv else theme.get_color("status_warning"), "PIPELINE PAUSED"); imgui.same_line() status_col = imgui.ImVec4(1, 1, 1, 1) - if app.mma_status == "idle": status_col = theme.get_color("text_disabled") + if app.mma_status == "idle": status_col = theme.get_color("text_disabled") elif app.mma_status == "running": status_col = theme.get_color("status_success") if is_nerv else theme.get_color("status_warning") - elif app.mma_status == "done": status_col = theme.get_color("status_success") - elif app.mma_status == "error": status_col = theme.get_color("status_error") if is_nerv else theme.get_color("status_error") - elif app.mma_status == "paused": status_col = theme.get_color("status_warning") + elif app.mma_status == "done": status_col = theme.get_color("status_success") + elif app.mma_status == "error": status_col = theme.get_color("status_error") if is_nerv else theme.get_color("status_error") + elif app.mma_status == "paused": status_col = theme.get_color("status_warning") imgui.text_colored(status_col, app.mma_status.upper()); imgui.same_line(); imgui.text(" | Cost:"); imgui.same_line(); imgui.text_colored(theme.get_color("status_success"), f"${total_cost:,.4f}") - perc = track_stats["percentage"] / 100.0 + perc = track_stats["percentage"] / 100.0 p_color = theme.get_color("status_error") if track_stats["percentage"] < 33 else (theme.get_color("status_warning") if track_stats["percentage"] < 66 else theme.get_color("status_success")) imgui.push_style_color(imgui.Col_.plot_histogram, p_color); imgui.progress_bar(perc, imgui.ImVec2(-1, 0), f"{track_stats['percentage']:.1f}%"); imgui.pop_style_color() if imgui.begin_table("ticket_stats_breakdown", 4): @@ -6439,7 +6358,7 @@ def render_mma_track_summary(app: App) -> None: imgui.end_table() if app.active_track: remaining = track_stats["total"] - track_stats["completed"] - eta_mins = (app._avg_ticket_time * remaining) / 60.0 + eta_mins = (app._avg_ticket_time * remaining) / 60.0 imgui.text_colored(C_LBL(), "ETA:"); imgui.same_line(); imgui.text_colored(C_VAL(), f"~{int(eta_mins)}m ({remaining} tickets remaining)") def render_mma_epic_planner(app: App) -> None: @@ -6507,10 +6426,10 @@ def render_mma_track_browser(app: App) -> None: for track in app.tracks: imgui.table_next_row(); imgui.table_next_column(); imgui.text(track.get("title", "Untitled")); imgui.table_next_column() status = track.get("status", "unknown").lower() - c = theme.get_color("text_disabled") if status == "new" else (theme.get_color("status_success") if status == "active" and theme.is_nerv_active() else (theme.get_color("status_warning") if status == "active" else (theme.get_color("status_success") if status == "done" else (theme.get_color("status_error") if status == "blocked" else imgui.ImVec4(1, 1, 1, 1))))) + c = theme.get_color("text_disabled") if status == "new" else (theme.get_color("status_success") if status == "active" and theme.is_nerv_active() else (theme.get_color("status_warning") if status == "active" else (theme.get_color("status_success") if status == "done" else (theme.get_color("status_error") if status == "blocked" else imgui.ImVec4(1, 1, 1, 1))))) imgui.text_colored(c, status.upper()); imgui.table_next_column() prog = track.get("progress", 0.0) - p_c = theme.get_color("status_error") if prog < 0.33 else (theme.get_color("status_warning") if prog < 0.66 else theme.get_color("status_success")) + p_c = theme.get_color("status_error") if prog < 0.33 else (theme.get_color("status_warning") if prog < 0.66 else theme.get_color("status_success")) imgui.push_style_color(imgui.Col_.plot_histogram, p_c); imgui.progress_bar(prog, imgui.ImVec2(-1, 0), f"{int(prog*100)}%"); imgui.pop_style_color(); imgui.table_next_column() if imgui.button(f"Load##{track.get('id')}"): app._cb_load_track(str(track.get("id") or "")) imgui.end_table() @@ -6554,7 +6473,7 @@ def render_mma_global_controls(app: App) -> None: any_pending = len(app._pending_mma_spawns) > 0 or len(app._pending_mma_approvals) > 0 or app._pending_ask_dialog if any_pending: alpha = abs(math.sin(time.time() * 5)) - c = theme.get_color("status_error", alpha=alpha) + c = theme.get_color("status_error", alpha=alpha) imgui.same_line(); imgui.text_colored(c, " APPROVAL PENDING"); imgui.same_line() if imgui.button("Go to Approval"): pass imgui.separator() @@ -6562,10 +6481,8 @@ def render_mma_global_controls(app: App) -> None: imgui.same_line() if imgui.button("Reload GUI"): success = app._trigger_hot_reload() - if success: - imgui.text_colored(theme.get_color("status_success"), "Reloaded!") - else: - imgui.text_colored(theme.get_color("status_error"), f"Error: {app._hot_reload_error or 'Unknown'}") + if success: imgui.text_colored(theme.get_color("status_success"), "Reloaded!") + else: imgui.text_colored(theme.get_color("status_error"), f"Error: {app._hot_reload_error or 'Unknown'}") imgui.same_line(); imgui.text_disabled("(Ctrl+Alt+R)") def render_mma_usage_section(app: App) -> None: @@ -6593,8 +6510,23 @@ def render_mma_usage_section(app: App) -> None: imgui.table_setup_column("Tier"); imgui.table_setup_column("Model"); imgui.table_setup_column("Input"); imgui.table_setup_column("Output"); imgui.table_setup_column("Est. Cost"); imgui.table_headers_row() total_cost = 0.0 for tier, stats in app.mma_tier_usage.items(): - imgui.table_next_row(); imgui.table_next_column(); imgui.text(tier); imgui.table_next_column(); model = stats.get('model', 'unknown'); imgui.text(model); imgui.table_next_column(); in_t = stats.get('input', 0); imgui.text(f"{in_t:,}"); imgui.table_next_column(); out_t = stats.get('output', 0); imgui.text(f"{out_t:,}"); imgui.table_next_column(); cost = cost_tracker.estimate_cost(model, in_t, out_t); total_cost += cost; imgui.text(f"${cost:,.4f}") - imgui.table_next_row(); imgui.table_set_bg_color(imgui.TableBgTarget_.row_bg0, imgui.get_color_u32(imgui.Col_.plot_lines_hovered)); imgui.table_next_column(); imgui.text("TOTAL"); imgui.table_next_column(); imgui.text(""); imgui.table_next_column(); imgui.text(""); imgui.table_next_column(); imgui.text(""); imgui.table_next_column(); imgui.text(f"${total_cost:,.4f}"); imgui.end_table() + imgui.table_next_row(); + imgui.table_next_column(); + imgui.text(tier); imgui.table_next_column(); + model = stats.get('model', 'unknown'); imgui.text(model); imgui.table_next_column(); + in_t = stats.get('input', 0); imgui.text(f"{in_t:,}"); imgui.table_next_column(); + out_t = stats.get('output', 0); imgui.text(f"{out_t:,}"); imgui.table_next_column(); + cost = cost_tracker.estimate_cost(model, in_t, out_t); + total_cost += cost; imgui.text(f"${cost:,.4f}") + imgui.table_next_row(); + imgui.table_set_bg_color(imgui.TableBgTarget_.row_bg0, imgui.get_color_u32(imgui.Col_.plot_lines_hovered)); imgui.table_next_column(); + imgui.text("TOTAL"); imgui.table_next_column(); + imgui.text(""); imgui.table_next_column(); + imgui.text(""); imgui.table_next_column(); + imgui.text(""); imgui.table_next_column(); + imgui.text(f"${total_cost:,.4f}"); + imgui.end_table() + if imgui.collapsing_header("Tier Model Config"): for tier in app.mma_tier_usage.keys(): imgui.text(f"{tier}:"); imgui.same_line(); curr_model, curr_prov = app.mma_tier_usage[tier].get("model", "unknown"), app.mma_tier_usage[tier].get("provider", "gemini") @@ -6622,7 +6554,7 @@ def render_mma_usage_section(app: App) -> None: imgui.end_combo() imgui.pop_item_width(); imgui.same_line(); imgui.push_item_width(150) curr_pers = app.mma_tier_usage[tier].get("persona") or "None" - personas = getattr(app.controller, 'personas', {}) + personas = getattr(app.controller, 'personas', {}) pers_opts = ["None"] + sorted(personas.keys()) if imgui.begin_combo("##persona", curr_pers): for pern in pers_opts: @@ -6631,12 +6563,10 @@ def render_mma_usage_section(app: App) -> None: imgui.pop_item_width() def render_mma_ticket_editor(app: App) -> None: - """ - Renders the ticket detail editor panel, letting the user modify priority, target, + """Renders the ticket detail editor panel, letting the user modify priority, target, and persona override, mark a ticket complete, or delete it. - SSDL Shape: - `[I:ticket_details] -> [B:combo_selectors] => [B:action_buttons]` + SSDL: `[I:ticket_details] -> [B:combo_selectors] => [B:action_buttons]` ASCII Layout Map: +---------------------------------------------------------+ @@ -6661,19 +6591,21 @@ def render_mma_ticket_editor(app: App) -> None: imgui.text(f"Target: {ticket.get('target_file', '')}"); imgui.text(f"Depends on: {', '.join(ticket.get('depends_on', []))}") personas = getattr(app.controller, 'personas', {}); curr_pers = ticket.get('persona_id', '') imgui.text("Persona Override:"); imgui.same_line() - pers_opts = ["None"] + sorted(personas.keys()); curr_idx = pers_opts.index(curr_pers) + 1 if curr_pers in pers_opts else 0 - _, curr_idx = imgui.combo(f"##ticket_persona_{ticket.get('id')}", curr_idx, pers_opts) + pers_opts = ["None"] + sorted(personas.keys()); + curr_idx = pers_opts.index(curr_pers) + 1 if curr_pers in pers_opts else 0 + _, curr_idx = imgui.combo(f"##ticket_persona_{ticket.get('id')}", curr_idx, pers_opts) ticket['persona_id'] = None if curr_idx == 0 or pers_opts[curr_idx] == "None" else pers_opts[curr_idx] if imgui.button(f"Mark Complete##{app.ui_selected_ticket_id}"): ticket['status'] = 'done'; app._push_mma_state_update() imgui.same_line() - if imgui.button(f"Delete##{app.ui_selected_ticket_id}"): app.active_tickets = [t for t in app.active_tickets if str(t.get('id', '')) != app.ui_selected_ticket_id]; app.ui_selected_ticket_id = None; app._push_mma_state_update() + if imgui.button(f"Delete##{app.ui_selected_ticket_id}"): + app.active_tickets = [t for t in app.active_tickets if str(t.get('id', '')) != app.ui_selected_ticket_id] + app.ui_selected_ticket_id = None + app._push_mma_state_update() def render_mma_agent_streams(app: App) -> None: - """ - Renders the agent execution stream panels in a tabbed view for Tier 1, 2, 3, and 4. + """Renders the agent execution stream panels in a tabbed view for Tier 1, 2, 3, and 4. - SSDL Shape: - `[I] -> [B:tab_bar] => [I:stream_panels]` + SSDL: `[I] -> [B:tab_bar] => [I:stream_panels]` ASCII Layout Map: +---------------------------------------------------------+ @@ -6689,7 +6621,8 @@ def render_mma_agent_streams(app: App) -> None: for tier, label, sep_flag_attr in [("Tier 1", "Tier 1", "ui_separate_tier1"), ("Tier 2", "Tier 2 (Tech Lead)", "ui_separate_tier2"), ("Tier 3", None, "ui_separate_tier3"), ("Tier 4", "Tier 4 (QA)", "ui_separate_tier4")]: with imscope.tab_item(tier) as (exp, _): if exp: - sep_val = getattr(app, sep_flag_attr); ch, new_val = imgui.checkbox(f"Pop Out {tier}", sep_val) + sep_val = getattr(app, sep_flag_attr); + ch, new_val = imgui.checkbox(f"Pop Out {tier}", sep_val) if ch: setattr(app, sep_flag_attr, new_val) app.show_windows[f"{tier}: Strategy" if tier == "Tier 1" else (f"{tier}: Tech Lead" if tier == "Tier 2" else (f"{tier}: Workers" if tier == "Tier 3" else f"{tier}: QA"))] = new_val @@ -6753,16 +6686,13 @@ def render_tier_stream_panel(app: App, tier_key: str, stream_key: str | None) -> for key in tier3_keys: ticket_id = key.split(": ", 1)[-1] if ": " in key else key status = worker_status.get(key, "unknown") - if status == "running": - imgui.text_colored(theme.get_color("status_warning"), f"{ticket_id} [{status}]") - elif status == "completed": - imgui.text_colored(theme.get_color("status_success"), f"{ticket_id} [{status}]") - elif status == "failed": - imgui.text_colored(theme.get_color("status_error"), f"{ticket_id} [{status}]") - else: - imgui.text(f"{ticket_id} [{status}]") + if status == "running": imgui.text_colored(theme.get_color("status_warning"), f"{ticket_id} [{status}]") + elif status == "completed": imgui.text_colored(theme.get_color("status_success"), f"{ticket_id} [{status}]") + elif status == "failed": imgui.text_colored(theme.get_color("status_error"), f"{ticket_id} [{status}]") + else: imgui.text( f"{ticket_id} [{status}]") imgui.begin_child(f"##tier3_{ticket_id}_scroll", imgui.ImVec2(-1, 150), True) render_selectable_label(app, f'stream_t3_{ticket_id}', app.mma_streams[key], width=-1, multiline=True, height=0) + #NOTE(Ed): Exception(Thirdparty) try: if len(app.mma_streams[key]) != app._tier_stream_last_len.get(key, -1): imgui.set_scroll_here_y(1.0) @@ -6797,7 +6727,7 @@ def render_track_proposal_modal(app: App) -> None: if app._show_track_proposal_modal: imgui.open_popup("Track Proposal") if imgui.begin_popup_modal("Track Proposal", True, imgui.WindowFlags_.always_auto_resize)[0]: - from src import shaders + from src import shaders #TODO(Ed): Review local import p_min = imgui.get_window_pos() p_max = imgui.ImVec2(p_min.x + imgui.get_window_size().x, p_min.y + imgui.get_window_size().y) # Render soft shadow behind the modal @@ -6879,13 +6809,13 @@ def render_ticket_queue(app: App) -> None: # 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("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_setup_column("Actions", imgui.TableColumnFlags_.width_fixed, 80) imgui.table_headers_row() for i, t in enumerate(app.active_tickets): @@ -6960,7 +6890,7 @@ def render_ticket_queue(app: App) -> None: # Actions - Kill button for in_progress tickets imgui.table_next_column() status = t.get('status', 'todo') - if status == 'in_progress': + if status == 'in_progress': if imgui.button(f"Kill##{tid}"): app._cb_kill_ticket(tid) elif status == 'todo': if imgui.button(f"Block##{tid}"): app._cb_block_ticket(tid) @@ -7001,7 +6931,7 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer break break for t in app.active_tickets: - tid = str(t.get('id', '??')) + tid = str(t.get('id', '??')) int_id = abs(hash(tid)) ed.begin_node(ed.NodeId(int_id)) if getattr(app, "ui_project_execution_mode", "native") == "beads": @@ -7009,9 +6939,9 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer 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() + s_col = C_VAL() + if status == 'done' or status == 'complete': s_col = C_IN() + elif status == 'in_progress' or status == 'running': s_col = C_OUT() elif status == 'error': s_col = theme.get_color("status_error") imgui.text("Status: ") imgui.same_line() @@ -7062,22 +6992,25 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer if ed.accept_deleted_item(): lid_val = link_id.id() for t in app.active_tickets: - tid = str(t.get('id', '')) + tid = str(t.get('id', '')) deps = t.get('depends_on', []) if any(abs(hash(d + "_" + tid)) == lid_val for d in deps): t['depends_on'] = [dep for dep in deps if abs(hash(dep + "_" + tid)) != lid_val] app._push_mma_state_update() break ed.end_delete() + # Validate DAG after any changes + #TODO(Ed): Exception(Review) try: - from src.dag_engine import TrackDAG + from src.dag_engine import TrackDAG #TODO(Ed) Reivew local import ticket_dicts = [{'id': str(t.get('id', '')), 'depends_on': t.get('depends_on', [])} for t in app.active_tickets] - temp_dag = TrackDAG(ticket_dicts) + 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() @@ -7089,6 +7022,7 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer for t in app.active_tickets: tid = t.get('id', '') if tid.startswith('T-'): + #TODO(Ed): Exception(Review) try: max_id = max(max_id, int(tid[2:])) except: pass app.ui_new_ticket_id = f"T-{max_id + 1:03d}"