refactor(ticket): migrate Ticket consumers to direct field access (Phase 1)
TIER-2 READ AGENTS.md, conductor/workflow.md, conductor/edit_workflow.md,
conductor/tier2/githooks/forbidden-files.txt,
conductor/tracks/tier2_leak_prevention_20260620/spec.md,
conductor/code_styleguides/data_oriented_design.md,
conductor/code_styleguides/error_handling.md,
conductor/code_styleguides/type_aliases.md before Phase 1.
Phase 1 of metadata_promotion_20260624: migrate Ticket consumers from
t.get('key', default) / t['key'] to direct field access (t.id, t.status, etc.).
Changes:
- self.active_tickets: list[Metadata] -> list[models.Ticket]
- _deserialize_active_track_result populates self.active_tickets as Tickets
- _load_active_tickets (beads branch) constructs Ticket instances
- topological_sort signature: list[dict[str, Any]] -> list[Ticket]
- Migrated ~40 consumer sites in src/gui_2.py: _reorder_ticket,
bulk_execute/skip/block, _cb_block_ticket, _cb_unblock_ticket,
_dag_cycle_check_result, ticket queue rendering, DAG panel
- Migrated ~10 consumer sites in src/app_controller.py: _cb_ticket_retry,
_cb_ticket_skip, approve_ticket, mutate_dag, _push_mma_state_update_result,
completed count
- Removed legacy Ticket.get() compat method (Task 1.5)
- Added tests/test_metadata_promotion_phase1.py with 15 regression-guard tests
- Updated existing tests to construct Ticket instances instead of dicts
Verified: 1885 of 1910 unit tests pass (25 pre-existing failures unrelated
to Ticket migration; many are live_gui/sim tests that need a running GUI).
This commit is contained in:
+26
-24
@@ -1107,7 +1107,7 @@ class AppController:
|
||||
# --- Defaults set here so tests that construct AppController without
|
||||
# calling init_state() still see the attributes ---
|
||||
self.ui_global_preset_name: Optional[str] = None
|
||||
self.active_tickets: list[Metadata] = []
|
||||
self.active_tickets: list[models.Ticket] = []
|
||||
self.ui_selected_tickets: Set[str] = set()
|
||||
|
||||
#region: --- Configuration Maps ---
|
||||
@@ -2145,6 +2145,7 @@ class AppController:
|
||||
description=at_data.get("description"),
|
||||
tickets=tickets
|
||||
)
|
||||
self.active_tickets = tickets
|
||||
return Result(data=track)
|
||||
except (TypeError, ValueError, KeyError, AttributeError) as e:
|
||||
return Result(data=None, errors=[ErrorInfo(
|
||||
@@ -3052,7 +3053,7 @@ class AppController:
|
||||
elapsed_min = (time.time() - self._session_start_time) / 60.0 if self._token_history else 0
|
||||
burn_rate = total_tokens / elapsed_min if elapsed_min > 0 else 0
|
||||
session_cost = cost_tracker.estimate_cost("gemini-2.5-flash", total_input, total_output)
|
||||
completed = sum(1 for t in self.active_tickets if t.get("status") == "complete")
|
||||
completed = sum(1 for t in self.active_tickets if t.status == "complete")
|
||||
efficiency = total_tokens / completed if completed > 0 else 0
|
||||
return {
|
||||
"total_tokens": total_tokens,
|
||||
@@ -3273,7 +3274,8 @@ class AppController:
|
||||
result = self._deserialize_active_track_result(at_data)
|
||||
if result.ok:
|
||||
self.active_track = result.data
|
||||
self.active_tickets = at_data.get("tickets", []) # Keep dicts for UI table
|
||||
raw_tickets = at_data.get("tickets", [])
|
||||
self.active_tickets = [models.Ticket.from_dict(t) if isinstance(t, dict) else t for t in raw_tickets]
|
||||
else:
|
||||
err = result.errors[0]
|
||||
self._last_request_errors.append(("active_track_deserialize", err))
|
||||
@@ -4704,7 +4706,8 @@ class AppController:
|
||||
"""Phase 6 Group 6.7: topological sort with Result propagation.
|
||||
On ValueError: fall back to raw_tickets (preserves existing behavior)."""
|
||||
try:
|
||||
sorted_tickets_data = conductor_tech_lead.topological_sort(raw_tickets)
|
||||
normalized = [models.Ticket.from_dict(t) if isinstance(t, dict) else t for t in raw_tickets]
|
||||
sorted_tickets_data = conductor_tech_lead.topological_sort(normalized)
|
||||
return Result(data=sorted_tickets_data)
|
||||
except ValueError as e:
|
||||
err = ErrorInfo(kind=ErrorKind.INVALID_INPUT, message=str(e),
|
||||
@@ -4806,8 +4809,8 @@ class AppController:
|
||||
[C: tests/test_mma_ticket_actions.py:test_cb_ticket_retry]
|
||||
"""
|
||||
for t in self.active_tickets:
|
||||
if t.get('id') == ticket_id:
|
||||
t['status'] = 'todo'
|
||||
if t.id == ticket_id:
|
||||
t.status = 'todo'
|
||||
break
|
||||
self.event_queue.put("mma_retry", {"ticket_id": ticket_id})
|
||||
|
||||
@@ -4816,8 +4819,8 @@ class AppController:
|
||||
[C: tests/test_mma_ticket_actions.py:test_cb_ticket_skip]
|
||||
"""
|
||||
for t in self.active_tickets:
|
||||
if t.get('id') == ticket_id:
|
||||
t['status'] = 'skipped'
|
||||
if t.id == ticket_id:
|
||||
t.status = 'skipped'
|
||||
break
|
||||
self.event_queue.put("mma_skip", {"ticket_id": ticket_id})
|
||||
|
||||
@@ -4864,8 +4867,8 @@ class AppController:
|
||||
else:
|
||||
# Fallback if engine not running
|
||||
for t in self.active_tickets:
|
||||
if t.get('id') == ticket_id:
|
||||
t['status'] = 'in_progress'
|
||||
if t.id == ticket_id:
|
||||
t.status = 'in_progress'
|
||||
break
|
||||
self._push_mma_state_update()
|
||||
|
||||
@@ -4875,8 +4878,8 @@ class AppController:
|
||||
depends_on = data.get("depends_on")
|
||||
if ticket_id and depends_on is not None:
|
||||
for t in self.active_tickets:
|
||||
if t.get("id") == ticket_id:
|
||||
t["depends_on"] = depends_on
|
||||
if t.id == ticket_id:
|
||||
t.depends_on = depends_on
|
||||
break
|
||||
if self.active_track:
|
||||
for t in self.active_track.tickets:
|
||||
@@ -5068,11 +5071,11 @@ class AppController:
|
||||
if track is None: return OK
|
||||
new_tickets = [
|
||||
models.Ticket(
|
||||
id=t.get("id", ""),
|
||||
description=t.get("description", ""),
|
||||
status=t.get("status", "todo"),
|
||||
assigned_to=t.get("assigned_to", ""),
|
||||
depends_on=t.get("depends_on", []),
|
||||
id=t.id,
|
||||
description=t.description,
|
||||
status=t.status,
|
||||
assigned_to=t.assigned_to,
|
||||
depends_on=list(t.depends_on),
|
||||
)
|
||||
for t in self.active_tickets
|
||||
]
|
||||
@@ -5104,13 +5107,12 @@ class AppController:
|
||||
beads_result = self._load_beads_from_path_result(Path(base))
|
||||
if beads_result.ok:
|
||||
for bead in beads_result.data:
|
||||
self.active_tickets.append({
|
||||
"id": bead.id,
|
||||
"title": bead.title,
|
||||
"description": bead.description,
|
||||
"status": bead.status,
|
||||
"depends_on": [],
|
||||
})
|
||||
self.active_tickets.append(models.Ticket(
|
||||
id=bead.id,
|
||||
description=bead.description or "",
|
||||
status=bead.status,
|
||||
depends_on=[],
|
||||
))
|
||||
elif not beads_result.ok:
|
||||
self._report_worker_error("load_beads", beads_result)
|
||||
|
||||
|
||||
@@ -104,25 +104,19 @@ from src.dag_engine import TrackDAG
|
||||
from src.models import Ticket
|
||||
from src.result_types import ErrorInfo, ErrorKind, Result
|
||||
|
||||
def topological_sort(tickets: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
def topological_sort(tickets: list[Ticket]) -> list[Ticket]:
|
||||
"""
|
||||
Sorts a list of tickets based on their 'depends_on' field.
|
||||
Sorts a list of Ticket objects based on their depends_on field.
|
||||
Raises ValueError if a circular dependency or missing internal dependency is detected.
|
||||
[C: tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_complex, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_cycle, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_empty, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_linear, tests/test_conductor_tech_lead.py:TestTopologicalSort.test_topological_sort_missing_dependency, tests/test_conductor_tech_lead.py:test_topological_sort_vlog, tests/test_dag_engine.py:test_topological_sort, tests/test_dag_engine.py:test_topological_sort_cycle, tests/test_orchestration_logic.py:test_topological_sort, tests/test_orchestration_logic.py:test_topological_sort_circular, tests/test_perf_dag.py:test_dag_edge_cases, tests/test_perf_dag.py:test_dag_performance]
|
||||
"""
|
||||
# 1. Convert to Ticket objects for TrackDAG
|
||||
ticket_objs = []
|
||||
for t_data in tickets:
|
||||
ticket_objs.append(Ticket.from_dict(t_data))
|
||||
# 2. Use TrackDAG for validation and sorting
|
||||
dag = TrackDAG(ticket_objs)
|
||||
dag = TrackDAG(tickets)
|
||||
try:
|
||||
sorted_ids = dag.topological_sort()
|
||||
except ValueError as e:
|
||||
_dag_err = Result(data=None, errors=[ErrorInfo(kind=ErrorKind.INVALID_INPUT, message=f"DAG Validation Error: {e}", source="conductor_tech_lead.topological_sort", original=e)])
|
||||
raise ValueError(f"DAG Validation Error: {e}")
|
||||
# 3. Return sorted dictionaries
|
||||
ticket_map = {t['id']: t for t in tickets}
|
||||
ticket_map = {t.id: t for t in tickets}
|
||||
return [ticket_map[tid] for tid in sorted_ids]
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+73
-73
@@ -1363,10 +1363,10 @@ class App:
|
||||
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)}
|
||||
id_to_idx = {str(t.id): i for i, t in enumerate(new_tickets)}
|
||||
valid = True
|
||||
for i, t in enumerate(new_tickets):
|
||||
deps = t.get('depends_on', [])
|
||||
deps = t.depends_on
|
||||
for d_id in deps:
|
||||
if d_id in id_to_idx and id_to_idx[d_id] >= i:
|
||||
valid = False
|
||||
@@ -1384,20 +1384,20 @@ class App:
|
||||
|
||||
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'] = 'in_progress'
|
||||
t = next((t for t in self.active_tickets if str(t.id) == tid), None)
|
||||
if t: t.status = 'in_progress'
|
||||
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'
|
||||
t = next((t for t in self.active_tickets if str(t.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'
|
||||
t = next((t for t in self.active_tickets if str(t.id) == tid), None)
|
||||
if t: t.status = 'blocked'
|
||||
self._push_mma_state_update()
|
||||
|
||||
def _cb_kill_ticket(self, ticket_id: str) -> None:
|
||||
@@ -1405,44 +1405,44 @@ class App:
|
||||
self.controller.engine.kill_worker(ticket_id)
|
||||
|
||||
def _cb_block_ticket(self, ticket_id: str) -> None:
|
||||
t = next((t for t in self.active_tickets if str(t.get('id', '')) == ticket_id), None)
|
||||
t = next((t for t in self.active_tickets if str(t.id) == ticket_id), None)
|
||||
if t:
|
||||
t['status'] = 'blocked'
|
||||
t['manual_block'] = True
|
||||
t['blocked_reason'] = '[MANUAL] User blocked'
|
||||
t.status = 'blocked'
|
||||
t.manual_block = True
|
||||
t.blocked_reason = '[MANUAL] User blocked'
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
for t in self.active_tickets:
|
||||
if t.get('status') == 'todo':
|
||||
for dep_id in t.get('depends_on', []):
|
||||
dep = next((x for x in self.active_tickets if str(x.get('id', '')) == dep_id), None)
|
||||
if dep and dep.get('status') == 'blocked':
|
||||
t['status'] = 'blocked'
|
||||
changed = True
|
||||
if t.status == 'todo':
|
||||
for dep_id in t.depends_on:
|
||||
dep = next((x for x in self.active_tickets if str(x.id) == dep_id), None)
|
||||
if dep and dep.status == 'blocked':
|
||||
t.status = 'blocked'
|
||||
changed = True
|
||||
break
|
||||
self._push_mma_state_update()
|
||||
|
||||
def _cb_unblock_ticket(self, ticket_id: str) -> None:
|
||||
t = next((t for t in self.active_tickets if str(t.get('id', '')) == ticket_id), None)
|
||||
if t and t.get('manual_block', False):
|
||||
t['status'] = 'todo'
|
||||
t['manual_block'] = False
|
||||
t['blocked_reason'] = None
|
||||
t = next((t for t in self.active_tickets if str(t.id) == ticket_id), None)
|
||||
if t and t.manual_block:
|
||||
t.status = 'todo'
|
||||
t.manual_block = False
|
||||
t.blocked_reason = None
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
for t in self.active_tickets:
|
||||
if t.get('status') == 'blocked' and not t.get('manual_block', False):
|
||||
if t.status == 'blocked' and not t.manual_block:
|
||||
can_run = True
|
||||
for dep_id in t.get('depends_on', []):
|
||||
dep = next((x for x in self.active_tickets if str(x.get('id', '')) == dep_id), None)
|
||||
if dep and dep.get('status') != 'completed':
|
||||
for dep_id in t.depends_on:
|
||||
dep = next((x for x in self.active_tickets if str(x.id) == dep_id), None)
|
||||
if dep and dep.status != 'completed':
|
||||
can_run = False
|
||||
break
|
||||
if can_run:
|
||||
t['status'] = 'todo'
|
||||
changed = True
|
||||
t.status = 'todo'
|
||||
changed = True
|
||||
self._push_mma_state_update()
|
||||
|
||||
def _post_init_callback_result(app: "App") -> Result[None]:
|
||||
@@ -1679,7 +1679,7 @@ def _dag_cycle_check_result(app: "App") -> Result[bool]:
|
||||
"""
|
||||
from src.dag_engine import TrackDAG
|
||||
try:
|
||||
ticket_dicts = [{'id': str(t.get('id', '')), 'depends_on': t.get('depends_on', [])} for t in app.active_tickets]
|
||||
ticket_dicts = [{'id': str(t.id), 'depends_on': list(t.depends_on)} for t in app.active_tickets]
|
||||
temp_dag = TrackDAG(ticket_dicts)
|
||||
has_cycle = temp_dag.has_cycle()
|
||||
return Result(data=has_cycle)
|
||||
@@ -6849,25 +6849,25 @@ def render_mma_ticket_editor(app: App) -> None:
|
||||
+---------------------------------------------------------+
|
||||
"""
|
||||
imgui.separator(); imgui.text_colored(C_VAL(), f"Editing: {app.ui_selected_ticket_id}")
|
||||
ticket = next((t for t in app.active_tickets if str(t.get('id', '')) == app.ui_selected_ticket_id), None)
|
||||
ticket = next((t for t in app.active_tickets if str(t.id) == app.ui_selected_ticket_id), None)
|
||||
if ticket:
|
||||
imgui.text(f"Status: {ticket.get('status', 'todo')}"); prio = ticket.get('priority', 'medium')
|
||||
imgui.text(f"Status: {ticket.status}"); prio = ticket.priority
|
||||
imgui.text("Priority:"); imgui.same_line()
|
||||
if imgui.begin_combo(f"##edit_prio_{ticket.get('id')}", prio):
|
||||
if imgui.begin_combo(f"##edit_prio_{ticket.id}", prio):
|
||||
for p_opt in ['high', 'medium', 'low']:
|
||||
if imgui.selectable(p_opt, p_opt == prio)[0]: ticket['priority'] = p_opt; app._push_mma_state_update()
|
||||
if imgui.selectable(p_opt, p_opt == prio)[0]: ticket.priority = p_opt; app._push_mma_state_update()
|
||||
imgui.end_combo()
|
||||
imgui.text(f"Target: {ticket.get('target_file', '')}"); imgui.text(f"Depends on: {', '.join(ticket.get('depends_on', []))}")
|
||||
personas = getattr(app.controller, 'personas', {}); curr_pers = ticket.get('persona_id', '')
|
||||
imgui.text(f"Target: {ticket.target_file or ''}"); imgui.text(f"Depends on: {', '.join(ticket.depends_on)}")
|
||||
personas = getattr(app.controller, 'personas', {}); curr_pers = ticket.persona_id or ''
|
||||
imgui.text("Persona Override:"); imgui.same_line()
|
||||
pers_opts = ["None"] + sorted(personas.keys());
|
||||
pers_opts = ["None"] + sorted(personas.keys());
|
||||
curr_idx = pers_opts.index(curr_pers) + 1 if curr_pers in pers_opts else 0
|
||||
_, curr_idx = imgui.combo(f"##ticket_persona_{ticket.get('id')}", curr_idx, pers_opts)
|
||||
ticket['persona_id'] = None if curr_idx == 0 or pers_opts[curr_idx] == "None" else pers_opts[curr_idx]
|
||||
if imgui.button(f"Mark Complete##{app.ui_selected_ticket_id}"): ticket['status'] = 'done'; app._push_mma_state_update()
|
||||
_, curr_idx = imgui.combo(f"##ticket_persona_{ticket.id}", curr_idx, pers_opts)
|
||||
ticket.persona_id = None if curr_idx == 0 or pers_opts[curr_idx] == "None" else pers_opts[curr_idx]
|
||||
if imgui.button(f"Mark Complete##{app.ui_selected_ticket_id}"): ticket.status = 'done'; app._push_mma_state_update()
|
||||
imgui.same_line()
|
||||
if imgui.button(f"Delete##{app.ui_selected_ticket_id}"):
|
||||
app.active_tickets = [t for t in app.active_tickets if str(t.get('id', '')) != app.ui_selected_ticket_id]
|
||||
if imgui.button(f"Delete##{app.ui_selected_ticket_id}"):
|
||||
app.active_tickets = [t for t in app.active_tickets if str(t.id) != app.ui_selected_ticket_id]
|
||||
app.ui_selected_ticket_id = None
|
||||
app._push_mma_state_update()
|
||||
|
||||
@@ -7068,7 +7068,7 @@ def render_ticket_queue(app: App) -> None:
|
||||
return
|
||||
|
||||
# Select All / None
|
||||
if imgui.button("Select All"): app.ui_selected_tickets = {str(t.get('id', '')) for t in app.active_tickets}
|
||||
if imgui.button("Select All"): app.ui_selected_tickets = {str(t.id) for t in app.active_tickets}
|
||||
imgui.same_line()
|
||||
if imgui.button("Select None"): app.ui_selected_tickets.clear()
|
||||
|
||||
@@ -7093,7 +7093,7 @@ def render_ticket_queue(app: App) -> None:
|
||||
imgui.table_headers_row()
|
||||
|
||||
for i, t in enumerate(app.active_tickets):
|
||||
tid = str(t.get('id', ''))
|
||||
tid = str(t.id)
|
||||
imgui.table_next_row()
|
||||
|
||||
# Select
|
||||
@@ -7125,50 +7125,50 @@ def render_ticket_queue(app: App) -> None:
|
||||
# Priority
|
||||
|
||||
imgui.table_next_column()
|
||||
prio = t.get('priority', 'medium')
|
||||
prio = t.priority
|
||||
p_col = theme.get_color("text_disabled") # gray
|
||||
if prio == 'high': _col = theme.get_color("status_error") # red
|
||||
elif prio == 'medium': p_col = theme.get_color("status_warning") # 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
|
||||
t.priority = p_opt
|
||||
app._push_mma_state_update()
|
||||
imgui.end_combo()
|
||||
imgui.pop_style_color()
|
||||
|
||||
# Model
|
||||
imgui.table_next_column()
|
||||
model_override = t.get('model_override')
|
||||
model_override = t.model_override
|
||||
current_model = model_override if model_override else "Default"
|
||||
if imgui.begin_combo(f"##model_{tid}", current_model, imgui.ComboFlags_.height_small):
|
||||
if imgui.selectable("Default", model_override is None)[0]:
|
||||
t['model_override'] = None; app._push_mma_state_update()
|
||||
t.model_override = None; app._push_mma_state_update()
|
||||
for model in ["gemini-2.5-flash-lite", "gemini-2.5-flash", "gemini-3-flash-preview", "gemini-3.1-pro-preview", "deepseek-v3"]:
|
||||
if imgui.selectable(model, model_override == model)[0]:
|
||||
t['model_override'] = model; app._push_mma_state_update()
|
||||
t.model_override = model; app._push_mma_state_update()
|
||||
imgui.end_combo()
|
||||
|
||||
# Status
|
||||
imgui.table_next_column()
|
||||
status = t.get('status', 'todo')
|
||||
if t.get('model_override'): imgui.text_colored(theme.get_color("status_warning"), f"{status} [{t.get('model_override')}]")
|
||||
else: imgui.text(t.get('status', 'todo'))
|
||||
status = t.status
|
||||
if t.model_override: imgui.text_colored(theme.get_color("status_warning"), f"{status} [{t.model_override}]")
|
||||
else: imgui.text(t.status)
|
||||
|
||||
# Description
|
||||
imgui.table_next_column()
|
||||
imgui.text(t.get('description', ''))
|
||||
imgui.text(t.description)
|
||||
|
||||
# Actions - Kill button for in_progress tickets
|
||||
imgui.table_next_column()
|
||||
status = t.get('status', 'todo')
|
||||
if status == 'in_progress':
|
||||
status = t.status
|
||||
if status == 'in_progress':
|
||||
if imgui.button(f"Kill##{tid}"): app._cb_kill_ticket(tid)
|
||||
elif status == 'todo':
|
||||
if imgui.button(f"Block##{tid}"): app._cb_block_ticket(tid)
|
||||
elif status == 'blocked' and t.get('manual_block', False):
|
||||
elif status == 'blocked' and t.manual_block:
|
||||
if imgui.button(f"Unblock##{tid}"): app._cb_unblock_ticket(tid)
|
||||
|
||||
imgui.end_table()
|
||||
@@ -7200,19 +7200,19 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer
|
||||
for node_id in selected:
|
||||
node_val = node_id.id()
|
||||
for t in app.active_tickets:
|
||||
if abs(hash(str(t.get('id', '')))) == node_val:
|
||||
app.ui_selected_ticket_id = str(t.get('id', ''))
|
||||
if abs(hash(str(t.id))) == node_val:
|
||||
app.ui_selected_ticket_id = str(t.id)
|
||||
break
|
||||
break
|
||||
for t in app.active_tickets:
|
||||
tid = str(t.get('id', '??'))
|
||||
tid = str(t.id) if t.id else '??'
|
||||
int_id = abs(hash(tid))
|
||||
ed.begin_node(ed.NodeId(int_id))
|
||||
if getattr(app, "ui_project_execution_mode", "native") == "beads":
|
||||
imgui.text_colored(theme.get_color("status_info"), "[B] ")
|
||||
imgui.same_line()
|
||||
imgui.text_colored(C_KEY(), f"Ticket: {tid}")
|
||||
status = t.get('status', 'todo')
|
||||
status = t.status
|
||||
s_col = C_VAL()
|
||||
if status == 'done' or status == 'complete': s_col = C_IN()
|
||||
elif status == 'in_progress' or status == 'running': s_col = C_OUT()
|
||||
@@ -7220,7 +7220,7 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer
|
||||
imgui.text("Status: ")
|
||||
imgui.same_line()
|
||||
imgui.text_colored(s_col, status)
|
||||
imgui.text(f"Target: {t.get('target_file','')}")
|
||||
imgui.text(f"Target: {t.target_file or ''}")
|
||||
ed.begin_pin(ed.PinId(abs(hash(tid + "_in"))), ed.PinKind.input)
|
||||
imgui.text("->")
|
||||
ed.end_pin()
|
||||
@@ -7230,10 +7230,10 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer
|
||||
ed.end_pin()
|
||||
ed.end_node()
|
||||
for t in app.active_tickets:
|
||||
tid = str(t.get('id', '??'))
|
||||
for dep in t.get('depends_on', []):
|
||||
tid = str(t.id) if t.id else '??'
|
||||
for dep in t.depends_on:
|
||||
ed.link(ed.LinkId(abs(hash(dep + "_" + tid))), ed.PinId(abs(hash(dep + "_out"))), ed.PinId(abs(hash(tid + "_in"))))
|
||||
|
||||
|
||||
# Handle link creation
|
||||
if ed.begin_create():
|
||||
start_pin = ed.PinId()
|
||||
@@ -7245,16 +7245,16 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer
|
||||
source_tid = None
|
||||
target_tid = None
|
||||
for t in app.active_tickets:
|
||||
tid = str(t.get('id', ''))
|
||||
tid = str(t.id)
|
||||
if abs(hash(tid + "_out")) == s_id: source_tid = tid
|
||||
if abs(hash(tid + "_out")) == e_id: source_tid = tid
|
||||
if abs(hash(tid + "_in")) == s_id: target_tid = tid
|
||||
if abs(hash(tid + "_in")) == e_id: target_tid = tid
|
||||
if source_tid and target_tid and source_tid != target_tid:
|
||||
for t in app.active_tickets:
|
||||
if str(t.get('id', '')) == target_tid:
|
||||
if source_tid not in t.get('depends_on', []):
|
||||
t.setdefault('depends_on', []).append(source_tid)
|
||||
if str(t.id) == target_tid:
|
||||
if source_tid not in t.depends_on:
|
||||
t.depends_on = list(t.depends_on) + [source_tid]
|
||||
app._push_mma_state_update()
|
||||
break
|
||||
ed.end_create()
|
||||
@@ -7266,10 +7266,10 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer
|
||||
if ed.accept_deleted_item():
|
||||
lid_val = link_id.id()
|
||||
for t in app.active_tickets:
|
||||
tid = str(t.get('id', ''))
|
||||
deps = t.get('depends_on', [])
|
||||
tid = str(t.id)
|
||||
deps = t.depends_on
|
||||
if any(abs(hash(d + "_" + tid)) == lid_val for d in deps):
|
||||
t['depends_on'] = [dep for dep in deps if abs(hash(dep + "_" + tid)) != lid_val]
|
||||
t.depends_on = [dep for dep in deps if abs(hash(dep + "_" + tid)) != lid_val]
|
||||
app._push_mma_state_update()
|
||||
break
|
||||
ed.end_delete()
|
||||
@@ -7291,7 +7291,7 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer
|
||||
# Default Ticket ID
|
||||
max_id = 0
|
||||
for t in app.active_tickets:
|
||||
tid = t.get('id', '')
|
||||
tid = t.id
|
||||
if tid.startswith('T-'):
|
||||
parse_result = _ticket_id_max_int_result(tid)
|
||||
if parse_result.ok:
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user