From 66844e83683131e4182689a5074d0062550687a1 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 9 Mar 2026 23:16:02 -0400 Subject: [PATCH] feat(mma): Implement Pop Out Task DAG option in MMA Dashboard --- .../tracks/saved_presets_20260308/plan.md | 1 + config.toml | 10 +-- manualslop_layout.ini | 56 +++++++-------- presets.toml | 4 +- scripts/tasks/popout_dag.toml | 15 ++++ scripts/tasks/popout_dag.txt | 13 ++++ src/app_controller.py | 11 ++- src/gui_2.py | 72 +++++++++++-------- tests/test_task_dag_popout_sim.py | 30 ++++++++ 9 files changed, 147 insertions(+), 65 deletions(-) create mode 100644 scripts/tasks/popout_dag.toml create mode 100644 scripts/tasks/popout_dag.txt create mode 100644 tests/test_task_dag_popout_sim.py diff --git a/conductor/tracks/saved_presets_20260308/plan.md b/conductor/tracks/saved_presets_20260308/plan.md index 8b08766..a9558be 100644 --- a/conductor/tracks/saved_presets_20260308/plan.md +++ b/conductor/tracks/saved_presets_20260308/plan.md @@ -44,6 +44,7 @@ - [x] Task: Bugfix: Correct `PresetManager` initialization to use project parent directory. - [x] Task: Hardening: Wrap modal rendering in `try...finally` to prevent ImGui state corruption. - [x] Task: Hardening: Ensure `PresetManager._save_file` validates that parent is a directory. +- [x] Task: Feature: Implement "Pop Out Task DAG" option in MMA Dashboard. - [x] Task: Final UI polish (spacing, icons, tooltips). - [x] Task: Run full suite of relevant tests. - [x] Task: Conductor - User Manual Verification 'Phase 4: Final Integration & Polish' (Protocol in workflow.md) diff --git a/config.toml b/config.toml index a31d916..20f7200 100644 --- a/config.toml +++ b/config.toml @@ -2,10 +2,10 @@ provider = "minimax" model = "MiniMax-M2.5" temperature = 0.0 -max_tokens = 4096 +max_tokens = 32000 history_trunc_limit = 900000 active_preset = "Default" -system_prompt = "Not sure yet." +system_prompt = "" [projects] paths = [ @@ -41,13 +41,13 @@ Response = false "Tool Calls" = false Theme = true "Log Management" = true -Diagnostics = false +Diagnostics = true [theme] palette = "Nord Dark" font_path = "C:/projects/manual_slop/assets/fonts/Inter-Regular.ttf" -font_size = 12.0 -scale = 1.2999999523162842 +font_size = 14.0 +scale = 1.2000000476837158 transparency = 0.550000011920929 child_transparency = 0.6399999856948853 diff --git a/manualslop_layout.ini b/manualslop_layout.ini index b9ec079..11d1e94 100644 --- a/manualslop_layout.ini +++ b/manualslop_layout.ini @@ -73,8 +73,8 @@ Collapsed=0 DockId=0xAFC85805,2 [Window][Theme] -Pos=0,30 -Size=762,943 +Pos=0,29 +Size=762,736 Collapsed=0 DockId=0x00000005,1 @@ -90,8 +90,8 @@ Collapsed=0 DockId=0x0000000C,2 [Window][Context Hub] -Pos=0,30 -Size=762,943 +Pos=0,29 +Size=762,736 Collapsed=0 DockId=0x00000005,0 @@ -102,26 +102,26 @@ Collapsed=0 DockId=0x0000000D,0 [Window][Discussion Hub] -Pos=1668,30 -Size=1004,2107 +Pos=1668,29 +Size=1004,2108 Collapsed=0 DockId=0x00000013,0 [Window][Operations Hub] -Pos=764,30 -Size=902,2107 +Pos=764,29 +Size=902,2108 Collapsed=0 DockId=0x00000012,0 [Window][Files & Media] -Pos=0,2013 -Size=762,124 +Pos=0,1520 +Size=762,617 Collapsed=0 DockId=0x00000002,0 [Window][AI Settings] -Pos=0,975 -Size=762,1036 +Pos=0,767 +Size=762,751 Collapsed=0 DockId=0x00000001,0 @@ -131,14 +131,14 @@ Size=416,325 Collapsed=0 [Window][MMA Dashboard] -Pos=2674,30 -Size=1166,1675 +Pos=2674,29 +Size=1166,1676 Collapsed=0 DockId=0x0000000C,0 [Window][Log Management] -Pos=2674,30 -Size=1166,1675 +Pos=2674,29 +Size=1166,1676 Collapsed=0 DockId=0x0000000C,1 @@ -166,8 +166,8 @@ Collapsed=0 DockId=0x0000000F,2 [Window][Tier 3: Workers] -Pos=764,30 -Size=902,2107 +Pos=764,29 +Size=902,2108 Collapsed=0 DockId=0x00000012,1 @@ -323,7 +323,7 @@ Collapsed=0 [Window][Preset Manager] Pos=786,858 -Size=780,650 +Size=956,942 Collapsed=0 [Table][0xFB6E3870,4] @@ -380,11 +380,11 @@ Column 2 Weight=1.0000 Column 3 Width=106 [Table][0x2C515046,4] -RefScale=18 -Column 0 Width=58 +RefScale=20 +Column 0 Width=64 Column 1 Weight=1.0000 -Column 2 Width=138 -Column 3 Width=54 +Column 2 Width=153 +Column 3 Width=60 [Table][0xD99F45C5,4] Column 0 Sort=0v @@ -414,14 +414,14 @@ Column 2 Weight=1.0000 DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02 -DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,30 Size=3840,2107 Split=X +DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,29 Size=3840,2108 Split=X DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2672,1183 Split=X DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2 DockNode ID=0x00000007 Parent=0x0000000B SizeRef=762,858 Split=Y Selected=0x8CA2375C - DockNode ID=0x00000005 Parent=0x00000007 SizeRef=295,943 Selected=0xF4139CA2 - DockNode ID=0x00000006 Parent=0x00000007 SizeRef=295,1168 Split=Y Selected=0x7BD57D6A - DockNode ID=0x00000001 Parent=0x00000006 SizeRef=824,1036 CentralNode=1 Selected=0x7BD57D6A - DockNode ID=0x00000002 Parent=0x00000006 SizeRef=824,124 Selected=0x1DCB2623 + DockNode ID=0x00000005 Parent=0x00000007 SizeRef=295,736 Selected=0xF4139CA2 + DockNode ID=0x00000006 Parent=0x00000007 SizeRef=295,1369 Split=Y Selected=0x7BD57D6A + DockNode ID=0x00000001 Parent=0x00000006 SizeRef=824,750 CentralNode=1 Selected=0x7BD57D6A + DockNode ID=0x00000002 Parent=0x00000006 SizeRef=824,617 Selected=0x1DCB2623 DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1908,858 Split=X Selected=0x418C7449 DockNode ID=0x00000012 Parent=0x0000000E SizeRef=902,402 Selected=0x418C7449 DockNode ID=0x00000013 Parent=0x0000000E SizeRef=1004,402 Selected=0x6F2B5B04 diff --git a/presets.toml b/presets.toml index ba90548..fef9079 100644 --- a/presets.toml +++ b/presets.toml @@ -1,5 +1,5 @@ [presets.Default] -system_prompt = "Not sure yet." +system_prompt = "" temperature = 0.0 top_p = 1.0 -max_output_tokens = 4096 +max_output_tokens = 32000 diff --git a/scripts/tasks/popout_dag.toml b/scripts/tasks/popout_dag.toml new file mode 100644 index 0000000..8ed9b32 --- /dev/null +++ b/scripts/tasks/popout_dag.toml @@ -0,0 +1,15 @@ +prompt = """ +In src/gui_2.py: +1. In '__init__', initialize 'self.ui_separate_task_dag = gui_cfg.get("separate_task_dag", False)'. +2. In '_gui_func', add logic to render the 'Task DAG' window if 'self.ui_separate_task_dag' and 'self.show_windows.get("Task DAG", False)' are true. Use: + if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False): + exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"]) + self.show_windows["Task DAG"] = bool(opened) + if exp: + self._render_task_dag_panel() + imgui.end() +3. In '_render_mma_dashboard', add 'ch, self.ui_separate_task_dag = imgui.checkbox("Pop Out Task DAG", self.ui_separate_task_dag)' before the '4. Task DAG Visualizer' section. If 'ch' is true, update 'self.show_windows["Task DAG"] = self.ui_separate_task_dag'. +4. Move the entire '4. Task DAG Visualizer' and '5. Add Ticket Form' sections into a new method '_render_task_dag_panel(self)'. +5. In '_render_mma_dashboard', call 'self._render_task_dag_panel()' only if 'not self.ui_separate_task_dag'. +Use 1-space indentation. +""" diff --git a/scripts/tasks/popout_dag.txt b/scripts/tasks/popout_dag.txt new file mode 100644 index 0000000..c4e05d5 --- /dev/null +++ b/scripts/tasks/popout_dag.txt @@ -0,0 +1,13 @@ +In src/gui_2.py: +1. In '__init__', initialize 'self.ui_separate_task_dag = gui_cfg.get("separate_task_dag", False)'. +2. In '_gui_func', add logic to render the 'Task DAG' window if 'self.ui_separate_task_dag' and 'self.show_windows.get("Task DAG", False)' are true. Use: + if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False): + exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"]) + self.show_windows["Task DAG"] = bool(opened) + if exp: + self._render_task_dag_panel() + imgui.end() +3. In '_render_mma_dashboard', add 'ch, self.ui_separate_task_dag = imgui.checkbox("Pop Out Task DAG", self.ui_separate_task_dag)' before the '4. Task DAG Visualizer' section. If 'ch' is true, update 'self.show_windows["Task DAG"] = self.ui_separate_task_dag'. +4. Move the entire '4. Task DAG Visualizer' and '5. Add Ticket Form' sections into a new method '_render_task_dag_panel(self)'. +5. In '_render_mma_dashboard', call 'self._render_task_dag_panel()' only if 'not self.ui_separate_task_dag'. +Use 1-space indentation. diff --git a/src/app_controller.py b/src/app_controller.py index 1d16c8a..e29a507 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -342,10 +342,13 @@ class AppController: '_editing_preset_temperature': '_editing_preset_temperature', '_editing_preset_top_p': '_editing_preset_top_p', '_editing_preset_max_output_tokens': '_editing_preset_max_output_tokens', - '_editing_preset_scope': '_editing_preset_scope' + '_editing_preset_scope': '_editing_preset_scope', + 'show_windows': 'show_windows', + 'ui_separate_task_dag': 'ui_separate_task_dag' } self._gettable_fields = dict(self._settable_fields) self._gettable_fields.update({ + 'show_windows': 'show_windows', 'ui_focus_agent': 'ui_focus_agent', 'active_discussion': 'active_discussion', '_track_discussion_active': '_track_discussion_active', @@ -378,7 +381,8 @@ class AppController: '_editing_preset_temperature': '_editing_preset_temperature', '_editing_preset_top_p': '_editing_preset_top_p', '_editing_preset_max_output_tokens': '_editing_preset_max_output_tokens', - '_editing_preset_scope': '_editing_preset_scope' + '_editing_preset_scope': '_editing_preset_scope', + 'ui_separate_task_dag': 'ui_separate_task_dag' }) self.perf_monitor = performance_monitor.get_monitor() self._perf_profiling_enabled = False @@ -778,6 +782,7 @@ class AppController: def init_state(self): """Initializes the application state from configurations.""" + self.ui_separate_task_dag = False self.config = models.load_config() theme.load_from_config(self.config) ai_cfg = self.config.get("ai", {}) @@ -836,6 +841,7 @@ class AppController: "Files & Media": True, "AI Settings": True, "MMA Dashboard": True, + "Task DAG": False, "Tier 1: Strategy": True, "Tier 2: Tech Lead": True, "Tier 3: Workers": True, @@ -2177,6 +2183,7 @@ class AppController: "separate_message_panel": getattr(self, "ui_separate_message_panel", False), "separate_response_panel": getattr(self, "ui_separate_response_panel", False), "separate_tool_calls_panel": getattr(self, "ui_separate_tool_calls_panel", False), + "separate_task_dag": self.ui_separate_task_dag, "bg_shader_enabled": bg_shader.get_bg().enabled }) self.config["gui"] = gui_cfg diff --git a/src/gui_2.py b/src/gui_2.py index ee76e0a..b8e7485 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -126,6 +126,7 @@ class App: self.ui_separate_message_panel = gui_cfg.get("separate_message_panel", False) self.ui_separate_response_panel = gui_cfg.get("separate_response_panel", False) self.ui_separate_tool_calls_panel = gui_cfg.get("separate_tool_calls_panel", False) + self.ui_separate_task_dag = gui_cfg.get("separate_task_dag", False) self.ui_multi_viewport = gui_cfg.get("multi_viewport", False) self.layout_presets = self.config.get("layout_presets", {}) self._new_preset_name = "" @@ -426,6 +427,13 @@ class App: self._render_mma_dashboard() if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard") imgui.end() + + if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False): + exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"]) + self.show_windows["Task DAG"] = bool(opened) + if exp: + self._render_task_dag_panel() + imgui.end() if self.show_windows.get("Tier 1: Strategy", False): exp, opened = imgui.begin("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"]) self.show_windows["Tier 1: Strategy"] = bool(opened) @@ -2709,6 +2717,42 @@ def hello(): imgui.separator() self._render_ticket_queue() imgui.separator() + ch, self.ui_separate_task_dag = imgui.checkbox("Pop Out Task DAG", self.ui_separate_task_dag) + if ch: + self.show_windows["Task DAG"] = self.ui_separate_task_dag + + if not self.ui_separate_task_dag: + self._render_task_dag_panel() + + # 6. Edit Selected Ticket + if self.ui_selected_ticket_id: + imgui.separator() + imgui.text_colored(C_VAL, f"Editing: {self.ui_selected_ticket_id}") + ticket = next((t for t in self.active_tickets if str(t.get('id', '')) == self.ui_selected_ticket_id), None) + if ticket: + imgui.text(f"Status: {ticket.get('status', 'todo')}") + prio = ticket.get('priority', 'medium') + imgui.text("Priority:") + imgui.same_line() + if imgui.begin_combo(f"##edit_prio_{ticket.get('id')}", prio): + for p_opt in ['high', 'medium', 'low']: + if imgui.selectable(p_opt, p_opt == prio)[0]: + ticket['priority'] = p_opt + self._push_mma_state_update() + imgui.end_combo() + imgui.text(f"Target: {ticket.get('target_file', '')}") + deps = ticket.get('depends_on', []) + imgui.text(f"Depends on: {', '.join(deps)}") + if imgui.button(f"Mark Complete##{self.ui_selected_ticket_id}"): + ticket['status'] = 'done' + self._push_mma_state_update() + imgui.same_line() + if imgui.button(f"Delete##{self.ui_selected_ticket_id}"): + self.active_tickets = [t for t in self.active_tickets if str(t.get('id', '')) != self.ui_selected_ticket_id] + self.ui_selected_ticket_id = None + self._push_mma_state_update() + + def _render_task_dag_panel(self) -> None: # 4. Task DAG Visualizer imgui.text("Task DAG") if self.active_track and self.node_editor_ctx: @@ -2850,34 +2894,6 @@ def hello(): else: imgui.text_disabled("No active MMA track.") - # 6. Edit Selected Ticket - if self.ui_selected_ticket_id: - imgui.separator() - imgui.text_colored(C_VAL, f"Editing: {self.ui_selected_ticket_id}") - ticket = next((t for t in self.active_tickets if str(t.get('id', '')) == self.ui_selected_ticket_id), None) - if ticket: - imgui.text(f"Status: {ticket.get('status', 'todo')}") - prio = ticket.get('priority', 'medium') - imgui.text("Priority:") - imgui.same_line() - if imgui.begin_combo(f"##edit_prio_{ticket.get('id')}", prio): - for p_opt in ['high', 'medium', 'low']: - if imgui.selectable(p_opt, p_opt == prio)[0]: - ticket['priority'] = p_opt - self._push_mma_state_update() - imgui.end_combo() - imgui.text(f"Target: {ticket.get('target_file', '')}") - deps = ticket.get('depends_on', []) - imgui.text(f"Depends on: {', '.join(deps)}") - if imgui.button(f"Mark Complete##{self.ui_selected_ticket_id}"): - ticket['status'] = 'done' - self._push_mma_state_update() - imgui.same_line() - if imgui.button(f"Delete##{self.ui_selected_ticket_id}"): - self.active_tickets = [t for t in self.active_tickets if str(t.get('id', '')) != self.ui_selected_ticket_id] - self.ui_selected_ticket_id = None - self._push_mma_state_update() - def _render_tier_stream_panel(self, tier_key: str, stream_key: str | None) -> None: if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_tier_stream_panel") if self.is_viewing_prior_session: diff --git a/tests/test_task_dag_popout_sim.py b/tests/test_task_dag_popout_sim.py new file mode 100644 index 0000000..249f454 --- /dev/null +++ b/tests/test_task_dag_popout_sim.py @@ -0,0 +1,30 @@ +import pytest +import time +from src.api_hook_client import ApiHookClient + +def test_task_dag_popout(live_gui): + client = ApiHookClient() + + # 1. Check initial state + state = client.get_gui_state() + assert state.get("ui_separate_task_dag") is False + + # 2. Enable popout and ensure window is shown + client.set_value("ui_separate_task_dag", True) + # We need to manually set show_windows["Task DAG"] as well since we are bypassing the checkbox logic + show_windows = state.get("show_windows", {}) + show_windows["Task DAG"] = True + client.set_value("show_windows", show_windows) + time.sleep(1) + + # 3. Verify state + state = client.get_gui_state() + assert state.get("ui_separate_task_dag") is True + assert state.get("show_windows", {}).get("Task DAG") is True + + # 4. Disable popout + client.set_value("ui_separate_task_dag", False) + time.sleep(1) + + state = client.get_gui_state() + assert state.get("ui_separate_task_dag") is False