From c858ff49796fabbe91e9f2e288fda2d99ca0ff4b Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 16 Feb 2026 17:13:45 -0700 Subject: [PATCH] Step 430: add workflow dependency graph view model --- editor/CMakeLists.txt | 9 ++ editor/src/WorkflowDependencyView.h | 94 +++++++++++++++++ editor/tests/step430_test.cpp | 155 ++++++++++++++++++++++++++++ progress.md | 48 +++++++++ 4 files changed, 306 insertions(+) create mode 100644 editor/src/WorkflowDependencyView.h create mode 100644 editor/tests/step430_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 0f521c8..5ae4aa8 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2749,4 +2749,13 @@ target_link_libraries(step429_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step430_test tests/step430_test.cpp) +target_include_directories(step430_test PRIVATE src) +target_link_libraries(step430_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/WorkflowDependencyView.h b/editor/src/WorkflowDependencyView.h new file mode 100644 index 0000000..f411ce8 --- /dev/null +++ b/editor/src/WorkflowDependencyView.h @@ -0,0 +1,94 @@ +#pragma once + +#include "DependencyGraph.h" + +#include +#include +#include +#include + +struct DependencyNodeView { + std::string id; + std::string label; + std::string status; + std::string workerType; + std::string priority; + std::string colorHex; + bool criticalPath = false; + std::string hoverText; + std::string taskId; +}; + +struct DependencyEdgeView { + std::string from; + std::string to; + bool criticalPath = false; +}; + +struct DependencyGraphView { + std::vector nodes; + std::vector edges; + std::vector criticalPath; + + std::optional getNode(const std::string& id) const { + for (const auto& n : nodes) if (n.id == id) return n; + return std::nullopt; + } +}; + +class WorkflowDependencyView { +public: + static DependencyGraphView build(const WorkflowState& wf) { + auto graph = buildDependencyGraph(wf); + auto critical = getCriticalPath(graph); + std::set criticalSet(critical.begin(), critical.end()); + std::set criticalEdges; + for (size_t i = 1; i < critical.size(); ++i) { + criticalEdges.insert(critical[i - 1] + "->" + critical[i]); + } + + DependencyGraphView out; + out.criticalPath = critical; + + for (const auto& n : graph.nodes) { + DependencyNodeView v; + v.id = n.id; + v.label = n.label; + v.status = n.status; + v.workerType = n.workerType; + v.priority = n.priority; + v.colorHex = colorForStatus(n.status); + v.criticalPath = criticalSet.count(n.id) > 0; + v.taskId = n.id; + v.hoverText = "Task: " + n.label + "\nStatus: " + n.status + + "\nWorker: " + n.workerType + + "\nPriority: " + n.priority; + out.nodes.push_back(v); + } + + for (const auto& e : graph.edges) { + DependencyEdgeView ev; + ev.from = e.from; + ev.to = e.to; + ev.criticalPath = criticalEdges.count(e.from + "->" + e.to) > 0; + out.edges.push_back(ev); + } + + return out; + } + + static std::string colorForStatus(const std::string& status) { + if (status == WI_COMPLETE) return "#2ECC71"; // green + if (status == WI_IN_PROGRESS || status == WI_ASSIGNED) return "#3498DB"; // blue + if (status == WI_PENDING || status == WI_READY) return "#95A5A6"; // gray + if (status == WI_REVIEW || status == WI_REJECTED) return "#E74C3C"; // red + return "#7F8C8D"; + } + + static std::string navigateToTaskId(const DependencyGraphView& view, + const std::string& nodeId) { + auto n = view.getNode(nodeId); + if (!n.has_value()) return ""; + return n->taskId; + } +}; diff --git a/editor/tests/step430_test.cpp b/editor/tests/step430_test.cpp new file mode 100644 index 0000000..8450d16 --- /dev/null +++ b/editor/tests/step430_test.cpp @@ -0,0 +1,155 @@ +// Step 430: Dependency Graph Visualization Tests (12 tests) + +#include "WorkflowDependencyView.h" + +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {} + +static WorkItem makeItem(const std::string& id, const std::string& status, + const std::vector& deps = {}) { + WorkItem wi; + wi.id = id; + wi.nodeId = id + "_node"; + wi.nodeName = id; + wi.nodeType = "Function"; + wi.status = status; + wi.workerType = "llm"; + wi.priority = "medium"; + wi.dependencies = deps; + return wi; +} + +static WorkflowState makeChainWorkflow() { + WorkflowState wf("graph"); + wf.queue.enqueue(makeItem("a", WI_COMPLETE, {})); + wf.queue.enqueue(makeItem("b", WI_IN_PROGRESS, {"a"})); + wf.queue.enqueue(makeItem("c", WI_READY, {"b"})); + return wf; +} + +void test_build_graph_view_node_and_edge_counts() { + TEST(build_graph_view_node_and_edge_counts); + auto view = WorkflowDependencyView::build(makeChainWorkflow()); + CHECK(view.nodes.size() == 3, "expected 3 nodes"); + CHECK(view.edges.size() == 2, "expected 2 edges"); + PASS(); +} + +void test_status_color_complete_green() { + TEST(status_color_complete_green); + CHECK(WorkflowDependencyView::colorForStatus(WI_COMPLETE) == "#2ECC71", + "complete should be green"); + PASS(); +} + +void test_status_color_in_progress_blue() { + TEST(status_color_in_progress_blue); + CHECK(WorkflowDependencyView::colorForStatus(WI_IN_PROGRESS) == "#3498DB", + "in-progress should be blue"); + PASS(); +} + +void test_status_color_pending_ready_gray() { + TEST(status_color_pending_ready_gray); + CHECK(WorkflowDependencyView::colorForStatus(WI_PENDING) == "#95A5A6", "pending gray"); + CHECK(WorkflowDependencyView::colorForStatus(WI_READY) == "#95A5A6", "ready gray"); + PASS(); +} + +void test_status_color_review_red() { + TEST(status_color_review_red); + CHECK(WorkflowDependencyView::colorForStatus(WI_REVIEW) == "#E74C3C", "review red"); + PASS(); +} + +void test_critical_path_detected_for_chain() { + TEST(critical_path_detected_for_chain); + auto view = WorkflowDependencyView::build(makeChainWorkflow()); + CHECK(view.criticalPath.size() == 3, "chain critical path should be 3"); + PASS(); +} + +void test_critical_path_nodes_marked() { + TEST(critical_path_nodes_marked); + auto view = WorkflowDependencyView::build(makeChainWorkflow()); + int crit = 0; + for (const auto& n : view.nodes) if (n.criticalPath) ++crit; + CHECK(crit >= 3, "all chain nodes should be critical"); + PASS(); +} + +void test_critical_path_edges_marked() { + TEST(critical_path_edges_marked); + auto view = WorkflowDependencyView::build(makeChainWorkflow()); + int crit = 0; + for (const auto& e : view.edges) if (e.criticalPath) ++crit; + CHECK(crit == 2, "both chain edges should be critical"); + PASS(); +} + +void test_hover_text_contains_details() { + TEST(hover_text_contains_details); + auto view = WorkflowDependencyView::build(makeChainWorkflow()); + auto node = view.getNode("b"); + CHECK(node.has_value(), "node b missing"); + CHECK(node->hoverText.find("Status:") != std::string::npos, "hover missing status"); + CHECK(node->hoverText.find("Worker:") != std::string::npos, "hover missing worker"); + PASS(); +} + +void test_navigate_to_task_id_from_node_click() { + TEST(navigate_to_task_id_from_node_click); + auto view = WorkflowDependencyView::build(makeChainWorkflow()); + std::string taskId = WorkflowDependencyView::navigateToTaskId(view, "c"); + CHECK(taskId == "c", "navigation should return task id"); + PASS(); +} + +void test_navigate_missing_node_returns_empty() { + TEST(navigate_missing_node_returns_empty); + auto view = WorkflowDependencyView::build(makeChainWorkflow()); + CHECK(WorkflowDependencyView::navigateToTaskId(view, "missing").empty(), + "missing node should return empty task id"); + PASS(); +} + +void test_graph_updates_after_status_change() { + TEST(graph_updates_after_status_change); + auto wf = makeChainWorkflow(); + auto v1 = WorkflowDependencyView::build(wf); + auto b1 = v1.getNode("b"); + CHECK(b1.has_value() && b1->colorHex == "#3498DB", "b should start blue"); + auto item = wf.queue.getItem("b"); + item->status = WI_COMPLETE; + wf.queue.updateItem("b", *item); + auto v2 = WorkflowDependencyView::build(wf); + auto b2 = v2.getNode("b"); + CHECK(b2.has_value() && b2->colorHex == "#2ECC71", "b should turn green"); + PASS(); +} + +int main() { + std::cout << "Step 430: Dependency Graph Visualization Tests\n"; + + test_build_graph_view_node_and_edge_counts(); // 1 + test_status_color_complete_green(); // 2 + test_status_color_in_progress_blue(); // 3 + test_status_color_pending_ready_gray(); // 4 + test_status_color_review_red(); // 5 + test_critical_path_detected_for_chain(); // 6 + test_critical_path_nodes_marked(); // 7 + test_critical_path_edges_marked(); // 8 + test_hover_text_contains_details(); // 9 + test_navigate_to_task_id_from_node_click(); // 10 + test_navigate_missing_node_returns_empty(); // 11 + test_graph_updates_after_status_change(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) + << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 8120a5e..564a7ef 100644 --- a/progress.md +++ b/progress.md @@ -4852,6 +4852,54 @@ rejection operations in one consistent view model. - `editor/src/MCPServer.h` (`1940` > `600`) - `editor/src/HeadlessAgentRPCHandler.h` (`2768` > `600`) +### Step 430: Dependency Graph Visualization +**Status:** PASS (12/12 tests) + +Added a dependency-graph visualization adapter that projects workflow +dependencies into render-ready node/edge view data, including status-based color +coding, critical-path highlighting, hover details, and click navigation IDs. + +**Files created:** +- `editor/src/WorkflowDependencyView.h` — dependency-view support: + - node/edge view structs for DAG rendering + - status->color mapping: + - complete: green + - in-progress/assigned: blue + - pending/ready: gray + - review/rejected: red + - critical path integration from `DependencyGraph.h` + - critical-edge tagging + - hover payload synthesis and task-navigation mapping +- `editor/tests/step430_test.cpp` — 12 tests covering: + 1. graph node/edge projection counts + 2. complete color mapping + 3. in-progress color mapping + 4. pending/ready color mapping + 5. review color mapping + 6. critical-path detection on chain + 7. critical-path node tagging + 8. critical-path edge tagging + 9. hover payload content + 10. node->task navigation ID mapping + 11. missing-node navigation edge case + 12. re-render update after status change + +**Files modified:** +- `editor/CMakeLists.txt` — `step430_test` target + +**Verification run:** +- `step430_test` — PASS (12/12) new step coverage +- `step429_test` — PASS (12/12) regression coverage +- `step428_test` — PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/WorkflowDependencyView.h` within header-size limit (`94` <= `600`) +- `editor/tests/step430_test.cpp` within test-file size guidance (`155` lines) +- Legacy oversized headers persist: + - `editor/src/ast/Serialization.h` (`1427` > `600`) + - `editor/src/MCPServer.h` (`1940` > `600`) + - `editor/src/HeadlessAgentRPCHandler.h` (`2768` > `600`) + # Roadmap Planning — Sprints 12-25+ ## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)