Add step435 workflow status overlay and tests
This commit is contained in:
@@ -2794,4 +2794,13 @@ target_link_libraries(step434_test PRIVATE
|
|||||||
tree_sitter_javascript tree_sitter_typescript
|
tree_sitter_javascript tree_sitter_typescript
|
||||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
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)
|
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||||
|
|||||||
94
editor/src/WorkflowStatusOverlay.h
Normal file
94
editor/src/WorkflowStatusOverlay.h
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "WorkflowState.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<WorkflowStatusMarker> build(const WorkflowState& wf,
|
||||||
|
const std::map<std::string, int>& nodeLines,
|
||||||
|
const std::string& bufferFilter = "") {
|
||||||
|
std::vector<WorkflowStatusMarker> 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<std::string, int>& nodeLines) {
|
||||||
|
auto it = nodeLines.find(nodeId);
|
||||||
|
if (it == nodeLines.end()) return 0;
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
};
|
||||||
155
editor/tests/step435_test.cpp
Normal file
155
editor/tests/step435_test.cpp
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
// Step 435: Workflow Status Overlay Tests (12 tests)
|
||||||
|
|
||||||
|
#include "WorkflowStatusOverlay.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
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<std::string, int> 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<std::string, int> 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<std::string, int> 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<std::string, int> 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<std::string, int> 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;
|
||||||
|
}
|
||||||
46
progress.md
46
progress.md
@@ -5083,6 +5083,52 @@ visualization in workflow overlays, including category weighting and color mappi
|
|||||||
- `editor/src/MCPServer.h` (`1940` > `600`)
|
- `editor/src/MCPServer.h` (`1940` > `600`)
|
||||||
- `editor/src/HeadlessAgentRPCHandler.h` (`2768` > `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+
|
# Roadmap Planning — Sprints 12-25+
|
||||||
|
|
||||||
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)
|
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)
|
||||||
|
|||||||
Reference in New Issue
Block a user