Step 584: add value forward onboarding flow
This commit is contained in:
@@ -4135,4 +4135,13 @@ target_link_libraries(step583_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step584_test tests/step584_test.cpp)
|
||||
target_include_directories(step584_test PRIVATE src)
|
||||
target_link_libraries(step584_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)
|
||||
|
||||
105
editor/src/ValueForwardOnboardingFlow.h
Normal file
105
editor/src/ValueForwardOnboardingFlow.h
Normal file
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
// Step 584: Value-Forward Onboarding Flow
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum class OnboardingCardType {
|
||||
MCPDepth,
|
||||
ConstrainedExecution,
|
||||
VerificationLoop,
|
||||
WorkflowRouting
|
||||
};
|
||||
|
||||
struct OnboardingCard {
|
||||
std::string cardId;
|
||||
OnboardingCardType type = OnboardingCardType::MCPDepth;
|
||||
std::string title;
|
||||
std::string message;
|
||||
bool completed = false;
|
||||
};
|
||||
|
||||
struct OnboardingFlowState {
|
||||
std::string profileId;
|
||||
bool active = false;
|
||||
std::size_t currentIndex = 0;
|
||||
std::vector<OnboardingCard> cards;
|
||||
};
|
||||
|
||||
class ValueForwardOnboardingFlow {
|
||||
public:
|
||||
static bool start(const std::string& profileId,
|
||||
OnboardingFlowState* state,
|
||||
std::string* error) {
|
||||
if (!state || !error) return false;
|
||||
error->clear();
|
||||
if (profileId.empty()) {
|
||||
*error = "profile_id_missing";
|
||||
return false;
|
||||
}
|
||||
|
||||
state->profileId = profileId;
|
||||
state->active = true;
|
||||
state->currentIndex = 0;
|
||||
state->cards = defaultCards();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool completeCurrent(OnboardingFlowState* state, std::string* error) {
|
||||
if (!state || !error) return false;
|
||||
error->clear();
|
||||
if (!state->active) return fail(error, "flow_inactive");
|
||||
if (state->currentIndex >= state->cards.size()) return fail(error, "card_index_invalid");
|
||||
state->cards[state->currentIndex].completed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool next(OnboardingFlowState* state, std::string* error) {
|
||||
if (!state || !error) return false;
|
||||
error->clear();
|
||||
if (!state->active) return fail(error, "flow_inactive");
|
||||
if (state->currentIndex >= state->cards.size()) return fail(error, "card_index_invalid");
|
||||
if (!state->cards[state->currentIndex].completed) return fail(error, "current_card_not_completed");
|
||||
|
||||
if (state->currentIndex + 1 >= state->cards.size()) {
|
||||
state->active = false;
|
||||
return true;
|
||||
}
|
||||
++state->currentIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
static float progress(const OnboardingFlowState& state) {
|
||||
if (state.cards.empty()) return 0.0f;
|
||||
int completed = 0;
|
||||
for (const auto& card : state.cards) if (card.completed) ++completed;
|
||||
return static_cast<float>(completed) / static_cast<float>(state.cards.size());
|
||||
}
|
||||
|
||||
static std::vector<std::string> valueHighlights(const OnboardingFlowState& state) {
|
||||
std::vector<std::string> highlights;
|
||||
for (const auto& card : state.cards) {
|
||||
if (!card.completed) continue;
|
||||
if (card.type == OnboardingCardType::MCPDepth) highlights.push_back("mcp_tool_depth");
|
||||
if (card.type == OnboardingCardType::ConstrainedExecution) highlights.push_back("safe_constrained_ops");
|
||||
if (card.type == OnboardingCardType::VerificationLoop) highlights.push_back("verify_before_merge");
|
||||
if (card.type == OnboardingCardType::WorkflowRouting) highlights.push_back("transparent_routing_gates");
|
||||
}
|
||||
return highlights;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool fail(std::string* error, const char* code) {
|
||||
*error = code;
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::vector<OnboardingCard> defaultCards() {
|
||||
return {
|
||||
{"onboarding-1", OnboardingCardType::MCPDepth, "Deep Tool Reach", "Discover multi-tool MCP depth.", false},
|
||||
{"onboarding-2", OnboardingCardType::ConstrainedExecution, "Safe Execution", "Apply constrained operations with clear guardrails.", false},
|
||||
{"onboarding-3", OnboardingCardType::VerificationLoop, "Built-in Verification", "Run build/test loops before finalization.", false},
|
||||
{"onboarding-4", OnboardingCardType::WorkflowRouting, "Explainable Routing", "Understand review gates and decision routing.", false}
|
||||
};
|
||||
}
|
||||
};
|
||||
154
editor/tests/step584_test.cpp
Normal file
154
editor/tests/step584_test.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
// Step 584: Value-Forward Onboarding Flow (12 tests)
|
||||
|
||||
#include "ValueForwardOnboardingFlow.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 {}
|
||||
|
||||
void test_start_success() {
|
||||
TEST(start_success);
|
||||
OnboardingFlowState state;
|
||||
std::string error;
|
||||
CHECK(ValueForwardOnboardingFlow::start("profile-1", &state, &error), "start should succeed");
|
||||
CHECK(state.active, "flow should be active");
|
||||
CHECK(state.cards.size() == 4, "default card count mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_start_fails_without_profile_id() {
|
||||
TEST(start_fails_without_profile_id);
|
||||
OnboardingFlowState state;
|
||||
std::string error;
|
||||
CHECK(!ValueForwardOnboardingFlow::start("", &state, &error), "start should fail");
|
||||
CHECK(error == "profile_id_missing", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_complete_current_marks_card_completed() {
|
||||
TEST(complete_current_marks_card_completed);
|
||||
OnboardingFlowState state;
|
||||
std::string error;
|
||||
CHECK(ValueForwardOnboardingFlow::start("profile-1", &state, &error), "start failed");
|
||||
CHECK(ValueForwardOnboardingFlow::completeCurrent(&state, &error), "complete current failed");
|
||||
CHECK(state.cards[0].completed, "current card should be completed");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_complete_current_fails_when_inactive() {
|
||||
TEST(complete_current_fails_when_inactive);
|
||||
OnboardingFlowState state;
|
||||
std::string error;
|
||||
CHECK(!ValueForwardOnboardingFlow::completeCurrent(&state, &error), "complete should fail");
|
||||
CHECK(error == "flow_inactive", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_next_requires_current_completion() {
|
||||
TEST(next_requires_current_completion);
|
||||
OnboardingFlowState state;
|
||||
std::string error;
|
||||
CHECK(ValueForwardOnboardingFlow::start("profile-1", &state, &error), "start failed");
|
||||
CHECK(!ValueForwardOnboardingFlow::next(&state, &error), "next should fail");
|
||||
CHECK(error == "current_card_not_completed", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_next_advances_index_after_completion() {
|
||||
TEST(next_advances_index_after_completion);
|
||||
OnboardingFlowState state;
|
||||
std::string error;
|
||||
CHECK(ValueForwardOnboardingFlow::start("profile-1", &state, &error), "start failed");
|
||||
CHECK(ValueForwardOnboardingFlow::completeCurrent(&state, &error), "complete failed");
|
||||
CHECK(ValueForwardOnboardingFlow::next(&state, &error), "next failed");
|
||||
CHECK(state.currentIndex == 1, "index should advance");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_next_on_last_card_finishes_flow() {
|
||||
TEST(next_on_last_card_finishes_flow);
|
||||
OnboardingFlowState state;
|
||||
std::string error;
|
||||
CHECK(ValueForwardOnboardingFlow::start("profile-1", &state, &error), "start failed");
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
CHECK(ValueForwardOnboardingFlow::completeCurrent(&state, &error), "complete failed");
|
||||
CHECK(ValueForwardOnboardingFlow::next(&state, &error), "next failed");
|
||||
}
|
||||
CHECK(!state.active, "flow should be inactive at end");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_progress_reflects_completion_fraction() {
|
||||
TEST(progress_reflects_completion_fraction);
|
||||
OnboardingFlowState state;
|
||||
std::string error;
|
||||
CHECK(ValueForwardOnboardingFlow::start("profile-1", &state, &error), "start failed");
|
||||
CHECK(ValueForwardOnboardingFlow::completeCurrent(&state, &error), "complete failed");
|
||||
const float p = ValueForwardOnboardingFlow::progress(state);
|
||||
CHECK(p > 0.24f && p < 0.26f, "progress should be 0.25");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_progress_zero_for_empty_state() {
|
||||
TEST(progress_zero_for_empty_state);
|
||||
OnboardingFlowState state;
|
||||
CHECK(ValueForwardOnboardingFlow::progress(state) == 0.0f, "empty progress should be zero");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_value_highlights_include_completed_card_signals() {
|
||||
TEST(value_highlights_include_completed_card_signals);
|
||||
OnboardingFlowState state;
|
||||
std::string error;
|
||||
CHECK(ValueForwardOnboardingFlow::start("profile-1", &state, &error), "start failed");
|
||||
CHECK(ValueForwardOnboardingFlow::completeCurrent(&state, &error), "complete card 1 failed");
|
||||
CHECK(ValueForwardOnboardingFlow::next(&state, &error), "next failed");
|
||||
CHECK(ValueForwardOnboardingFlow::completeCurrent(&state, &error), "complete card 2 failed");
|
||||
const auto highlights = ValueForwardOnboardingFlow::valueHighlights(state);
|
||||
CHECK(highlights.size() == 2, "two highlights expected");
|
||||
CHECK(highlights[0] == "mcp_tool_depth", "first highlight mismatch");
|
||||
CHECK(highlights[1] == "safe_constrained_ops", "second highlight mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_value_highlights_empty_when_nothing_completed() {
|
||||
TEST(value_highlights_empty_when_nothing_completed);
|
||||
OnboardingFlowState state;
|
||||
std::string error;
|
||||
CHECK(ValueForwardOnboardingFlow::start("profile-1", &state, &error), "start failed");
|
||||
CHECK(ValueForwardOnboardingFlow::valueHighlights(state).empty(), "highlights should be empty");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_next_fails_when_inactive() {
|
||||
TEST(next_fails_when_inactive);
|
||||
OnboardingFlowState state;
|
||||
std::string error;
|
||||
CHECK(!ValueForwardOnboardingFlow::next(&state, &error), "next should fail");
|
||||
CHECK(error == "flow_inactive", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 584: Value-Forward Onboarding Flow\n";
|
||||
|
||||
test_start_success(); // 1
|
||||
test_start_fails_without_profile_id(); // 2
|
||||
test_complete_current_marks_card_completed(); // 3
|
||||
test_complete_current_fails_when_inactive(); // 4
|
||||
test_next_requires_current_completion(); // 5
|
||||
test_next_advances_index_after_completion(); // 6
|
||||
test_next_on_last_card_finishes_flow(); // 7
|
||||
test_progress_reflects_completion_fraction(); // 8
|
||||
test_progress_zero_for_empty_state(); // 9
|
||||
test_value_highlights_include_completed_card_signals();// 10
|
||||
test_value_highlights_empty_when_nothing_completed();// 11
|
||||
test_next_fails_when_inactive(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
34
progress.md
34
progress.md
@@ -10951,3 +10951,37 @@ normalization, and acceptance test-skeleton generation.
|
||||
- `editor/src/RequirementNormalizationConflictDetector.h` within header-size limit (`153` <= `600`)
|
||||
- `editor/src/AcceptanceCriteriaBinding.h` within header-size limit (`68` <= `600`)
|
||||
- Shared normalization centralization reduces duplicated string-normalization logic and stays aligned with `ARCHITECTURE.md`
|
||||
|
||||
### Step 584: Value-Forward Onboarding Flow
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Implements a first-run onboarding flow that explicitly surfaces MCP depth,
|
||||
constrained execution safeguards, verification, and routing transparency.
|
||||
|
||||
**Files added:**
|
||||
- `editor/src/ValueForwardOnboardingFlow.h` - onboarding flow module:
|
||||
- profile-based first-run flow initialization
|
||||
- card completion and gated forward navigation
|
||||
- completion progress reporting
|
||||
- completed-card value highlight extraction
|
||||
- inactive/index guard behavior
|
||||
- `editor/tests/step584_test.cpp` - 12 tests covering:
|
||||
- flow start success/failure behavior
|
||||
- completion/navigation guard behavior
|
||||
- end-of-flow behavior
|
||||
- progress computation behavior
|
||||
- value highlight extraction behavior
|
||||
|
||||
**Files modified:**
|
||||
- `editor/CMakeLists.txt` - `step584_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `cmake -S editor -B editor/build-native` - PASS
|
||||
- `cmake --build editor/build-native --target step584_test step583_test` - PASS
|
||||
- `./editor/build-native/step584_test` - PASS (12/12)
|
||||
- `./editor/build-native/step583_test` - PASS (8/8) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/ValueForwardOnboardingFlow.h` within header-size limit (`105` <= `600`)
|
||||
- `editor/tests/step584_test.cpp` within test-file size guidance (`154` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
Reference in New Issue
Block a user