feat(gui): Implement manual ticket queue management with priority, multi-select, and drag-drop reordering

This commit is contained in:
2026-03-07 15:42:32 -05:00
parent c56c8db6db
commit a22603d136
3 changed files with 225 additions and 1 deletions

View File

@@ -105,6 +105,8 @@ class App:
self.node_editor_config = ed.Config()
self.node_editor_ctx = ed.create_editor(self.node_editor_config)
self.ui_selected_ticket_id: Optional[str] = None
self.ui_selected_tickets: set[str] = set()
self.ui_new_ticket_priority: str = "medium"
self._autofocus_response_tab = False
gui_cfg = self.config.get("gui", {})
self.ui_separate_message_panel = gui_cfg.get("separate_message_panel", False)
@@ -1930,6 +1932,137 @@ class App:
self._scroll_tool_calls_to_bottom = False
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_tool_calls_panel")
def bulk_execute(self) -> None:
for tid in self.ui_selected_tickets:
t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
if t: t['status'] = 'ready'
self._push_mma_state_update()
def bulk_skip(self) -> None:
for tid in self.ui_selected_tickets:
t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
if t: t['status'] = 'completed'
self._push_mma_state_update()
def bulk_block(self) -> None:
for tid in self.ui_selected_tickets:
t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
if t: t['status'] = 'blocked'
self._push_mma_state_update()
def _reorder_ticket(self, src_idx: int, dst_idx: int) -> None:
if src_idx == dst_idx: return
new_tickets = list(self.active_tickets)
ticket = new_tickets.pop(src_idx)
new_tickets.insert(dst_idx, ticket)
# Validate dependencies: a ticket cannot be placed before any of its dependencies
id_to_idx = {str(t.get('id', '')): i for i, t in enumerate(new_tickets)}
valid = True
for i, t in enumerate(new_tickets):
deps = t.get('depends_on', [])
for d_id in deps:
if d_id in id_to_idx and id_to_idx[d_id] >= i:
valid = False
break
if not valid: break
if valid:
self.active_tickets = new_tickets
self._push_mma_state_update()
def _render_ticket_queue(self) -> None:
imgui.text("Ticket Queue Management")
if not self.active_track:
imgui.text_disabled("No active track.")
return
# Select All / None
if imgui.button("Select All"):
self.ui_selected_tickets = {str(t.get('id', '')) for t in self.active_tickets}
imgui.same_line()
if imgui.button("Select None"):
self.ui_selected_tickets.clear()
imgui.same_line()
imgui.spacing()
imgui.same_line()
# Bulk Actions
if imgui.button("Bulk Execute"):
self.bulk_execute()
imgui.same_line()
if imgui.button("Bulk Skip"):
self.bulk_skip()
imgui.same_line()
if imgui.button("Bulk Block"):
self.bulk_block()
# Table
flags = imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable | imgui.TableFlags_.scroll_y
if imgui.begin_table("ticket_queue_table", 5, flags, imgui.ImVec2(0, 300)):
imgui.table_setup_column("Select", imgui.TableColumnFlags_.width_fixed, 40)
imgui.table_setup_column("ID", imgui.TableColumnFlags_.width_fixed, 80)
imgui.table_setup_column("Priority", imgui.TableColumnFlags_.width_fixed, 100)
imgui.table_setup_column("Status", imgui.TableColumnFlags_.width_fixed, 100)
imgui.table_setup_column("Description", imgui.TableColumnFlags_.width_stretch)
imgui.table_headers_row()
for i, t in enumerate(self.active_tickets):
tid = str(t.get('id', ''))
imgui.table_next_row()
# Select
imgui.table_next_column()
is_sel = tid in self.ui_selected_tickets
changed, is_sel = imgui.checkbox(f"##sel_{tid}", is_sel)
if changed:
if is_sel: self.ui_selected_tickets.add(tid)
else: self.ui_selected_tickets.discard(tid)
# ID
imgui.table_next_column()
is_selected = (tid == self.ui_selected_ticket_id)
opened, _ = imgui.selectable(f"{tid}##drag_{tid}", is_selected)
if opened:
self.ui_selected_ticket_id = tid
if imgui.begin_drag_drop_source():
imgui.set_drag_drop_payload("TICKET_REORDER", i)
imgui.text(f"Moving {tid}")
imgui.end_drag_drop_source()
if imgui.begin_drag_drop_target():
payload = imgui.accept_drag_drop_payload("TICKET_REORDER")
if payload:
src_idx = int(payload.data)
self._reorder_ticket(src_idx, i)
imgui.end_drag_drop_target()
# Priority
imgui.table_next_column()
prio = t.get('priority', 'medium')
p_col = vec4(180, 180, 180) # gray
if prio == 'high': p_col = vec4(255, 100, 100) # red
elif prio == 'medium': p_col = vec4(255, 255, 100) # yellow
imgui.push_style_color(imgui.Col_.text, p_col)
if imgui.begin_combo(f"##prio_{tid}", prio, imgui.ComboFlags_.height_small):
for p_opt in ['high', 'medium', 'low']:
if imgui.selectable(p_opt, p_opt == prio)[0]:
t['priority'] = p_opt
self._push_mma_state_update()
imgui.end_combo()
imgui.pop_style_color()
# Status
imgui.table_next_column()
imgui.text(t.get('status', 'todo'))
# Description
imgui.table_next_column()
imgui.text(t.get('description', ''))
imgui.end_table()
def _render_mma_dashboard(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
# Task 5.3: Dense Summary Line
@@ -2178,6 +2311,8 @@ class App:
imgui.pop_id()
imgui.separator()
self._render_ticket_queue()
imgui.separator()
# 4. Task DAG Visualizer
imgui.text("Task DAG")
if self.active_track and self.node_editor_ctx:
@@ -2292,11 +2427,19 @@ class App:
_, self.ui_new_ticket_desc = imgui.input_text_multiline("Description##new_ticket", self.ui_new_ticket_desc, imgui.ImVec2(-1, 60))
_, self.ui_new_ticket_target = imgui.input_text("Target File##new_ticket", self.ui_new_ticket_target)
_, self.ui_new_ticket_deps = imgui.input_text("Depends On (IDs, comma-separated)##new_ticket", self.ui_new_ticket_deps)
imgui.text("Priority:")
imgui.same_line()
if imgui.begin_combo("##new_prio", self.ui_new_ticket_priority):
for p_opt in ['high', 'medium', 'low']:
if imgui.selectable(p_opt, p_opt == self.ui_new_ticket_priority)[0]:
self.ui_new_ticket_priority = p_opt
imgui.end_combo()
if imgui.button("Create"):
new_ticket = {
"id": self.ui_new_ticket_id,
"description": self.ui_new_ticket_desc,
"status": "todo",
"priority": self.ui_new_ticket_priority,
"assigned_to": "tier3-worker",
"target_file": self.ui_new_ticket_target,
"depends_on": [d.strip() for d in self.ui_new_ticket_deps.split(",") if d.strip()]
@@ -2318,6 +2461,15 @@ class App:
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)}")