From 8bb9287dfe8133ad55c6b9a987c0e0488b45878a Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 10 May 2026 11:50:00 -0400 Subject: [PATCH] refactor(ui): cull unused UI helpers and redundant modules --- src/diff_viewer.py | 372 +++++++++++--------------- src/models.py | 75 ------ src/native_orchestrator.py | 139 ---------- src/shader_manager.py | 171 ------------ src/shaders.py | 106 +++----- src/theme.py | 424 ------------------------------ tests/test_diff_viewer.py | 55 +--- tests/test_dynamic_background.py | 33 --- tests/test_native_orchestrator.py | 75 ------ tests/test_post_process.py | 53 ---- tests/test_shader_config.py | 23 -- tests/test_shader_manager.py | 51 ---- 12 files changed, 188 insertions(+), 1389 deletions(-) delete mode 100644 src/native_orchestrator.py delete mode 100644 src/shader_manager.py delete mode 100644 src/theme.py delete mode 100644 tests/test_dynamic_background.py delete mode 100644 tests/test_native_orchestrator.py delete mode 100644 tests/test_post_process.py delete mode 100644 tests/test_shader_config.py delete mode 100644 tests/test_shader_manager.py diff --git a/src/diff_viewer.py b/src/diff_viewer.py index ce6e033..b7b3072 100644 --- a/src/diff_viewer.py +++ b/src/diff_viewer.py @@ -6,239 +6,169 @@ from pathlib import Path @dataclass class DiffHunk: - header: str - lines: List[str] - old_start: int - old_count: int - new_start: int - new_count: int + header: str + lines: List[str] + old_start: int + old_count: int + new_start: int + new_count: int @dataclass class DiffFile: - old_path: str - new_path: str - hunks: List[DiffHunk] - -def parse_diff_header(line: str) -> tuple[Optional[str], Optional[str], Optional[tuple[int, int, int, int]]]: - if not line.startswith(("--- ", "+++ ")): - return None, None, None - - if line.startswith("--- "): - path = line[4:] - if path.startswith("a/"): - path = path[2:] - return path, None, None - elif line.startswith("+++ "): - path = line[4:] - if path.startswith("b/"): - path = path[2:] - return None, path, None - return None, None, None + old_path: str + new_path: str + hunks: List[DiffHunk] def parse_hunk_header(line: str) -> Optional[tuple[int, int, int, int]]: - """ - [C: tests/test_diff_viewer.py:test_parse_hunk_header] - """ - if not line.startswith("@@"): - return None - - parts = line.split() - if len(parts) < 2: - return None - - old_part = parts[1][1:] - new_part = parts[2][1:] - - old_parts = old_part.split(",") - new_parts = new_part.split(",") - - old_start = int(old_parts[0]) - old_count = int(old_parts[1]) if len(old_parts) > 1 else 1 - new_start = int(new_parts[0]) - new_count = int(new_parts[1]) if len(new_parts) > 1 else 1 - - return (old_start, old_count, new_start, new_count) + """ + [C: tests/test_diff_viewer.py:test_parse_hunk_header] + """ + if not line.startswith("@@"): + return None + + parts = line.split() + if len(parts) < 2: + return None + + old_part = parts[1][1:] + new_part = parts[2][1:] + + old_parts = old_part.split(",") + new_parts = new_part.split(",") + + old_start = int(old_parts[0]) + old_count = int(old_parts[1]) if len(old_parts) > 1 else 1 + new_start = int(new_parts[0]) + new_count = int(new_parts[1]) if len(new_parts) > 1 else 1 + + return (old_start, old_count, new_start, new_count) def parse_diff(diff_text: str) -> List[DiffFile]: - """ - [C: src/gui_2.py:App.request_patch_from_tier4, tests/test_diff_viewer.py:test_diff_line_classification, tests/test_diff_viewer.py:test_parse_diff_empty, tests/test_diff_viewer.py:test_parse_diff_none, tests/test_diff_viewer.py:test_parse_diff_with_context, tests/test_diff_viewer.py:test_parse_multiple_files, tests/test_diff_viewer.py:test_parse_simple_diff, tests/test_diff_viewer.py:test_render_diff_text_immediate] - """ - if not diff_text or not diff_text.strip(): - return [] - - files: List[DiffFile] = [] - current_file: Optional[DiffFile] = None - current_hunk: Optional[DiffHunk] = None - - for line in diff_text.split("\n"): - if line.startswith("--- "): - if current_file: - if current_hunk: - current_file.hunks.append(current_hunk) - current_hunk = None - files.append(current_file) - - path = line[4:] - if path.startswith("a/"): - path = path[2:] - current_file = DiffFile(old_path=path, new_path="", hunks=[]) - - elif line.startswith("+++ ") and current_file: - path = line[4:] - if path.startswith("b/"): - path = path[2:] - current_file.new_path = path - - elif line.startswith("@@") and current_file: - if current_hunk: - current_file.hunks.append(current_hunk) - - hunk_info = parse_hunk_header(line) - if hunk_info: - old_start, old_count, new_start, new_count = hunk_info - current_hunk = DiffHunk( - header=line, - lines=[], - old_start=old_start, - old_count=old_count, - new_start=new_start, - new_count=new_count - ) - else: - current_hunk = DiffHunk( - header=line, - lines=[], - old_start=0, - old_count=0, - new_start=0, - new_count=0 - ) - - elif current_hunk is not None: - current_hunk.lines.append(line) - - elif line and not line.startswith("diff ") and not line.startswith("index "): - pass - - if current_file: + """ + [C: src/gui_2.py:App.request_patch_from_tier4, tests/test_diff_viewer.py:test_diff_line_classification, tests/test_diff_viewer.py:test_parse_diff_empty, tests/test_diff_viewer.py:test_parse_diff_none, tests/test_diff_viewer.py:test_parse_diff_with_context, tests/test_diff_viewer.py:test_parse_multiple_files, tests/test_diff_viewer.py:test_parse_simple_diff, tests/test_diff_viewer.py:test_render_diff_text_immediate] + """ + if not diff_text or not diff_text.strip(): + return [] + + files: List[DiffFile] = [] + current_file: Optional[DiffFile] = None + current_hunk: Optional[DiffHunk] = None + + for line in diff_text.split("\n"): + if line.startswith("--- "): + if current_file: if current_hunk: - current_file.hunks.append(current_hunk) + current_file.hunks.append(current_hunk) + current_hunk = None files.append(current_file) - - return files - -def format_diff_for_display(diff_files: List[DiffFile]) -> str: - output = [] - for df in diff_files: - output.append(f"File: {df.old_path}") - for hunk in df.hunks: - output.append(f" {hunk.header}") - for line in hunk.lines: - output.append(f" {line}") - return "\n".join(output) + + path = line[4:] + if path.startswith("a/"): + path = path[2:] + current_file = DiffFile(old_path=path, new_path="", hunks=[]) + + elif line.startswith("+++ ") and current_file: + path = line[4:] + if path.startswith("b/"): + path = path[2:] + current_file.new_path = path + + elif line.startswith("@@") and current_file: + if current_hunk: + current_file.hunks.append(current_hunk) + + hunk_info = parse_hunk_header(line) + if hunk_info: + old_start, old_count, new_start, new_count = hunk_info + current_hunk = DiffHunk( + header=line, + lines=[], + old_start=old_start, + old_count=old_count, + new_start=new_start, + new_count=new_count + ) + else: + current_hunk = DiffHunk( + header=line, + lines=[], + old_start=0, + old_count=0, + new_start=0, + new_count=0 + ) + + elif current_hunk is not None: + current_hunk.lines.append(line) + + elif line and not line.startswith("diff ") and not line.startswith("index "): + pass + + if current_file: + if current_hunk: + current_file.hunks.append(current_hunk) + files.append(current_file) + + return files def get_line_color(line: str) -> Optional[str]: - """ - [C: tests/test_diff_viewer.py:test_get_line_color] - """ - if line.startswith("+"): - return "green" - elif line.startswith("-"): - return "red" - elif line.startswith("@@"): - return "cyan" - return None - -def render_diff_text_immediate(diff_files: List[DiffFile]) -> List[tuple[str, Optional[str]]]: - """ - [C: tests/test_diff_viewer.py:test_render_diff_text_immediate] - """ - output: List[tuple[str, Optional[str]]] = [] - for df in diff_files: - output.append((f"File: {df.old_path}", "white")) - for hunk in df.hunks: - output.append((hunk.header, "cyan")) - for line in hunk.lines: - color = get_line_color(line) - output.append((line, color)) - return output - -def create_backup(file_path: str) -> Optional[str]: - """ - [C: tests/test_diff_viewer.py:test_create_backup, tests/test_diff_viewer.py:test_create_backup_nonexistent] - """ - path = Path(file_path) - if not path.exists(): - return None - backup_path = path.with_suffix(path.suffix + ".backup") - shutil.copy2(path, backup_path) - return str(backup_path) + """ + [C: tests/test_diff_viewer.py:test_get_line_color] + """ + if line.startswith("+"): + return "green" + elif line.startswith("-"): + return "red" + elif line.startswith("@@"): + return "cyan" + return None def apply_patch_to_file(patch_text: str, base_dir: str = ".") -> Tuple[bool, str]: - """ - [C: src/gui_2.py:App._apply_pending_patch, tests/test_diff_viewer.py:test_apply_patch_simple, tests/test_diff_viewer.py:test_apply_patch_with_context] - """ - import difflib + """ + [C: src/gui_2.py:App._apply_pending_patch, tests/test_diff_viewer.py:test_apply_patch_simple, tests/test_diff_viewer.py:test_apply_patch_with_context] + """ + import difflib + + diff_files = parse_diff(patch_text) + if not diff_files: + return False, "No valid diff found" + + results = [] + for df in diff_files: + file_path = Path(base_dir) / df.old_path + if not file_path.exists(): + results.append(f"File not found: {file_path}") + continue - diff_files = parse_diff(patch_text) - if not diff_files: - return False, "No valid diff found" - - results = [] - for df in diff_files: - file_path = Path(base_dir) / df.old_path - if not file_path.exists(): - results.append(f"File not found: {file_path}") - continue + try: + with open(file_path, "r", encoding="utf-8") as f: + original_lines = f.read().splitlines(keepends=True) + + new_lines = original_lines.copy() + offset = 0 + + for hunk in df.hunks: + hunk_old_start = hunk.old_start - 1 + hunk_old_count = hunk.old_count - try: - with open(file_path, "r", encoding="utf-8") as f: - original_lines = f.read().splitlines(keepends=True) - - new_lines = original_lines.copy() - offset = 0 - - for hunk in df.hunks: - hunk_old_start = hunk.old_start - 1 - hunk_old_count = hunk.old_count - - replace_start = hunk_old_start + offset - replace_count = hunk_old_count - - hunk_new_content: List[str] = [] - for line in hunk.lines: - if line.startswith("+") and not line.startswith("+++"): - hunk_new_content.append(line[1:] + "\n") - elif line.startswith(" ") or (line and not line.startswith(("-", "+", "@@"))): - hunk_new_content.append(line + "\n") - - new_lines = new_lines[:replace_start] + hunk_new_content + new_lines[replace_start + replace_count:] - offset += len(hunk_new_content) - replace_count - - with open(file_path, "w", encoding="utf-8", newline="") as f: - f.writelines(new_lines) - - results.append(f"Patched: {file_path}") - except Exception as e: - return False, f"Error patching {file_path}: {e}" - - return True, "\n".join(results) - -def restore_from_backup(file_path: str) -> bool: - """ - [C: tests/test_diff_viewer.py:test_restore_from_backup] - """ - backup_path = Path(str(file_path) + ".backup") - if not backup_path.exists(): - return False - - shutil.copy2(backup_path, file_path) - return True - -def cleanup_backup(file_path: str) -> None: - """ - [C: tests/test_diff_viewer.py:test_cleanup_backup] - """ - backup_path = Path(str(file_path) + ".backup") - if backup_path.exists(): - backup_path.unlink() \ No newline at end of file + replace_start = hunk_old_start + offset + replace_count = hunk_old_count + + hunk_new_content: List[str] = [] + for line in hunk.lines: + if line.startswith("+") and not line.startswith("+++"): + hunk_new_content.append(line[1:] + "\n") + elif line.startswith(" ") or (line and not line.startswith(("-", "+", "@@"))): + hunk_new_content.append(line + "\n") + + new_lines = new_lines[:replace_start] + hunk_new_content + new_lines[replace_start + replace_count:] + offset += len(hunk_new_content) - replace_count + + with open(file_path, "w", encoding="utf-8", newline="") as f: + f.writelines(new_lines) + + results.append(f"Patched: {file_path}") + except Exception as e: + return False, f"Error patching {file_path}: {e}" + + return True, "\n".join(results) diff --git a/src/models.py b/src/models.py index bf148b2..1e46a75 100644 --- a/src/models.py +++ b/src/models.py @@ -325,8 +325,6 @@ class WorkerContext: persona_id: Optional[str] = None -@dataclass -@dataclass @dataclass class Metadata: id: str @@ -442,79 +440,6 @@ class ExternalEditorConfig: ) -@dataclass -class TextEditorConfig: - name: str - path: str - diff_args: List[str] = field(default_factory=list) - - def to_dict(self) -> Dict[str, Any]: - """ - [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] - """ - return { - "name": self.name, - "path": self.path, - "diff_args": self.diff_args, - } - - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "TextEditorConfig": - """ - [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] - """ - return cls( - name=data["name"], - path=data["path"], - diff_args=data.get("diff_args", []), - ) - - -@dataclass -class ExternalEditorConfig: - editors: Dict[str, TextEditorConfig] = field(default_factory=dict) - default_editor: Optional[str] = None - - def get_default(self) -> Optional[TextEditorConfig]: - """ - [C: tests/test_external_editor.py:TestExternalEditorConfig.test_get_default_fallback_to_first, tests/test_external_editor.py:TestExternalEditorConfig.test_get_default_returns_configured, tests/test_external_editor.py:TestExternalEditorConfig.test_get_default_returns_none_when_empty] - """ - if self.default_editor and self.default_editor in self.editors: - return self.editors[self.default_editor] - if self.editors: - return next(iter(self.editors.values())) - return None - - def to_dict(self) -> Dict[str, Any]: - """ - [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] - """ - return { - "editors": {k: v.to_dict() for k, v in self.editors.items()}, - "default_editor": self.default_editor, - } - - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "ExternalEditorConfig": - """ - [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] - """ - editors = {} - for name, ed_data in data.get("editors", {}).items(): - if isinstance(ed_data, dict): - editors[name] = TextEditorConfig( - name=name, - path=ed_data.get("path", ""), - diff_args=ed_data.get("diff_args", []), - ) - elif isinstance(ed_data, str): - editors[name] = TextEditorConfig(name=name, path=ed_data) - return cls( - editors=editors, - default_editor=data.get("default_editor"), - ) - - @dataclass class TrackState: metadata: Metadata diff --git a/src/native_orchestrator.py b/src/native_orchestrator.py deleted file mode 100644 index e56ebed..0000000 --- a/src/native_orchestrator.py +++ /dev/null @@ -1,139 +0,0 @@ -# src/native_orchestrator.py -# DEPRECATED: This module is legacy and is being replaced by src/orchestrator_pm.py -# and the MMA multi-agent conductor system. Avoid using this for new features. - -from pathlib import Path -from typing import Optional -import json -import re -from src import paths - -def read_plan(track_id: str, base_dir: str = ".") -> str: - """ - Reads the implementation plan (plan.md) for a track. - [C: tests/test_native_orchestrator.py:test_read_plan_nonexistent, tests/test_native_orchestrator.py:test_write_and_read_plan] - """ - plan_path = paths.get_track_state_dir(track_id, base_dir) / "plan.md" - if not plan_path.exists(): - return "" - return plan_path.read_text(encoding="utf-8") - -def write_plan(track_id: str, content: str, base_dir: str = ".") -> None: - """ - Writes the implementation plan (plan.md) for a track. - [C: tests/test_native_orchestrator.py:test_write_and_read_plan] - """ - plan_path = paths.get_track_state_dir(track_id, base_dir) / "plan.md" - plan_path.parent.mkdir(parents=True, exist_ok=True) - plan_path.write_text(content, encoding="utf-8") - -def parse_plan_tasks(content: str) -> list[dict[str, str]]: - """ - Parses the tasks from a plan.md file. - [C: tests/test_native_orchestrator.py:test_parse_plan_tasks] - """ - tasks = [] - for line in content.split("\n"): - stripped = line.strip() - if stripped.startswith("- ["): - checked = "[x]" in stripped - text = stripped[4:] if len(stripped) > 4 else "" - tasks.append({"text": text, "completed": checked}) - return tasks - -def read_metadata(track_id: str, base_dir: str = ".") -> dict: - """ - Reads the metadata (metadata.json) for a track. - [C: tests/test_native_orchestrator.py:test_read_metadata_nonexistent, tests/test_native_orchestrator.py:test_write_and_read_metadata] - """ - meta_path = paths.get_track_state_dir(track_id, base_dir) / "metadata.json" - if not meta_path.exists(): - return {} - return json.loads(meta_path.read_text(encoding="utf-8")) - -def write_metadata(track_id: str, data: dict, base_dir: str = ".") -> None: - """ - Writes the metadata (metadata.json) for a track. - [C: tests/test_native_orchestrator.py:test_write_and_read_metadata] - """ - meta_path = paths.get_track_state_dir(track_id, base_dir) / "metadata.json" - meta_path.parent.mkdir(parents=True, exist_ok=True) - meta_path.write_text(json.dumps(data, indent=2), encoding="utf-8") - -def get_track_dir(track_id: str, base_dir: str = ".") -> Path: - """ - Returns the state directory for a specific track. - [C: tests/test_native_orchestrator.py:test_get_track_dir] - """ - return paths.get_track_state_dir(track_id, base_dir) - -def get_archive_dir(base_dir: str = ".") -> Path: - """ - Returns the central archive directory for completed tracks. - [C: src/orchestrator_pm.py:get_track_history_summary, tests/test_native_orchestrator.py:test_get_archive_dir, tests/test_paths.py:test_conductor_dir_project_relative] - """ - return paths.get_archive_dir(base_dir) - -class NativeOrchestrator: - def __init__(self, base_dir: str = "."): - self.base_dir = Path(base_dir) - self._conductor = None - - def load_track(self, track_id: str) -> dict: - """ - Load track from metadata.json - [C: tests/test_native_orchestrator.py:test_native_orchestrator_class] - """ - return read_metadata(track_id, str(self.base_dir)) - - def save_track(self, track_id: str, data: dict) -> None: - """ - Persist track metadata - [C: tests/test_native_orchestrator.py:test_native_orchestrator_class] - """ - write_metadata(track_id, data, str(self.base_dir)) - - def load_plan(self, track_id: str) -> str: - """ - Load plan.md content - [C: tests/test_native_orchestrator.py:test_native_orchestrator_class] - """ - return read_plan(track_id, str(self.base_dir)) - - def save_plan(self, track_id: str, content: str) -> None: - """ - Persist plan.md content - [C: tests/test_native_orchestrator.py:test_native_orchestrator_class] - """ - write_plan(track_id, content, str(self.base_dir)) - - def get_track_tasks(self, track_id: str) -> list[dict]: - """ - Get parsed task list from plan.md - [C: tests/test_native_orchestrator.py:test_native_orchestrator_class] - """ - content = self.load_plan(track_id) - return parse_plan_tasks(content) - - def generate_tickets(self, brief: str, module_skeletons: str = "") -> list[dict]: - """ - Tier 2: Generate tickets from brief - [C: tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_retry_failure, tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_retry_success, tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_success, tests/test_orchestration_logic.py:test_generate_tickets] - """ - from src import conductor_tech_lead - return conductor_tech_lead.generate_tickets(brief, module_skeletons) - - def execute_ticket(self, ticket: dict, context: str) -> str: - """Tier 3: Execute single ticket""" - from src import ai_client - return ai_client.send(context, ticket.get("description", ""), str(self.base_dir)) - - def analyze_error(self, error: str) -> str: - """Tier 4: Analyze error""" - from src import ai_client - return ai_client.run_tier4_analysis(error) - - def run_tier4_patch(self, error: str, file_context: str) -> str: - """Tier 4: Generate patch for error""" - from src import ai_client - return ai_client.run_tier4_patch_generation(error, file_context) diff --git a/src/shader_manager.py b/src/shader_manager.py deleted file mode 100644 index 622ee43..0000000 --- a/src/shader_manager.py +++ /dev/null @@ -1,171 +0,0 @@ -import OpenGL.GL as gl - -class ShaderManager: - def __init__(self): - self.program = None - self.bg_program = None - self.pp_program = None - - def compile_shader(self, vertex_src: str, fragment_src: str) -> int: - """ - [C: tests/test_shader_manager.py:test_shader_manager_initialization_and_compilation] - """ - program = gl.glCreateProgram() - - def _compile(src, shader_type): - shader = gl.glCreateShader(shader_type) - gl.glShaderSource(shader, src) - gl.glCompileShader(shader) - - if not gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS): - info_log = gl.glGetShaderInfoLog(shader) - if hasattr(info_log, "decode"): - info_log = info_log.decode() - raise RuntimeError(f"Shader compilation failed: {info_log}") - return shader - - vert_shader = _compile(vertex_src, gl.GL_VERTEX_SHADER) - frag_shader = _compile(fragment_src, gl.GL_FRAGMENT_SHADER) - - gl.glAttachShader(program, vert_shader) - gl.glAttachShader(program, frag_shader) - gl.glLinkProgram(program) - - if not gl.glGetProgramiv(program, gl.GL_LINK_STATUS): - info_log = gl.glGetProgramInfoLog(program) - if hasattr(info_log, "decode"): - info_log = info_log.decode() - raise RuntimeError(f"Program linking failed: {info_log}") - - gl.glDeleteShader(vert_shader) - gl.glDeleteShader(frag_shader) - - self.program = program - return program - - def update_uniforms(self, uniforms: dict): - """ - [C: tests/test_shader_manager.py:test_shader_manager_uniform_update] - """ - if self.program is None: - return - - for name, value in uniforms.items(): - loc = gl.glGetUniformLocation(self.program, name) - if loc == -1: - continue - - if isinstance(value, float): - gl.glUniform1f(loc, value) - elif isinstance(value, int): - gl.glUniform1i(loc, value) - elif isinstance(value, (list, tuple)): - if len(value) == 2: - gl.glUniform2f(loc, value[0], value[1]) - elif len(value) == 3: - gl.glUniform3f(loc, value[0], value[1], value[2]) - elif len(value) == 4: - gl.glUniform4f(loc, value[0], value[1], value[2], value[3]) - - def setup_background_shader(self): - """ - [C: tests/test_dynamic_background.py:test_dynamic_background_rendering] - """ - vertex_src = """ -#version 330 core -const vec2 positions[4] = vec2[]( - vec2(-1.0, -1.0), - vec2( 1.0, -1.0), - vec2(-1.0, 1.0), - vec2( 1.0, 1.0) -); -void main() { - gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0); -} -""" - fragment_src = """ -#version 330 core -uniform float u_time; -uniform vec2 u_resolution; -out vec4 FragColor; -void main() { - vec2 uv = gl_FragCoord.xy / u_resolution.xy; - vec3 col = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0, 2, 4)); - FragColor = vec4(col, 1.0); -} -""" - self.bg_program = self.compile_shader(vertex_src, fragment_src) - - def render_background(self, width, height, time): - """ - [C: tests/test_dynamic_background.py:test_dynamic_background_rendering] - """ - if not self.bg_program: - return - gl.glUseProgram(self.bg_program) - u_time_loc = gl.glGetUniformLocation(self.bg_program, "u_time") - if u_time_loc != -1: - gl.glUniform1f(u_time_loc, float(time)) - u_res_loc = gl.glGetUniformLocation(self.bg_program, "u_resolution") - if u_res_loc != -1: - gl.glUniform2f(u_res_loc, float(width), float(height)) - gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4) - gl.glUseProgram(0) - - def setup_post_process_shader(self): - """ - [C: tests/test_post_process.py:TestPostProcess.test_setup_post_process_shader] - """ - vertex_src = """ -#version 330 core -const vec2 positions[4] = vec2[]( - vec2(-1.0, -1.0), - vec2( 1.0, -1.0), - vec2(-1.0, 1.0), - vec2( 1.0, 1.0) -); -const vec2 uvs[4] = vec2[]( - vec2(0.0, 0.0), - vec2(1.0, 0.0), - vec2(0.0, 1.0), - vec2(1.0, 1.0) -); -out vec2 v_uv; -void main() { - gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0); - v_uv = uvs[gl_VertexID]; -} -""" - fragment_src = """ -#version 330 core -in vec2 v_uv; -uniform sampler2D u_texture; -uniform float u_time; -out vec4 FragColor; -void main() { - vec4 color = texture(u_texture, v_uv); - float scanline = sin(v_uv.y * 800.0 + u_time * 2.0) * 0.04; - color.rgb -= scanline; - FragColor = color; -} -""" - self.pp_program = self.compile_shader(vertex_src, fragment_src) - - def render_post_process(self, texture_id, width, height, time): - """ - [C: tests/test_post_process.py:TestPostProcess.test_render_post_process] - """ - if not self.pp_program: - return - gl.glUseProgram(self.pp_program) - gl.glActiveTexture(gl.GL_TEXTURE0) - gl.glBindTexture(gl.GL_TEXTURE_2D, texture_id) - u_tex_loc = gl.glGetUniformLocation(self.pp_program, "u_texture") - if u_tex_loc != -1: - gl.glUniform1i(u_tex_loc, 0) - u_time_loc = gl.glGetUniformLocation(self.pp_program, "u_time") - if u_time_loc != -1: - gl.glUniform1f(u_time_loc, float(time)) - gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4) - gl.glBindTexture(gl.GL_TEXTURE_2D, 0) - gl.glUseProgram(0) \ No newline at end of file diff --git a/src/shaders.py b/src/shaders.py index 5f3a617..54eae2d 100644 --- a/src/shaders.py +++ b/src/shaders.py @@ -1,74 +1,38 @@ from imgui_bundle import imgui def draw_soft_shadow(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p_max: imgui.ImVec2, color: imgui.ImVec4, shadow_size: float = 10.0, rounding: float = 0.0) -> None: - """ - - Simulates a soft shadow effect by drawing multiple concentric rounded rectangles - with decreasing alpha values. This is a faux-shader effect using primitive batching. - """ - r, g, b, a = color.x, color.y, color.z, color.w - steps = int(shadow_size) - if steps <= 0: - return - - alpha_step = a / steps - - for i in range(steps): - current_alpha = a - (i * alpha_step) - # Apply an easing function (e.g., cubic) for a smoother shadow falloff - current_alpha = current_alpha * (1.0 - (i / steps)**2) - - if current_alpha <= 0.01: - continue - - expand = float(i) - - c_min = imgui.ImVec2(p_min.x - expand, p_min.y - expand) - c_max = imgui.ImVec2(p_max.x + expand, p_max.y + expand) - - u32_color = imgui.get_color_u32(imgui.ImVec4(r, g, b, current_alpha)) - - draw_list.add_rect( - c_min, - c_max, - u32_color, - rounding + expand if rounding > 0 else 0.0, - flags=imgui.ImDrawFlags_.round_corners_all if rounding > 0 else imgui.ImDrawFlags_.none, - thickness=1.0 - ) - -def apply_faux_acrylic_glass(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p_max: imgui.ImVec2, base_color: imgui.ImVec4, rounding: float = 0.0) -> None: - """ - - Simulates a faux acrylic/glass effect by drawing a semi-transparent base, - a gradient overlay for 'shine', and a subtle inner border for 'edge glow'. - """ - r, g, b, a = base_color.x, base_color.y, base_color.z, base_color.w - - # 1. Base tinted semi-transparent fill (fake blur base) - fill_color = imgui.get_color_u32(imgui.ImVec4(r, g, b, a * 0.7)) - draw_list.add_rect_filled( - p_min, p_max, fill_color, rounding, - flags=imgui.ImDrawFlags_.round_corners_all if rounding > 0 else imgui.ImDrawFlags_.none - ) - - # 2. Gradient overlay to simulate light scattering (acrylic reflection) - shine_top = imgui.get_color_u32(imgui.ImVec4(1.0, 1.0, 1.0, 0.15)) - shine_bot = imgui.get_color_u32(imgui.ImVec4(1.0, 1.0, 1.0, 0.0)) - # We can't do rounded corners with add_rect_filled_multicolor easily, but we can do it with clip rects or just draw it directly if rounding=0 - # For now, we'll just draw a subtle overlay in the upper half - if rounding == 0: - draw_list.add_rect_filled_multicolor( - p_min, imgui.ImVec2(p_max.x, p_min.y + (p_max.y - p_min.y) * 0.5), - shine_top, shine_top, shine_bot, shine_bot - ) - - # 3. Inner bright border to simulate "glass edge" refraction - inner_glow = imgui.get_color_u32(imgui.ImVec4(1.0, 1.0, 1.0, 0.2)) - draw_list.add_rect( - imgui.ImVec2(p_min.x + 1, p_min.y + 1), - imgui.ImVec2(p_max.x - 1, p_max.y - 1), - inner_glow, rounding, - flags=imgui.ImDrawFlags_.round_corners_all if rounding > 0 else imgui.ImDrawFlags_.none, - thickness=1.0 - ) + """ + + Simulates a soft shadow effect by drawing multiple concentric rounded rectangles + with decreasing alpha values. This is a faux-shader effect using primitive batching. + """ + r, g, b, a = color.x, color.y, color.z, color.w + steps = int(shadow_size) + if steps <= 0: + return + + alpha_step = a / steps + + for i in range(steps): + current_alpha = a - (i * alpha_step) + # Apply an easing function (e.g., cubic) for a smoother shadow falloff + current_alpha = current_alpha * (1.0 - (i / steps)**2) + + if current_alpha <= 0.01: + continue + + expand = float(i) + + c_min = imgui.ImVec2(p_min.x - expand, p_min.y - expand) + c_max = imgui.ImVec2(p_max.x + expand, p_max.y + expand) + + u32_color = imgui.get_color_u32(imgui.ImVec4(r, g, b, current_alpha)) + + draw_list.add_rect( + c_min, + c_max, + u32_color, + rounding + expand if rounding > 0 else 0.0, + flags=imgui.ImDrawFlags_.round_corners_all if rounding > 0 else imgui.ImDrawFlags_.none, + thickness=1.0 + ) diff --git a/src/theme.py b/src/theme.py deleted file mode 100644 index 965270f..0000000 --- a/src/theme.py +++ /dev/null @@ -1,424 +0,0 @@ -# theme.py -""" -Theming support for manual_slop GUI. - -Palettes --------- -Each palette is a dict mapping semantic names to (R,G,B) or (R,G,B,A) tuples. -The names correspond to dpg theme colour / style constants. - -Font handling -------------- -Call apply_font(path, size) to load a TTF and bind it as the global default. -Call set_scale(factor) to set the global font scale (DPI scaling). - -Usage ------ - import theme - theme.apply("10x") # apply a named palette - theme.apply_font("C:/Windows/Fonts/CascadiaCode.ttf", 15) - theme.set_scale(1.25) -""" - -import dearpygui.dearpygui as dpg -from pathlib import Path -from typing import Any - -# ------------------------------------------------------------------ palettes - -# Colour key names match the DPG mvThemeCol_* constants (string lookup below). -# Only keys that differ from DPG defaults need to be listed. - -_PALETTES: dict[str, dict[str, Any]] = { - "DPG Default": {}, # empty = reset to DPG built-in defaults - "10x Dark": { - # Window / frame chrome - "WindowBg": ( 34, 32, 28), - "ChildBg": ( 30, 28, 24), - "PopupBg": ( 35, 30, 20), - "Border": ( 60, 55, 50), - "BorderShadow": ( 0, 0, 0, 0), - "FrameBg": ( 45, 42, 38), - "FrameBgHovered": ( 60, 56, 50), - "FrameBgActive": ( 75, 70, 62), - # Title bars - "TitleBg": ( 40, 35, 25), - "TitleBgActive": ( 60, 45, 15), - "TitleBgCollapsed": ( 30, 27, 20), - # Menu bar - "MenuBarBg": ( 35, 30, 20), - # Scrollbar - "ScrollbarBg": ( 30, 28, 24), - "ScrollbarGrab": ( 80, 78, 72), - "ScrollbarGrabHovered": (100, 100, 92), - "ScrollbarGrabActive": (120, 118, 110), - # Check marks / radio buttons - "CheckMark": (194, 164, 74), - # Sliders - "SliderGrab": (126, 78, 14), - "SliderGrabActive": (194, 140, 30), - # Buttons - "Button": ( 83, 76, 60), - "ButtonHovered": (126, 78, 14), - "ButtonActive": (115, 90, 70), - # Headers (collapsing headers, selectables, listbox items) - "Header": ( 83, 76, 60), - "HeaderHovered": (126, 78, 14), - "HeaderActive": (115, 90, 70), - # Separator - "Separator": ( 70, 65, 55), - "SeparatorHovered": (126, 78, 14), - "SeparatorActive": (194, 164, 74), - # Resize grip - "ResizeGrip": ( 60, 55, 44), - "ResizeGripHovered": (126, 78, 14), - "ResizeGripActive": (194, 164, 74), - # Tab bar - "Tab": ( 83, 83, 70), - "TabHovered": (126, 77, 25), - "TabActive": (126, 77, 25), - "TabUnfocused": ( 60, 58, 50), - "TabUnfocusedActive": ( 90, 80, 55), - # Docking - "DockingPreview": (126, 78, 14, 180), - "DockingEmptyBg": ( 20, 20, 20), - # Text - "Text": (200, 200, 200), - "TextDisabled": (130, 130, 120), - # Input text cursor / selection - "TextSelectedBg": ( 59, 86, 142, 180), - # Plot / table lines - "TableHeaderBg": ( 55, 50, 38), - "TableBorderStrong": ( 70, 65, 55), - "TableBorderLight": ( 50, 47, 42), - "TableRowBg": ( 0, 0, 0, 0), - "TableRowBgAlt": ( 40, 38, 34, 40), - # Misc - "NavHighlight": (126, 78, 14), - "NavWindowingHighlight":(194, 164, 74, 180), - "NavWindowingDimBg": ( 20, 20, 20, 80), - "ModalWindowDimBg": ( 10, 10, 10, 100), - }, - "Nord Dark": { - "WindowBg": ( 36, 41, 49), - "ChildBg": ( 30, 34, 42), - "PopupBg": ( 36, 41, 49), - "Border": ( 59, 66, 82), - "BorderShadow": ( 0, 0, 0, 0), - "FrameBg": ( 46, 52, 64), - "FrameBgHovered": ( 59, 66, 82), - "FrameBgActive": ( 67, 76, 94), - "TitleBg": ( 36, 41, 49), - "TitleBgActive": ( 59, 66, 82), - "TitleBgCollapsed": ( 30, 34, 42), - "MenuBarBg": ( 46, 52, 64), - "ScrollbarBg": ( 30, 34, 42), - "ScrollbarGrab": ( 76, 86, 106), - "ScrollbarGrabHovered": ( 94, 129, 172), - "ScrollbarGrabActive": (129, 161, 193), - "CheckMark": (136, 192, 208), - "SliderGrab": ( 94, 129, 172), - "SliderGrabActive": (129, 161, 193), - "Button": ( 59, 66, 82), - "ButtonHovered": ( 94, 129, 172), - "ButtonActive": (129, 161, 193), - "Header": ( 59, 66, 82), - "HeaderHovered": ( 94, 129, 172), - "HeaderActive": (129, 161, 193), - "Separator": ( 59, 66, 82), - "SeparatorHovered": ( 94, 129, 172), - "SeparatorActive": (136, 192, 208), - "ResizeGrip": ( 59, 66, 82), - "ResizeGripHovered": ( 94, 129, 172), - "ResizeGripActive": (136, 192, 208), - "Tab": ( 46, 52, 64), - "TabHovered": ( 94, 129, 172), - "TabActive": ( 76, 86, 106), - "TabUnfocused": ( 36, 41, 49), - "TabUnfocusedActive": ( 59, 66, 82), - "DockingPreview": ( 94, 129, 172, 180), - "DockingEmptyBg": ( 20, 22, 28), - "Text": (216, 222, 233), - "TextDisabled": (116, 128, 150), - "TextSelectedBg": ( 94, 129, 172, 180), - "TableHeaderBg": ( 59, 66, 82), - "TableBorderStrong": ( 76, 86, 106), - "TableBorderLight": ( 59, 66, 82), - "TableRowBg": ( 0, 0, 0, 0), - "TableRowBgAlt": ( 46, 52, 64, 40), - "NavHighlight": (136, 192, 208), - "ModalWindowDimBg": ( 10, 12, 16, 100), - }, - "Monokai": { - "WindowBg": ( 39, 40, 34), - "ChildBg": ( 34, 35, 29), - "PopupBg": ( 39, 40, 34), - "Border": ( 60, 61, 52), - "BorderShadow": ( 0, 0, 0, 0), - "FrameBg": ( 50, 51, 44), - "FrameBgHovered": ( 65, 67, 56), - "FrameBgActive": ( 80, 82, 68), - "TitleBg": ( 39, 40, 34), - "TitleBgActive": ( 73, 72, 62), - "TitleBgCollapsed": ( 30, 31, 26), - "MenuBarBg": ( 50, 51, 44), - "ScrollbarBg": ( 34, 35, 29), - "ScrollbarGrab": ( 80, 80, 72), - "ScrollbarGrabHovered": (102, 217, 39), - "ScrollbarGrabActive": (166, 226, 46), - "CheckMark": (166, 226, 46), - "SliderGrab": (102, 217, 39), - "SliderGrabActive": (166, 226, 46), - "Button": ( 73, 72, 62), - "ButtonHovered": (249, 38, 114), - "ButtonActive": (198, 30, 92), - "Header": ( 73, 72, 62), - "HeaderHovered": (249, 38, 114), - "HeaderActive": (198, 30, 92), - "Separator": ( 60, 61, 52), - "SeparatorHovered": (249, 38, 114), - "SeparatorActive": (166, 226, 46), - "ResizeGrip": ( 73, 72, 62), - "ResizeGripHovered": (249, 38, 114), - "ResizeGripActive": (166, 226, 46), - "Tab": ( 73, 72, 62), - "TabHovered": (249, 38, 114), - "TabActive": (249, 38, 114), - "TabUnfocused": ( 50, 51, 44), - "TabUnfocusedActive": ( 90, 88, 76), - "DockingPreview": (249, 38, 114, 180), - "DockingEmptyBg": ( 20, 20, 18), - "Text": (248, 248, 242), - "TextDisabled": (117, 113, 94), - "TextSelectedBg": (249, 38, 114, 150), - "TableHeaderBg": ( 60, 61, 52), - "TableBorderStrong": ( 73, 72, 62), - "TableBorderLight": ( 55, 56, 48), - "TableRowBg": ( 0, 0, 0, 0), - "TableRowBgAlt": ( 50, 51, 44, 40), - "NavHighlight": (166, 226, 46), - "ModalWindowDimBg": ( 10, 10, 8, 100), - }, -} - -PALETTE_NAMES: list[str] = list(_PALETTES.keys()) - -# ------------------------------------------------------------------ colour key -> mvThemeCol_* mapping - -# Maps our friendly name -> dpg constant name -_COL_MAP: dict[str, str] = { - "Text": "mvThemeCol_Text", - "TextDisabled": "mvThemeCol_TextDisabled", - "WindowBg": "mvThemeCol_WindowBg", - "ChildBg": "mvThemeCol_ChildBg", - "PopupBg": "mvThemeCol_PopupBg", - "Border": "mvThemeCol_Border", - "BorderShadow": "mvThemeCol_BorderShadow", - "FrameBg": "mvThemeCol_FrameBg", - "FrameBgHovered": "mvThemeCol_FrameBgHovered", - "FrameBgActive": "mvThemeCol_FrameBgActive", - "TitleBg": "mvThemeCol_TitleBg", - "TitleBgActive": "mvThemeCol_TitleBgActive", - "TitleBgCollapsed": "mvThemeCol_TitleBgCollapsed", - "MenuBarBg": "mvThemeCol_MenuBarBg", - "ScrollbarBg": "mvThemeCol_ScrollbarBg", - "ScrollbarGrab": "mvThemeCol_ScrollbarGrab", - "ScrollbarGrabHovered": "mvThemeCol_ScrollbarGrabHovered", - "ScrollbarGrabActive": "mvThemeCol_ScrollbarGrabActive", - "CheckMark": "mvThemeCol_CheckMark", - "SliderGrab": "mvThemeCol_SliderGrab", - "SliderGrabActive": "mvThemeCol_SliderGrabActive", - "Button": "mvThemeCol_Button", - "ButtonHovered": "mvThemeCol_ButtonHovered", - "ButtonActive": "mvThemeCol_ButtonActive", - "Header": "mvThemeCol_Header", - "HeaderHovered": "mvThemeCol_HeaderHovered", - "HeaderActive": "mvThemeCol_HeaderActive", - "Separator": "mvThemeCol_Separator", - "SeparatorHovered": "mvThemeCol_SeparatorHovered", - "SeparatorActive": "mvThemeCol_SeparatorActive", - "ResizeGrip": "mvThemeCol_ResizeGrip", - "ResizeGripHovered": "mvThemeCol_ResizeGripHovered", - "ResizeGripActive": "mvThemeCol_ResizeGripActive", - "Tab": "mvThemeCol_Tab", - "TabHovered": "mvThemeCol_TabHovered", - "TabActive": "mvThemeCol_TabActive", - "TabUnfocused": "mvThemeCol_TabUnfocused", - "TabUnfocusedActive": "mvThemeCol_TabUnfocusedActive", - "DockingPreview": "mvThemeCol_DockingPreview", - "DockingEmptyBg": "mvThemeCol_DockingEmptyBg", - "TextSelectedBg": "mvThemeCol_TextSelectedBg", - "TableHeaderBg": "mvThemeCol_TableHeaderBg", - "TableBorderStrong": "mvThemeCol_TableBorderStrong", - "TableBorderLight": "mvThemeCol_TableBorderLight", - "TableRowBg": "mvThemeCol_TableRowBg", - "TableRowBgAlt": "mvThemeCol_TableRowBgAlt", - "NavHighlight": "mvThemeCol_NavHighlight", - "NavWindowingHighlight": "mvThemeCol_NavWindowingHighlight", - "NavWindowingDimBg": "mvThemeCol_NavWindowingDimBg", - "ModalWindowDimBg": "mvThemeCol_ModalWindowDimBg", -} - -# ------------------------------------------------------------------ state - -_current_theme_tag: str | None = None -_current_font_tag: str | None = None -_font_registry_tag: str | None = None -_current_palette: str = "DPG Default" -_current_font_path: str = "" -_current_font_size: float = 14.0 -_current_scale: float = 1.0 -_shader_config: dict[str, Any] = { - "crt": False, - "bloom": False, - "bg": "none", - "custom_window_frame": False, -} - -# ------------------------------------------------------------------ public API - -def get_palette_names() -> list[str]: - return list(_PALETTES.keys()) - -def get_current_palette() -> str: - return _current_palette - -def get_current_font_path() -> str: - return _current_font_path - -def get_current_font_size() -> float: - return _current_font_size - -def get_current_scale() -> float: - return _current_scale - -def get_shader_config(key: str) -> Any: - """ - Get a specific shader configuration value. - [C: tests/test_shader_config.py:test_shader_config_parsing] - """ - return _shader_config.get(key) - -def get_window_frame_config() -> bool: - """ - Get the window frame configuration. - [C: tests/test_shader_config.py:test_shader_config_parsing] - """ - return _shader_config.get("custom_window_frame", False) - -def get_palette_colours(name: str) -> dict[str, Any]: - """Return a copy of the colour dict for the named palette.""" - return dict(_PALETTES.get(name, {})) - -def apply(palette_name: str, overrides: dict[str, Any] | None = None) -> None: - """ - - Build a global DPG theme from the named palette plus optional per-colour - overrides, and bind it as the default theme. - - overrides: {colour_key: (R,G,B) or (R,G,B,A)} — merged on top of palette. - [C: src/theme_2.py:apply_current, src/theme_2.py:set_child_transparency, src/theme_2.py:set_transparency, tests/test_theme.py:test_theme_apply_sets_rounding_and_padding] - """ - global _current_theme_tag, _current_palette - _current_palette = palette_name - colours = dict(_PALETTES.get(palette_name, {})) - if overrides: - colours.update(overrides) - # Delete the old theme if one exists - if _current_theme_tag is not None: - try: - dpg.delete_item(_current_theme_tag) - except Exception: - pass - _current_theme_tag = None - if palette_name == "DPG Default" and not overrides: - # Bind an empty theme to reset to DPG defaults - with dpg.theme() as t: - with dpg.theme_component(dpg.mvAll): - pass - dpg.bind_theme(t) - _current_theme_tag = t - return - with dpg.theme() as t: - with dpg.theme_component(dpg.mvAll): - for name, colour in colours.items(): - const_name = _COL_MAP.get(name) - if const_name is None: - continue - const = getattr(dpg, const_name, None) - if const is None: - continue - # Ensure 4-tuple - if len(colour) == 3: - colour = (*colour, 255) - dpg.add_theme_color(const, colour) - dpg.bind_theme(t) - _current_theme_tag = t - -def apply_font(font_path: str, size: float = 14.0) -> None: - """ - - Load the TTF at font_path at the given point size and bind it globally. - Safe to call multiple times. Uses a single persistent font_registry; only - the font *item* tag is tracked. Passing an empty path or a missing file - resets to the DPG built-in font. - """ - global _current_font_tag, _current_font_path, _current_font_size, _font_registry_tag - _current_font_path = font_path - _current_font_size = size - if not font_path or not Path(font_path).exists(): - # Reset to default built-in font - dpg.bind_font(0) - _current_font_tag = None - return - # Create the registry once - if _font_registry_tag is None or not dpg.does_item_exist(_font_registry_tag): - with dpg.font_registry() as reg: - _font_registry_tag = reg - # Delete previous custom font item only (not the registry) - if _current_font_tag is not None: - try: - dpg.delete_item(_current_font_tag) - except Exception: - pass - _current_font_tag = None - font = dpg.add_font(font_path, size, parent=_font_registry_tag) - _current_font_tag = font - dpg.bind_font(font) - -def set_scale(factor: float) -> None: - """ - Set the global Dear PyGui font/UI scale factor. - [C: src/theme_2.py:apply_current] - """ - global _current_scale - _current_scale = factor - dpg.set_global_font_scale(factor) - -def save_to_config(config: dict[str, Any]) -> None: - """Persist theme settings into the config dict under [theme].""" - config.setdefault("theme", {}) - config["theme"]["palette"] = _current_palette - config["theme"]["font_path"] = _current_font_path - config["theme"]["font_size"] = _current_font_size - config["theme"]["scale"] = _current_scale - -def load_from_config(config: dict[str, Any]) -> None: - """ - Read [theme] from config and apply everything. - [C: tests/test_shader_config.py:test_shader_config_parsing] - """ - t = config.get("theme", {}) - palette = t.get("palette", "DPG Default") - font_path = t.get("font_path", "") - font_size = float(t.get("font_size", 14.0)) - scale = float(t.get("scale", 1.0)) - apply(palette) - if font_path: - apply_font(font_path, font_size) - set_scale(scale) - global _shader_config - _shader_config["crt"] = t.get("shader_crt", False) - _shader_config["bloom"] = t.get("shader_bloom", False) - _shader_config["bg"] = t.get("shader_bg", "none") - _shader_config["custom_window_frame"] = t.get("custom_window_frame", False) diff --git a/tests/test_diff_viewer.py b/tests/test_diff_viewer.py index 33bed81..09765b4 100644 --- a/tests/test_diff_viewer.py +++ b/tests/test_diff_viewer.py @@ -1,11 +1,10 @@ -import pytest +import pytest import tempfile import os from pathlib import Path from src.diff_viewer import ( parse_diff, DiffFile, DiffHunk, parse_hunk_header, - get_line_color, render_diff_text_immediate, - create_backup, apply_patch_to_file, restore_from_backup, cleanup_backup + get_line_color, apply_patch_to_file ) def test_parse_diff_empty() -> None: @@ -95,35 +94,6 @@ def test_get_line_color() -> None: assert get_line_color("@@ -1,3 +1,4 @@") == "cyan" assert get_line_color(" context") == None -def test_render_diff_text_immediate() -> None: - diff_text = """--- a/test.py -+++ b/test.py -@@ -1 +1 @@ --old -+new""" - diff_files = parse_diff(diff_text) - output = render_diff_text_immediate(diff_files) - - assert len(output) > 0 - assert ("File: test.py", "white") in output - assert ("@@ -1 +1 @@", "cyan") in output - assert ("-old", "red") in output - assert ("+new", "green") in output - -def test_create_backup() -> None: - with tempfile.TemporaryDirectory() as tmpdir: - test_file = Path(tmpdir) / "test.py" - test_file.write_text("original content\n") - - backup_path = create_backup(str(test_file)) - assert backup_path is not None - assert Path(backup_path).exists() - assert Path(backup_path).read_text() == "original content\n" - -def test_create_backup_nonexistent() -> None: - result = create_backup("/nonexistent/file.py") - assert result is None - def test_apply_patch_simple() -> None: with tempfile.TemporaryDirectory() as tmpdir: test_file = Path(tmpdir) / "test.py" @@ -158,24 +128,3 @@ def test_apply_patch_with_context() -> None: content = test_file.read_text() assert "line one" in content assert "line two" in content - -def test_restore_from_backup() -> None: - with tempfile.TemporaryDirectory() as tmpdir: - test_file = Path(tmpdir) / "test.py" - test_file.write_text("modified\n") - backup_file = test_file.with_suffix(".py.backup") - backup_file.write_text("original\n") - - success = restore_from_backup(str(test_file)) - assert success - assert test_file.read_text() == "original\n" - -def test_cleanup_backup() -> None: - with tempfile.TemporaryDirectory() as tmpdir: - test_file = Path(tmpdir) / "test.py" - test_file.write_text("content\n") - backup_file = test_file.with_suffix(".py.backup") - backup_file.write_text("backup\n") - - cleanup_backup(str(test_file)) - assert not backup_file.exists() \ No newline at end of file diff --git a/tests/test_dynamic_background.py b/tests/test_dynamic_background.py deleted file mode 100644 index 7814dba..0000000 --- a/tests/test_dynamic_background.py +++ /dev/null @@ -1,33 +0,0 @@ -import pytest -from unittest.mock import patch, MagicMock - -def test_dynamic_background_rendering(): - # Mock OpenGL before importing - with patch("src.shader_manager.gl") as mock_gl: - from src.shader_manager import ShaderManager - - # Setup mock return values - mock_gl.glCreateProgram.return_value = 1 - mock_gl.glCreateShader.return_value = 2 - mock_gl.glGetShaderiv.return_value = 1 # GL_TRUE - mock_gl.glGetProgramiv.return_value = 1 # GL_TRUE - mock_gl.glGetUniformLocation.return_value = 10 - - manager = ShaderManager() - manager.setup_background_shader() - - # Verify background program was created - assert manager.bg_program == 1 - assert mock_gl.glCreateProgram.called - - # Render background - manager.render_background(800, 600, 1.0) - - # Verify OpenGL calls - mock_gl.glUseProgram.assert_any_call(1) - mock_gl.glDrawArrays.assert_called_with(mock_gl.GL_TRIANGLE_STRIP, 0, 4) - mock_gl.glUseProgram.assert_any_call(0) - - # Verify uniforms were updated - mock_gl.glUniform1f.assert_called() - mock_gl.glUniform2f.assert_called() diff --git a/tests/test_native_orchestrator.py b/tests/test_native_orchestrator.py deleted file mode 100644 index 3e0f291..0000000 --- a/tests/test_native_orchestrator.py +++ /dev/null @@ -1,75 +0,0 @@ -import pytest -import os -import sys -import tempfile -import json -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).parent.parent)) -from src import native_orchestrator - -def test_read_plan_nonexistent(): - with tempfile.TemporaryDirectory() as tmpdir: - result = native_orchestrator.read_plan("nonexistent_track", tmpdir) - assert result == "" - -def test_write_and_read_plan(): - with tempfile.TemporaryDirectory() as tmpdir: - content = "# Test Plan\n- [x] Task 1\n- [ ] Task 2" - native_orchestrator.write_plan("test_track", content, tmpdir) - result = native_orchestrator.read_plan("test_track", tmpdir) - assert result == content - -def test_parse_plan_tasks(): - content = """# Plan -- [x] Completed task -- [ ] Pending task -- [x] Another done""" - tasks = native_orchestrator.parse_plan_tasks(content) - assert len(tasks) == 3 - assert tasks[0]["completed"] == True - assert tasks[1]["completed"] == False - assert tasks[2]["completed"] == True - -def test_read_metadata_nonexistent(): - with tempfile.TemporaryDirectory() as tmpdir: - result = native_orchestrator.read_metadata("nonexistent_track", tmpdir) - assert result == {} - -def test_write_and_read_metadata(): - with tempfile.TemporaryDirectory() as tmpdir: - data = {"name": "Test Track", "status": "active"} - native_orchestrator.write_metadata("test_track", data, tmpdir) - result = native_orchestrator.read_metadata("test_track", tmpdir) - assert result["name"] == "Test Track" - assert result["status"] == "active" - -def test_get_track_dir(): - with tempfile.TemporaryDirectory() as tmpdir: - track_dir = native_orchestrator.get_track_dir("my_track", tmpdir) - expected = Path(tmpdir) / "conductor" / "tracks" / "my_track" - assert track_dir == expected - -def test_get_archive_dir(): - with tempfile.TemporaryDirectory() as tmpdir: - archive_dir = native_orchestrator.get_archive_dir(tmpdir) - expected = Path(tmpdir) / "conductor" / "archive" - assert archive_dir == expected - -def test_native_orchestrator_class(): - with tempfile.TemporaryDirectory() as tmpdir: - orch = native_orchestrator.NativeOrchestrator(tmpdir) - assert orch.base_dir == Path(tmpdir) - - data = {"name": "Test"} - orch.save_track("track1", data) - loaded = orch.load_track("track1") - assert loaded["name"] == "Test" - - plan_content = "# Plan\n- [x] Done" - orch.save_plan("track1", plan_content) - loaded_plan = orch.load_plan("track1") - assert loaded_plan == plan_content - - tasks = orch.get_track_tasks("track1") - assert len(tasks) == 1 diff --git a/tests/test_post_process.py b/tests/test_post_process.py deleted file mode 100644 index 16703a2..0000000 --- a/tests/test_post_process.py +++ /dev/null @@ -1,53 +0,0 @@ -import unittest -from unittest.mock import MagicMock, patch -import sys - -# Mock OpenGL.GL before importing ShaderManager -gl_mock = MagicMock() -# Setup some constants -gl_mock.GL_VERTEX_SHADER = 0x8B31 -gl_mock.GL_FRAGMENT_SHADER = 0x8B30 -gl_mock.GL_COMPILE_STATUS = 0x8B81 -gl_mock.GL_LINK_STATUS = 0x8B82 -gl_mock.GL_TEXTURE0 = 0x84C0 -gl_mock.GL_TEXTURE_2D = 0x0DE1 -gl_mock.GL_TRIANGLE_STRIP = 0x0005 - -opengl_mock = MagicMock() -sys.modules['OpenGL'] = opengl_mock -sys.modules['OpenGL.GL'] = gl_mock -opengl_mock.GL = gl_mock - -from src.shader_manager import ShaderManager - -class TestPostProcess(unittest.TestCase): - def setUp(self): - gl_mock.reset_mock() - # Mock return values for shader compilation - gl_mock.glCreateProgram.return_value = 1 - gl_mock.glCreateShader.return_value = 2 - gl_mock.glGetShaderiv.return_value = 1 # GL_TRUE - gl_mock.glGetProgramiv.return_value = 1 # GL_TRUE - gl_mock.glGetUniformLocation.return_value = 10 - - def test_setup_post_process_shader(self): - sm = ShaderManager() - sm.setup_post_process_shader() - self.assertEqual(sm.pp_program, 1) - gl_mock.glCreateProgram.assert_called() - gl_mock.glLinkProgram.assert_called_with(1) - - def test_render_post_process(self): - sm = ShaderManager() - sm.pp_program = 1 - sm.render_post_process(texture_id=5, width=800, height=600, time=1.0) - - gl_mock.glUseProgram.assert_any_call(1) - gl_mock.glActiveTexture.assert_called_with(gl_mock.GL_TEXTURE0) - gl_mock.glBindTexture.assert_any_call(gl_mock.GL_TEXTURE_2D, 5) - gl_mock.glUniform1f.assert_called() - gl_mock.glDrawArrays.assert_called_with(gl_mock.GL_TRIANGLE_STRIP, 0, 4) - gl_mock.glUseProgram.assert_any_call(0) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_shader_config.py b/tests/test_shader_config.py deleted file mode 100644 index 7a10d31..0000000 --- a/tests/test_shader_config.py +++ /dev/null @@ -1,23 +0,0 @@ -import pytest -from unittest.mock import patch -from src import theme - -def test_shader_config_parsing(): - config = { - "theme": { - "shader_crt": True, - "shader_bloom": False, - "shader_bg": "noise", - "custom_window_frame": True - } - } - - with patch("src.theme.apply"), \ - patch("src.theme.apply_font"), \ - patch("src.theme.set_scale"): - theme.load_from_config(config) - - assert theme.get_shader_config("crt") is True - assert theme.get_shader_config("bloom") is False - assert theme.get_shader_config("bg") == "noise" - assert theme.get_window_frame_config() is True diff --git a/tests/test_shader_manager.py b/tests/test_shader_manager.py deleted file mode 100644 index 124d15c..0000000 --- a/tests/test_shader_manager.py +++ /dev/null @@ -1,51 +0,0 @@ -import pytest -from unittest.mock import patch, MagicMock - -def test_shader_manager_initialization_and_compilation(): - # Import inside test to allow patching OpenGL before import if needed - # In this case, we patch the OpenGL.GL functions used by ShaderManager - with patch("src.shader_manager.gl") as mock_gl: - mock_gl.glCreateProgram.return_value = 1 - mock_gl.glCreateShader.return_value = 2 - mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE - mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE - - from src.shader_manager import ShaderManager - - manager = ShaderManager() - - # Basic vertex and fragment shader source - vert_src = "void main() {}" - frag_src = "void main() {}" - - program_id = manager.compile_shader(vert_src, frag_src) - - assert program_id == 1 - assert mock_gl.glCreateProgram.called - assert mock_gl.glCreateShader.called - -def test_shader_manager_uniform_update(): - # Mock OpenGL.GL functions - with patch("src.shader_manager.gl") as mock_gl: - from src.shader_manager import ShaderManager - manager = ShaderManager() - # Set a mock program ID - manager.program = 1 - - # Mock glGetUniformLocation to return some valid locations - # u_time -> 10, u_resolution -> 20 - def mock_get_loc(prog, name): - if name == "u_time": return 10 - if name == "u_resolution": return 20 - return -1 - - mock_gl.glGetUniformLocation.side_effect = mock_get_loc - - # Call the method - manager.update_uniforms({"u_time": 1.5, "u_resolution": (800, 600)}) - - # Assert calls - mock_gl.glGetUniformLocation.assert_any_call(1, "u_time") - mock_gl.glGetUniformLocation.assert_any_call(1, "u_resolution") - mock_gl.glUniform1f.assert_called_once_with(10, 1.5) - mock_gl.glUniform2f.assert_called_once_with(20, 800, 600)