From 8e57ae12477cb83de23523d0b1e4ced552999b29 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 1 Mar 2026 15:49:18 -0500 Subject: [PATCH] feat(gui): Add blinking APPROVAL PENDING badge to MMA dashboard --- gui_2.py | 13 ++++ scripts/tasks/gui_ux_1_2_impl.toml | 34 +++++++++ scripts/tasks/gui_ux_1_2_tests.toml | 44 +++++++++++ tests/test_mma_approval_indicators.py | 102 ++++++++++++++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 scripts/tasks/gui_ux_1_2_impl.toml create mode 100644 scripts/tasks/gui_ux_1_2_tests.toml create mode 100644 tests/test_mma_approval_indicators.py diff --git a/gui_2.py b/gui_2.py index d9bfd5f..dc85b92 100644 --- a/gui_2.py +++ b/gui_2.py @@ -2685,6 +2685,19 @@ class App: if self.active_tier: imgui.same_line() imgui.text_colored(C_VAL, f"| Active: {self.active_tier}") + # Approval pending indicator + any_pending = ( + self._pending_mma_spawn is not None or + self._pending_mma_approval is not None or + self._pending_ask_dialog + ) + if any_pending: + alpha = abs(math.sin(time.time() * 5)) + imgui.same_line() + imgui.text_colored(imgui.ImVec4(1.0, 0.3, 0.3, alpha), " APPROVAL PENDING") + imgui.same_line() + if imgui.button("Go to Approval"): + pass # scroll/focus handled by existing dialog rendering imgui.separator() # 2. Active Track Info if self.active_track: diff --git a/scripts/tasks/gui_ux_1_2_impl.toml b/scripts/tasks/gui_ux_1_2_impl.toml new file mode 100644 index 0000000..7e8af3b --- /dev/null +++ b/scripts/tasks/gui_ux_1_2_impl.toml @@ -0,0 +1,34 @@ +role = "tier3-worker" +docs = ["conductor/workflow.md"] +prompt = """ +Implement Task 1.2 in @gui_2.py: add a blinking "APPROVAL PENDING" badge in _render_mma_dashboard. + +LOCATION: In _render_mma_dashboard, after the block that renders the Status line and active_tier text (around line 2686-2690 after this existing code): + imgui.same_line() + imgui.text(f"Status: {self.mma_status.upper()}") + if self.active_tier: + imgui.same_line() + imgui.text_colored(C_VAL, f"| Active: {self.active_tier}") + +ADD immediately after (before the next imgui.separator()): + # Approval pending indicator + any_pending = ( + self._pending_mma_spawn is not None or + self._pending_mma_approval is not None or + self._pending_ask_dialog + ) + if any_pending: + alpha = abs(math.sin(time.time() * 5)) + imgui.same_line() + imgui.text_colored(imgui.ImVec4(1.0, 0.3, 0.3, alpha), " APPROVAL PENDING") + imgui.same_line() + if imgui.button("Go to Approval"): + pass # scroll/focus handled by existing dialog rendering + +Also ensure `import math` is present at the top of gui_2.py (check if it's already imported — if not, add it alongside the other stdlib imports). + +TESTS that must pass: @tests/test_mma_approval_indicators.py +Also confirm @tests/test_mma_dashboard_streams.py still passes. + +Use exactly 1-space indentation for Python code. +""" diff --git a/scripts/tasks/gui_ux_1_2_tests.toml b/scripts/tasks/gui_ux_1_2_tests.toml new file mode 100644 index 0000000..68d7217 --- /dev/null +++ b/scripts/tasks/gui_ux_1_2_tests.toml @@ -0,0 +1,44 @@ +role = "tier3-worker" +docs = ["conductor/workflow.md"] +prompt = """ +Create tests/test_mma_approval_indicators.py — failing tests for Task 1.2 of comprehensive_gui_ux track. + +CONTEXT: Task 1.2 adds a blinking "APPROVAL PENDING" badge after the Status line in _render_mma_dashboard in @gui_2.py. It checks self._pending_mma_spawn, self._pending_mma_approval, and self._pending_ask_dialog. + +TESTS TO WRITE (all should FAIL against current code — test new behaviour): + +Use the same _make_app / _make_imgui_mock pattern as @tests/test_mma_dashboard_streams.py but add these attributes: + app._pending_mma_spawn = None + app._pending_mma_approval = None + app._pending_ask_dialog = False + +1. test_no_approval_badge_when_idle: + - All three pending attrs are None/False + - Patch gui_2.imgui, call App._render_mma_dashboard(app) + - Assert imgui.text_colored was NOT called with any string containing "APPROVAL PENDING" + +2. test_approval_badge_shown_when_spawn_pending: + - app._pending_mma_spawn = {"ticket_id": "T-001"} + - Patch gui_2.imgui, patch 'gui_2.math' (so math.sin returns 0.8) + - Call App._render_mma_dashboard(app) + - Collect all text_colored call args as a single joined string + - Assert "APPROVAL PENDING" appears in that string + +3. test_approval_badge_shown_when_mma_approval_pending: + - app._pending_mma_approval = {"step": "test"} + - Same patch pattern + - Assert "APPROVAL PENDING" in text_colored calls + +4. test_approval_badge_shown_when_ask_dialog_pending: + - app._pending_ask_dialog = True + - Same patch pattern + - Assert "APPROVAL PENDING" in text_colored calls + +IMPLEMENTATION DETAILS: +- Import: import pytest, math, from unittest.mock import patch, MagicMock, call +- Use the _make_app helper from test_mma_dashboard_streams (copy it or import it — prefer copying to keep tests self-contained) +- For _make_imgui_mock: same as test_mma_dashboard_streams, also set imgui_mock.ImVec4.return_value = MagicMock() +- Patch 'gui_2.math' where needed: with patch('gui_2.math') as mock_math: mock_math.sin.return_value = 0.8 +- Use 1-space indentation throughout. +- All 4 tests should FAIL against current gui_2.py (which has no approval badge logic yet). +""" diff --git a/tests/test_mma_approval_indicators.py b/tests/test_mma_approval_indicators.py new file mode 100644 index 0000000..6e113b5 --- /dev/null +++ b/tests/test_mma_approval_indicators.py @@ -0,0 +1,102 @@ +from __future__ import annotations +import math +import pytest +from unittest.mock import patch, MagicMock, call + +from gui_2 import App + + +def _make_app(**kwargs): + app = MagicMock(spec=App) + app.mma_streams = kwargs.get("mma_streams", {}) + app.mma_tier_usage = kwargs.get("mma_tier_usage", { + "Tier 1": {"input": 0, "output": 0}, + "Tier 2": {"input": 0, "output": 0}, + "Tier 3": {"input": 0, "output": 0}, + "Tier 4": {"input": 0, "output": 0}, + }) + app.tracks = kwargs.get("tracks", []) + app.active_track = kwargs.get("active_track", None) + app.active_tickets = kwargs.get("active_tickets", []) + app.mma_status = kwargs.get("mma_status", "idle") + app.active_tier = kwargs.get("active_tier", None) + app.mma_step_mode = kwargs.get("mma_step_mode", False) + app._pending_mma_spawn = kwargs.get("_pending_mma_spawn", None) + app._pending_mma_approval = kwargs.get("_pending_mma_approval", None) + app._pending_ask_dialog = kwargs.get("_pending_ask_dialog", False) + return app + + +def _make_imgui_mock(): + m = MagicMock() + m.begin_table.return_value = False + m.begin_child.return_value = False + m.checkbox.return_value = (False, False) + m.collapsing_header.return_value = False + m.ImVec2.return_value = MagicMock() + m.ImVec4.return_value = MagicMock() + return m + + +def _collect_text_colored_args(imgui_mock): + """Return a single joined string of all text_colored second-arg strings.""" + parts = [] + for c in imgui_mock.text_colored.call_args_list: + args = c.args + if len(args) >= 2: + parts.append(str(args[1])) + return " ".join(parts) + + +class TestMMAApprovalIndicators: + + def test_no_approval_badge_when_idle(self): + """No 'APPROVAL PENDING' badge when all pending attrs are None/False.""" + app = _make_app( + _pending_mma_spawn=None, + _pending_mma_approval=None, + _pending_ask_dialog=False, + ) + imgui_mock = _make_imgui_mock() + with patch("gui_2.imgui", imgui_mock): + App._render_mma_dashboard(app) + combined = _collect_text_colored_args(imgui_mock) + assert "APPROVAL PENDING" not in combined, ( + "text_colored called with 'APPROVAL PENDING' when no approval is pending" + ) + + def test_approval_badge_shown_when_spawn_pending(self): + """'APPROVAL PENDING' badge must appear when _pending_mma_spawn is set.""" + app = _make_app(_pending_mma_spawn={"ticket_id": "T-001"}) + imgui_mock = _make_imgui_mock() + with patch("gui_2.imgui", imgui_mock), patch("gui_2.math") as mock_math: + mock_math.sin.return_value = 0.8 + App._render_mma_dashboard(app) + combined = _collect_text_colored_args(imgui_mock) + assert "APPROVAL PENDING" in combined, ( + "text_colored not called with 'APPROVAL PENDING' when _pending_mma_spawn is set" + ) + + def test_approval_badge_shown_when_mma_approval_pending(self): + """'APPROVAL PENDING' badge must appear when _pending_mma_approval is set.""" + app = _make_app(_pending_mma_approval={"step": "test"}) + imgui_mock = _make_imgui_mock() + with patch("gui_2.imgui", imgui_mock), patch("gui_2.math") as mock_math: + mock_math.sin.return_value = 0.8 + App._render_mma_dashboard(app) + combined = _collect_text_colored_args(imgui_mock) + assert "APPROVAL PENDING" in combined, ( + "text_colored not called with 'APPROVAL PENDING' when _pending_mma_approval is set" + ) + + def test_approval_badge_shown_when_ask_dialog_pending(self): + """'APPROVAL PENDING' badge must appear when _pending_ask_dialog is True.""" + app = _make_app(_pending_ask_dialog=True) + imgui_mock = _make_imgui_mock() + with patch("gui_2.imgui", imgui_mock), patch("gui_2.math") as mock_math: + mock_math.sin.return_value = 0.8 + App._render_mma_dashboard(app) + combined = _collect_text_colored_args(imgui_mock) + assert "APPROVAL PENDING" in combined, ( + "text_colored not called with 'APPROVAL PENDING' when _pending_ask_dialog is True" + )