Compare commits
5 Commits
90670b9671
...
fa29c53b1e
| Author | SHA1 | Date | |
|---|---|---|---|
| fa29c53b1e | |||
| 4f4f914c64 | |||
| f8e1a5b405 | |||
| d520d5d6c2 | |||
| 14dab8e67f |
@@ -74,7 +74,7 @@ DockId=0xAFC85805,2
|
|||||||
|
|
||||||
[Window][Theme]
|
[Window][Theme]
|
||||||
Pos=0,17
|
Pos=0,17
|
||||||
Size=517,960
|
Size=32,960
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000005,1
|
DockId=0x00000005,1
|
||||||
|
|
||||||
@@ -84,14 +84,14 @@ Size=900,700
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Diagnostics]
|
[Window][Diagnostics]
|
||||||
Pos=2989,1760
|
Pos=829,990
|
||||||
Size=851,377
|
Size=851,210
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,0
|
DockId=0x00000002,0
|
||||||
|
|
||||||
[Window][Context Hub]
|
[Window][Context Hub]
|
||||||
Pos=0,17
|
Pos=0,17
|
||||||
Size=517,960
|
Size=32,960
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000005,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
@@ -102,26 +102,26 @@ Collapsed=0
|
|||||||
DockId=0x0000000D,0
|
DockId=0x0000000D,0
|
||||||
|
|
||||||
[Window][Discussion Hub]
|
[Window][Discussion Hub]
|
||||||
Pos=1750,17
|
Pos=430,17
|
||||||
Size=1237,1142
|
Size=397,637
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000013,0
|
DockId=0x00000013,0
|
||||||
|
|
||||||
[Window][Operations Hub]
|
[Window][Operations Hub]
|
||||||
Pos=519,17
|
Pos=34,17
|
||||||
Size=1229,1142
|
Size=394,637
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000012,0
|
DockId=0x00000012,0
|
||||||
|
|
||||||
[Window][Files & Media]
|
[Window][Files & Media]
|
||||||
Pos=0,979
|
Pos=0,979
|
||||||
Size=517,1158
|
Size=32,221
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,1
|
DockId=0x00000006,1
|
||||||
|
|
||||||
[Window][AI Settings]
|
[Window][AI Settings]
|
||||||
Pos=0,979
|
Pos=0,979
|
||||||
Size=517,1158
|
Size=32,221
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,0
|
DockId=0x00000006,0
|
||||||
|
|
||||||
@@ -131,14 +131,14 @@ Size=416,325
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][MMA Dashboard]
|
[Window][MMA Dashboard]
|
||||||
Pos=2989,17
|
Pos=829,17
|
||||||
Size=851,1741
|
Size=851,971
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,0
|
DockId=0x00000001,0
|
||||||
|
|
||||||
[Window][Log Management]
|
[Window][Log Management]
|
||||||
Pos=2989,17
|
Pos=829,17
|
||||||
Size=851,1741
|
Size=851,971
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,1
|
DockId=0x00000001,1
|
||||||
|
|
||||||
@@ -148,26 +148,26 @@ Size=262,209
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Tier 1: Strategy]
|
[Window][Tier 1: Strategy]
|
||||||
Pos=519,1161
|
Pos=34,656
|
||||||
Size=513,976
|
Size=165,544
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000014,0
|
DockId=0x00000014,0
|
||||||
|
|
||||||
[Window][Tier 2: Tech Lead]
|
[Window][Tier 2: Tech Lead]
|
||||||
Pos=1034,1161
|
Pos=201,656
|
||||||
Size=714,976
|
Size=228,544
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000016,0
|
DockId=0x00000016,0
|
||||||
|
|
||||||
[Window][Tier 4: QA]
|
[Window][Tier 4: QA]
|
||||||
Pos=2576,1161
|
Pos=696,656
|
||||||
Size=411,976
|
Size=131,544
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000019,0
|
DockId=0x00000019,0
|
||||||
|
|
||||||
[Window][Tier 3: Workers]
|
[Window][Tier 3: Workers]
|
||||||
Pos=1750,1161
|
Pos=431,656
|
||||||
Size=824,976
|
Size=263,544
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000018,0
|
DockId=0x00000018,0
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ Column 3 Width=100
|
|||||||
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
||||||
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
||||||
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
||||||
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,17 Size=3840,2120 Split=X
|
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,17 Size=1680,1183 Split=X
|
||||||
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2987,1183 Split=X
|
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2987,1183 Split=X
|
||||||
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
||||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=517,858 Split=Y Selected=0x8CA2375C
|
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=517,858 Split=Y Selected=0x8CA2375C
|
||||||
|
|||||||
@@ -171,3 +171,22 @@ class ApiHookClient:
|
|||||||
def reset_session(self) -> None:
|
def reset_session(self) -> None:
|
||||||
"""Resets the current session via button click."""
|
"""Resets the current session via button click."""
|
||||||
self.click("btn_reset")
|
self.click("btn_reset")
|
||||||
|
|
||||||
|
def trigger_patch(self, patch_text: str, file_paths: list[str]) -> dict[str, Any]:
|
||||||
|
"""Triggers the patch modal to show in the GUI."""
|
||||||
|
return self._make_request('POST', '/api/patch/trigger', data={
|
||||||
|
"patch_text": patch_text,
|
||||||
|
"file_paths": file_paths
|
||||||
|
}) or {}
|
||||||
|
|
||||||
|
def apply_patch(self) -> dict[str, Any]:
|
||||||
|
"""Applies the pending patch."""
|
||||||
|
return self._make_request('POST', '/api/patch/apply') or {}
|
||||||
|
|
||||||
|
def reject_patch(self) -> dict[str, Any]:
|
||||||
|
"""Rejects the pending patch."""
|
||||||
|
return self._make_request('POST', '/api/patch/reject') or {}
|
||||||
|
|
||||||
|
def get_patch_status(self) -> dict[str, Any]:
|
||||||
|
"""Gets the current patch modal status."""
|
||||||
|
return self._make_request('GET', '/api/patch/status') or {}
|
||||||
|
|||||||
105
src/api_hooks.py
105
src/api_hooks.py
@@ -288,6 +288,111 @@ class HookHandler(BaseHTTPRequestHandler):
|
|||||||
else:
|
else:
|
||||||
self.send_response(504)
|
self.send_response(504)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
elif self.path == "/api/patch/trigger":
|
||||||
|
sys.stderr.write(f"[DEBUG] /api/patch/trigger called with data: {data}\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
patch_text = data.get("patch_text", "")
|
||||||
|
file_paths = data.get("file_paths", [])
|
||||||
|
sys.stderr.write(f"[DEBUG] patch_text length: {len(patch_text)}, files: {file_paths}\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
event = threading.Event()
|
||||||
|
result = {"status": "queued"}
|
||||||
|
def trigger_patch():
|
||||||
|
try:
|
||||||
|
sys.stderr.write(f"[DEBUG] trigger_patch callback executing...\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
app._pending_patch_text = patch_text
|
||||||
|
app._pending_patch_files = file_paths
|
||||||
|
app._show_patch_modal = True
|
||||||
|
sys.stderr.write(f"[DEBUG] Set patch modal: show={app._show_patch_modal}, text={'yes' if app._pending_patch_text else 'no'}\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
result["status"] = "ok"
|
||||||
|
except Exception as e:
|
||||||
|
sys.stderr.write(f"[DEBUG] trigger_patch error: {e}\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
result["status"] = "error"
|
||||||
|
result["error"] = str(e)
|
||||||
|
finally:
|
||||||
|
event.set()
|
||||||
|
lock = _get_app_attr(app, "_pending_gui_tasks_lock")
|
||||||
|
tasks = _get_app_attr(app, "_pending_gui_tasks")
|
||||||
|
if lock and tasks is not None:
|
||||||
|
with lock: tasks.append({"action": "custom_callback", "callback": trigger_patch})
|
||||||
|
if event.wait(timeout=10):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/json")
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(json.dumps(result).encode("utf-8"))
|
||||||
|
else:
|
||||||
|
self.send_response(504)
|
||||||
|
self.end_headers()
|
||||||
|
elif self.path == "/api/patch/apply":
|
||||||
|
event = threading.Event()
|
||||||
|
result = {"status": "done"}
|
||||||
|
def apply_patch():
|
||||||
|
try:
|
||||||
|
if hasattr(app, "_apply_pending_patch"):
|
||||||
|
app._apply_pending_patch()
|
||||||
|
else:
|
||||||
|
result["status"] = "no_method"
|
||||||
|
except Exception as e:
|
||||||
|
result["status"] = "error"
|
||||||
|
result["error"] = str(e)
|
||||||
|
finally:
|
||||||
|
event.set()
|
||||||
|
lock = _get_app_attr(app, "_pending_gui_tasks_lock")
|
||||||
|
tasks = _get_app_attr(app, "_pending_gui_tasks")
|
||||||
|
if lock and tasks is not None:
|
||||||
|
with lock: tasks.append({"action": "custom_callback", "callback": apply_patch})
|
||||||
|
if event.wait(timeout=10):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/json")
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(json.dumps(result).encode("utf-8"))
|
||||||
|
else:
|
||||||
|
self.send_response(504)
|
||||||
|
self.end_headers()
|
||||||
|
elif self.path == "/api/patch/reject":
|
||||||
|
event = threading.Event()
|
||||||
|
result = {"status": "done"}
|
||||||
|
def reject_patch():
|
||||||
|
try:
|
||||||
|
app._show_patch_modal = False
|
||||||
|
app._pending_patch_text = None
|
||||||
|
app._pending_patch_files = []
|
||||||
|
except Exception as e:
|
||||||
|
result["status"] = "error"
|
||||||
|
result["error"] = str(e)
|
||||||
|
finally:
|
||||||
|
event.set()
|
||||||
|
lock = _get_app_attr(app, "_pending_gui_tasks_lock")
|
||||||
|
tasks = _get_app_attr(app, "_pending_gui_tasks")
|
||||||
|
if lock and tasks is not None:
|
||||||
|
with lock: tasks.append({"action": "custom_callback", "callback": reject_patch})
|
||||||
|
if event.wait(timeout=10):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/json")
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(json.dumps(result).encode("utf-8"))
|
||||||
|
else:
|
||||||
|
self.send_response(504)
|
||||||
|
self.end_headers()
|
||||||
|
elif self.path == "/api/patch/status":
|
||||||
|
sys.stderr.write(f"[DEBUG] /api/patch/status called\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
show_modal = _get_app_attr(app, "_show_patch_modal", False)
|
||||||
|
patch_text = _get_app_attr(app, "_pending_patch_text", None)
|
||||||
|
patch_files = _get_app_attr(app, "_pending_patch_files", [])
|
||||||
|
sys.stderr.write(f"[DEBUG] patch status: show_modal={show_modal}, patch_text={patch_text is not None}, files={patch_files}\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/json")
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(json.dumps({
|
||||||
|
"show_modal": show_modal,
|
||||||
|
"patch_text": patch_text,
|
||||||
|
"file_paths": patch_files
|
||||||
|
}).encode("utf-8"))
|
||||||
elif self.path == "/api/ask":
|
elif self.path == "/api/ask":
|
||||||
request_id = str(uuid.uuid4())
|
request_id = str(uuid.uuid4())
|
||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
|
|||||||
@@ -137,6 +137,10 @@ class AppController:
|
|||||||
self.active_track: Optional[models.Track] = None
|
self.active_track: Optional[models.Track] = None
|
||||||
self.active_tickets: List[Dict[str, Any]] = []
|
self.active_tickets: List[Dict[str, Any]] = []
|
||||||
self.mma_streams: Dict[str, str] = {}
|
self.mma_streams: Dict[str, str] = {}
|
||||||
|
self._pending_patch_text: Optional[str] = None
|
||||||
|
self._pending_patch_files: List[str] = []
|
||||||
|
self._show_patch_modal: bool = False
|
||||||
|
self._patch_error_message: Optional[str] = None
|
||||||
self.mma_status: str = "idle"
|
self.mma_status: str = "idle"
|
||||||
self._tool_log: List[Dict[str, Any]] = []
|
self._tool_log: List[Dict[str, Any]] = []
|
||||||
self._comms_log: List[Dict[str, Any]] = []
|
self._comms_log: List[Dict[str, Any]] = []
|
||||||
@@ -294,7 +298,10 @@ class AppController:
|
|||||||
'tracks': 'tracks',
|
'tracks': 'tracks',
|
||||||
'thinking_indicator': 'thinking_indicator',
|
'thinking_indicator': 'thinking_indicator',
|
||||||
'operations_live_indicator': 'operations_live_indicator',
|
'operations_live_indicator': 'operations_live_indicator',
|
||||||
'prior_session_indicator': 'prior_session_indicator'
|
'prior_session_indicator': 'prior_session_indicator',
|
||||||
|
'_show_patch_modal': '_show_patch_modal',
|
||||||
|
'_pending_patch_text': '_pending_patch_text',
|
||||||
|
'_pending_patch_files': '_pending_patch_files'
|
||||||
})
|
})
|
||||||
self._init_actions()
|
self._init_actions()
|
||||||
|
|
||||||
@@ -532,6 +539,14 @@ class AppController:
|
|||||||
task["dialog_container"][0] = dlg
|
task["dialog_container"][0] = dlg
|
||||||
elif action == 'refresh_from_project':
|
elif action == 'refresh_from_project':
|
||||||
self._refresh_from_project()
|
self._refresh_from_project()
|
||||||
|
elif action == "show_patch_modal":
|
||||||
|
self._pending_patch_text = task.get("patch_text", "")
|
||||||
|
self._pending_patch_files = task.get("file_paths", [])
|
||||||
|
self._show_patch_modal = True
|
||||||
|
elif action == "hide_patch_modal":
|
||||||
|
self._show_patch_modal = False
|
||||||
|
self._pending_patch_text = None
|
||||||
|
self._pending_patch_files = []
|
||||||
elif action == "mma_spawn_approval":
|
elif action == "mma_spawn_approval":
|
||||||
spawn_dlg = MMASpawnApprovalDialog(
|
spawn_dlg = MMASpawnApprovalDialog(
|
||||||
str(task.get("ticket_id") or ""),
|
str(task.get("ticket_id") or ""),
|
||||||
|
|||||||
21
src/gui_2.py
21
src/gui_2.py
@@ -114,11 +114,6 @@ class App:
|
|||||||
self._tool_log_dirty: bool = True
|
self._tool_log_dirty: bool = True
|
||||||
self._last_ui_focus_agent: Optional[str] = None
|
self._last_ui_focus_agent: Optional[str] = None
|
||||||
self._log_registry: Optional[log_registry.LogRegistry] = None
|
self._log_registry: Optional[log_registry.LogRegistry] = None
|
||||||
# Patch viewer state for Tier 4 auto-patching
|
|
||||||
self._pending_patch_text: Optional[str] = None
|
|
||||||
self._pending_patch_files: list[str] = []
|
|
||||||
self._show_patch_modal: bool = False
|
|
||||||
self._patch_error_message: Optional[str] = None
|
|
||||||
|
|
||||||
def _handle_approve_tool(self, user_data=None) -> None:
|
def _handle_approve_tool(self, user_data=None) -> None:
|
||||||
"""UI-level wrapper for approving a pending tool execution ask."""
|
"""UI-level wrapper for approving a pending tool execution ask."""
|
||||||
@@ -883,8 +878,8 @@ class App:
|
|||||||
if not self._show_patch_modal:
|
if not self._show_patch_modal:
|
||||||
return
|
return
|
||||||
imgui.open_popup("Apply Patch?")
|
imgui.open_popup("Apply Patch?")
|
||||||
if imgui.begin_popup_modal("Apply Patch?", True, imgui.ImVec2(600, 500))[0]:
|
if imgui.begin_popup_modal("Apply Patch?", True, imgui.WindowFlags_.always_auto_resize)[0]:
|
||||||
imgui.text_colored(imgui.ImVec4(1, 0.9, 0.3, 1), "Tier 4 QA Generated a Patch")
|
imgui.text_colored(vec4(255, 230, 77), "Tier 4 QA Generated a Patch")
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
if self._pending_patch_files:
|
if self._pending_patch_files:
|
||||||
imgui.text("Files to modify:")
|
imgui.text("Files to modify:")
|
||||||
@@ -892,7 +887,7 @@ class App:
|
|||||||
imgui.text(f" - {f}")
|
imgui.text(f" - {f}")
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
if self._patch_error_message:
|
if self._patch_error_message:
|
||||||
imgui.text_colored(imgui.ImVec4(1, 0.3, 0.3, 1), f"Error: {self._patch_error_message}")
|
imgui.text_colored(vec4(255, 77, 77), f"Error: {self._patch_error_message}")
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
imgui.text("Diff Preview:")
|
imgui.text("Diff Preview:")
|
||||||
imgui.begin_child("patch_diff_scroll", imgui.ImVec2(-1, 280), True)
|
imgui.begin_child("patch_diff_scroll", imgui.ImVec2(-1, 280), True)
|
||||||
@@ -900,19 +895,19 @@ class App:
|
|||||||
diff_lines = self._pending_patch_text.split("\n")
|
diff_lines = self._pending_patch_text.split("\n")
|
||||||
for line in diff_lines:
|
for line in diff_lines:
|
||||||
if line.startswith("+++") or line.startswith("---") or line.startswith("@@"):
|
if line.startswith("+++") or line.startswith("---") or line.startswith("@@"):
|
||||||
imgui.text_colored(imgui.ImVec4(0.3, 0.7, 1, 1), line)
|
imgui.text_colored(vec4(77, 178, 255), line)
|
||||||
elif line.startswith("+"):
|
elif line.startswith("+"):
|
||||||
imgui.text_colored(imgui.ImVec4(0.2, 0.9, 0.2, 1), line)
|
imgui.text_colored(vec4(51, 230, 51), line)
|
||||||
elif line.startswith("-"):
|
elif line.startswith("-"):
|
||||||
imgui.text_colored(imgui.ImVec4(0.9, 0.2, 0.2, 1), line)
|
imgui.text_colored(vec4(230, 51, 51), line)
|
||||||
else:
|
else:
|
||||||
imgui.text(line)
|
imgui.text(line)
|
||||||
imgui.end_child()
|
imgui.end_child()
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
if imgui.button("Apply Patch", imgui.ImVec2(150, 0)):
|
if imgui.button("Apply Patch"):
|
||||||
self._apply_pending_patch()
|
self._apply_pending_patch()
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Reject", imgui.ImVec2(150, 0)):
|
if imgui.button("Reject"):
|
||||||
self._show_patch_modal = False
|
self._show_patch_modal = False
|
||||||
self._pending_patch_text = None
|
self._pending_patch_text = None
|
||||||
self._pending_patch_files = []
|
self._pending_patch_files = []
|
||||||
|
|||||||
80
tests/test_patch_modal_gui.py
Normal file
80
tests/test_patch_modal_gui.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import pytest
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||||
|
|
||||||
|
from src import api_hook_client
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
|
@pytest.mark.timeout(120)
|
||||||
|
def test_patch_modal_appears_on_trigger(live_gui) -> None:
|
||||||
|
"""
|
||||||
|
Test that triggering a patch shows the modal in the GUI.
|
||||||
|
Uses live_gui fixture to start the GUI with test hooks enabled.
|
||||||
|
"""
|
||||||
|
proc, _ = live_gui
|
||||||
|
client = api_hook_client.ApiHookClient()
|
||||||
|
|
||||||
|
if not client.wait_for_server(timeout=15):
|
||||||
|
pytest.skip("GUI server not available")
|
||||||
|
|
||||||
|
sample_patch = """--- a/test_file.py
|
||||||
|
+++ b/test_file.py
|
||||||
|
@@ -1,3 +1,4 @@
|
||||||
|
def hello():
|
||||||
|
- print("old")
|
||||||
|
+ print("new")
|
||||||
|
+ print("extra")
|
||||||
|
return True"""
|
||||||
|
|
||||||
|
client.push_event("show_patch_modal", {"patch_text": sample_patch, "file_paths": ["test_file.py"]})
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
status = client.get_gui_state()
|
||||||
|
print(f"[DEBUG] GUI state: {status}")
|
||||||
|
assert status.get("_show_patch_modal") == True, f"Patch modal should be visible but got {status}"
|
||||||
|
|
||||||
|
client.push_event("hide_patch_modal", {})
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
status = client.get_gui_state()
|
||||||
|
assert status.get("_show_patch_modal") == False, "Patch modal should be closed after hide"
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
|
@pytest.mark.timeout(120)
|
||||||
|
def test_patch_apply_modal_workflow(live_gui) -> None:
|
||||||
|
"""
|
||||||
|
Test the full patch apply workflow: trigger -> apply -> verify modal closes.
|
||||||
|
"""
|
||||||
|
proc, _ = live_gui
|
||||||
|
client = api_hook_client.ApiHookClient()
|
||||||
|
|
||||||
|
if not client.wait_for_server(timeout=15):
|
||||||
|
pytest.skip("GUI server not available")
|
||||||
|
|
||||||
|
sample_patch = """--- a/example.py
|
||||||
|
+++ b/example.py
|
||||||
|
@@ -5,3 +5,4 @@
|
||||||
|
def calculate():
|
||||||
|
x = 1
|
||||||
|
- return x
|
||||||
|
+ return x + 1
|
||||||
|
+ # added line"""
|
||||||
|
|
||||||
|
client.push_event("show_patch_modal", {"patch_text": sample_patch, "file_paths": ["example.py"]})
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
status = client.get_gui_state()
|
||||||
|
assert status.get("_show_patch_modal") == True, f"Modal should be visible: {status}"
|
||||||
|
|
||||||
|
client.push_event("hide_patch_modal", {})
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
status = client.get_gui_state()
|
||||||
|
assert status.get("_show_patch_modal") == False, "Patch modal should close after hide"
|
||||||
Reference in New Issue
Block a user