From 142e8263189513eec99ffa50f495296be45afa60 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 16 Feb 2026 17:24:41 -0700 Subject: [PATCH] Add step435 workflow status overlay and tests --- editor/CMakeLists.txt | 9 ++ editor/src/WorkflowStatusOverlay.h | 94 +++++++++++++++++ editor/tests/step435_test.cpp | 155 +++++++++++++++++++++++++++++ progress.md | 46 +++++++++ 4 files changed, 304 insertions(+) create mode 100644 editor/src/WorkflowStatusOverlay.h create mode 100644 editor/tests/step435_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index e7286a7..d0ef1f5 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2794,4 +2794,13 @@ target_link_libraries(step434_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step435_test tests/step435_test.cpp) +target_include_directories(step435_test PRIVATE src) +target_link_libraries(step435_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/WorkflowStatusOverlay.h b/editor/src/WorkflowStatusOverlay.h new file mode 100644 index 0000000..a8ceb71 --- /dev/null +++ b/editor/src/WorkflowStatusOverlay.h @@ -0,0 +1,94 @@ +#pragma once + +#include "WorkflowState.h" + +#include +#include +#include +#include + +struct WorkflowStatusMarker { + std::string itemId; + std::string nodeId; + std::string displayName; + std::string bufferId; + int line = 0; + std::string status; + std::string statusIcon; + std::string statusColor; + std::string boardColumn; + std::string detailRoute; +}; + +class WorkflowStatusOverlay { +public: + static std::vector build(const WorkflowState& wf, + const std::map& nodeLines, + const std::string& bufferFilter = "") { + std::vector out; + for (const auto& status : {WI_PENDING, WI_READY, WI_ASSIGNED, WI_IN_PROGRESS, + WI_REVIEW, WI_COMPLETE, WI_REJECTED}) { + for (const auto& wi : wf.queue.getByStatus(status)) { + if (!bufferFilter.empty() && wi.bufferId != bufferFilter) continue; + WorkflowStatusMarker marker; + marker.itemId = wi.id; + marker.nodeId = wi.nodeId; + marker.displayName = wi.nodeName.empty() ? wi.nodeId : wi.nodeName; + marker.bufferId = wi.bufferId; + marker.line = lineForNode(wi.nodeId, nodeLines); + marker.status = wi.status; + marker.statusIcon = iconForStatus(wi.status); + marker.statusColor = colorForStatus(wi.status); + marker.boardColumn = boardColumnForStatus(wi.status); + marker.detailRoute = detailRoute(wi.id); + out.push_back(marker); + } + } + + std::sort(out.begin(), out.end(), [](const WorkflowStatusMarker& a, + const WorkflowStatusMarker& b) { + if (a.bufferId != b.bufferId) return a.bufferId < b.bufferId; + if (a.line != b.line) return a.line < b.line; + return a.itemId < b.itemId; + }); + return out; + } + + static std::string iconForStatus(const std::string& status) { + if (status == WI_PENDING || status == WI_READY || status == WI_REJECTED) return "outline"; + if (status == WI_ASSIGNED || status == WI_IN_PROGRESS) return "spinner"; + if (status == WI_REVIEW) return "eye"; + if (status == WI_COMPLETE) return "check"; + return "outline"; + } + + static std::string colorForStatus(const std::string& status) { + if (status == WI_PENDING || status == WI_REJECTED) return "#7F8C8D"; + if (status == WI_READY) return "#3498DB"; + if (status == WI_ASSIGNED || status == WI_IN_PROGRESS) return "#2980B9"; + if (status == WI_REVIEW) return "#F39C12"; + if (status == WI_COMPLETE) return "#27AE60"; + return "#7F8C8D"; + } + + static std::string boardColumnForStatus(const std::string& status) { + if (status == WI_PENDING || status == WI_REJECTED) return "pending"; + if (status == WI_READY) return "ready"; + if (status == WI_ASSIGNED || status == WI_IN_PROGRESS) return "in-progress"; + if (status == WI_REVIEW) return "review"; + if (status == WI_COMPLETE) return "complete"; + return "pending"; + } + + static std::string detailRoute(const std::string& itemId) { + return "task://detail/" + itemId; + } + +private: + static int lineForNode(const std::string& nodeId, + const std::map& nodeLines) { + auto it = nodeLines.find(nodeId); + if (it == nodeLines.end()) return 0; + return it->second; + } +}; diff --git a/editor/tests/step435_test.cpp b/editor/tests/step435_test.cpp new file mode 100644 index 0000000..0f3125a --- /dev/null +++ b/editor/tests/step435_test.cpp @@ -0,0 +1,155 @@ +// Step 435: Workflow Status Overlay Tests (12 tests) + +#include "WorkflowStatusOverlay.h" + +#include +#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& nodeId, + const std::string& name, + const std::string& status, + const std::string& file) { + WorkItem wi; + wi.id = id; + wi.nodeId = nodeId; + wi.nodeName = name; + wi.status = status; + wi.bufferId = file; + wi.workerType = "llm"; + wi.priority = "medium"; + return wi; +} + +static WorkflowState sampleState() { + WorkflowState wf("overlay"); + wf.queue.enqueue(makeItem("w1", "n1", "parseTokens", WI_PENDING, "src/a.cpp")); + wf.queue.enqueue(makeItem("w2", "n2", "routeTask", WI_READY, "src/a.cpp")); + wf.queue.enqueue(makeItem("w3", "n3", "emitCode", WI_IN_PROGRESS, "src/a.cpp")); + wf.queue.enqueue(makeItem("w4", "n4", "reviewOutput", WI_REVIEW, "src/b.cpp")); + wf.queue.enqueue(makeItem("w5", "n5", "finalize", WI_COMPLETE, "src/b.cpp")); + return wf; +} + +void test_build_overlay_returns_marker_per_item() { + TEST(build_overlay_returns_marker_per_item); + auto wf = sampleState(); + std::map lines = {{"n1", 10}, {"n2", 20}, {"n3", 30}, {"n4", 40}, {"n5", 50}}; + auto markers = WorkflowStatusOverlay::build(wf, lines); + CHECK(markers.size() == 5, "expected one marker per work item"); + PASS(); +} + +void test_buffer_filter_limits_markers() { + TEST(buffer_filter_limits_markers); + auto wf = sampleState(); + std::map lines = {{"n1", 10}, {"n2", 20}, {"n3", 30}, {"n4", 40}, {"n5", 50}}; + auto markers = WorkflowStatusOverlay::build(wf, lines, "src/a.cpp"); + CHECK(markers.size() == 3, "expected only src/a.cpp markers"); + PASS(); +} + +void test_missing_line_mapping_defaults_to_zero() { + TEST(missing_line_mapping_defaults_to_zero); + auto wf = sampleState(); + std::map lines = {{"n1", 10}}; + auto markers = WorkflowStatusOverlay::build(wf, lines); + bool hasZero = false; + for (const auto& m : markers) if (m.nodeId == "n2" && m.line == 0) hasZero = true; + CHECK(hasZero, "missing line map should produce line 0"); + PASS(); +} + +void test_icon_mapping_for_pending_is_outline() { + TEST(icon_mapping_for_pending_is_outline); + CHECK(WorkflowStatusOverlay::iconForStatus(WI_PENDING) == "outline", "pending icon mismatch"); + PASS(); +} + +void test_icon_mapping_for_in_progress_is_spinner() { + TEST(icon_mapping_for_in_progress_is_spinner); + CHECK(WorkflowStatusOverlay::iconForStatus(WI_IN_PROGRESS) == "spinner", "in-progress icon mismatch"); + PASS(); +} + +void test_icon_mapping_for_review_is_eye() { + TEST(icon_mapping_for_review_is_eye); + CHECK(WorkflowStatusOverlay::iconForStatus(WI_REVIEW) == "eye", "review icon mismatch"); + PASS(); +} + +void test_icon_mapping_for_complete_is_check() { + TEST(icon_mapping_for_complete_is_check); + CHECK(WorkflowStatusOverlay::iconForStatus(WI_COMPLETE) == "check", "complete icon mismatch"); + PASS(); +} + +void test_color_mapping_matches_board_statuses() { + TEST(color_mapping_matches_board_statuses); + CHECK(WorkflowStatusOverlay::colorForStatus(WI_REVIEW) == "#F39C12", "review color mismatch"); + CHECK(WorkflowStatusOverlay::colorForStatus(WI_COMPLETE) == "#27AE60", "complete color mismatch"); + PASS(); +} + +void test_board_column_mapping_for_assigned() { + TEST(board_column_mapping_for_assigned); + CHECK(WorkflowStatusOverlay::boardColumnForStatus(WI_ASSIGNED) == "in-progress", "assigned column mismatch"); + PASS(); +} + +void test_click_route_points_to_task_detail() { + TEST(click_route_points_to_task_detail); + CHECK(WorkflowStatusOverlay::detailRoute("work-9") == "task://detail/work-9", "detail route mismatch"); + PASS(); +} + +void test_display_name_prefers_node_name() { + TEST(display_name_prefers_node_name); + WorkflowState wf("overlay"); + wf.queue.enqueue(makeItem("x1", "node-x", "prettyName", WI_READY, "src/c.cpp")); + std::map lines = {{"node-x", 12}}; + auto markers = WorkflowStatusOverlay::build(wf, lines); + CHECK(markers.size() == 1, "expected one marker"); + CHECK(markers[0].displayName == "prettyName", "display name should prefer nodeName"); + PASS(); +} + +void test_markers_sort_by_file_then_line() { + TEST(markers_sort_by_file_then_line); + WorkflowState wf("overlay"); + wf.queue.enqueue(makeItem("z2", "n2", "b", WI_READY, "src/z.cpp")); + wf.queue.enqueue(makeItem("a1", "n1", "a", WI_READY, "src/a.cpp")); + std::map lines = {{"n1", 40}, {"n2", 5}}; + auto markers = WorkflowStatusOverlay::build(wf, lines); + CHECK(markers.size() == 2, "expected two markers"); + CHECK(markers[0].bufferId == "src/a.cpp", "file order mismatch"); + CHECK(markers[1].bufferId == "src/z.cpp", "file order mismatch"); + PASS(); +} + +int main() { + std::cout << "Step 435: Workflow Status Overlay Tests\n"; + + test_build_overlay_returns_marker_per_item(); // 1 + test_buffer_filter_limits_markers(); // 2 + test_missing_line_mapping_defaults_to_zero(); // 3 + test_icon_mapping_for_pending_is_outline(); // 4 + test_icon_mapping_for_in_progress_is_spinner(); // 5 + test_icon_mapping_for_review_is_eye(); // 6 + test_icon_mapping_for_complete_is_check(); // 7 + test_color_mapping_matches_board_statuses(); // 8 + test_board_column_mapping_for_assigned(); // 9 + test_click_route_points_to_task_detail(); // 10 + test_display_name_prefers_node_name(); // 11 + test_markers_sort_by_file_then_line(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) + << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 7168996..6efdb18 100644 --- a/progress.md +++ b/progress.md @@ -5083,6 +5083,52 @@ visualization in workflow overlays, including category weighting and color mappi - `editor/src/MCPServer.h` (`1940` > `600`) - `editor/src/HeadlessAgentRPCHandler.h` (`2768` > `600`) +### Step 435: Workflow Status Overlay +**Status:** PASS (12/12 tests) + +Added a workflow status overlay model to project per-function/class lifecycle +state into editor views, with status icon/color mapping and task-detail routes. + +**Files created:** +- `editor/src/WorkflowStatusOverlay.h` — overlay marker support: + - work-item -> status marker projection + - optional buffer filter for multi-file views + - node-id -> line mapping with safe fallback + - status -> icon mapping (outline/spinner/eye/check) + - status -> color mapping aligned with board states + - status -> task-board column normalization + - click route helper to task-detail URI + - stable sorting by file/line/item for deterministic rendering +- `editor/tests/step435_test.cpp` — 12 tests covering: + 1. marker generation count parity with work items + 2. file-scope buffer filtering behavior + 3. missing node-line fallback behavior + 4. pending icon mapping + 5. in-progress icon mapping + 6. review icon mapping + 7. complete icon mapping + 8. color mapping for review/complete + 9. board-column mapping for assigned/in-progress + 10. task-detail click route format + 11. node-name display preference + 12. deterministic sorting by file then line + +**Files modified:** +- `editor/CMakeLists.txt` — `step435_test` target + +**Verification run:** +- `step435_test` — PASS (12/12) new step coverage +- `step434_test` — PASS (12/12) regression coverage +- `step433_test` — PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/WorkflowStatusOverlay.h` within header-size limit (`94` <= `600`) +- `editor/tests/step435_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)