Step 433: add inline code annotation badges model
This commit is contained in:
@@ -2776,4 +2776,13 @@ target_link_libraries(step432_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step433_test tests/step433_test.cpp)
|
||||
target_include_directories(step433_test PRIVATE src)
|
||||
target_link_libraries(step433_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)
|
||||
|
||||
88
editor/src/CodeAnnotationBadges.h
Normal file
88
editor/src/CodeAnnotationBadges.h
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct AnnotationBadgeInput {
|
||||
int line = 0;
|
||||
std::string type;
|
||||
std::string detail;
|
||||
std::string nodeId;
|
||||
};
|
||||
|
||||
struct InlineBadge {
|
||||
int line = 0;
|
||||
std::string type;
|
||||
std::string icon;
|
||||
std::string colorHex;
|
||||
std::string detail;
|
||||
std::string nodeId;
|
||||
};
|
||||
|
||||
struct LineBadgeStack {
|
||||
int line = 0;
|
||||
std::vector<InlineBadge> badges;
|
||||
};
|
||||
|
||||
class CodeAnnotationBadges {
|
||||
public:
|
||||
static std::vector<InlineBadge> buildBadges(const std::vector<AnnotationBadgeInput>& in) {
|
||||
std::vector<InlineBadge> out;
|
||||
for (const auto& a : in) {
|
||||
InlineBadge b;
|
||||
b.line = a.line;
|
||||
b.type = a.type;
|
||||
b.icon = iconForType(a.type);
|
||||
b.colorHex = colorForType(a.type);
|
||||
b.detail = a.detail;
|
||||
b.nodeId = a.nodeId;
|
||||
out.push_back(b);
|
||||
}
|
||||
std::sort(out.begin(), out.end(),
|
||||
[](const InlineBadge& a, const InlineBadge& b) {
|
||||
if (a.line != b.line) return a.line < b.line;
|
||||
return a.type < b.type;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::vector<LineBadgeStack> stackByLine(const std::vector<InlineBadge>& badges) {
|
||||
std::map<int, std::vector<InlineBadge>> grouped;
|
||||
for (const auto& b : badges) grouped[b.line].push_back(b);
|
||||
std::vector<LineBadgeStack> out;
|
||||
for (auto& [line, lineBadges] : grouped) {
|
||||
out.push_back({line, lineBadges});
|
||||
}
|
||||
std::sort(out.begin(), out.end(),
|
||||
[](const LineBadgeStack& a, const LineBadgeStack& b) {
|
||||
return a.line < b.line;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::string expandDetail(const InlineBadge& badge) {
|
||||
return badge.type + " [" + badge.nodeId + "]: " + badge.detail;
|
||||
}
|
||||
|
||||
static std::string iconForType(const std::string& type) {
|
||||
if (type.find("Intent") != std::string::npos) return "I";
|
||||
if (type.find("Complexity") != std::string::npos) return "C";
|
||||
if (type.find("Risk") != std::string::npos) return "R";
|
||||
if (type.find("Contract") != std::string::npos) return "K";
|
||||
if (type.find("Review") != std::string::npos) return "V";
|
||||
if (type.find("Priority") != std::string::npos) return "P";
|
||||
return "A";
|
||||
}
|
||||
|
||||
static std::string colorForType(const std::string& type) {
|
||||
if (type.find("Intent") != std::string::npos) return "#1ABC9C";
|
||||
if (type.find("Complexity") != std::string::npos) return "#F39C12";
|
||||
if (type.find("Risk") != std::string::npos) return "#E74C3C";
|
||||
if (type.find("Contract") != std::string::npos) return "#3498DB";
|
||||
if (type.find("Review") != std::string::npos) return "#9B59B6";
|
||||
if (type.find("Priority") != std::string::npos) return "#2ECC71";
|
||||
return "#95A5A6";
|
||||
}
|
||||
};
|
||||
141
editor/tests/step433_test.cpp
Normal file
141
editor/tests/step433_test.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
// Step 433: Inline Annotation Badges Tests (12 tests)
|
||||
|
||||
#include "CodeAnnotationBadges.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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 std::vector<AnnotationBadgeInput> sampleInputs() {
|
||||
return {
|
||||
{10, "IntentAnnotation", "Parse auth tokens", "n1"},
|
||||
{10, "RiskAnnotation", "High external exposure", "n1"},
|
||||
{20, "ComplexityAnnotation", "Cyclomatic=12", "n2"},
|
||||
{30, "ReviewAnnotation", "Human review required", "n3"}
|
||||
};
|
||||
}
|
||||
|
||||
void test_build_badges_count_matches_input() {
|
||||
TEST(build_badges_count_matches_input);
|
||||
auto badges = CodeAnnotationBadges::buildBadges(sampleInputs());
|
||||
CHECK(badges.size() == sampleInputs().size(), "badge count mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_badges_sorted_by_line_then_type() {
|
||||
TEST(badges_sorted_by_line_then_type);
|
||||
auto badges = CodeAnnotationBadges::buildBadges({
|
||||
{30, "RiskAnnotation", "r", "n3"},
|
||||
{10, "IntentAnnotation", "i", "n1"},
|
||||
{10, "ComplexityAnnotation", "c", "n1"}
|
||||
});
|
||||
CHECK(badges[0].line == 10 && badges[1].line == 10 && badges[2].line == 30,
|
||||
"line sort mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_icon_mapping_for_known_types() {
|
||||
TEST(icon_mapping_for_known_types);
|
||||
CHECK(CodeAnnotationBadges::iconForType("IntentAnnotation") == "I", "intent icon");
|
||||
CHECK(CodeAnnotationBadges::iconForType("ComplexityAnnotation") == "C", "complexity icon");
|
||||
CHECK(CodeAnnotationBadges::iconForType("RiskAnnotation") == "R", "risk icon");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_color_mapping_for_known_types() {
|
||||
TEST(color_mapping_for_known_types);
|
||||
CHECK(CodeAnnotationBadges::colorForType("IntentAnnotation") == "#1ABC9C", "intent color");
|
||||
CHECK(CodeAnnotationBadges::colorForType("RiskAnnotation") == "#E74C3C", "risk color");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_unknown_type_fallback_icon_and_color() {
|
||||
TEST(unknown_type_fallback_icon_and_color);
|
||||
CHECK(CodeAnnotationBadges::iconForType("UnknownAnnotation") == "A", "fallback icon");
|
||||
CHECK(CodeAnnotationBadges::colorForType("UnknownAnnotation") == "#95A5A6", "fallback color");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_stack_by_line_groups_multiple_badges_same_line() {
|
||||
TEST(stack_by_line_groups_multiple_badges_same_line);
|
||||
auto badges = CodeAnnotationBadges::buildBadges(sampleInputs());
|
||||
auto stacks = CodeAnnotationBadges::stackByLine(badges);
|
||||
int line10Count = 0;
|
||||
for (const auto& s : stacks) if (s.line == 10) line10Count = (int)s.badges.size();
|
||||
CHECK(line10Count == 2, "line 10 should have two stacked badges");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_stack_by_line_sorted_by_line() {
|
||||
TEST(stack_by_line_sorted_by_line);
|
||||
auto badges = CodeAnnotationBadges::buildBadges(sampleInputs());
|
||||
auto stacks = CodeAnnotationBadges::stackByLine(badges);
|
||||
CHECK(stacks.size() >= 3, "expected three lines");
|
||||
CHECK(stacks[0].line < stacks[1].line, "stacks should be sorted by line");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_expand_detail_contains_type_node_and_detail() {
|
||||
TEST(expand_detail_contains_type_node_and_detail);
|
||||
auto badges = CodeAnnotationBadges::buildBadges(sampleInputs());
|
||||
auto text = CodeAnnotationBadges::expandDetail(badges[0]);
|
||||
CHECK(text.find(badges[0].type) != std::string::npos, "missing type");
|
||||
CHECK(text.find(badges[0].nodeId) != std::string::npos, "missing node");
|
||||
CHECK(text.find(badges[0].detail) != std::string::npos, "missing detail");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_badge_preserves_line_and_node_id() {
|
||||
TEST(badge_preserves_line_and_node_id);
|
||||
auto badges = CodeAnnotationBadges::buildBadges(sampleInputs());
|
||||
CHECK(badges[0].line > 0, "line missing");
|
||||
CHECK(!badges[0].nodeId.empty(), "node id missing");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_empty_input_returns_empty_badges() {
|
||||
TEST(empty_input_returns_empty_badges);
|
||||
auto badges = CodeAnnotationBadges::buildBadges({});
|
||||
CHECK(badges.empty(), "expected empty badges");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_empty_badges_return_empty_stacks() {
|
||||
TEST(empty_badges_return_empty_stacks);
|
||||
auto stacks = CodeAnnotationBadges::stackByLine({});
|
||||
CHECK(stacks.empty(), "expected empty stacks");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_review_annotation_has_review_icon() {
|
||||
TEST(review_annotation_has_review_icon);
|
||||
auto badges = CodeAnnotationBadges::buildBadges({{5, "ReviewAnnotation", "review me", "n1"}});
|
||||
CHECK(!badges.empty(), "badge missing");
|
||||
CHECK(badges[0].icon == "V", "review icon mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 433: Inline Annotation Badges Tests\n";
|
||||
|
||||
test_build_badges_count_matches_input(); // 1
|
||||
test_badges_sorted_by_line_then_type(); // 2
|
||||
test_icon_mapping_for_known_types(); // 3
|
||||
test_color_mapping_for_known_types(); // 4
|
||||
test_unknown_type_fallback_icon_and_color(); // 5
|
||||
test_stack_by_line_groups_multiple_badges_same_line();// 6
|
||||
test_stack_by_line_sorted_by_line(); // 7
|
||||
test_expand_detail_contains_type_node_and_detail(); // 8
|
||||
test_badge_preserves_line_and_node_id(); // 9
|
||||
test_empty_input_returns_empty_badges(); // 10
|
||||
test_empty_badges_return_empty_stacks(); // 11
|
||||
test_review_annotation_has_review_icon(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed)
|
||||
<< " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
46
progress.md
46
progress.md
@@ -4994,6 +4994,52 @@ changes and review lifecycle transitions.
|
||||
- `editor/src/MCPServer.h` (`1940` > `600`)
|
||||
- `editor/src/HeadlessAgentRPCHandler.h` (`2768` > `600`)
|
||||
|
||||
## Phase 19b: Code Annotation Overlay
|
||||
|
||||
### Step 433: Inline Annotation Badges
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Added an inline-annotation badge model for editor gutter overlays, including
|
||||
type-specific icon/color mapping, stacked line badges, and expandable detail text.
|
||||
|
||||
**Files created:**
|
||||
- `editor/src/CodeAnnotationBadges.h` — inline badge support:
|
||||
- annotation input -> badge projection
|
||||
- type->icon mapping (intent/complexity/risk/contract/review/priority)
|
||||
- type->color mapping for visual encoding
|
||||
- deterministic line/type sorting
|
||||
- line-based stacking for multiple annotations on one line
|
||||
- expandable detail formatter
|
||||
- `editor/tests/step433_test.cpp` — 12 tests covering:
|
||||
1. input->badge count preservation
|
||||
2. sort stability by line/type
|
||||
3. known icon mapping
|
||||
4. known color mapping
|
||||
5. unknown-type fallback icon/color
|
||||
6. multi-badge same-line stacking
|
||||
7. stack ordering by line
|
||||
8. expanded detail payload content
|
||||
9. line/node identity preservation
|
||||
10. empty input edge case
|
||||
11. empty stack edge case
|
||||
12. review-annotation icon mapping
|
||||
|
||||
**Files modified:**
|
||||
- `editor/CMakeLists.txt` — `step433_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `step433_test` — PASS (12/12) new step coverage
|
||||
- `step432_test` — PASS (8/8) regression coverage
|
||||
- `step431_test` — PASS (12/12) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/CodeAnnotationBadges.h` within header-size limit (`88` <= `600`)
|
||||
- `editor/tests/step433_test.cpp` within test-file size guidance (`141` 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)
|
||||
|
||||
Reference in New Issue
Block a user